Automatically allocate one uid per session
This commit is contained in:
parent
e9ff1d92d3
commit
afad563d56
|
@ -38,8 +38,7 @@ EXPOSE 6120
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/pid1.bash"]
|
ENTRYPOINT ["/usr/local/bin/pid1.bash"]
|
||||||
COPY scripts/pid1.bash /usr/local/bin/
|
COPY scripts/pid1.bash /usr/local/bin/
|
||||||
|
CMD ["yarn", "run", "server"]
|
||||||
RUN sudo deluser docker sudo
|
|
||||||
|
|
||||||
RUN mkdir /tmp/riju
|
RUN mkdir /tmp/riju
|
||||||
COPY --chown=docker:docker package.json yarn.lock /tmp/riju/
|
COPY --chown=docker:docker package.json yarn.lock /tmp/riju/
|
||||||
|
@ -49,8 +48,10 @@ COPY --chown=docker:docker frontend /tmp/riju/frontend
|
||||||
RUN cd /tmp/riju && yarn run frontend
|
RUN cd /tmp/riju && yarn run frontend
|
||||||
COPY --chown=docker:docker backend /tmp/riju/backend
|
COPY --chown=docker:docker backend /tmp/riju/backend
|
||||||
RUN cd /tmp/riju && yarn run backend
|
RUN cd /tmp/riju && yarn run backend
|
||||||
|
COPY --chown=docker:docker system /tmp/riju/system
|
||||||
|
RUN cd /tmp/riju && RIJU_PRIVILEGED=1 yarn run system
|
||||||
COPY --chown=docker:docker . /home/docker/src
|
COPY --chown=docker:docker . /home/docker/src
|
||||||
RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
|
RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
|
||||||
|
|
||||||
WORKDIR /home/docker/src
|
WORKDIR /home/docker/src
|
||||||
CMD ["yarn", "run", "server"]
|
RUN sudo deluser docker sudo
|
||||||
|
|
12
README.md
12
README.md
|
@ -20,6 +20,7 @@ usual to install dependencies. For production, it's:
|
||||||
|
|
||||||
$ yarn backend
|
$ yarn backend
|
||||||
$ yarn frontend
|
$ yarn frontend
|
||||||
|
$ yarn system
|
||||||
$ yarn server
|
$ yarn server
|
||||||
|
|
||||||
For development with file watching and automatic server rebooting and
|
For development with file watching and automatic server rebooting and
|
||||||
|
@ -27,15 +28,18 @@ all that, it's:
|
||||||
|
|
||||||
$ yarn backend-dev
|
$ yarn backend-dev
|
||||||
$ yarn frontend-dev
|
$ yarn frontend-dev
|
||||||
|
$ yarn system-dev
|
||||||
$ yarn server-dev
|
$ yarn server-dev
|
||||||
|
|
||||||
The webserver listens on `localhost:6119`. Now, although the server
|
The webserver listens on `localhost:6119`. Now, although the server
|
||||||
itself will work, the only languages that will work are the ones that
|
itself will work, the only languages that will work are the ones that
|
||||||
happen to be installed on your machine. (I'm sure you can find a few
|
happen to be installed on your machine. (I'm sure you can find a few
|
||||||
that are already.) If you want to test with *all* the languages (or
|
that are already.) Also, sandboxing using UNIX filesystem permissions
|
||||||
you're working on adding a new language), then you need to use Docker.
|
will be disabled, because that requires root privileges. If you want
|
||||||
Running the app is exactly the same as before, you just have to jump
|
to test with *all* the languages plus sandboxing (or you're working on
|
||||||
into the container first:
|
adding a new language), then you need to use Docker. Running the app
|
||||||
|
is exactly the same as before, you just have to jump into the
|
||||||
|
container first:
|
||||||
|
|
||||||
$ make docker
|
$ make docker
|
||||||
|
|
||||||
|
|
|
@ -1,64 +1,88 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as mkdirp from "mkdirp";
|
|
||||||
import * as pty from "node-pty";
|
|
||||||
import { IPty } from "node-pty";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as tmp from "tmp";
|
|
||||||
import * as WebSocket from "ws";
|
import * as WebSocket from "ws";
|
||||||
|
|
||||||
|
import * as mkdirp from "mkdirp";
|
||||||
|
import * as nodeCleanup from "node-cleanup";
|
||||||
|
import * as pty from "node-pty";
|
||||||
|
import { IPty } from "node-pty";
|
||||||
|
import * as tmp from "tmp";
|
||||||
|
import { v4 as getUUID } from "uuid";
|
||||||
|
|
||||||
import { LangConfig, langs } from "./langs";
|
import { LangConfig, langs } from "./langs";
|
||||||
|
import { borrowUser } from "./users";
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
|
id: string;
|
||||||
code: string;
|
code: string;
|
||||||
config: LangConfig;
|
config: LangConfig;
|
||||||
term: { pty: IPty | null; live: boolean };
|
term: { pty: IPty | null; live: boolean };
|
||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
tmpdir: string | null;
|
tmpdir: string | null;
|
||||||
tmpdirCleanup: (() => void) | null;
|
tmpdirCleanup: (() => void) | null;
|
||||||
|
uid: number | null;
|
||||||
|
uidCleanup: (() => Promise<void>) | null;
|
||||||
|
|
||||||
|
log = (msg: string) => console.log(`[${this.id}] ${msg}`);
|
||||||
|
|
||||||
constructor(ws: WebSocket, lang: string) {
|
constructor(ws: WebSocket, lang: string) {
|
||||||
|
this.id = getUUID();
|
||||||
|
this.log(`Creating session, language ${lang}`);
|
||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
this.config = langs[lang];
|
this.config = langs[lang];
|
||||||
this.term = { pty: null, live: false };
|
this.term = { pty: null, live: false };
|
||||||
this.code = "";
|
this.code = "";
|
||||||
this.tmpdir = null;
|
this.tmpdir = null;
|
||||||
this.tmpdirCleanup = null;
|
this.tmpdirCleanup = null;
|
||||||
|
this.uid = null;
|
||||||
|
this.uidCleanup = null;
|
||||||
ws.on("message", this.handleClientMessage);
|
ws.on("message", this.handleClientMessage);
|
||||||
ws.on("close", this.cleanup);
|
ws.on("close", () =>
|
||||||
this.run().catch(console.error);
|
this.cleanup().catch((err) =>
|
||||||
|
this.log(`Error during session cleanup: ${err}`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
nodeCleanup();
|
||||||
|
this.run().catch((err) => this.log(`Error while running: ${err}`));
|
||||||
}
|
}
|
||||||
handleClientMessage = (event: string) => {
|
handleClientMessage = (event: string) => {
|
||||||
let msg: any;
|
let msg: any;
|
||||||
try {
|
try {
|
||||||
msg = JSON.parse(event);
|
msg = JSON.parse(event);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`failed to parse client message: ${msg}`);
|
this.log(`Failed to parse client message: ${msg}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (msg?.event) {
|
switch (msg?.event) {
|
||||||
case "terminalInput":
|
case "terminalInput":
|
||||||
if (!this.term) {
|
if (!this.term) {
|
||||||
console.error(`terminalInput: no terminal`);
|
this.log(`Got terminal input before pty was started`);
|
||||||
} else if (typeof msg.input !== "string") {
|
} else if (typeof msg.input !== "string") {
|
||||||
console.error(`terminalInput: missing or malformed input field`);
|
this.log(`Got malformed terminal input message`);
|
||||||
} else {
|
} else {
|
||||||
this.term.pty!.write(msg.input);
|
this.term.pty!.write(msg.input);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "runCode":
|
case "runCode":
|
||||||
if (typeof msg.code !== "string") {
|
if (typeof msg.code !== "string") {
|
||||||
console.error(`runCode: missing or malformed code field`);
|
this.log(`Got malformed run message`);
|
||||||
} else {
|
} else {
|
||||||
this.code = msg.code;
|
this.code = msg.code;
|
||||||
this.run();
|
this.run();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(`unknown client message type: ${msg.event}`);
|
this.log(`Got unknown message type: ${msg.event}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
run = async () => {
|
run = async () => {
|
||||||
|
if (this.uid === null) {
|
||||||
|
({ uid: this.uid, cleanup: this.uidCleanup } = await borrowUser(
|
||||||
|
this.log
|
||||||
|
));
|
||||||
|
}
|
||||||
|
this.log(`Borrowed uid ${this.uid}`);
|
||||||
const { name, repl, main, suffix, compile, run, hacks } = this.config;
|
const { name, repl, main, suffix, compile, run, hacks } = this.config;
|
||||||
if (this.term.pty) {
|
if (this.term.pty) {
|
||||||
this.term.pty.kill();
|
this.term.pty.kill();
|
||||||
|
@ -72,13 +96,16 @@ export class Session {
|
||||||
if (this.tmpdir == null) {
|
if (this.tmpdir == null) {
|
||||||
({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise(
|
({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise(
|
||||||
(resolve, reject) =>
|
(resolve, reject) =>
|
||||||
tmp.dir({ unsafeCleanup: true }, (err, path, cleanup) => {
|
tmp.dir(
|
||||||
if (err) {
|
{ unsafeCleanup: true, dir: "riju" },
|
||||||
reject(err);
|
(err, path, cleanup) => {
|
||||||
} else {
|
if (err) {
|
||||||
resolve({ path, cleanup });
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve({ path, cleanup });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let cmdline: string;
|
let cmdline: string;
|
||||||
|
@ -157,9 +184,13 @@ export class Session {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
cleanup = () => {
|
cleanup = async () => {
|
||||||
|
this.log(`Cleaning up session`);
|
||||||
if (this.tmpdirCleanup) {
|
if (this.tmpdirCleanup) {
|
||||||
this.tmpdirCleanup();
|
this.tmpdirCleanup();
|
||||||
}
|
}
|
||||||
|
if (this.uidCleanup) {
|
||||||
|
await this.uidCleanup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as process from "process";
|
||||||
|
|
||||||
|
import * as AsyncLock from "async-lock";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
import * as parsePasswd from "parse-passwd";
|
||||||
|
|
||||||
|
// Keep in sync with system/src/riju-system-privileged.c
|
||||||
|
const MIN_UID = 2000;
|
||||||
|
const MAX_UID = 65000;
|
||||||
|
|
||||||
|
const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false;
|
||||||
|
const CUR_UID = parseInt(process.env.UID || "") || null;
|
||||||
|
|
||||||
|
let availIds: number[] | null = null;
|
||||||
|
let nextId: number | null = null;
|
||||||
|
let lock = new AsyncLock();
|
||||||
|
|
||||||
|
async function readExistingUsers(log: (msg: string) => void) {
|
||||||
|
availIds = parsePasswd(
|
||||||
|
await new Promise((resolve, reject) =>
|
||||||
|
fs.readFile("/etc/passwd", "utf-8", (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter(({ username }) => username.startsWith("riju_user"))
|
||||||
|
.map(({ uid }) => parseInt(uid))
|
||||||
|
.filter((uid) => !isNaN(uid) && uid >= MIN_UID && uid < MAX_UID);
|
||||||
|
nextId = (_.max(availIds) || MIN_UID - 1) + 1;
|
||||||
|
log(`Found ${availIds.length} existing users, next ID is ${nextId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUser(log: (msg: string) => void): Promise<number> {
|
||||||
|
if (nextId! >= MAX_UID) {
|
||||||
|
throw new Error("too many users");
|
||||||
|
}
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const uid = nextId!;
|
||||||
|
const useradd = spawn("system/out/riju-system-privileged", [
|
||||||
|
"useradd",
|
||||||
|
`${uid}`,
|
||||||
|
]);
|
||||||
|
let output = "";
|
||||||
|
useradd.stdout.on("data", (data) => {
|
||||||
|
output += `${data}`;
|
||||||
|
});
|
||||||
|
useradd.stderr.on("data", (data) => {
|
||||||
|
output += `${data}`;
|
||||||
|
});
|
||||||
|
useradd.on("close", (code) => {
|
||||||
|
output = output.trim();
|
||||||
|
if (output) {
|
||||||
|
log("Output from useradd:\n" + output);
|
||||||
|
}
|
||||||
|
if (code === 0) {
|
||||||
|
log(`Created new user with ID ${uid}`);
|
||||||
|
nextId! += 1;
|
||||||
|
resolve(uid);
|
||||||
|
} else {
|
||||||
|
reject(`useradd failed with error code ${code}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function borrowUser(log: (msg: string) => void) {
|
||||||
|
if (!PRIVILEGED) {
|
||||||
|
if (CUR_UID === null) {
|
||||||
|
throw new Error("unable to determine current UID");
|
||||||
|
} else {
|
||||||
|
return { uid: CUR_UID, cleanup: async () => {} };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return await lock.acquire("key", async () => {
|
||||||
|
if (availIds === null || nextId === null) {
|
||||||
|
await readExistingUsers(log);
|
||||||
|
}
|
||||||
|
let uid: number;
|
||||||
|
if (availIds!.length > 0) {
|
||||||
|
uid = availIds!.pop()!;
|
||||||
|
} else {
|
||||||
|
uid = await createUser(log);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uid,
|
||||||
|
cleanup: async () => {
|
||||||
|
await lock.acquire("key", () => {
|
||||||
|
availIds!.push(uid);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
13
package.json
13
package.json
|
@ -5,12 +5,16 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/app-root-path": "^1.2.4",
|
"@types/app-root-path": "^1.2.4",
|
||||||
|
"@types/async-lock": "^1.1.2",
|
||||||
"@types/express": "^4.17.6",
|
"@types/express": "^4.17.6",
|
||||||
"@types/express-ws": "^3.0.0",
|
"@types/express-ws": "^3.0.0",
|
||||||
"@types/lodash": "^4.14.155",
|
"@types/lodash": "^4.14.155",
|
||||||
"@types/mkdirp": "^1.0.1",
|
"@types/mkdirp": "^1.0.1",
|
||||||
|
"@types/parse-passwd": "^1.0.0",
|
||||||
"@types/tmp": "^0.2.0",
|
"@types/tmp": "^0.2.0",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
|
"async-lock": "^1.2.4",
|
||||||
"css-loader": "^3.5.3",
|
"css-loader": "^3.5.3",
|
||||||
"ejs": "^3.1.3",
|
"ejs": "^3.1.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
@ -19,11 +23,14 @@
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"monaco-editor": "^0.20.0",
|
"monaco-editor": "^0.20.0",
|
||||||
|
"node-cleanup": "^2.1.2",
|
||||||
"node-pty": "^0.9.0",
|
"node-pty": "^0.9.0",
|
||||||
|
"parse-passwd": "^1.0.0",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"typescript": "^3.9.5",
|
"typescript": "^3.9.5",
|
||||||
|
"uuid": "^8.1.0",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.43.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"xterm": "^4.6.0",
|
"xterm": "^4.6.0",
|
||||||
|
@ -34,7 +41,9 @@
|
||||||
"backend-dev": "tsc --watch",
|
"backend-dev": "tsc --watch",
|
||||||
"frontend": "webpack --production",
|
"frontend": "webpack --production",
|
||||||
"frontend-dev": "webpack --development --watch",
|
"frontend-dev": "webpack --development --watch",
|
||||||
"server": "node backend/out/server.js",
|
"server": "scripts/setup.bash && node backend/out/server.js",
|
||||||
"server-dev": "watchexec -w backend/out -r -n node backend/out/server.js"
|
"server-dev": "watchexec -w backend/out -r 'scripts/setup.bash && node backend/out/server.js'",
|
||||||
|
"system": "scripts/compile-system.bash",
|
||||||
|
"system-dev": "watchexec -w system/src -n scripts/compile-system.bash"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [[ ! -d system/src ]]; then
|
||||||
|
echo "compile-system.bash: no system/src directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function verbosely {
|
||||||
|
echo "$@"
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p system/out
|
||||||
|
rm -f system/out/*
|
||||||
|
for src in system/src/*.c; do
|
||||||
|
out="${src/src/out}"
|
||||||
|
out="${out/.c}"
|
||||||
|
verbosely clang -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
|
||||||
|
if [[ "${out}" == *-privileged && -n "${RIJU_PRIVILEGED}" ]]; then
|
||||||
|
sudo chown root:docker "${out}"
|
||||||
|
sudo chmod a=,g=rx,u=rwxs "${out}"
|
||||||
|
fi
|
||||||
|
done
|
|
@ -8,6 +8,7 @@ export LC_ALL=C.UTF-8
|
||||||
export SHELL="$(which bash)"
|
export SHELL="$(which bash)"
|
||||||
|
|
||||||
export HOST=0.0.0.0
|
export HOST=0.0.0.0
|
||||||
|
export RIJU_PRIVILEGED=yes
|
||||||
|
|
||||||
if [[ -d /home/docker/src ]]; then
|
if [[ -d /home/docker/src ]]; then
|
||||||
cd /home/docker/src
|
cd /home/docker/src
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
mkdir -p /tmp/riju
|
||||||
|
rm -rf /tmp/riju/*
|
|
@ -0,0 +1,55 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// Keep in sync with backend/src/users.ts
|
||||||
|
const int MIN_UID = 2000;
|
||||||
|
const int MAX_UID = 65000;
|
||||||
|
|
||||||
|
void die(const char *msg)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s\n", msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void die_with_usage()
|
||||||
|
{
|
||||||
|
die("usage:\n"
|
||||||
|
" riju-system-privileged useradd UID");
|
||||||
|
}
|
||||||
|
|
||||||
|
void useradd(int uid)
|
||||||
|
{
|
||||||
|
char *cmdline;
|
||||||
|
if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d riju_user%1$d", uid) < 0) {
|
||||||
|
die("asprintf failed");
|
||||||
|
}
|
||||||
|
int status = system(cmdline);
|
||||||
|
if (status) {
|
||||||
|
die("useradd failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
setuid(0);
|
||||||
|
if (argc < 2)
|
||||||
|
die_with_usage();
|
||||||
|
if (!strcmp(argv[1], "useradd")) {
|
||||||
|
if (argc != 3)
|
||||||
|
die_with_usage();
|
||||||
|
char *endptr;
|
||||||
|
long uid = strtol(argv[2], &endptr, 10);
|
||||||
|
if (!argv[2] || *endptr) {
|
||||||
|
die("uid must be an integer");
|
||||||
|
}
|
||||||
|
if (uid < MIN_UID || uid >= MAX_UID) {
|
||||||
|
die("uid is out of range");
|
||||||
|
}
|
||||||
|
useradd(uid);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
die_with_usage();
|
||||||
|
}
|
30
yarn.lock
30
yarn.lock
|
@ -7,6 +7,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7"
|
resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7"
|
||||||
integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc=
|
integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc=
|
||||||
|
|
||||||
|
"@types/async-lock@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.2.tgz#cbc26a34b11b83b28f7783a843c393b443ef8bef"
|
||||||
|
integrity sha512-j9n4bb6RhgFIydBe0+kpjnBPYumDaDyU8zvbWykyVMkku+c2CSu31MZkLeaBfqIwU+XCxlDpYDfyMQRkM0AkeQ==
|
||||||
|
|
||||||
"@types/body-parser@*":
|
"@types/body-parser@*":
|
||||||
version "1.19.0"
|
version "1.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
|
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
|
||||||
|
@ -77,6 +82,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
|
||||||
integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==
|
integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==
|
||||||
|
|
||||||
|
"@types/parse-passwd@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/parse-passwd/-/parse-passwd-1.0.0.tgz#72fc50feb14d484547f40ba7da54dfcfc711dcf1"
|
||||||
|
integrity sha512-+kETlH3XJMQKUpJ4dYfU4MlfYqyjKwsOC9PLJcGiUhPcHrNEgvcEbllSJ5HlsvflDLswTtyDoKJEUcCQg2l9PA==
|
||||||
|
|
||||||
"@types/qs@*":
|
"@types/qs@*":
|
||||||
version "6.9.3"
|
version "6.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.3.tgz#b755a0934564a200d3efdf88546ec93c369abd03"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.3.tgz#b755a0934564a200d3efdf88546ec93c369abd03"
|
||||||
|
@ -100,6 +110,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c"
|
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c"
|
||||||
integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==
|
integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==
|
||||||
|
|
||||||
|
"@types/uuid@^8.0.0":
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0"
|
||||||
|
integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw==
|
||||||
|
|
||||||
"@types/ws@*":
|
"@types/ws@*":
|
||||||
version "7.2.5"
|
version "7.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49"
|
||||||
|
@ -390,6 +405,11 @@ async-limiter@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||||
|
|
||||||
|
async-lock@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.4.tgz#80d0d612383045dd0c30eb5aad08510c1397cb91"
|
||||||
|
integrity sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA==
|
||||||
|
|
||||||
async@0.9.x:
|
async@0.9.x:
|
||||||
version "0.9.2"
|
version "0.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||||
|
@ -2190,6 +2210,11 @@ nice-try@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
|
node-cleanup@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
|
||||||
|
integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=
|
||||||
|
|
||||||
node-libs-browser@^2.2.1:
|
node-libs-browser@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
||||||
|
@ -3299,6 +3324,11 @@ utils-merge@1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
uuid@^8.1.0:
|
||||||
|
version "8.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
|
||||||
|
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
|
||||||
|
|
||||||
v8-compile-cache@2.0.3:
|
v8-compile-cache@2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||||
|
|
Loading…
Reference in New Issue