Started experimenting with some auth stuff
This commit is contained in:
parent
321738a517
commit
94724879b6
@ -35,6 +35,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/amikhalev/sprinklers3#readme",
|
"homepage": "https://github.com/amikhalev/sprinklers3#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bcrypt": "^2.0.1",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
"chalk": "^2.4.1",
|
"chalk": "^2.4.1",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"express-pino-logger": "^3.0.2",
|
"express-pino-logger": "^3.0.2",
|
||||||
@ -51,6 +53,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/async": "^2.0.49",
|
"@types/async": "^2.0.49",
|
||||||
|
"@types/bcrypt": "^2.0.0",
|
||||||
"@types/classnames": "^2.2.4",
|
"@types/classnames": "^2.2.4",
|
||||||
"@types/core-js": "^2.5.0",
|
"@types/core-js": "^2.5.0",
|
||||||
"@types/express": "^4.16.0",
|
"@types/express": "^4.16.0",
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import * as express from "express";
|
|
||||||
|
|
||||||
import * as schema from "@common/sprinklers/schema";
|
|
||||||
import {serialize} from "serializr";
|
|
||||||
import { ServerState } from "../state";
|
|
||||||
import logger from "./logger";
|
|
||||||
import serveApp from "./serveApp";
|
|
||||||
|
|
||||||
export function createApp(state: ServerState) {
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(logger);
|
|
||||||
|
|
||||||
app.get("/api/grinklers", (req, res) => {
|
|
||||||
const j = serialize(schema.sprinklersDevice, state.device);
|
|
||||||
res.send(j);
|
|
||||||
});
|
|
||||||
|
|
||||||
serveApp(app);
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
66
server/express/index.ts
Normal file
66
server/express/index.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import * as bodyParser from "body-parser";
|
||||||
|
import * as express from "express";
|
||||||
|
import { serialize, serializeAll } from "serializr";
|
||||||
|
|
||||||
|
import * as schema from "@common/sprinklers/schema";
|
||||||
|
import { ServerState } from "../state";
|
||||||
|
import logger from "./logger";
|
||||||
|
import serveApp from "./serveApp";
|
||||||
|
|
||||||
|
import log from "@common/logger";
|
||||||
|
|
||||||
|
import { User } from "../models/User";
|
||||||
|
|
||||||
|
export function createApp(state: ServerState) {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(logger);
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
app.get("/api/grinklers", (req, res) => {
|
||||||
|
const j = serialize(schema.sprinklersDevice, state.device);
|
||||||
|
res.send(j);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/api/users", (req, res) => {
|
||||||
|
User.loadAll(state.database)
|
||||||
|
.then((users) => {
|
||||||
|
res.json({
|
||||||
|
data: users,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/api/users/:username", (req, res, next) => {
|
||||||
|
User.loadByUsername(state.database, req.params.username)
|
||||||
|
.then((user) => {
|
||||||
|
res.json({
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/authenticate", (req, res, next) => {
|
||||||
|
const body = req.body;
|
||||||
|
log.info({body}, "/api/authenticate: " + req.body);
|
||||||
|
if (!body || !body.username || !body.password) {
|
||||||
|
return next(new Error("must specify username and password"));
|
||||||
|
}
|
||||||
|
User.loadByUsername(state.database, body.username)
|
||||||
|
.then((user) => {
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("user does not exist");
|
||||||
|
}
|
||||||
|
return user.comparePassword(body.password);
|
||||||
|
}).then((passwordMatches) => {
|
||||||
|
res.json({
|
||||||
|
passwordMatches,
|
||||||
|
});
|
||||||
|
}).catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
serveApp(app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
@ -1,11 +1,14 @@
|
|||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
|
import * as path from "path";
|
||||||
import * as serveStatic from "serve-static";
|
import * as serveStatic from "serve-static";
|
||||||
|
|
||||||
import * as paths from "paths";
|
import * as paths from "paths";
|
||||||
|
|
||||||
|
const index = path.join(paths.publicDir, "index.html");
|
||||||
|
|
||||||
export default function serveApp(app: Express) {
|
export default function serveApp(app: Express) {
|
||||||
app.use(serveStatic(paths.appBuildDir));
|
app.use(serveStatic(paths.appBuildDir));
|
||||||
app.get("/*", (req, res) => {
|
app.get("/*", (req, res) => {
|
||||||
res.sendFile(path.join(paths.publicDir, "index.html"));
|
res.sendFile(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ import {Server} from "http";
|
|||||||
import * as WebSocket from "ws";
|
import * as WebSocket from "ws";
|
||||||
|
|
||||||
import { ServerState } from "./state";
|
import { ServerState } from "./state";
|
||||||
import { createApp } from "./app";
|
import { createApp } from "./express";
|
||||||
import { WebSocketApi } from "./websocket";
|
import { WebSocketApi } from "./websocket";
|
||||||
|
|
||||||
const state = new ServerState();
|
const state = new ServerState();
|
||||||
@ -20,9 +20,14 @@ const host = process.env.HOST || "0.0.0.0";
|
|||||||
|
|
||||||
const server = new Server(app);
|
const server = new Server(app);
|
||||||
const webSocketServer = new WebSocket.Server({server});
|
const webSocketServer = new WebSocket.Server({server});
|
||||||
webSocketServer.on("connection", webSocketApi.handleConnection);
|
webSocketApi.listen(webSocketServer);
|
||||||
|
|
||||||
state.start();
|
state.start()
|
||||||
server.listen(port, host, () => {
|
.then(() => {
|
||||||
log.info(`listening at ${host}:${port}`);
|
server.listen(port, host, () => {
|
||||||
});
|
log.info(`listening at ${host}:${port}`);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log.error({ err }, "error starting server");
|
||||||
|
});
|
||||||
|
57
server/models/Database.ts
Normal file
57
server/models/Database.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as r from "rethinkdb";
|
||||||
|
|
||||||
|
import { User } from "./User";
|
||||||
|
import logger from "@common/logger";
|
||||||
|
|
||||||
|
export class Database {
|
||||||
|
static readonly databaseName = "sprinklers3";
|
||||||
|
|
||||||
|
db: r.Db;
|
||||||
|
private _conn: r.Connection | null = null;
|
||||||
|
|
||||||
|
get conn(): r.Connection {
|
||||||
|
if (this._conn == null) {
|
||||||
|
throw new Error("Not connected to rethinkDB");
|
||||||
|
}
|
||||||
|
return this._conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.db = r.db(Database.databaseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
this._conn = await r.connect("localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
if (this._conn) {
|
||||||
|
return this._conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAll() {
|
||||||
|
const dbs = await r.dbList().run(this.conn);
|
||||||
|
if (dbs.indexOf(Database.databaseName) === -1) {
|
||||||
|
await r.dbCreate(Database.databaseName).run(this.conn);
|
||||||
|
}
|
||||||
|
await this.createTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTables() {
|
||||||
|
const tables = await this.db.tableList().run(this.conn);
|
||||||
|
if (tables.indexOf(User.tableName) === -1) {
|
||||||
|
await User.createTable(this);
|
||||||
|
}
|
||||||
|
const alex = new User(this, {
|
||||||
|
name: "Alex Mikhalev",
|
||||||
|
username: "alex",
|
||||||
|
});
|
||||||
|
await alex.setPassword("kakashka");
|
||||||
|
const created = await alex.createOrUpdate();
|
||||||
|
logger.info((created ? "created" : "updated") + " user alex");
|
||||||
|
|
||||||
|
const alex2 = await User.loadByUsername(this, "alex");
|
||||||
|
logger.info("password valid: " + await alex2!.comparePassword("kakashka"));
|
||||||
|
}
|
||||||
|
}
|
105
server/models/User.ts
Normal file
105
server/models/User.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import * as r from "rethinkdb";
|
||||||
|
import { createModelSchema, deserialize, primitive, serialize, update } from "serializr";
|
||||||
|
import { Database } from "./Database";
|
||||||
|
import * as bcrypt from "bcrypt";
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
id: string | undefined;
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
passwordHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HASH_ROUNDS = 10;
|
||||||
|
|
||||||
|
export class User implements IUser {
|
||||||
|
static readonly tableName = "users";
|
||||||
|
|
||||||
|
id: string | undefined;
|
||||||
|
username: string = "";
|
||||||
|
name: string = "";
|
||||||
|
passwordHash: string = "";
|
||||||
|
|
||||||
|
private db: Database;
|
||||||
|
|
||||||
|
private get _db() {
|
||||||
|
return this.db.db;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get table() {
|
||||||
|
return this.db.db.table(User.tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(db: Database, data?: Partial<IUser>) {
|
||||||
|
this.db = db;
|
||||||
|
if (data) {
|
||||||
|
update(this, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createTable(db: Database) {
|
||||||
|
await db.db.tableCreate(User.tableName).run(db.conn);
|
||||||
|
await db.db.table(User.tableName).indexCreate("username").run(db.conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async loadAll(db: Database): Promise<User[]> {
|
||||||
|
const cursor = await db.db.table(User.tableName)
|
||||||
|
.run(db.conn);
|
||||||
|
const users = await cursor.toArray();
|
||||||
|
return users.map((data) => {
|
||||||
|
return new User(db, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async loadByUsername(db: Database, username: string): Promise<User | null> {
|
||||||
|
const seq = await db.db.table(User.tableName)
|
||||||
|
.filter(r.row("username").eq(username))
|
||||||
|
.run(db.conn);
|
||||||
|
const data = await seq.toArray();
|
||||||
|
if (data.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new User(db, data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create() {
|
||||||
|
const data = serialize(this);
|
||||||
|
delete data.id;
|
||||||
|
const a = this.table
|
||||||
|
.insert(data)
|
||||||
|
.run(this.db.conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrUpdate() {
|
||||||
|
const data = serialize(this);
|
||||||
|
delete data.id;
|
||||||
|
const user = this.table.filter(r.row("username").eq(this.username));
|
||||||
|
const usernameDoesNotExist = user.isEmpty();
|
||||||
|
const a: r.WriteResult = await r.branch(usernameDoesNotExist,
|
||||||
|
this.table.insert(data) as r.Expression<any>,
|
||||||
|
user.nth(0).update(data) as r.Expression<any>)
|
||||||
|
.run(this.db.conn);
|
||||||
|
return a.inserted > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPassword(newPassword: string): Promise<void> {
|
||||||
|
this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async comparePassword(password: string): Promise<boolean> {
|
||||||
|
return bcrypt.compare(password, this.passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): any {
|
||||||
|
const data = serialize(this);
|
||||||
|
delete data.passwordHash;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createModelSchema(User, {
|
||||||
|
id: primitive(),
|
||||||
|
username: primitive(),
|
||||||
|
name: primitive(),
|
||||||
|
passwordHash: primitive(),
|
||||||
|
});
|
@ -1,17 +1,27 @@
|
|||||||
|
import logger from "@common/logger";
|
||||||
import {SprinklersDevice} from "@common/sprinklers";
|
import {SprinklersDevice} from "@common/sprinklers";
|
||||||
import * as mqtt from "@common/sprinklers/mqtt";
|
import * as mqtt from "@common/sprinklers/mqtt";
|
||||||
|
import { Database } from "./models/Database";
|
||||||
|
|
||||||
export class ServerState {
|
export class ServerState {
|
||||||
mqttClient!: mqtt.MqttApiClient;
|
mqttClient: mqtt.MqttApiClient;
|
||||||
device!: SprinklersDevice;
|
device: SprinklersDevice;
|
||||||
|
database: Database;
|
||||||
|
|
||||||
start() {
|
constructor() {
|
||||||
const mqttUrl = process.env.MQTT_URL;
|
const mqttUrl = process.env.MQTT_URL;
|
||||||
if (!mqttUrl) {
|
if (!mqttUrl) {
|
||||||
throw new Error("Must specify a MQTT_URL to connect to");
|
throw new Error("Must specify a MQTT_URL to connect to");
|
||||||
}
|
}
|
||||||
this.mqttClient = new mqtt.MqttApiClient(mqttUrl);
|
this.mqttClient = new mqtt.MqttApiClient(mqttUrl);
|
||||||
this.device = this.mqttClient.getDevice("grinklers");
|
this.device = this.mqttClient.getDevice("grinklers");
|
||||||
|
this.database = new Database();
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.database.connect();
|
||||||
|
await this.database.createAll();
|
||||||
|
logger.info("created database and tables");
|
||||||
|
|
||||||
this.mqttClient.start();
|
this.mqttClient.start();
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,10 @@ export class WebSocketApi {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listen(webSocketServer: WebSocket.Server) {
|
||||||
|
webSocketServer.on("connection", this.handleConnection);
|
||||||
|
}
|
||||||
|
|
||||||
handleConnection = (socket: WebSocket) => {
|
handleConnection = (socket: WebSocket) => {
|
||||||
const disposers = [
|
const disposers = [
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
|
62
yarn.lock
62
yarn.lock
@ -27,6 +27,10 @@
|
|||||||
version "2.0.49"
|
version "2.0.49"
|
||||||
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0"
|
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0"
|
||||||
|
|
||||||
|
"@types/bcrypt@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-2.0.0.tgz#74cccef82026341fd786cf2eb9c912c7f9107c55"
|
||||||
|
|
||||||
"@types/body-parser@*":
|
"@types/body-parser@*":
|
||||||
version "1.17.0"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c"
|
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c"
|
||||||
@ -655,6 +659,13 @@ bcrypt-pbkdf@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
bcrypt@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-2.0.1.tgz#229c5afe09379789f918efe86e5e5b682e509f85"
|
||||||
|
dependencies:
|
||||||
|
nan "2.10.0"
|
||||||
|
node-pre-gyp "0.9.1"
|
||||||
|
|
||||||
better-assert@~1.0.0:
|
better-assert@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
|
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
|
||||||
@ -725,6 +736,21 @@ body-parser@1.18.2:
|
|||||||
raw-body "2.3.2"
|
raw-body "2.3.2"
|
||||||
type-is "~1.6.15"
|
type-is "~1.6.15"
|
||||||
|
|
||||||
|
body-parser@^1.18.3:
|
||||||
|
version "1.18.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
http-errors "~1.6.3"
|
||||||
|
iconv-lite "0.4.23"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.5.2"
|
||||||
|
raw-body "2.3.3"
|
||||||
|
type-is "~1.6.16"
|
||||||
|
|
||||||
bonjour@^3.5.0:
|
bonjour@^3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
|
resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
|
||||||
@ -2896,7 +2922,7 @@ http-errors@1.6.2:
|
|||||||
setprototypeof "1.0.3"
|
setprototypeof "1.0.3"
|
||||||
statuses ">= 1.3.1 < 2"
|
statuses ">= 1.3.1 < 2"
|
||||||
|
|
||||||
http-errors@~1.6.2:
|
http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
|
||||||
version "1.6.3"
|
version "1.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2942,7 +2968,7 @@ iconv-lite@0.4.19:
|
|||||||
version "0.4.19"
|
version "0.4.19"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||||
|
|
||||||
iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
||||||
version "0.4.23"
|
version "0.4.23"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4108,7 +4134,7 @@ mute-stream@0.0.7:
|
|||||||
version "0.0.7"
|
version "0.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||||
|
|
||||||
nan@^2.10.0, nan@^2.9.2:
|
nan@2.10.0, nan@^2.10.0, nan@^2.9.2:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||||
|
|
||||||
@ -4215,6 +4241,21 @@ node-libs-browser@^2.0.0:
|
|||||||
util "^0.10.3"
|
util "^0.10.3"
|
||||||
vm-browserify "0.0.4"
|
vm-browserify "0.0.4"
|
||||||
|
|
||||||
|
node-pre-gyp@0.9.1:
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz#f11c07516dd92f87199dbc7e1838eab7cd56c9e0"
|
||||||
|
dependencies:
|
||||||
|
detect-libc "^1.0.2"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
needle "^2.2.0"
|
||||||
|
nopt "^4.0.1"
|
||||||
|
npm-packlist "^1.1.6"
|
||||||
|
npmlog "^4.0.2"
|
||||||
|
rc "^1.1.7"
|
||||||
|
rimraf "^2.6.1"
|
||||||
|
semver "^5.3.0"
|
||||||
|
tar "^4"
|
||||||
|
|
||||||
node-pre-gyp@^0.10.0:
|
node-pre-gyp@^0.10.0:
|
||||||
version "0.10.2"
|
version "0.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz#e8945c20ef6795a20aac2b44f036eb13cf5146e3"
|
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz#e8945c20ef6795a20aac2b44f036eb13cf5146e3"
|
||||||
@ -5431,6 +5472,10 @@ qs@6.5.1:
|
|||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||||
|
|
||||||
|
qs@6.5.2:
|
||||||
|
version "6.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
qs@~6.3.0:
|
qs@~6.3.0:
|
||||||
version "6.3.2"
|
version "6.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
|
||||||
@ -5498,7 +5543,16 @@ raw-body@2.3.2:
|
|||||||
iconv-lite "0.4.19"
|
iconv-lite "0.4.19"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
|
raw-body@2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
http-errors "1.6.3"
|
||||||
|
iconv-lite "0.4.23"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7, rc@^1.2.7:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user