Browse Source

用户注册

john 8 tháng trước cách đây
mục cha
commit
7a0ffec513

+ 2 - 1
node_expores/.gitignore

@@ -1,3 +1,4 @@
 node_modules
 base_files/*
-*.tar
+*.tar
+pm2Log/*

+ 23 - 0
node_expores/.zed/settings.json

@@ -0,0 +1,23 @@
+// Folder-specific settings
+//
+// For a full list of overridable settings, and general information on folder-specific settings,
+// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
+{
+  //
+  // 1. "VSCode"
+  // 2. "Atom"
+  // 3. "JetBrains"
+  // 4. "None"
+  // 5. "SublimeText"
+  // 6. "TextMate"
+  "base_keymap": "JetBrains",
+  // Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
+  "format_on_save": "off",
+  "ui_font_size": 17,
+  "buffer_font_size": 17,
+  "theme": {
+    "mode": "system",
+    "light": "Andromeda",
+    "dark": "One Dark"
+  }
+}

+ 21 - 9
node_expores/app.js

@@ -1,25 +1,37 @@
 import express from "express";
+import fileUpload from "express-fileupload";
+import bodyParser from "body-parser";
+
 import authors from "./router/authors/index.js";
+import authorsLogin from "./router/authors/login.js";
 import books from "./router/books/index.js";
 import files from "./router/files/index.js";
 import record from "./router/record/index.js";
 import types from "./router/types/index.js";
-import fileUpload from 'express-fileupload'
-const app = express();
-// default options
-app.use(fileUpload());
+
+import { generateToken, verifyToken } from "#utils";
 
 const port = 3000;
+const app = express();
+const json = express.json({type: '*/json'})
+
+app.use(fileUpload());
+app.use(json);
+app.use(bodyParser.urlencoded({extended: false}));
 
 app.get("/", (req, res) => {
   res.send("Hello World!");
 });
 
-app.use("/authors", authors);
-app.use("/books", books);
-app.use("/files", files);
-app.use("/record", record);
-app.use("/types", types);
+app.use("/api/v1/login", authors);
+app.use("/api/v1/files", files);
+
+app.use("/api/v1/*", verifyToken); // 注册token验证中间件
+
+app.use("/api/v1/auth", authorsLogin);
+app.use("/api/v1/books", books);
+app.use("/api/v1/record", record);
+app.use("/api/v1/types", types);
 
 app.listen(port, () => {
   console.log(`Example app listening on port ${port}`);

+ 102 - 0
node_expores/db/auth.js

@@ -0,0 +1,102 @@
+import connection from "./base.js";
+
+export async function auth_insert({
+  name = "",
+  user_id = "",
+  login_type,
+  email = "",
+  mobile = "",
+  password = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO authors (name, user_id, login_type, email, mobile, password, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        name,
+        user_id,
+        login_type,
+        email,
+        mobile,
+        password,
+        create_time,
+        update_time,
+      ];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      // console.log("Record inserted successfully:", result);
+      return resolve(result);
+    } catch (err) {
+      console.error("Error inserting data:", err);
+      // throw err;
+      return resolve(false);
+    }
+  });
+}
+
+// 是否存在重复的数据
+export function isHaveUserByUserId({ user_id = "", mobile = "", email = "" }) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE user_id = ? OR mobile = ? OR email = ?`,
+      [user_id, mobile, email],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}
+
+// 获取用户详情
+export function getUserInfoByuserId(user_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE user_id = ?`,
+      [user_id],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}
+
+// 用户是否已经注册
+export function isLoginUserByUserId({
+  mobile = "",
+  email = "",
+  password = "",
+}) {
+  console.log(7979, {
+    mobile,
+    email,
+    password,
+  });
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE password = ? AND (mobile = ? OR email = ? )`,
+      [password, mobile, email],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}

+ 1 - 1
node_expores/db/base.js

@@ -1,5 +1,5 @@
 import mysql from "mysql2";
-import environment from "../environment/index.js";
+import environment from "#environment";
 const connection = mysql.createConnection({
   ...environment.dbInfo(),
 });

+ 13 - 0
node_expores/db/files.js

@@ -52,3 +52,16 @@ export function getFileBymd5(md5Str) {
     });
   });
 }
+
+// 删除图片
+export function delFileBymd5(md5Str) {
+  return new Promise((resolve, reject) => {
+    connection.query(`update files SET is_del = 1 WHERE md5=?`, [md5Str], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(true); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}

+ 1 - 0
node_expores/db/index.js

@@ -1 +1,2 @@
 export * from './files.js';
+export * from './auth.js';

+ 8 - 0
node_expores/db/update.sql

@@ -0,0 +1,8 @@
+-- 更新 files 增加软删除数据
+ALTER TABLE cashbook.files ADD is_del INT DEFAULT 0 NOT NULL;
+
+-- 更新 authors 增加新字段
+ALTER TABLE cashbook.authors ADD user_id varchar(300) NOT NULL;
+ALTER TABLE cashbook.authors ADD CONSTRAINT authors_unique_2 UNIQUE KEY (user_id);
+ALTER TABLE cashbook.authors ADD create_time TIMESTAMP NOT NULL;
+ALTER TABLE cashbook.authors ADD update_time TIMESTAMP NOT NULL;

+ 11 - 7
node_expores/ecosystem.config.cjs

@@ -1,9 +1,13 @@
 module.exports = {
-  apps : [{
-    name   : "cashBook",
-    script: './app.js',
-    watch: true,
-    max_restarts: 20,
-    ignore_watch: [ "node_modules"]
-  }]
+  apps: [
+    {
+      name: "cashBook",
+      script: "./app.js",
+      error_file: "./pm2Log/err.log",
+      out_file: "./pm2Log/out.log",
+      watch: true,
+      max_restarts: 20,
+      ignore_watch: ["node_modules"],
+    },
+  ],
 };

+ 11 - 0
node_expores/environment/index.js

@@ -16,6 +16,17 @@ function dbInfo() {
   };
 }
 
+
+
 export default {
   dbInfo,
+  aes_info: () => {
+    return {
+    // key的长度必须为32bytes:
+    key: 'Passw0rdPassw0rdPassw0rdPassw0rd',
+    // iv的长度必须为16bytes:
+    iv: 'a1b2c3d4e5f6g7h8'
+    }
+  },
+  privateKey: 'cashbook1994'
 };

+ 9 - 2
node_expores/package.json

@@ -6,6 +6,8 @@
   "main": "index.js",
   "scripts": {
     "dev": "pm2 start ecosystem.config.cjs",
+    "dev:stop": "pm2 stop cashBook",
+    "dev:del": "pm2 delete cashBook",
     "start": "node app.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
@@ -21,13 +23,16 @@
   },
   "homepage": "https://github.com/Johnhong9527/newCashBook#readme",
   "dependencies": {
+    "body-parser": "^1.20.3",
     "crypto": "^1.0.1",
     "dayjs": "^1.11.13",
     "express": "^4.21.1",
     "express-fileupload": "^1.5.1",
+    "jsonwebtoken": "^9.0.2",
     "mysql": "^2.18.1",
     "mysql2": "^3.11.4",
-    "spark-md5": "^3.0.2"
+    "spark-md5": "^3.0.2",
+    "uuid": "^11.0.2"
   },
   "volta": {
     "node": "18.20.4"
@@ -35,11 +40,13 @@
   "devDependencies": {
     "@types/express": "^5.0.0",
     "@types/express-fileupload": "^1.5.1",
+    "@types/jsonwebtoken": "^9.0.7",
     "@types/mysql": "^2.15.26",
     "@types/node": "^22.9.0"
   },
   "imports": {
     "#db": "./db/index.js",
-    "#utils": "./utils/index.js"
+    "#utils": "./utils/index.js",
+    "#environment": "./environment/index.js"
   }
 }

+ 61 - 7
node_expores/router/authors/index.js

@@ -1,20 +1,74 @@
 // 添加账本
 import express from "express";
-
+import { generateToken, aes_encrypt } from "#utils";
+import {
+  isHaveUserByUserId,
+  auth_insert,
+  getUserInfoByuserId,
+  isLoginUserByUserId,
+} from "#db";
 const router = express.Router();
+import dayjs from "dayjs";
+
+import { v4 as uuidv4 } from "uuid";
 
 // middleware that is specific to this router
 router.use(function timeLog(req, res, next) {
   console.log("Time: ", Date.now());
   next();
 });
-// define the home page route
-router.get("/", function (req, res) {
-  res.send("authors home page");
+
+// 注册
+router.post("/register", async function (req, res) {
+  const { account, name = "", password = "", account_type } = req.body;
+
+  const user_id = uuidv4();
+
+  if (await isHaveUserByUserId({ user_id, mobile: account, email: account })) {
+    res.status(500).send("当前注册信息有重复,请检查之后重新提交!");
+    return;
+  }
+  // 写入数据
+  const insertInfo = await auth_insert({
+    name,
+    user_id,
+    login_type: account_type,
+    email: account_type === 1 ? "" : account,
+    mobile: account_type === 2 ? "" : account,
+    password: aes_encrypt(password),
+    create_time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+  });
+  res.send("注册成功,请重新登陆!");
 });
-// define the about route
-router.get("/about", function (req, res) {
-  res.send("About authors");
+
+// 登陆
+router.post("/", async function (req, res) {
+  const { account, account_type, password } = req.body;
+  const islogin = await isLoginUserByUserId({
+    password: aes_encrypt(password),
+    email: account,
+    mobile: account,
+  });
+  console.log(48, islogin);
+  if (!islogin) {
+    res.status(404).json({
+      code: 404,
+      msg: "登录失败,当前用户不存在",
+      data: {},
+    });
+    return;
+  }
+  delete islogin.password
+  delete islogin.login_type
+  delete islogin.id
+  const token = generateToken(islogin);
+  res.json({
+    code: 200,
+    msg: "登录成功",
+    data: { token },
+  });
+  // res.send("authors home page");
 });
 
 export default router;

+ 33 - 0
node_expores/router/authors/login.js

@@ -0,0 +1,33 @@
+// 添加账本
+import express from "express";
+
+const router = express.Router();
+
+// 更新密码
+router.put("/", function (req, res) {
+  res.send("authors home page");
+});
+
+// 注销
+router.delete("/", function (req, res) {
+  res.send("authors home page");
+});
+
+// 获取用户详情
+router.post("/user_info", function (req, res) {
+  let userInfo = {
+    name: req.body.decoded.name,
+    user_id: req.body.decoded.user_id,
+    create_time: req.body.decoded.create_time,
+    update_time: req.body.decoded.update_time,
+    mobile: req.body.decoded.mobile,
+    email: req.body.decoded.email
+  }
+  res.json({
+    code: 200,
+    data: userInfo
+  })
+});
+
+
+export default router;

+ 21 - 22
node_expores/router/files/index.js

@@ -2,12 +2,10 @@
 import express from "express";
 import path from "path";
 import fs from "node:fs";
-import {files_insert, ishaveFileBymd5, getFileBymd5} from '#db';
-import { computeFileMD5 } from '#utils';
-
+import { files_insert, ishaveFileBymd5, getFileBymd5 } from "#db";
 const router = express.Router();
 
-import dayjs from 'dayjs'
+import dayjs from "dayjs";
 
 router.use(function timeLog(req, res, next) {
   console.log("Time: ", Date.now());
@@ -16,13 +14,13 @@ router.use(function timeLog(req, res, next) {
 
 router.get("/:fileId", async function (req, res) {
   const fileId = req.params.fileId; // 获取 fileId 参数
-  const fileRow = await getFileBymd5(fileId)
+  const fileRow = await getFileBymd5(fileId);
 
-  if(!fileRow) {
-     return res.status(404).send("文件查询失败");
+  if (!fileRow) {
+    return res.status(404).send("文件查询失败");
   }
 
-  const uploadPath = './base_files/' + fileId;
+  const uploadPath = "./base_files/" + fileId;
   const filePath = path.resolve(uploadPath);
 
   // 检查文件是否存在
@@ -41,7 +39,7 @@ router.put("/", async function (req, res) {
   let uploadPath;
 
   if (!req.files || Object.keys(req.files).length === 0) {
-    return res.status(400).send('No files were uploaded.');
+    return res.status(400).send("No files were uploaded.");
   }
 
   sampleFile = req.files.sampleFile;
@@ -49,11 +47,11 @@ router.put("/", async function (req, res) {
   // const fileMd5 = await computeFileMD5(sampleFile);
   const fileMd5 = sampleFile.md5;
   // 如果存在重复的数据, 终止后续的操作
-  if(await ishaveFileBymd5(fileMd5)) {
+  if (await ishaveFileBymd5(fileMd5)) {
     res.send({
-      file_id: fileMd5
+      file_id: fileMd5,
     });
-    return
+    return;
   }
 
   const fileInfo = {
@@ -61,28 +59,29 @@ router.put("/", async function (req, res) {
     mimetype: sampleFile.mimetype,
     size: sampleFile.size,
     md5: sampleFile.md5,
-    create_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
-    update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
-  }
-  uploadPath = './base_files/' + sampleFile.md5;
+    create_time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+  };
+  uploadPath = "./base_files/" + sampleFile.md5;
 
   // 、移动上传文件至指定目录
-  sampleFile.mv(uploadPath, async function(err) {
+  sampleFile.mv(uploadPath, async function (err) {
     if (err) {
       return res.status(500).send(err);
     }
-    const isAdd = await files_insert(fileInfo)
-    if(!isAdd) {
+    const isAdd = await files_insert(fileInfo);
+    if (!isAdd) {
       res.status(500).send({
-        msg: '添加失败'
+        msg: "添加失败",
       });
-      return
+      return;
     }
     res.send({
-      file_id: fileMd5
+      file_id: fileMd5,
     });
   });
 });
+
 // define the about route
 router.get("/about", function (req, res) {
   res.send("About files");

+ 31 - 0
node_expores/utils/authorization.js

@@ -0,0 +1,31 @@
+import jwt from "jsonwebtoken";
+import environment from "#environment";
+const secretKey = "secretKey";
+
+// 生成token
+export function generateToken(payload) {
+  const token =
+    "Bearer " +
+    jwt.sign(payload, secretKey, {
+      // expiresIn: 60 * 60 * 24 * 7 * 4 * 12, // 一年过期
+      // expiresIn: 60 * 60 * 24 * 7 * 4, // 一个月
+      // expiresIn: 60 * 60 * 24 * 7, // 一周
+      expiresIn: 60 * 60 * 24, // 一天
+      // expiresIn: 60 * 60, // 一个小时
+    }, environment.privateKey, { algorithm: 'RS256' });
+
+  return token;
+}
+
+// 验证token
+export function verifyToken(req, res, next) {
+  const token = req.headers.authorization.split(" ")[1];
+  jwt.verify(token, secretKey, function (err, decoded) {
+    if (err) {
+      console.log("verify error", err);
+      return res.json({ code: "404", msg: "token无效" });
+    }
+    req.body.decoded = decoded
+    next();
+  });
+}

+ 36 - 13
node_expores/utils/createMD5.js

@@ -1,23 +1,21 @@
-import crypto from 'crypto';
-import fs from 'node:fs';
-import SparkMD5 from 'spark-md5';
+import crypto from "crypto";
+import fs from "node:fs";
+import SparkMD5 from "spark-md5";
+import environment from "#environment";
 
 export function createMD5(filePath) {
   return new Promise((res, rej) => {
-    const hash = crypto.createHash('md5');
+    const hash = crypto.createHash("md5");
     const rStream = fs.createReadStream(filePath);
-    rStream.on('data', (data) => {
+    rStream.on("data", (data) => {
       hash.update(data);
     });
-    rStream.on('end', () => {
-      res(hash.digest('hex'));
+    rStream.on("end", () => {
+      res(hash.digest("hex"));
     });
-  })
+  });
 }
 
-
-
-
 /**
  * 作者:起一个可以中奖的名字
  * 链接:https://juejin.cn/post/7142831050052665381
@@ -70,13 +68,38 @@ export function createMD5(filePath) {
 
 export function computeFileMD5(file) {
   return new Promise((resolve, reject) => {
-    const hash = crypto.createHash('md5');
+    const hash = crypto.createHash("md5");
 
     // 直接将整个 Buffer 更新到 hash
     hash.update(file.data);
 
     // 计算哈希值
-    const md5 = hash.digest('hex');
+    const md5 = hash.digest("hex");
     resolve(md5);
   });
 }
+
+
+// 加密
+export function aes_encrypt(msg) {
+  const aesInfo = environment.aes_info();
+  const cipher = crypto.createCipheriv("aes-256-cbc", aesInfo.key, aesInfo.iv);
+  // input encoding: utf8
+  // output encoding: hex
+  let encrypted = cipher.update(msg, "utf8", "hex");
+  encrypted += cipher.final("hex");
+  return encrypted;
+}
+
+// 解谜
+export function aes_decrypt(encrypted) {
+  const aesInfo = environment.aes_info();
+  const decipher = crypto.createDecipheriv(
+    "aes-256-cbc",
+    aesInfo.key,
+    aesInfo.iv,
+  );
+  let decrypted = decipher.update(encrypted, "hex", "utf8");
+  decrypted += decipher.final("utf8");
+  return decrypted;
+}

+ 1 - 0
node_expores/utils/index.js

@@ -1,2 +1,3 @@
 export * from './createMD5.js'
 export * from './files.js'
+export * from './authorization.js'

+ 100 - 3
node_expores/yarn.lock

@@ -57,6 +57,13 @@
   resolved "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
   integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
 
+"@types/jsonwebtoken@^9.0.7":
+  version "9.0.7"
+  resolved "https://registry.npmmirror.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2"
+  integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/mime@^1":
   version "1.3.5"
   resolved "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
@@ -126,7 +133,7 @@ bignumber.js@9.0.0:
   resolved "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
   integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
 
-body-parser@1.20.3:
+body-parser@1.20.3, body-parser@^1.20.3:
   version "1.20.3"
   resolved "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
   integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
@@ -144,6 +151,11 @@ body-parser@1.20.3:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
+
 busboy@^1.6.0:
   version "1.6.0"
   resolved "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@@ -235,6 +247,13 @@ destroy@1.2.0:
   resolved "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
   integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
 
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+  dependencies:
+    safe-buffer "^5.0.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -438,6 +457,74 @@ isarray@~1.0.0:
   resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
 
+jsonwebtoken@^9.0.2:
+  version "9.0.2"
+  resolved "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
+  integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^7.5.4"
+
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
+
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
+lodash.isstring@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+  integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
+
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
+
 long@^5.2.1:
   version "5.2.3"
   resolved "https://registry.npmmirror.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
@@ -490,7 +577,7 @@ ms@2.0.0:
   resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
 
-ms@2.1.3:
+ms@2.1.3, ms@^2.1.1:
   version "2.1.3"
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -607,7 +694,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@5.2.1:
+safe-buffer@5.2.1, safe-buffer@^5.0.1:
   version "5.2.1"
   resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -617,6 +704,11 @@ safe-buffer@5.2.1:
   resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+semver@^7.5.4:
+  version "7.6.3"
+  resolved "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+  integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
 send@0.19.0:
   version "0.19.0"
   resolved "https://registry.npmmirror.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
@@ -743,6 +835,11 @@ utils-merge@1.0.1:
   resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
+uuid@^11.0.2:
+  version "11.0.2"
+  resolved "https://registry.npmmirror.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875"
+  integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==
+
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"