diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2424aa6 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,294 @@ +{ + "env": { + "node": true, + "es2022": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [ + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "constructor-super": "warn", + "for-direction": "warn", + "getter-return": "warn", + "no-async-promise-executor": "error", + "no-await-in-loop": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "off", + "no-cond-assign": ["error", "always"], + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-constructor-return": "error", + "no-control-regex": "warn", + "no-debugger": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "off", + "no-duplicate-imports": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-ex-assign": "warn", + "no-fallthrough": "off", + "no-func-assign": "warn", + "no-import-assign": "error", + "no-inner-declarations": ["error", "both"], + "no-invalid-regexp": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-new-symbol": "error", + "no-obj-calls": "error", + "no-promise-executor-return": "warn", + "no-prototype-builtins": "off", + + "accessor-pairs": "off", + "arrow-body-style": ["warn", "as-needed"], + "block-scoped-var": "error", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "off", + "complexity": ["warn", 24], + "consistent-return": "off", + "consistent-this": ["warn", "that"], + "curly": ["error", "multi-or-nest"], + "default-case": "off", + "default-case-last": "error", + "default-param-last": "off", + "dot-notation": "error", + "eqeqeq": "off", + "func-name-matching": "off", + "func-names": ["warn", "never"], + "grouped-accessor-pairs": "off", + "guard-for-in": "off", + "id-denylist": "off", + "id-length": ["warn", {"min": 1, "max": 32}], + "id-match": "off", + "init-declarations": "off", + "logical-assignment-operators": ["warn", "always"], + "max-classes-per-file": "off", + "max-lines-per-function": "off", + "max-nested-callbacks": "off", + "max-params": "off", + "max-statements": "off", + "multiline-comment-style": "off", + "new-cap": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-bitwise": "off", + "no-caller": "error", + "no-case-declarations": "error", + "no-confusing-arrow": ["error", {"allowParens": true, "onlyOneSimpleParam": true}], + "no-console": ["error", {"allow": ["table", "group", "groupEnd", "dir"]}], + "no-continue": "off", + "no-delete-var": "error", + "no-div-regex": "warn", + "no-else-return": "error", + "no-empty": "warn", + "no-empty-function": "off", + "no-empty-static-block": "warn", + "no-eq-null": "off", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "warn", + "no-extra-boolean-cast": "error", + "no-extra-label": "error", + "no-extra-semi": "error", + "no-floating-decimal": "off", + "no-global-assign": "error", + "no-implicit-coercion": "off", + "no-implicit-globals": "warn", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "off", + "no-lone-blocks": "warn", + "no-lonely-if": "warn", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": ["warn"], + "no-multi-assign": "off", + "no-multi-str": "error", + "no-negated-condition": "error", + "no-nested-ternary": "error", + "no-new": "off", + "no-new-func": "error", + "no-new-object": "warn", + "no-new-wrappers": "error", + "no-nonoctal-decimal-escape": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-plusplus": "off", + "no-proto": "error", + "no-redeclare": "error", + "no-regex-spaces": "warn", + "no-restricted-exports": "off", + "no-restricted-globals": "off", + "no-restricted-imports": "off", + "no-restricted-properties": "off", + "no-restricted-syntax": "off", + "no-return-assign": "error", + "no-return-await": "warn", + "no-script-url": "error", + "no-sequences": "warn", + "no-shadow": "warn", + "no-shadow-restricted-names": "error", + "no-ternary": "off", + "no-throw-literal": "off", + "no-undef-init": "warn", + "no-undefined": "off", + "no-underscore-dangle": "warn", + "no-unneeded-ternary": "warn", + "no-unused-expressions": "warn", + "no-unused-labels": "error", + "no-useless-call": "error", + "no-useless-catch": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "warn", + "no-useless-escape": "error", + "no-useless-rename": "error", + "no-var": "error", + "no-void": "off", + "no-warning-comments": "off", + "no-with": "error", + "object-shorthand": ["warn", "always"], + "one-var": "off", + "one-var-declaration-per-line": "off", + "operator-assignment": ["warn", "always"], + "prefer-arrow-callback": "error", + "prefer-const": "error", + "prefer-destructuring": "warn", + "prefer-exponentiation-operator": "warn", + "prefer-named-capture-group": "off", + "prefer-numeric-literals": "warn", + "prefer-object-has-own": "error", + "prefer-object-spread": "warn", + "prefer-promise-reject-errors": "warn", + "prefer-regex-literals": "off", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "warn", + "quote-props": ["warn", "as-needed"], + "radix": "warn", + "require-await": "warn", + "require-unicode-regexp": "off", + "require-yield": "warn", + "sort-imports": "warn", + "sort-keys": "off", + "sort-vars": "warn", + "spaced-comment": ["warn", "always"], + "strict": "warn", + "symbol-description": "error", + "vars-on-top": "warn", + "yoda": ["warn", "never"], + + "array-bracket-newline": ["error", {"multiline": true}], + "array-bracket-spacing": ["warn", "never"], + "array-element-newline": ["warn", "consistent"], + "arrow-parens": ["warn", "as-needed"], + "arrow-spacing": ["error", {"before": true, "after": true}], + "block-spacing": ["error", "always"], + "brace-style": ["error", "1tbs", {"allowSingleLine": true}], + "comma-dangle": ["warn", "never"], + "comma-spacing": ["error", {"before": false, "after": true}], + "comma-style": ["error", "last"], + "computed-property-spacing": ["error", "never"], + "dot-location": ["error", "property"], + "eol-last": ["error", "always"], + "func-call-spacing": ["error", "never"], + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "never"], + "generator-star-spacing": ["warn", {"before": false, "after": true}], + "implicit-arrow-linebreak": ["error", "beside"], + "indent": ["error", "tab"], + "jsx-quotes": ["warn", "prefer-double"], + "key-spacing": ["warn", { + "beforeColon": false, "afterColon": true, + "mode": "minimum", "align": "value" + }], + "keyword-spacing": ["error", {"before": true, "after": true}], + "line-comment-position": "off", + "linebreak-style": ["error", "unix"], + "lines-around-comment": ["warn", { + "beforeBlockComment": true, + "beforeLineComment": true, + "allowBlockStart": true, + "allowBlockEnd": false, + "allowObjectStart": true, + "allowObjectEnd": false, + "allowArrayStart": true, + "allowArrayEnd": false, + "allowClassStart": false, + "allowClassEnd": false + }], + "lines-between-class-members": ["error", "always", {"exceptAfterSingleLine": true}], + "max-len": ["warn", { + "code": 160, "tabWidth": 2, "comments": 120, "ignoreUrls": true + }], + "max-statements-per-line": ["error", {"max": 1}], + "multiline-ternary": ["warn", "never"], + "new-parens": ["error", "always"], + "newline-per-chained-call": ["error", {"ignoreChainWithDepth": 3}], + "no-extra-parens": "off", + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], + "no-trailing-spaces": "error", + "no-whitespace-before-property": "error", + "nonblock-statement-body-position": ["error", "beside"], + "object-curly-newline": ["error", {"multiline": true}], + "object-curly-spacing": ["warn", "always"], + "object-property-newline": ["error", {"allowAllPropertiesOnSameLine": true}], + "operator-linebreak": ["error", "after"], + "padded-blocks": ["warn", {"blocks": "never", "classes": "always", "switches": "never"}, {"allowSingleLineBlocks": true}], + "padding-line-between-statements": ["warn", + { "blankLine": "always", "prev": "*", "next": ["return", "throw", "break"] }, + { "blankLine": "always", "prev": "*", "next": "case" }, + { "blankLine": "never", "prev": "case", "next": "*" }, + { "blankLine": "always", "prev": "*", "next": "default" }, + { "blankLine": "never", "prev": "default", "next": "*" }, + { "blankLine": "never", "prev": ["case", "default"], "next": ["case", "default"] }, + { "blankLine": "always", "prev": ["block", "block-like", "class", "function", "iife", "switch", "try", "while"], "next": ["class", "function", "if", "for", "switch", "while"] }, + { "blankLine": "always", "prev": ["class", "function", "if", "for", "switch", "while"], "next": "*" }, + { "blankLine": "any", "prev": "if", "next": "if" }, + { "blankLine": "always", "prev": ["let", "const"], "next": "*" }, + { "blankLine": "any", "prev": ["let", "const"], "next": ["let", "const"] }, + { "blankLine": "any", "prev": "let", "next": ["if", "for", "while"] }, + { "blankLine": "always", "prev": "import", "next": "*" }, + { "blankLine": "never", "prev": "import", "next": "import" } + ], + "quotes": ["error", "double", {"avoidEscape": true, "allowTemplateLiterals": true}], + "rest-spread-spacing": ["error", "never"], + "semi": ["error", "never"], + "semi-spacing": ["error", {"before": false, "after": true}], + "semi-style": ["error", "first"], + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always" }], + "space-in-parens": ["error", "never"], + "space-infix-ops": ["off"], + "space-unary-ops": ["error", {"words": true, "nonwords": false}], + "switch-colon-spacing": ["error", {"before": false, "after": true}], + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": ["error", "never"], + "unicode-bom": "off", + "wrap-iife": ["error", "outside", {"functionPrototypeMethods": true}], + "wrap-regex": "warn", + "yield-star-spacing": ["warn", "after"], + + "@typescript-eslint/no-empty-function": "off" + } +} diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000..0813e65 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,52 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# ESLint is a tool for identifying and reporting on patterns +# found in ECMAScript/JavaScript code. +# More details at https://github.com/eslint/eslint +# and https://eslint.org + +name: ESLint + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '0 12 * * *' + +jobs: + eslint: + name: Run eslint scanning + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install ESLint + run: | + npm install eslint@8.39.0 + npm install @microsoft/eslint-formatter-sarif@3.0.0 + npm install @typescript-eslint/eslint-plugin@5.59.1 + npm install @typescript-eslint/parser@5.59.1 + + - name: Run ESLint + run: npx eslint ./src/ + --config .eslintrc.json + --ext .js,.jsx,.ts,.tsx + --format @microsoft/eslint-formatter-sarif + --output-file eslint-results.sarif + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: eslint-results.sarif + wait-for-processing: true diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..f1303af Binary files /dev/null and b/bun.lockb differ diff --git a/dist/Logger.js b/dist/Logger.js index 34a6dde..1fd5aa6 100644 --- a/dist/Logger.js +++ b/dist/Logger.js @@ -8,18 +8,16 @@ const YELLOW = ESC + "[93m"; const PINK = ESC + "[95m"; const TEAL = ESC + "[96m"; const WHITE = ESC + "[97m"; -const COLOR = { - GRAY, DARK, RED, GREEN, YELLOW, PINK, TEAL, WHITE -}; +const COLOR = { GRAY, DARK, RED, GREEN, YELLOW, PINK, TEAL, WHITE }; +/* eslint no-console: "off" */ export default class Logger { actor; color; constructor(actor, color) { this.actor = actor; this.color = color; - if (!global.debug) { + if (!global.debug) this.debug = () => { }; // only enable debug logs when debugging is on - } } log(...message) { console.log(`${WHITE}[${DARK}${time()}${WHITE}] [${COLOR[this.color]}${this.actor}${WHITE}]${GRAY}`, message.join(" ")); @@ -37,6 +35,7 @@ export default class Logger { console.trace(`${WHITE}[${DARK}${time()}${WHITE}] [${COLOR[this.color]}${this.actor}${WHITE}]${WHITE}`, message.join(" ")); } } +/* eslint prefer-template: "off" */ function time() { const d = new Date(); const year = d.getUTCFullYear(); diff --git a/dist/config.d.ts b/dist/config.d.ts index c6af5d4..d9e61ac 100644 --- a/dist/config.d.ts +++ b/dist/config.d.ts @@ -1,4 +1,3 @@ -export declare const config: any; type ConfigValue = string | number | boolean; export default function getConfig(key: string, defaultValue?: T): T; export {}; diff --git a/dist/config.js b/dist/config.js index 26a3ed6..fa59406 100644 --- a/dist/config.js +++ b/dist/config.js @@ -1,34 +1,35 @@ +import Logger from "./Logger.js"; import { existsSync } from "fs"; import { readFile } from "fs/promises"; -import Logger from "./Logger.js"; -export const config = existsSync("config.json") ? JSON.parse(await readFile("config.json", "utf-8")) : {}; +const config = existsSync("config.json") ? JSON.parse(await readFile("config.json", "utf-8")) : {}; const logger = new Logger("config", "PINK"); export default function getConfig(key, defaultValue) { - let value = undefined; - let error = undefined; + let value; + let error; try { value = searchJsonKey(key); - //console.log(`json: [${searchJsonKey(key)}]`) + // console.log(`json: [${searchJsonKey(key)}]`) } catch (e) { error = e; } value = value ?? getEnvVar(key) ?? defaultValue; - //console.log("config:", key, value) + // console.log("config:", key, value) if (value != undefined) { if (error) logger.warn(error); if (defaultValue != undefined && typeof value != typeof defaultValue) { + // throw error when types don't match throw new Error(`Config type of ${key} does not match default value (${typeof defaultValue}).`); } return value; } - throw new Error(error) || new Error("Config key " + key + " not found"); + throw error || new Error(`Config key ${key} not found`); } function getEnvVar(key) { const value = process.env[key] ?? process.env[key.replaceAll(".", "_")]; if (!value) - return; + return undefined; return parseStringValue(value); } function parseStringValue(value) { @@ -43,25 +44,27 @@ function parseStringValue(value) { // string return value; } -function searchJsonKey(key, defaultValue) { +function searchJsonKey(key) { const path = key.split("."); - //console.log("[getConfig]", key, path) + // console.log("[getConfig]", key, path) let value = config[path[0]]; if (!value) { if (path.length > 1) throw `Config key ${key} not found (${path[0]} is invalid: ${value})`; - return; + return undefined; } for (let i = 1; i < path.length; i++) { - if (typeof value != "object") + if (typeof value != "object" || value == null) throw `Config key ${key} not found (${path.slice(0, i).join(".")} is ${typeof value})`; + if (!(path[i] in value)) + throw `Config key ${key} not found (${path[i]} does not exist on ${path.slice(0, i).join(".")})`; value = value[path[i]]; } if (isValid(value)) return value; if (typeof value == "object") logger.warn(`Invalid config value (${key}):`, value instanceof Array ? "[...]" : "{...}"); - return; + return undefined; } function isValid(value) { return ["string", "number", "boolean"].includes(typeof value); diff --git a/dist/main.d.ts b/dist/main.d.ts index 6b7fd1c..1013027 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -1,2 +1,2 @@ -import { Sequelize } from 'sequelize-typescript'; +import { Sequelize } from "sequelize-typescript"; export declare const sql: Sequelize; diff --git a/dist/main.js b/dist/main.js index 6cb7d2a..8215a3e 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,10 +1,11 @@ +import Mail from "./models/Mail.js"; import POP3Server from "./pop3/POP3Server.js"; import SMTPServer from "./smtp/SMTPServer.js"; -import { Sequelize } from 'sequelize-typescript'; +import { Sequelize } from "sequelize-typescript"; import User from "./models/User.js"; -import Mail from "./models/Mail.js"; import getConfig from "./config.js"; import { readFile } from "node:fs/promises"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any global.debug = getConfig("debug", false); export const sql = new Sequelize({ database: getConfig("db.database"), @@ -20,7 +21,7 @@ export const sql = new Sequelize({ // password: "1234" // }) secure: if (getConfig("pop3s.enabled", false) || getConfig("smtps.enabled", false)) { - let tlsKey, tlsCert; + let tlsCert, tlsKey; try { tlsKey = await readFile(getConfig("tls.key", "cert/privkey.pem")); tlsCert = await readFile(getConfig("tls.cert", "cert/fullchain.pem")); diff --git a/dist/pop3/POP3Server.js b/dist/pop3/POP3Server.js index da61419..682793c 100644 --- a/dist/pop3/POP3Server.js +++ b/dist/pop3/POP3Server.js @@ -15,10 +15,10 @@ export default class POP3Server { throw new Error("TLS key or certificate not provided"); this.server = useTLS ? tls.createServer({ key, - cert, + cert }, this.connection) : net.createServer(); this.server.listen(port, () => { - logger.log("Server listening on port " + port); + logger.log(`Server listening on port ${port}`); }); if (!useTLS) this.server.on("connection", this.connection); @@ -31,7 +31,7 @@ export default class POP3Server { const markedForDeletion = []; sock.on("data", async (data) => { const msg = data.toString(); - logger.log("Received data: " + msg); + logger.log(`Received data: ${msg}`); const args = msg.split(" ").slice(1); if (msg.startsWith("CAPA")) { // list capabilities sock.write("+OK Capability list follows\r\nUSER\r\n.\r\n"); @@ -41,19 +41,17 @@ export default class POP3Server { return void sock.write("-ERR Invalid username or password\r\n"); username = args[0].trim().toLowerCase(); if (username.includes("@")) { - if (!username.endsWith("@" + getConfig("host", "localhost"))) { + if (!username.endsWith(`@${getConfig("host", "localhost")}`)) return void sock.write("-ERR Invalid username or password\r\n"); - } username = username.substring(0, username.lastIndexOf("@")); } - if (username.startsWith("\"") && username.endsWith("\"")) { + if (username.startsWith("\"") && username.endsWith("\"")) username = username.substring(1, username.length - 1); - } else if (username.includes("@")) { // this is not allowed return void sock.write("-ERR Invalid username or password\r\n"); } - let _user = await User.findOne({ where: { username: username } }); + const _user = await User.findOne({ where: { username } }); if (!_user) return void sock.write("-ERR Invalid username or password\r\n"); user = _user; @@ -66,16 +64,14 @@ export default class POP3Server { const hash = createHash("sha256"); hash.update(password); const hashedPassword = hash.digest("hex"); - if (user.password == hashedPassword) { + if (user.password == hashedPassword) sock.write("+OK Logged in\r\n"); - } - else { + else sock.write("-ERR Invalid username or password\r\n"); - } } else if (msg.startsWith("STAT")) { // get number of messages and total size const mails = await user.$count("mails"); - sock.write("+OK " + mails + "\r\n"); + sock.write(`+OK ${mails}\r\n`); } else if (msg.startsWith("QUIT")) { sock.write("+OK Bye\r\n"); @@ -83,10 +79,9 @@ export default class POP3Server { } else if (msg.startsWith("LIST")) { const mails = await user.$get("mails"); - sock.write("+OK " + mails.length + " messages\r\n"); - for (let i = 0; i < mails.length; i++) { - sock.write(i + "\r\n"); - } + sock.write(`+OK ${mails.length} messages\r\n`); + for (let i = 0; i < mails.length; i++) + sock.write(`${i}\r\n`); sock.write(".\r\n"); } else if (msg.startsWith("RETR")) { @@ -95,8 +90,8 @@ export default class POP3Server { const mail = await user.getMail(parseInt(args[0].trim())); if (!mail) return void sock.write("-ERR No such message\r\n"); - const content = await readFile("mails/" + mail.content + ".txt", "utf-8"); - sock.write("+OK\r\n" + content + "\r\n.\r\n"); + const content = await readFile(`mails/${mail.content}.txt`, "utf-8"); + sock.write(`+OK\r\n${content}\r\n.\r\n`); } else if (msg.startsWith("TOP")) { if (args.length < 2) @@ -104,16 +99,15 @@ export default class POP3Server { const mail = await user.getMail(parseInt(args[0].trim())); if (!mail) return void sock.write("-ERR No such message\r\n"); - const content = await readFile("mails/" + mail.content + ".txt", "utf-8"); + const content = await readFile(`mails/${mail.content}.txt`, "utf-8"); const lines = content.split("\r\n"); const top = lines.slice(0, 10).join("\r\n"); - sock.write("+OK\r\n" + top + "\r\n.\r\n"); + sock.write(`+OK\r\n${top}\r\n.\r\n`); } else if (msg.startsWith("UIDL")) { // get unique id of message const mails = await user.$get("mails"); - for (let i = 0; i < mails.length; i++) { - sock.write(i + " " + mails[i].uuid + "\r\n"); - } + for (let i = 0; i < mails.length; i++) + sock.write(`${i} ${mails[i].uuid}\r\n`); sock.write(".\r\n"); } else if (msg.startsWith("DELE")) { @@ -129,9 +123,8 @@ export default class POP3Server { sock.write("+OK\r\n"); } else if (msg.startsWith("RSET")) { - for (const mail of markedForDeletion) { + for (const mail of markedForDeletion) await mail.restore(); - } sock.write("+OK\r\n"); } }); diff --git a/dist/smtp/SMTP.js b/dist/smtp/SMTP.js index 5cc1e2f..08c0a6f 100644 --- a/dist/smtp/SMTP.js +++ b/dist/smtp/SMTP.js @@ -11,9 +11,9 @@ export default class SMTP { await writeFile(`mails/${id}.txt`, info.content); logger.log(`Saved mail to mails/${id}.txt`); const serverName = getConfig("host"); - if (info.from.endsWith("@" + serverName)) { + if (info.from.endsWith(`@${serverName}`)) { logger.log("Mail is from this server."); - if (!info.to.every(email => email.endsWith("@" + serverName))) { // if not all recipients are on this server + if (!info.to.every(email => email.endsWith(`@${serverName}`))) { // if not all recipients are on this server logger.log("Not all recipients are on this server. Will forward mail to other servers."); logger.error("Forwarding mails to other servers is not implemented yet."); // TODO: forward mail to other servers using SMTPClient @@ -21,15 +21,14 @@ export default class SMTP { } logger.log("All recipients are on this server."); } - else if (!info.to.every(email => email.endsWith("@" + serverName))) { + else if (!info.to.every(email => email.endsWith(`@${serverName}`))) logger.warn("Not all recipients are from this server. Will NOT forward mail to other servers."); - } - const recipients = info.to.filter(email => email.endsWith("@" + serverName)); + const recipients = info.to.filter(email => email.endsWith(`@${serverName}`)); for (const rec of recipients) { - logger.log("Forwarding mail to " + rec); + logger.log(`Forwarding mail to ${rec}`); const user = await User.findOne({ where: { username: rec.substring(0, rec.lastIndexOf("@")) } }); if (!user) { - logger.error("User " + rec + " does not exist."); + logger.error(`User ${rec} does not exist.`); // Since we verify the recipients at the RCPT TO command, we should never get here, but you never know continue; } @@ -38,7 +37,7 @@ export default class SMTP { to: rec, content: id }); - logger.log("Forwarded mail to " + rec); + logger.log(`Forwarded mail to ${rec}`); } } } diff --git a/dist/smtp/SMTPClient.js b/dist/smtp/SMTPClient.js index d0c7b77..700636d 100644 --- a/dist/smtp/SMTPClient.js +++ b/dist/smtp/SMTPClient.js @@ -13,10 +13,10 @@ export default class SMTPClient { sock.on("connect", async () => { logger.log("Connected to server"); sock.write(`EHLO ${getConfig("host", "localhost")}\r\n`); - sock.write("MAIL FROM:<" + from + ">\r\n"); - sock.write("RCPT TO:<" + to + ">\r\n"); + sock.write(`MAIL FROM:<${from}>\r\n`); + sock.write(`RCPT TO:<${to}>\r\n`); sock.write("DATA\r\n"); - sock.write(content + "\r\n.\r\n"); + sock.write(`${content}\r\n.\r\n`); sock.write("QUIT\r\n"); }); } diff --git a/dist/smtp/SMTPServer.js b/dist/smtp/SMTPServer.js index d3e7edd..d30bbcf 100644 --- a/dist/smtp/SMTPServer.js +++ b/dist/smtp/SMTPServer.js @@ -109,12 +109,10 @@ export default class SMTPServer { // This command is used to verify if a user exists, but that can be a security risk + it is also done with RCPT TO anyway status(502); } - else if (msg.startsWith("EXPN")) { + else if (msg.startsWith("EXPN")) status(502); - } - else { + else status(502); - } }); sock.on("close", () => { logger.log("Client disconnected"); diff --git a/dist/smtp/status.js b/dist/smtp/status.js index 253afb1..2c00270 100644 --- a/dist/smtp/status.js +++ b/dist/smtp/status.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars const STATUS_SUBJECTS = [ { code: 0, name: "Other or Undefined" }, { code: 1, name: "Addressing" }, @@ -8,7 +9,7 @@ const STATUS_SUBJECTS = [ { code: 6, name: "Message Content or Media" }, { code: 7, name: "Security or Policy" } ]; -let ok = true; +const ok = true; const STATUS_CODES = { // OK 211: { code: 211, ok }, @@ -91,9 +92,8 @@ export function status(code, options) { function replaceArgs(statusCode, args, msg) { if (!("message" in statusCode)) return msg || ""; - let message = statusCode.message; - for (let i = 0; message.includes("%"); i++) { + const message = statusCode.message; + for (let i = 0; message.includes("%"); i++) message.replace("%", args[i] ?? msg); - } return message; } diff --git a/dist/test/main.js b/dist/test/main.js index 8e8f8b1..7d6b5b9 100644 --- a/dist/test/main.js +++ b/dist/test/main.js @@ -1,10 +1,10 @@ import Logger from "../Logger.js"; import { status } from "../smtp/status.js"; const ESC = "\u001b"; -const RED = ESC + "[91m"; -const YELLOW = ESC + "[93m"; -const BLUE = ESC + "[94m"; -const GREEN = ESC + "[92m"; +const RED = `${ESC}[91m`; +const YELLOW = `${ESC}[93m`; +const BLUE = `${ESC}[94m`; +const GREEN = `${ESC}[92m`; let errors = 0; const logger = new Logger("test", "TEAL"); logger.debug("debug log"); @@ -18,8 +18,8 @@ const smtpStatusTestCases = [ ["530 5.7.0", "530 5.7.0 Authentication required\r\n"], ["538 5.7.11", "538 5.7.11 Encryption required for requested authentication mechanism\r\n"] ]; -for (let e of smtpStatusTestCases) { - const args = (e[0] + "").split(" "); +for (const e of smtpStatusTestCases) { + const args = (`${e[0]}`).split(" "); const statusMsg = status(Number(args[0]), args[1]); logger.log(`SMTP ${e[0]}: [${statusMsg.substring(0, statusMsg.length - 2)}]`); if (statusMsg != e[1]) { diff --git a/package-lock.json b/package-lock.json index b47e5b3..8fbba2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "mailverse", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,66 +9,626 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "argon2": "^0.31.2", "mariadb": "^3.1.2", "mysql2": "^3.3.0", "sequelize": "^6.31.1", "sequelize-typescript": "^2.1.5" }, "devDependencies": { - "@types/node": "^20.2.3" + "@microsoft/eslint-formatter-sarif": "^3.0.0", + "@types/node": "^18.16.3", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", + "eslint": "^8.39.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.40.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@microsoft/eslint-formatter-sarif": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint": "^8.9.0", + "jschardet": "latest", + "lodash": "^4.17.14", + "utf8": "^3.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "engines": { + "node": ">=10" } }, "node_modules/@types/debug": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/geojson": { "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "dev": true, + "license": "MIT" }, "node_modules/@types/ms": { "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", - "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" + "version": "18.16.5", + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "dev": true, + "license": "MIT" }, "node_modules/@types/validator": { "version": "13.7.15", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.15.tgz", - "integrity": "sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ==" + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/type-utils": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.8.2", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argon2": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.31.2.tgz", + "integrity": "sha512-QSnJ8By5Mth60IEte45w9Y7v6bWcQw3YhRtJKKN8oNCxnTLDiv/AXXkDPf2srTMfxFVn3QJdVv2nhXESsUa+Yg==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "@phc/format": "^1.0.0", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, "node_modules/debug": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -81,36 +641,383 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/denque": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { "node": ">=0.10" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dottie": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz", - "integrity": "sha512-4liA0PuRkZWQFQjwBypdxPfZaRWiv5tkhMXY2hzsa2pNf5s7U3m9cwUchfNKe8wZQxdGPQQzO6Rm2uGe0rvohQ==" + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.40.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.15.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "license": "ISC" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } }, "node_modules/generate-function": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", "dependencies": { "is-property": "^1.0.2" } }, "node_modules/glob": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -120,16 +1027,89 @@ "path-is-absolute": "^1.0.0" }, "engines": { - "node": "*" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 6" } }, "node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -137,18 +1117,47 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflection": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", - "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", "engines": [ "node >= 0.4.0" - ] + ], + "license": "MIT" }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -156,39 +1165,170 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/is-property": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jschardet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.0.0.tgz", + "integrity": "sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" }, "node_modules/long": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "license": "Apache-2.0" }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "7.18.3", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dependencies": { - "yallist": "^4.0.0" + "semver": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/mariadb": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.1.2.tgz", - "integrity": "sha512-ILlC54fkXkvizTJZC1uP7f/REBxuu1k+OWzpiIITIEdS+dGIjFe/Ob3EW9KrdtBa38l3z+odz6elva0RG/y5og==", + "license": "LGPL-2.1-or-later", "dependencies": { "@types/geojson": "^7946.0.10", "@types/node": "^17.0.45", @@ -202,21 +1342,31 @@ }, "node_modules/mariadb/node_modules/@types/node": { "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "license": "MIT" }, - "node_modules/mariadb/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, "node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -224,18 +1374,58 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "license": "MIT", "engines": { "node": "*" } }, "node_modules/moment-timezone": { "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "license": "MIT", "dependencies": { "moment": "^2.29.4" }, @@ -245,13 +1435,11 @@ }, "node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "license": "MIT" }, "node_modules/mysql2": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.3.0.tgz", - "integrity": "sha512-/+LGlWgXxppcomT1NqkUnaDchcS9tebsXsj5eZQhnAB/onlSLgqMA5W9ZRHcZPKyqg3XROJDgomB4eCkn6Ca2g==", + "license": "MIT", "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", @@ -268,16 +1456,14 @@ }, "node_modules/mysql2/node_modules/lru-cache": { "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "license": "ISC", "engines": { "node": ">=16.14" } }, "node_modules/named-placeholders": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", "dependencies": { "lru-cache": "^7.14.1" }, @@ -285,55 +1471,337 @@ "node": ">=12.0.0" } }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", "engines": { - "node": ">=12" + "node": "^16 || ^18 || >= 20" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pg-connection-string": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry-as-promised": { + "version": "7.0.4", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "peer": true + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } }, - "node_modules/retry-as-promised": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", - "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "license": "MIT" }, "node_modules/semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "version": "7.5.0", + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -344,21 +1812,28 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + "version": "0.0.5" }, "node_modules/sequelize": { "version": "6.31.1", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.31.1.tgz", - "integrity": "sha512-cahWtRrYLjqoZP/aurGBoaxn29qQCF4bxkAUPEQ/ozjJjt6mtL4Q113S3N39mQRmX5fgxRbli+bzZARP/N51eg==", "funding": [ { "type": "opencollective", "url": "https://opencollective.com/sequelize" } ], + "license": "MIT", "dependencies": { "@types/debug": "^4.1.7", "@types/validator": "^13.7.1", @@ -412,16 +1887,14 @@ }, "node_modules/sequelize-pool": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", - "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/sequelize-typescript": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/sequelize-typescript/-/sequelize-typescript-2.1.5.tgz", - "integrity": "sha512-x1CNODct8gJyfZPwEZBU5uVGNwgJI2Fda913ZxD5ZtCSRyTDPBTS/0uXciF+MlCpyqjpmoCAPtudQWzw579bzA==", + "license": "MIT", "dependencies": { "glob": "7.2.0" }, @@ -435,403 +1908,299 @@ "sequelize": ">=6.20.1" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, - "node_modules/validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/wkx": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", - "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", - "dependencies": { - "@types/node": "*" + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - }, - "dependencies": { - "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "requires": { - "@types/ms": "*" + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "@types/geojson": { - "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" - }, - "@types/node": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", - "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" - }, - "@types/validator": { - "version": "13.7.15", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.15.tgz", - "integrity": "sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/sqlstring": { + "version": "2.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" - }, - "dottie": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz", - "integrity": "sha512-4liA0PuRkZWQFQjwBypdxPfZaRWiv5tkhMXY2hzsa2pNf5s7U3m9cwUchfNKe8wZQxdGPQQzO6Rm2uGe0rvohQ==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "requires": { - "is-property": "^1.0.2" + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "inflection": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", - "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "mariadb": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.1.2.tgz", - "integrity": "sha512-ILlC54fkXkvizTJZC1uP7f/REBxuu1k+OWzpiIITIEdS+dGIjFe/Ob3EW9KrdtBa38l3z+odz6elva0RG/y5og==", - "requires": { - "@types/geojson": "^7946.0.10", - "@types/node": "^17.0.45", - "denque": "^2.1.0", - "iconv-lite": "^0.6.3", - "lru-cache": "^7.14.0" - }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" + "node_modules/toposort-class": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", - "requires": { - "moment": "^2.29.4" + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mysql2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.3.0.tgz", - "integrity": "sha512-/+LGlWgXxppcomT1NqkUnaDchcS9tebsXsj5eZQhnAB/onlSLgqMA5W9ZRHcZPKyqg3XROJDgomB4eCkn6Ca2g==", - "requires": { - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru-cache": "^8.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" }, - "dependencies": { - "lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", - "requires": { - "lru-cache": "^7.14.1" + "node_modules/typescript": { + "version": "5.0.4", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } + "engines": { + "node": ">=12.20" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "peer": true + "node_modules/utf8": { + "version": "3.0.0", + "dev": true, + "license": "MIT" }, - "retry-as-promised": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", - "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", - "requires": { - "lru-cache": "^6.0.0" + "node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, - "sequelize": { - "version": "6.31.1", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.31.1.tgz", - "integrity": "sha512-cahWtRrYLjqoZP/aurGBoaxn29qQCF4bxkAUPEQ/ozjJjt6mtL4Q113S3N39mQRmX5fgxRbli+bzZARP/N51eg==", - "requires": { - "@types/debug": "^4.1.7", - "@types/validator": "^13.7.1", - "debug": "^4.3.3", - "dottie": "^2.0.2", - "inflection": "^1.13.2", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "moment-timezone": "^0.5.35", - "pg-connection-string": "^2.5.0", - "retry-as-promised": "^7.0.3", - "semver": "^7.3.5", - "sequelize-pool": "^7.1.0", - "toposort-class": "^1.0.1", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "wkx": "^0.5.0" + "node_modules/validator": { + "version": "13.9.0", + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "sequelize-pool": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", - "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==" + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "sequelize-typescript": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/sequelize-typescript/-/sequelize-typescript-2.1.5.tgz", - "integrity": "sha512-x1CNODct8gJyfZPwEZBU5uVGNwgJI2Fda913ZxD5ZtCSRyTDPBTS/0uXciF+MlCpyqjpmoCAPtudQWzw579bzA==", - "requires": { - "glob": "7.2.0" + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, - "validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } }, - "wkx": { + "node_modules/wkx": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", - "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", - "requires": { + "license": "MIT", + "dependencies": { "@types/node": "*" } }, - "wrappy": { + "node_modules/word-wrap": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "license": "ISC" }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 0b2ac42..342a6e3 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "", "main": "dist/main.js", "scripts": { + "stylefix": "npx eslint ./src/ --fix", + "style": "npx eslint ./src/", + "build": "npm run stylefix & tsc", "test": "node ./dist/test/main.js" }, "repository": { @@ -18,12 +21,17 @@ "homepage": "https://github.com/creelonestudios/mailverse#readme", "type": "module", "dependencies": { + "argon2": "^0.31.2", "mariadb": "^3.1.2", "mysql2": "^3.3.0", "sequelize": "^6.31.1", "sequelize-typescript": "^2.1.5" }, "devDependencies": { - "@types/node": "^20.2.3" + "@microsoft/eslint-formatter-sarif": "^3.0.0", + "@types/node": "^18.16.3", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", + "eslint": "^8.39.0" } } diff --git a/src/Logger.ts b/src/Logger.ts index 26451e4..0781e39 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,4 +1,6 @@ const ESC = "\u001b" + +// eslint-disable-next-line @typescript-eslint/no-unused-vars const RESET = ESC + "[m" const GRAY = ESC + "[37m" const DARK = ESC + "[90m" @@ -9,18 +11,15 @@ const PINK = ESC + "[95m" const TEAL = ESC + "[96m" const WHITE = ESC + "[97m" -const COLOR = { - GRAY, DARK, RED, GREEN, YELLOW, PINK, TEAL, WHITE -} +const COLOR = { GRAY, DARK, RED, GREEN, YELLOW, PINK, TEAL, WHITE } +/* eslint no-console: "off" */ export default class Logger { constructor(private readonly actor: string, private readonly color: keyof typeof COLOR) { - if (!global.debug) { - this.debug = () => {} // only enable debug logs when debugging is on - } + if (!global.debug) this.debug = () => {} // only enable debug logs when debugging is on } - + log(...message: string[]) { console.log(`${WHITE}[${DARK}${time()}${WHITE}] [${COLOR[this.color]}${this.actor}${WHITE}]${GRAY}`, message.join(" ")) } @@ -43,13 +42,15 @@ export default class Logger { } +/* eslint prefer-template: "off" */ function time() { const d = new Date() const year = d.getUTCFullYear() - const month = (d.getUTCMonth()+"").padStart(2,"0") - const day = (d.getUTCDate()+"").padStart(2,"0") - const hours = (d.getUTCHours()+"").padStart(2,"0") - const mins = (d.getUTCMinutes()+"").padStart(2,"0") - const secs = (d.getUTCSeconds()+"").padStart(2,"0") + const month = (d.getUTCMonth()+"").padStart(2, "0") + const day = (d.getUTCDate()+"").padStart(2, "0") + const hours = (d.getUTCHours()+"").padStart(2, "0") + const mins = (d.getUTCMinutes()+"").padStart(2, "0") + const secs = (d.getUTCSeconds()+"").padStart(2, "0") + return `${year}-${month}-${day} ${hours}:${mins}:${secs}` } diff --git a/src/config.ts b/src/config.ts index 52ecddf..c58acdf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,39 +1,46 @@ +import Logger from "./Logger.js" import { existsSync } from "fs" import { readFile } from "fs/promises" -import Logger from "./Logger.js" -export const config = existsSync("config.json") ? JSON.parse(await readFile("config.json", "utf-8")) : {} +const config: Json = existsSync("config.json") ? JSON.parse(await readFile("config.json", "utf-8")) : {} const logger = new Logger("config", "PINK") type ConfigValue = string | number | boolean +type Json = { [P in string]: Json | ConfigValue | null } export default function getConfig(key: string, defaultValue?: T): T { - let value: ConfigValue | undefined = undefined - let error: string | undefined = undefined + let value: ConfigValue | undefined + let error: string | undefined + try { value = searchJsonKey(key) - //console.log(`json: [${searchJsonKey(key)}]`) - } catch (e: any) { - error = e + + // console.log(`json: [${searchJsonKey(key)}]`) + } catch (e: unknown) { + error = e as string } - + value = value ?? getEnvVar(key) ?? defaultValue - //console.log("config:", key, value) + + // console.log("config:", key, value) if (value != undefined) { if (error) logger.warn(error) if (defaultValue != undefined && typeof value != typeof defaultValue) { + // throw error when types don't match throw new Error(`Config type of ${key} does not match default value (${typeof defaultValue}).`) } + return value as T } - throw new Error(error) || new Error("Config key " + key + " not found") + throw error || new Error(`Config key ${key} not found`) } function getEnvVar(key: string): ConfigValue | undefined { const value = process.env[key] ?? process.env[key.replaceAll(".", "_")] - if (!value) return + + if (!value) return undefined return parseStringValue(value) } @@ -42,33 +49,40 @@ function parseStringValue(value: string): ConfigValue { // boolean if (value == "true") return true else if (value == "false") return false + // number else if (!isNaN(Number(value))) return Number(value) + + // string return value } -function searchJsonKey(key: string, defaultValue?: ConfigValue): ConfigValue | undefined { +function searchJsonKey(key: string): ConfigValue | undefined { const path = key.split(".") - //console.log("[getConfig]", key, path) - let value: any = config[path[0]] + // console.log("[getConfig]", key, path) + + let value: unknown = config[path[0]] if (!value) { if (path.length > 1) throw `Config key ${key} not found (${path[0]} is invalid: ${value})` - return + + return undefined } for (let i = 1; i < path.length; i++) { - if (typeof value != "object") throw `Config key ${key} not found (${path.slice(0, i).join(".")} is ${typeof value})` - value = value[path[i]] + if (typeof value != "object" || value == null) throw `Config key ${key} not found (${path.slice(0, i).join(".")} is ${typeof value})` + if (!(path[i] in value)) throw `Config key ${key} not found (${path[i]} does not exist on ${path.slice(0, i).join(".")})` + + value = (value as Json)[path[i]] } if (isValid(value)) return value if (typeof value == "object") logger.warn(`Invalid config value (${key}):`, value instanceof Array ? "[...]" : "{...}") - return + return undefined } -function isValid(value: any): value is ConfigValue { +function isValid(value: unknown): value is ConfigValue { return ["string", "number", "boolean"].includes(typeof value) } diff --git a/src/main.ts b/src/main.ts index b6518ef..c087ce3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,21 +1,22 @@ +import { Dialect } from "sequelize" +import Mail from "./models/Mail.js" import POP3Server from "./pop3/POP3Server.js" import SMTPServer from "./smtp/SMTPServer.js" -import { Sequelize } from 'sequelize-typescript'; -import User from "./models/User.js"; -import Mail from "./models/Mail.js"; -import getConfig from "./config.js"; -import { readFile } from "node:fs/promises"; -import { Dialect } from "sequelize" +import { Sequelize } from "sequelize-typescript" +import User from "./models/User.js" +import getConfig from "./config.js" +import { readFile } from "node:fs/promises" +// eslint-disable-next-line @typescript-eslint/no-explicit-any global.debug = getConfig("debug", false) as any export const sql = new Sequelize({ - database: getConfig("db.database"), - dialect: getConfig("db.dialect"), - username: getConfig("db.username"), - password: getConfig("db.password"), - models: [User, Mail] -}); + database: getConfig("db.database"), + dialect: getConfig("db.dialect"), + username: getConfig("db.username"), + password: getConfig("db.password"), + models: [User, Mail] +}) // await sql.sync({ alter: true }) @@ -26,7 +27,8 @@ export const sql = new Sequelize({ // }) secure: if (getConfig("pop3s.enabled", false) || getConfig("smtps.enabled", false)) { - let tlsKey: Buffer, tlsCert: Buffer + let tlsCert: Buffer, tlsKey: Buffer + try { tlsKey = await readFile(getConfig("tls.key", "cert/privkey.pem")) tlsCert = await readFile(getConfig("tls.cert", "cert/fullchain.pem")) diff --git a/src/models/Mail.ts b/src/models/Mail.ts index ddd9e86..766d71d 100644 --- a/src/models/Mail.ts +++ b/src/models/Mail.ts @@ -1,9 +1,9 @@ -import { DataTypes } from "sequelize"; -import { AllowNull, BelongsTo, Column, ForeignKey, Model, PrimaryKey, Table, Unique } from "sequelize-typescript"; -import User from "./User.js"; +import { AllowNull, BelongsTo, Column, ForeignKey, Model, PrimaryKey, Table, Unique } from "sequelize-typescript" +import { DataTypes } from "sequelize" +import User from "./User.js" @Table({ - paranoid: true, + paranoid: true, tableName: "mails" }) export default class Mail extends Model { @@ -12,7 +12,7 @@ export default class Mail extends Model { @Unique @PrimaryKey @Column({ - type: DataTypes.UUID, + type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }) declare uuid: string @@ -28,11 +28,11 @@ export default class Mail extends Model { @AllowNull(false) @Column(DataTypes.STRING) declare content: string - + @ForeignKey(() => User) - declare userUuid: string; - + declare userUuid: string + @BelongsTo(() => User) - declare user: User; + declare user: User -} \ No newline at end of file +} diff --git a/src/models/User.ts b/src/models/User.ts index f89d4fd..2fc1f7a 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,10 +1,10 @@ -import { DataTypes } from "sequelize"; -import { AllowNull, Column, HasMany, Model, PrimaryKey, Table, Unique } from "sequelize-typescript"; -import Mail from "./Mail.js"; -import { createHash } from "node:crypto"; +import { AllowNull, Column, HasMany, Model, PrimaryKey, Table, Unique } from "sequelize-typescript" +import { DataTypes } from "sequelize" +import Mail from "./Mail.js" +import { createHash } from "node:crypto" @Table({ - paranoid: true, + paranoid: true, tableName: "users" }) export default class User extends Model { @@ -25,19 +25,22 @@ export default class User extends Model { type: DataTypes.STRING, set(value) { const hash = createHash("sha256") + hash.update(String(value)) - this.setDataValue("password", hash.digest("hex")); + this.setDataValue("password", hash.digest("hex")) } }) declare password: string - + @HasMany(() => Mail) - declare mails: Mail[]; + declare mails: Mail[] async getMail(id: number) { const mails = await this.$get("mails") - if(!mails) return undefined; - return mails[id]; + + if (!mails) return undefined + + return mails[id] } -} \ No newline at end of file +} diff --git a/src/pop3/POP3Commands.ts b/src/pop3/POP3Commands.ts new file mode 100644 index 0000000..eba5510 --- /dev/null +++ b/src/pop3/POP3Commands.ts @@ -0,0 +1,165 @@ +import * as argon2 from "argon2" +import Mail from "../models/Mail.js" +import { Socket } from "net" +import User from "../models/User.js" +import getConfig from "../config.js" +import { readFile } from "fs/promises" + +type POP3ConnectionState = { + username: string + user: User | undefined + markedForDeletion: Mail[], + login: boolean +} +type CommandHandler = (sock: Socket, args: string[], state: POP3ConnectionState) => Promise + +export class POP3Command { + + command: string; description: string; loginRequired: boolean; handle: CommandHandler + + constructor(command: string, description: string, loginRequired: boolean, handler: CommandHandler) { + this.command = command + this.description = description + this.loginRequired = loginRequired + this.handle = handler + } + +} + +// eslint-disable-next-line require-await +const CAPA = new POP3Command("CAPA", "List capabilities", false, async (sock: Socket) => { + sock.write("+OK Capability list follows\r\nUSER\r\n.\r\n") +}) + +const USER = new POP3Command("USER", "Set username", false, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (args.length < 1) return void sock.write("-ERR Invalid username or password\r\n") + + let username = args[0].trim().toLowerCase() + if (username.includes("@")) { + if (!username.endsWith(`@${getConfig("host", "localhost")}`)) return void sock.write("-ERR Invalid username or password\r\n") + + username = username.substring(0, username.lastIndexOf("@")) + } + if (username.startsWith("\"") && username.endsWith("\"")) username = username.substring(1, username.length-1) + else if (username.includes("@")) { + // this is not allowed + return void sock.write("-ERR Invalid username or password\r\n") + } + + const dbuser = await User.findOne({ where: { username } }) + + if (!dbuser) return void sock.write("-ERR Invalid username or password\r\n") + + state.user = dbuser + state.username = username + sock.write("+OK\r\n") +}) + +const PASS = new POP3Command("PASS", "Set password and log in", false, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (args.length < 1 || !state.user) return void sock.write("-ERR Invalid username or password\r\n") + + const password = args[0].trim() + const valid = await argon2.verify(state.user.password, password) + + if (valid) { + state.login = true + sock.write("+OK Logged in\r\n") + } else sock.write("-ERR Invalid username or password\r\n") +}) + +const STAT = new POP3Command("STAT", "Get number of messages and total size", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + const mails = await state.user.$count("mails") + + sock.write(`+OK ${mails}\r\n`) +}) + +// eslint-disable-next-line require-await +const QUIT = new POP3Command("QUIT", "Log out", true, async (sock: Socket) => { + sock.write("+OK Bye\r\n") + sock.end() +}) + +const LIST = new POP3Command("LIST", "List messages", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + const mails = await state.user.$get("mails") + + sock.write(`+OK ${mails.length} messages\r\n`) + for (let i = 0; i < mails.length; i++) sock.write(`${i+1}\r\n`) + + sock.write(".\r\n") +}) + +const RETR = new POP3Command("RETR", "Retrieve message", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + if (args.length < 1) return void sock.write("-ERR No message specified\r\n") + + const mail = await state.user.getMail(parseInt(args[0].trim(), 10)) + + if (!mail) return void sock.write("-ERR No such message\r\n") + + const content = await readFile(`mails/${mail.content}.txt`) + + sock.write(`+OK\r\n${content}\r\n.\r\n`) +}) + +const TOP = new POP3Command("TOP", "Retrieve message headers", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + if (args.length < 2) return void sock.write("-ERR No message specified\r\n") + + const mail = await state.user.getMail(parseInt(args[0].trim(), 10)) + + if (!mail) return void sock.write("-ERR No such message\r\n") + + const content = await readFile(`mails/${mail.content}.txt`, "utf-8") + const lines = content.split("\r\n") + const top = lines.slice(0, 10).join("\r\n") + + sock.write(`+OK\r\n${top}\r\n.\r\n`) +}) + +const UIDL = new POP3Command("UIDL", "Get unique id of message", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + const mails = await state.user.$get("mails") + + for (let i = 0; i < mails.length; i++) sock.write(`${i} ${mails[i].uuid}\r\n`) + + sock.write(".\r\n") +}) + +const DELE = new POP3Command("DELE", "Mark message for deletion", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + if (args.length < 1) return void sock.write("-ERR No message specified\r\n") + + const mail = await state.user.getMail(parseInt(args[0].trim(), 10)) + + if (!mail) return void sock.write("-ERR No such message\r\n") + + await mail.destroy() + sock.write("+OK\r\n") +}) + +// eslint-disable-next-line require-await +const NOOP = new POP3Command("NOOP", "Do nothing", false, async (sock: Socket) => { + sock.write("+OK\r\n") +}) + +const RSET = new POP3Command("RSET", "Reset marked messages", true, async (sock: Socket, args: string[], state: POP3ConnectionState) => { + if (!state.user) return void sock.write("-ERR Not logged in\r\n") + + const restores = [] + + for (const mail of state.markedForDeletion) restores.push(mail.restore()) + + await Promise.all(restores) + + sock.write("+OK\r\n") +}) + +export default [CAPA, USER, PASS, STAT, QUIT, LIST, RETR, TOP, UIDL, DELE, NOOP, RSET] diff --git a/src/pop3/POP3Server.ts b/src/pop3/POP3Server.ts index 82fbf2a..c1b68a2 100644 --- a/src/pop3/POP3Server.ts +++ b/src/pop3/POP3Server.ts @@ -1,11 +1,9 @@ +import Logger from "../Logger.js" +import Mail from "../models/Mail.js" +import User from "../models/User.js" +import commands from "./POP3Commands.js" import net from "net" import tls from "tls" -import User from "../models/User.js" -import { createHash } from "node:crypto" -import Mail from "../models/Mail.js" -import Logger from "../Logger.js" -import getConfig from "../config.js" -import { readFile } from "fs/promises" const logger = new Logger("POP3", "YELLOW") @@ -16,106 +14,44 @@ export default class POP3Server { constructor(port: number, useTLS: boolean, key?: Buffer, cert?: Buffer) { this.useTLS = useTLS - if(useTLS && (!key || !cert)) throw new Error("TLS key or certificate not provided"); + if (useTLS && (!key || !cert)) throw new Error("TLS key or certificate not provided") + this.server = useTLS ? tls.createServer({ key, - cert, + cert }, this.connection) : net.createServer() this.server.listen(port, () => { - logger.log("Server listening on port " + port) + logger.log(`Server listening on port ${port}`) }) - if(!useTLS) this.server.on("connection", this.connection) + if (!useTLS) this.server.on("connection", this.connection) } connection(sock: net.Socket) { logger.log("Client connected") sock.write("+OK POP3 server ready\r\n") - let username = "" - let user: User; - const markedForDeletion: Mail[] = [] + const state = { + username: "", + user: undefined as User | undefined, + markedForDeletion: [] as Mail[], + login: false + } + + // eslint-disable-next-line complexity sock.on("data", async (data: Buffer) => { const msg = data.toString() - logger.log("Received data: " + msg) + + logger.log(`Received data: ${msg}`) const args = msg.split(" ").slice(1) - if(msg.startsWith("CAPA")) { // list capabilities - sock.write("+OK Capability list follows\r\nUSER\r\n.\r\n") - } else if(msg.startsWith("USER")) { // client gives username - if(args.length < 1) return void sock.write("-ERR Invalid username or password\r\n") - username = args[0].trim().toLowerCase() - if(username.includes("@")) { - if(!username.endsWith("@" + getConfig("host", "localhost"))) { - return void sock.write("-ERR Invalid username or password\r\n") - } - username = username.substring(0, username.lastIndexOf("@")) - } - if (username.startsWith("\"") && username.endsWith("\"")) { - username = username.substring(1, username.length-1) - } else if (username.includes("@")) { - // this is not allowed - return void sock.write("-ERR Invalid username or password\r\n") - } - let _user = await User.findOne({ where: { username: username } }) - if(!_user) return void sock.write("-ERR Invalid username or password\r\n") - user = _user - sock.write("+OK\r\n") - } else if(msg.startsWith("PASS")) { // client gives password - if(args.length < 1) return void sock.write("-ERR Invalid username or password\r\n") - const password = args[0].trim() - const hash = createHash("sha256") - hash.update(password) - const hashedPassword = hash.digest("hex") - if(user.password == hashedPassword) { - sock.write("+OK Logged in\r\n") - } else { - sock.write("-ERR Invalid username or password\r\n") - } - } else if(msg.startsWith("STAT")) { // get number of messages and total size - const mails = await user.$count("mails") - sock.write("+OK " + mails + "\r\n") - } else if(msg.startsWith("QUIT")) { - sock.write("+OK Bye\r\n") - sock.end() - } else if(msg.startsWith("LIST")) { - const mails = await user.$get("mails") - sock.write("+OK " + mails.length + " messages\r\n") - for(let i = 0; i < mails.length; i++) { - sock.write(i + "\r\n") - } - sock.write(".\r\n") - } else if(msg.startsWith("RETR")) { - if(args.length < 1) return void sock.write("-ERR No message specified\r\n") - const mail = await user.getMail(parseInt(args[0].trim())) - if(!mail) return void sock.write("-ERR No such message\r\n") - const content = await readFile("mails/" + mail.content + ".txt", "utf-8") - sock.write("+OK\r\n" + content + "\r\n.\r\n"); - } else if(msg.startsWith("TOP")) { - if(args.length < 2) return void sock.write("-ERR No message specified\r\n") - const mail = await user.getMail(parseInt(args[0].trim())) - if(!mail) return void sock.write("-ERR No such message\r\n") - const content = await readFile("mails/" + mail.content + ".txt", "utf-8") - const lines = content.split("\r\n") - const top = lines.slice(0, 10).join("\r\n") - sock.write("+OK\r\n" + top + "\r\n.\r\n"); - } else if(msg.startsWith("UIDL")) { // get unique id of message - const mails = await user.$get("mails") - for(let i = 0; i < mails.length; i++) { - sock.write(i + " " + mails[i].uuid + "\r\n") - } - sock.write(".\r\n") - } else if(msg.startsWith("DELE")) { - if(args.length < 1) return void sock.write("-ERR No message specified\r\n") - const mail = await user.getMail(parseInt(args[0].trim())) - if(!mail) return void sock.write("-ERR No such message\r\n") - await mail.destroy() - sock.write("+OK\r\n") - } else if(msg.startsWith("NOOP")) { // this is used to keep the connection alive - sock.write("+OK\r\n") - } else if(msg.startsWith("RSET")) { - for(const mail of markedForDeletion) { - await mail.restore(); - } - sock.write("+OK\r\n") + + const command = commands.find(c => c.command == msg.substring(0, msg.indexOf(" "))) + + if (!command) { + sock.write("-ERR Unknown command\r\n") + + return } + + await command?.handle(sock, args, state) }) sock.addListener("close", () => { logger.log("Client disconnected") diff --git a/src/smtp/SMTP.ts b/src/smtp/SMTP.ts index db4845b..716c051 100644 --- a/src/smtp/SMTP.ts +++ b/src/smtp/SMTP.ts @@ -1,51 +1,62 @@ import { mkdir, writeFile } from "fs/promises" -import getConfig from "../config.js" import Logger from "../Logger.js" import User from "../models/User.js" -import crypto from "node:crypto"; +import crypto from "node:crypto" +import getConfig from "../config.js" const logger = new Logger("SMTP", "GREEN") export default class SMTP { + static async handleNewMail(info: { from: string, to: string[], content: string }) { const id = crypto.randomUUID() + await mkdir(`mails/`, { recursive: true }) await writeFile(`mails/${id}.txt`, info.content) logger.log(`Saved mail to mails/${id}.txt`) const serverName = getConfig("host") - if(info.from.endsWith("@" + serverName)) { + + if (info.from.endsWith(`@${serverName}`)) { logger.log("Mail is from this server.") - if(!info.to.every(email => email.endsWith("@" + serverName))) { // if not all recipients are on this server - logger.log("Not all recipients are on this server. Will forward mail to other servers."); + if (!info.to.every(email => email.endsWith(`@${serverName}`))) { // if not all recipients are on this server + logger.log("Not all recipients are on this server. Will forward mail to other servers.") logger.error("Forwarding mails to other servers is not implemented yet.") + + // TODO: forward mail to other servers using SMTPClient return } + logger.log("All recipients are on this server.") - } else if(!info.to.every(email => email.endsWith("@" + serverName))) { + } else if (!info.to.every(email => email.endsWith(`@${serverName}`))) { + // We do not forward mail to other servers + // because that would make us an open relay logger.warn("Not all recipients are from this server. Will NOT forward mail to other servers.") } - const recipients = info.to.filter(email => email.endsWith("@" + serverName)) - for(const rec of recipients) { - logger.log("Forwarding mail to " + rec); + const recipients = info.to.filter(email => email.endsWith(`@${serverName}`)) + + recipients.forEach(async rec => { + logger.log(`Forwarding mail to ${rec}`) + const user = await User.findOne({ where: { username: rec.substring(0, rec.lastIndexOf("@")) } }) + + if (!user) { + logger.error(`User ${rec} does not exist.`) - const user = await User.findOne({ where: { username: rec.substring(0, rec.lastIndexOf("@")) } }); - if(!user) { - logger.error("User " + rec + " does not exist.") // Since we verify the recipients at the RCPT TO command, we should never get here, but you never know - continue + return } await user.$create("mail", { - from: info.from, - to: rec, + from: info.from, + to: rec, content: id - }); - - logger.log("Forwarded mail to " + rec); - } + }) + + logger.log(`Forwarded mail to ${rec}`) + }) } -} \ No newline at end of file + +} diff --git a/src/smtp/SMTPClient.ts b/src/smtp/SMTPClient.ts index 08d81a4..875a6e0 100644 --- a/src/smtp/SMTPClient.ts +++ b/src/smtp/SMTPClient.ts @@ -1,25 +1,26 @@ +import Logger from "../Logger.js" +import getConfig from "../config.js" import net from "net" import tls from "tls" -import getConfig from "../config.js" -import Logger from "../Logger.js" const logger = new Logger("SMTPClient", "TEAL") export default class SMTPClient { - static async sendMessage(host: string, port: number, from: string, to: string, content: string, useTLS: boolean) { + static sendMessage(host: string, port: number, from: string, to: string, content: string, useTLS: boolean) { // const sock = net.createConnection(port, host) const sock = useTLS ? tls.connect(port, host) : net.createConnection(port, host) + sock.on("data", (data: Buffer) => { logger.log(`Received data: ${data.toString()}`) }) - sock.on("connect", async () => { + sock.on("connect", () => { logger.log("Connected to server") sock.write(`EHLO ${getConfig("host", "localhost")}\r\n`) - sock.write("MAIL FROM:<" + from + ">\r\n") - sock.write("RCPT TO:<" + to + ">\r\n") + sock.write(`MAIL FROM:<${from}>\r\n`) + sock.write(`RCPT TO:<${to}>\r\n`) sock.write("DATA\r\n") - sock.write(content + "\r\n.\r\n") + sock.write(`${content}\r\n.\r\n`) sock.write("QUIT\r\n") }) } diff --git a/src/smtp/SMTPServer.ts b/src/smtp/SMTPServer.ts index 2c3784a..1f0bd68 100644 --- a/src/smtp/SMTPServer.ts +++ b/src/smtp/SMTPServer.ts @@ -1,13 +1,10 @@ -import net from "net" -import tls from "tls" -import { writeFileSync, mkdirSync } from "fs" -import getConfig from "../config.js" -import Mail from "../models/Mail.js" -import User from "../models/User.js" -import crypto from "node:crypto"; -import sendStatus from "./status.js" import Logger from "../Logger.js" import SMTP from "./SMTP.js" +import User from "../models/User.js" +import getConfig from "../config.js" +import net from "net" +import sendStatus from "./status.js" +import tls from "tls" const logger = new Logger("SMTPServer", "GREEN") @@ -18,15 +15,16 @@ export default class SMTPServer { constructor(port: number, useTLS: boolean, key?: Buffer, cert?: Buffer) { this.useTLS = useTLS - if(useTLS && (!key || !cert)) throw new Error("TLS key or certificate not provided"); + if (useTLS && (!key || !cert)) throw new Error("TLS key or certificate not provided") + this.server = useTLS ? tls.createServer({ key, cert }, this.connection) : net.createServer() this.server.listen(port, () => { logger.log(`Server listening on port ${port}`) - }); - if(!useTLS) this.server.on("connection", this.connection) + }) + if (!useTLS) this.server.on("connection", this.connection) } connection(sock: net.Socket) { @@ -36,86 +34,102 @@ export default class SMTPServer { status(220, { message: getConfig("smtp.header", "SMTP Server ready") }) let receivingData = false let info = { - from: "", - to: [] as string[], + from: "", + to: [] as string[], content: "" } + sock.on("data", async (data: Buffer) => { const msg = data.toString() + + // TODO implement regular HELO greeting - if(receivingData) { + if (receivingData) { logger.log(`Received message content: ${msg}`) info.content += msg - if(msg.endsWith(".\r\n")) { - receivingData = false; + if (msg.endsWith(".\r\n")) { + receivingData = false info.content = info.content.substring(0, info.content.length - 3).replaceAll("\r\n", "\n") await SMTP.handleNewMail(info) status(250) logger.log("No longer receiving data -----------------------------------") + return } + return } + logger.log(`Received data: ${msg}`) - if(msg.startsWith("EHLO")) { + if (msg.startsWith("EHLO")) { sock.write(`250-${getConfig("host", "localhost")}\r\n`) + // We dont have any smtp extensions yet - status(250, {message: "HELP"}) // was: 250 HELP - } else if(msg.startsWith("MAIL FROM:")) { + status(250, { message: "HELP" }) // was: 250 HELP + } else if (msg.startsWith("MAIL FROM:")) { // The spec says we should reset the state if the client sends MAIL FROM again info = { - from: "", - to: [], + from: "", + to: [], content: "" } const email = msg.split(":")[1].split(">")[0].replace("<", "") + logger.log(`MAIL FROM: ${email}`) info.from = email status(250) - } else if(msg.startsWith("RCPT TO:")) { - if(info.from == "") { + } else if (msg.startsWith("RCPT TO:")) { + if (info.from == "") { // The spec says we should return 503 if the client has not sent MAIL FROM yet status(503) + return } + const email = msg.split(":")[1].split(">")[0].replace("<", "") - const username = email.split("@")[0] - const domain = email.split("@")[1] - if(domain != getConfig("host")) { - // The spec says we MAY forward the message ourselves, but simply returning 550 is fine, and the client should handle it + const [username, domain] = email.split("@") + + if (domain != getConfig("host")) { + // The spec says we MAY forward the message ourselves, + // but simply returning 550 is fine, and the client should handle it status(550) + return } + const user = await User.findOne({ where: { username } }) - if(!user) { + + if (!user) { status(550) + return } + info.to.push(email) logger.log(`RCPT TO: ${email}`) status(250) - } else if(msg.startsWith("DATA")) { + } else if (msg.startsWith("DATA")) { // The spec says we should return either 503 or 554 if the client has not sent MAIL FROM or RCPT TO yet // We will send 554 because it is more specific - if(info.from == "" || info.to.length == 0) { - status(554, {message: "No valid recipients"}) + if (info.from == "" || info.to.length == 0) { + status(554, { message: "No valid recipients" }) + return } - receivingData = true; + + receivingData = true logger.log("Now receiving data -----------------------------------") status(354) - } else if(msg.startsWith("QUIT")) { + } else if (msg.startsWith("QUIT")) { status(221, "2.0.0") sock.end() - } else if(msg.startsWith("VRFY")) { - // This command is used to verify if a user exists, but that can be a security risk + it is also done with RCPT TO anyway + } else if (msg.startsWith("VRFY")) { + // This command is used to verify if a user exists, + // but that can be a security risk + it is also done with RCPT TO anyway status(502) - } else if(msg.startsWith("EXPN")) { - status(502) - } else { - status(502) - } - }); + } else if (msg.startsWith("EXPN")) status(502) + else status(502) + }) sock.on("close", () => { logger.log("Client disconnected") }) diff --git a/src/smtp/status.ts b/src/smtp/status.ts index 962cd1f..3004f48 100644 --- a/src/smtp/status.ts +++ b/src/smtp/status.ts @@ -19,6 +19,7 @@ export interface EnhancedStatusCode extends StatusCode { type EnhancedCode = `${bigint}.${bigint}.${bigint}` +// eslint-disable-next-line @typescript-eslint/no-unused-vars const STATUS_SUBJECTS: Record = [ { code: 0, name: "Other or Undefined" }, { code: 1, name: "Addressing" }, @@ -30,7 +31,7 @@ const STATUS_SUBJECTS: Record = [ { code: 7, name: "Security or Policy" } ] -let ok = true +const ok = true const STATUS_CODES: Record = { // OK 211: { code: 211, ok }, // system status, or system help reply @@ -44,12 +45,14 @@ const STATUS_CODES: Record = { // intermediate OK 334: { code: 334, ok }, // base64-encoded server challenge 354: { code: 354, ok, message: "Start mail input; end with ." }, + // transient NOT OK 421: { code: 421, ok: false, message: "Service not available, closing transmission channel" }, 450: { code: 450, ok: false, message: "Requested mail action not taken: mailbox unavailable" }, // mailbox busy or temp. blocked 451: { code: 451, ok: false, message: "Requested action aborted: local error in processing" }, 452: { code: 452, ok: false, message: "Requested action not taken: insufficient system storage" }, 455: { code: 455, ok: false, message: "Server unable to accommodate parameters" }, + // permanent NOT OK 500: { code: 500, ok: false, message: "Syntax error, command unrecognized" }, 501: { code: 501, ok: false, message: "Syntax error in parameters or arguments" }, @@ -97,6 +100,7 @@ export default function sendStatus(socket: net.Socket) { export function status(code: number, options?: StatusOptions | EnhancedCode) { if (typeof options == "string") options = { enhancedCode: options } + let statusCode = STATUS_CODES[code] let enhanced = false @@ -115,6 +119,7 @@ export function status(code: number, options?: StatusOptions | EnhancedCode) { if (enhanced) { const esc = statusCode as EnhancedStatusCode + return `${esc.code} ${esc.class}.${esc.subject}.${esc.detail} ${message}\r\n` } @@ -124,10 +129,10 @@ export function status(code: number, options?: StatusOptions | EnhancedCode) { function replaceArgs(statusCode: StatusCode, args: string[], msg?: string): string { if (!("message" in statusCode)) return msg || "" - let message = statusCode.message as string - for (let i = 0; message.includes("%"); i++) { - message.replace("%", args[i] ?? msg) - } + const message = statusCode.message as string + + for (let i = 0; message.includes("%"); i++) message.replace("%", args[i] ?? msg) + return message } diff --git a/src/test/main.ts b/src/test/main.ts index 5ef7e49..8f3fa0d 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -2,14 +2,15 @@ import Logger from "../Logger.js" import { status } from "../smtp/status.js" const ESC = "\u001b" -const RED = ESC + "[91m" -const YELLOW = ESC + "[93m" -const BLUE = ESC + "[94m" -const GREEN = ESC + "[92m" +const RED = `${ESC}[91m` +const YELLOW = `${ESC}[93m` +const BLUE = `${ESC}[94m` +const GREEN = `${ESC}[92m` let errors = 0 const logger = new Logger("test", "TEAL") + logger.debug("debug log") logger.log("info log") logger.warn("warning log") @@ -23,9 +24,10 @@ const smtpStatusTestCases: [number | `${number} ${bigint}.${bigint}.${bigint}`, ["538 5.7.11", "538 5.7.11 Encryption required for requested authentication mechanism\r\n"] ] -for (let e of smtpStatusTestCases) { - const args = (e[0]+"").split(" ") as [number, `${bigint}.${bigint}.${bigint}` | undefined] +for (const e of smtpStatusTestCases) { + const args = (`${e[0]}`).split(" ") as [number, `${bigint}.${bigint}.${bigint}` | undefined] const statusMsg = status(Number(args[0]), args[1]) + logger.log(`SMTP ${e[0]}: [${statusMsg.substring(0, statusMsg.length-2)}]`) if (statusMsg != e[1]) { errors++ @@ -35,6 +37,7 @@ for (let e of smtpStatusTestCases) { const totalCases = smtpStatusTestCases.length const errorColor = errors ? RED : GREEN + logger.log(`${BLUE}------------------------------------------------`) logger.log(`${BLUE}Test cases completed: ${errorColor}${errors}${BLUE} out of ${YELLOW}${totalCases}${BLUE} failed`)