diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..36af219 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..c92ebdc --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "printWidth": 120, + "quoteProps": "as-needed", + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/index.ts b/index.ts index 1a1c7eb..ae7aa25 100644 --- a/index.ts +++ b/index.ts @@ -1,33 +1,33 @@ -import { app } from "./app"; -import { logger } from "./logger"; +import { app } from './app'; +import { logger } from './logger'; -const port = app.get("port"); -const host = app.get("host"); +const port = app.get('port'); +const host = app.get('host'); const server = app.listen(port); app.listen(port).then(() => { - logger.info(`Walias app listening on http://${host}:${port}`); + logger.info(`Walias app listening on http://${host}:${port}`); }); -process.on("SIGINT", () => { - logger.info("Received SIGINT signal. Shutting down gracefully."); +process.on('SIGINT', () => { + logger.info('Received SIGINT signal. Shutting down gracefully.'); - server.close(() => { - logger.info("HTTP server closed."); - process.exit(0); - }); + server.close(() => { + logger.info('HTTP server closed.'); + process.exit(0); + }); }); -process.on("SIGTERM", () => { - logger.info("Received SIGTERM signal. Shutting down gracefully."); +process.on('SIGTERM', () => { + logger.info('Received SIGTERM signal. Shutting down gracefully.'); - server.close(() => { - logger.info("HTTP server closed."); - process.exit(0); - }); + server.close(() => { + logger.info('HTTP server closed.'); + process.exit(0); + }); }); -process.on("unhandledRejection", (reason) => { - logger.error("Unhandled rejection", reason); - process.exit(1); +process.on('unhandledRejection', reason => { + logger.error('Unhandled rejection', reason); + process.exit(1); }); diff --git a/logger.ts b/logger.ts index 57edc25..99097e3 100644 --- a/logger.ts +++ b/logger.ts @@ -1,10 +1,10 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html -import { createLogger, format, transports } from "winston"; +import { createLogger, format, transports } from 'winston'; // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston export const logger = createLogger({ - // To see more detailed errors, change this to 'debug' - level: "info", - format: format.combine(format.splat(), format.simple()), - transports: [new transports.Console()], + // To see more detailed errors, change this to 'debug' + level: 'info', + format: format.combine(format.splat(), format.simple()), + transports: [new transports.Console()], }); diff --git a/package-lock.json b/package-lock.json index b16f1d1..1295b2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,8 @@ "@types/mocha": "^10.0.1", "@types/node": "^20.4.5", "cross-env": "^7.0.3", + "husky": "^8.0.3", + "lint-staged": "^13.2.3", "mocha": "^10.2.0", "nodemon": "^3.0.1", "prettier": "^3.0.0", @@ -705,6 +707,19 @@ "node": ">=0.4.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -832,6 +847,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -1074,6 +1098,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -1098,6 +1131,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -1161,6 +1260,12 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -1422,6 +1527,12 @@ "node": ">=0.3.1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1550,6 +1661,68 @@ "node": ">=0.8.x" } }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -1849,6 +2022,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1969,6 +2154,30 @@ "node": ">= 0.8" } }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2006,6 +2215,15 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2272,6 +2490,146 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lint-staged": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", + "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "dev": true, + "dependencies": { + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/lint-staged/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lint-staged/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==", + "dev": true + }, + "node_modules/listr2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2308,6 +2666,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/logform": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", @@ -2361,6 +2768,12 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2369,6 +2782,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -2695,6 +3121,33 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2854,6 +3307,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2912,6 +3380,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/prettier": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", @@ -3108,6 +3588,12 @@ "node": ">=8" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -3307,6 +3793,46 @@ "node": ">=10" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/socket.io": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.1.tgz", @@ -3429,6 +3955,15 @@ } ] }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3455,6 +3990,18 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3843,6 +4390,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 45f0a14..03b6500 100644 --- a/package.json +++ b/package.json @@ -68,11 +68,16 @@ "@types/mocha": "^10.0.1", "@types/node": "^20.4.5", "cross-env": "^7.0.3", + "husky": "^8.0.3", + "lint-staged": "^13.2.3", "mocha": "^10.2.0", "nodemon": "^3.0.1", "prettier": "^3.0.0", "shx": "^0.3.4", "ts-node": "^10.9.1", "typescript": "^5.1.6" + }, + "lint-staged": { + "*.{ts,js,css,md}": "prettier --write" } } diff --git a/src/app.ts b/src/app.ts index f0a24a4..f9ad933 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,25 +1,17 @@ -import { feathers } from "@feathersjs/feathers"; -import express, { - rest, - json, - urlencoded, - cors, - serveStatic, - notFound, - errorHandler, -} from "@feathersjs/express"; -import configuration from "@feathersjs/configuration"; -import socketio from "@feathersjs/socketio"; -import session from "express-session"; -import cookieParser from "cookie-parser"; +import { feathers } from '@feathersjs/feathers'; +import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express'; +import configuration from '@feathersjs/configuration'; +import socketio from '@feathersjs/socketio'; +import session from 'express-session'; +import cookieParser from 'cookie-parser'; -import type { Application } from "./declarations"; +import type { Application } from './declarations'; -import { logger } from "./logger"; -import { logError } from "./hooks/log-error"; -import { services } from "./services/index"; -import { channels } from "./channels"; -import { randomUUID } from "crypto"; +import { logger } from './logger'; +import { logError } from './hooks/log-error'; +import { services } from './services/index'; +import { channels } from './channels'; +import { randomUUID } from 'crypto'; const app: Application = express(feathers()); @@ -27,42 +19,42 @@ const app: Application = express(feathers()); app.configure(configuration()); app.use(cors()); app.use( - json({ - limit: "20mb", - }), + json({ + limit: '20mb', + }) ); app.use(cookieParser()); app.use( - session({ - secret: randomUUID(), - resave: false, - saveUninitialized: true, - cookie: { secure: false }, - }), + session({ + secret: randomUUID(), + resave: false, + saveUninitialized: true, + cookie: { secure: false }, + }) ); // Propagate session to request.params in feathers services app.use(function (req, _res, next) { - req.feathers = { - ...req.feathers, - session: req.session, - }; - next(); + req.feathers = { + ...req.feathers, + session: req.session, + }; + next(); }); app.use(urlencoded({ extended: true })); // Host the public folder -app.use("/", serveStatic(app.get("public"))); +app.use('/', serveStatic(app.get('public'))); // Configure services and real-time functionality app.configure(rest()); app.configure( - socketio({ - cors: { - origin: app.get("origins"), - }, - }), + socketio({ + cors: { + origin: app.get('origins'), + }, + }) ); app.configure(services); app.configure(channels); @@ -73,17 +65,17 @@ app.use(errorHandler({ logger })); // Register hooks that run on all service methods app.hooks({ - around: { - all: [logError], - }, - before: {}, - after: {}, - error: {}, + around: { + all: [logError], + }, + before: {}, + after: {}, + error: {}, }); // Register application setup and teardown hooks here app.hooks({ - setup: [], - teardown: [], + setup: [], + teardown: [], }); export { app }; diff --git a/src/channels.ts b/src/channels.ts index 53d5203..6f0fcb9 100644 --- a/src/channels.ts +++ b/src/channels.ts @@ -1,41 +1,38 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/channels.html -import type { RealTimeConnection, Params } from "@feathersjs/feathers"; -import type { AuthenticationResult } from "@feathersjs/authentication"; -import "@feathersjs/transport-commons"; -import type { Application, HookContext } from "./declarations"; -import { logger } from "./logger"; +import type { RealTimeConnection, Params } from '@feathersjs/feathers'; +import type { AuthenticationResult } from '@feathersjs/authentication'; +import '@feathersjs/transport-commons'; +import type { Application, HookContext } from './declarations'; +import { logger } from './logger'; export const channels = (app: Application) => { - logger.warn( - "Publishing all events to all authenticated users. See `channels.ts` and https://dove.feathersjs.com/api/channels.html for more information.", - ); + logger.warn( + 'Publishing all events to all authenticated users. See `channels.ts` and https://dove.feathersjs.com/api/channels.html for more information.' + ); - app.on("connection", (connection: RealTimeConnection) => { - // On a new real-time connection, add it to the anonymous channel - app.channel("anonymous").join(connection); - }); + app.on('connection', (connection: RealTimeConnection) => { + // On a new real-time connection, add it to the anonymous channel + app.channel('anonymous').join(connection); + }); - app.on( - "login", - (authResult: AuthenticationResult, { connection }: Params) => { - // connection can be undefined if there is no - // real-time connection, e.g. when logging in via REST - if (connection) { - // The connection is no longer anonymous, remove it - app.channel("anonymous").leave(connection); + app.on('login', (authResult: AuthenticationResult, { connection }: Params) => { + // connection can be undefined if there is no + // real-time connection, e.g. when logging in via REST + if (connection) { + // The connection is no longer anonymous, remove it + app.channel('anonymous').leave(connection); - // Add it to the authenticated user channel - app.channel("authenticated").join(connection); - } - }, - ); + // Add it to the authenticated user channel + app.channel('authenticated').join(connection); + } + }); - // eslint-disable-next-line no-unused-vars - app.publish((data: any, context: HookContext) => { - // Here you can add event publishers to channels set up in `channels.js` - // To publish only for a specific event use `app.publish(eventname, () => {})` + // eslint-disable-next-line no-unused-vars + app.publish((data: any, context: HookContext) => { + // Here you can add event publishers to channels set up in `channels.js` + // To publish only for a specific event use `app.publish(eventname, () => {})` - // e.g. to publish all service events to all authenticated users use - return app.channel("authenticated"); - }); + // e.g. to publish all service events to all authenticated users use + return app.channel('authenticated'); + }); }; diff --git a/src/clients/wildduck.client.ts b/src/clients/wildduck.client.ts index ef880a2..383740c 100644 --- a/src/clients/wildduck.client.ts +++ b/src/clients/wildduck.client.ts @@ -1,12 +1,12 @@ -import axios from "axios"; -import config from "config"; +import axios from 'axios'; +import config from 'config'; const wildDuckClient = axios.create({ - baseURL: config.get("wildDuck.url"), - headers: { - "X-Access-Token": config.get("wildDuck.token"), - }, - responseType: "json", + baseURL: config.get('wildDuck.url'), + headers: { + 'X-Access-Token': config.get('wildDuck.token'), + }, + responseType: 'json', }); export default wildDuckClient; diff --git a/src/declarations.ts b/src/declarations.ts index 2f7319d..2a65a3b 100644 --- a/src/declarations.ts +++ b/src/declarations.ts @@ -1,9 +1,6 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/typescript.html -import { - HookContext as FeathersHookContext, - NextFunction, -} from "@feathersjs/feathers"; -import { Application as FeathersApplication } from "@feathersjs/express"; +import { HookContext as FeathersHookContext, NextFunction } from '@feathersjs/feathers'; +import { Application as FeathersApplication } from '@feathersjs/express'; type ApplicationConfiguration = any; export { NextFunction }; diff --git a/src/hooks/log-error.ts b/src/hooks/log-error.ts index 787730f..14e0ad0 100644 --- a/src/hooks/log-error.ts +++ b/src/hooks/log-error.ts @@ -1,17 +1,17 @@ -import type { HookContext, NextFunction } from "../declarations"; -import { logger } from "../logger"; +import type { HookContext, NextFunction } from '../declarations'; +import { logger } from '../logger'; export const logError = async (context: HookContext, next: NextFunction) => { - try { - await next(); - } catch (error: any) { - logger.error(error.stack); + try { + await next(); + } catch (error: any) { + logger.error(error.stack); - // Log validation errors - if (error.data) { - logger.error("Data: %O", error.data); + // Log validation errors + if (error.data) { + logger.error('Data: %O', error.data); + } + + throw error; } - - throw error; - } }; diff --git a/src/hooks/validate-auth.ts b/src/hooks/validate-auth.ts index 3de2f11..a9ec365 100644 --- a/src/hooks/validate-auth.ts +++ b/src/hooks/validate-auth.ts @@ -1,9 +1,9 @@ -import { NotAuthenticated } from "@feathersjs/errors"; -import type { HookContext, NextFunction } from "../declarations"; +import { NotAuthenticated } from '@feathersjs/errors'; +import type { HookContext, NextFunction } from '../declarations'; // Check if user is stored in session export const validateAuth = async (context: HookContext) => { - if (!context.params.session?.user) { - throw new NotAuthenticated("Not authenticated"); - } + if (!context.params.session?.user) { + throw new NotAuthenticated('Not authenticated'); + } }; diff --git a/src/index.ts b/src/index.ts index a203c16..f8fffc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,11 @@ -import { app } from "./app"; -import { logger } from "./logger"; +import { app } from './app'; +import { logger } from './logger'; -const port = app.get("port"); -const host = app.get("host"); +const port = app.get('port'); +const host = app.get('host'); -process.on("unhandledRejection", (reason) => - logger.error("Unhandled Rejection %O", reason), -); +process.on('unhandledRejection', reason => logger.error('Unhandled Rejection %O', reason)); app.listen(port).then(() => { - logger.info(`Feathers app listening on http://${host}:${port}`); + logger.info(`Feathers app listening on http://${host}:${port}`); }); diff --git a/src/logger.ts b/src/logger.ts index 57edc25..99097e3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,10 +1,10 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html -import { createLogger, format, transports } from "winston"; +import { createLogger, format, transports } from 'winston'; // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston export const logger = createLogger({ - // To see more detailed errors, change this to 'debug' - level: "info", - format: format.combine(format.splat(), format.simple()), - transports: [new transports.Console()], + // To see more detailed errors, change this to 'debug' + level: 'info', + format: format.combine(format.splat(), format.simple()), + transports: [new transports.Console()], }); diff --git a/src/services/aliases/aliases.class.ts b/src/services/aliases/aliases.class.ts index 928ec99..4d5a4fa 100644 --- a/src/services/aliases/aliases.class.ts +++ b/src/services/aliases/aliases.class.ts @@ -1,33 +1,29 @@ -import type { - NullableId, - Params, - ServiceInterface, -} from "@feathersjs/feathers"; +import type { NullableId, Params, ServiceInterface } from '@feathersjs/feathers'; -import type { Application } from "../../declarations"; -import wildDuckClient from "../../clients/wildduck.client"; -import { faker } from "@faker-js/faker"; -import { BadRequest } from "@feathersjs/errors"; -import config from "config"; +import type { Application } from '../../declarations'; +import wildDuckClient from '../../clients/wildduck.client'; +import { faker } from '@faker-js/faker'; +import { BadRequest } from '@feathersjs/errors'; +import config from 'config'; interface Alias { - success: boolean; - id: string; - address: string; - main: boolean; - user: string; - tags: string[]; - created: string; + success: boolean; + id: string; + address: string; + main: boolean; + user: string; + tags: string[]; + created: string; } interface GetAddressInfoResponse { - success: boolean; - results: Alias[]; + success: boolean; + results: Alias[]; } interface CreateAddressResponse { - success: boolean; - id: string; + success: boolean; + id: string; } type AliasesData = any; @@ -37,102 +33,85 @@ type AliasesQuery = any; export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; export interface AliasesServiceOptions { - app: Application; + app: Application; } export interface AliasesParams extends Params { - session?: any; + session?: any; } export class AliasesService - implements ServiceInterface + implements ServiceInterface { - constructor(public options: AliasesServiceOptions) {} + constructor(public options: AliasesServiceOptions) {} - async find(params: ServiceParams): Promise { - const userId = await this.getUserIdByEmailAddress(params); + async find(params: ServiceParams): Promise { + const userId = await this.getUserIdByEmailAddress(params); - return this.getUserAddresses(userId); - } - - async create(data: AliasesData, params: ServiceParams): Promise; - async create( - data: AliasesData, - params: ServiceParams, - ): Promise { - const userId = await this.getUserIdByEmailAddress(params); - - const randomString = faker.git.commitSha({ length: 4 }); - - // Replace all non-alphanumeric characters with nothing and spaces with dashes - const alias = - `${faker.color.human()}-${faker.animal.snake()}-${randomString}` - .replace(/\s+/g, "-") - .replace(/[^a-zA-Z0-9-]/g, "") - .toLowerCase(); - - const emailDomain = config.get("wildDuck.domain"); - - const createResult = await wildDuckClient.post( - `/users/${userId}/addresses`, - { - address: `${alias}@${emailDomain}`, - }, - ); - - if (!createResult.data.success) { - throw new BadRequest("Failed to create alias"); + return this.getUserAddresses(userId); } - return this.getUserAddresses(userId); - } + async create(data: AliasesData, params: ServiceParams): Promise; + async create(data: AliasesData, params: ServiceParams): Promise { + const userId = await this.getUserIdByEmailAddress(params); - private async getUserIdByEmailAddress( - params: ServiceParams, - ): Promise { - const emails = params.session?.user?.emails; + const randomString = faker.git.commitSha({ length: 4 }); - const addressInfoResponse = await Promise.any( - emails - .filter((email: string) => email.endsWith(config.get("wildDuck.preferredDomain"))) - .map((email: string) => - wildDuckClient.get(`addresses/resolve/${email}`), - ), - ); + // Replace all non-alphanumeric characters with nothing and spaces with dashes + const alias = `${faker.color.human()}-${faker.animal.snake()}-${randomString}` + .replace(/\s+/g, '-') + .replace(/[^a-zA-Z0-9-]/g, '') + .toLowerCase(); - return addressInfoResponse.data.user; - } + const emailDomain = config.get('wildDuck.domain'); - private async getUserAddresses(userId: string): Promise { - const { data: userAddressesResponse } = - await wildDuckClient.get( - `/users/${userId}/addresses`, - ); + const createResult = await wildDuckClient.post(`/users/${userId}/addresses`, { + address: `${alias}@${emailDomain}`, + }); - return userAddressesResponse.results; - } + if (!createResult.data.success) { + throw new BadRequest('Failed to create alias'); + } - async remove(id: NullableId, params: ServiceParams): Promise { - const { data: addressInfoResponse } = await wildDuckClient.get( - `addresses/resolve/${id}`, - ); - const allowedDomain: string = config.get("wildDuck.domain"); - - // If address does not match the allowed domain, throw an error - if ( - !allowedDomain || - !addressInfoResponse.address.endsWith(allowedDomain) - ) { - throw new BadRequest("Unable to delete address"); + return this.getUserAddresses(userId); } - const userId = await this.getUserIdByEmailAddress(params); - await wildDuckClient.delete(`users/${userId}/addresses/${id}`); + private async getUserIdByEmailAddress(params: ServiceParams): Promise { + const emails = params.session?.user?.emails; - return this.getUserAddresses(userId); - } + const addressInfoResponse = await Promise.any( + emails + .filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain'))) + .map((email: string) => wildDuckClient.get(`addresses/resolve/${email}`)) + ); + + return addressInfoResponse.data.user; + } + + private async getUserAddresses(userId: string): Promise { + const { data: userAddressesResponse } = await wildDuckClient.get( + `/users/${userId}/addresses` + ); + + return userAddressesResponse.results; + } + + async remove(id: NullableId, params: ServiceParams): Promise { + const { data: addressInfoResponse } = await wildDuckClient.get(`addresses/resolve/${id}`); + const allowedDomain: string = config.get('wildDuck.domain'); + + // If address does not match the allowed domain, throw an error + if (!allowedDomain || !addressInfoResponse.address.endsWith(allowedDomain)) { + throw new BadRequest('Unable to delete address'); + } + const userId = await this.getUserIdByEmailAddress(params); + + await wildDuckClient.delete(`users/${userId}/addresses/${id}`); + + return this.getUserAddresses(userId); + } } export const getOptions = (app: Application) => { - return { app }; + return { app }; }; diff --git a/src/services/aliases/aliases.ts b/src/services/aliases/aliases.ts index 97ba226..afc3f87 100644 --- a/src/services/aliases/aliases.ts +++ b/src/services/aliases/aliases.ts @@ -1,39 +1,39 @@ -import type { Application } from "../../declarations"; -import { validateAuth } from "../../hooks/validate-auth"; -import { AliasesService, getOptions } from "./aliases.class"; +import type { Application } from '../../declarations'; +import { validateAuth } from '../../hooks/validate-auth'; +import { AliasesService, getOptions } from './aliases.class'; -export const aliasesPath = "aliases"; -export const aliasesMethods = ["find", "create", "remove"] as const; +export const aliasesPath = 'aliases'; +export const aliasesMethods = ['find', 'create', 'remove'] as const; -export * from "./aliases.class"; +export * from './aliases.class'; export const aliases = (app: Application) => { - app.use(aliasesPath, new AliasesService(getOptions(app)), { - methods: aliasesMethods, - events: [], - }); + app.use(aliasesPath, new AliasesService(getOptions(app)), { + methods: aliasesMethods, + events: [], + }); - app.service(aliasesPath).hooks({ - around: { - all: [], - }, - before: { - all: [validateAuth], - find: [], - create: [], - }, - after: { - all: [], - }, - error: { - all: [], - }, - }); + app.service(aliasesPath).hooks({ + around: { + all: [], + }, + before: { + all: [validateAuth], + find: [], + create: [], + }, + after: { + all: [], + }, + error: { + all: [], + }, + }); }; // Add this service to the service type index -declare module "../../declarations" { - interface ServiceTypes { - [aliasesPath]: AliasesService; - } +declare module '../../declarations' { + interface ServiceTypes { + [aliasesPath]: AliasesService; + } } diff --git a/src/services/auth-oidc/auth-oidc.class.ts b/src/services/auth-oidc/auth-oidc.class.ts index 4a5797f..c0cc4d3 100644 --- a/src/services/auth-oidc/auth-oidc.class.ts +++ b/src/services/auth-oidc/auth-oidc.class.ts @@ -1,9 +1,9 @@ -import type { Params, ServiceInterface } from "@feathersjs/feathers"; +import type { Params, ServiceInterface } from '@feathersjs/feathers'; -import type { Application } from "../../declarations"; +import type { Application } from '../../declarations'; -import { Issuer, generators } from "openid-client"; -import config from "config"; +import { Issuer, generators } from 'openid-client'; +import config from 'config'; type AuthOidcResponse = string; type AuthOidcQuery = any; @@ -11,43 +11,42 @@ type AuthOidcQuery = any; export type { AuthOidcResponse as AuthOidc, AuthOidcQuery }; export interface AuthOidcServiceOptions { - app: Application; + app: Application; } export interface AuthOidcParams extends Params { - session?: any; + session?: any; } -export class AuthOidcService< - ServiceParams extends AuthOidcParams = AuthOidcParams, -> implements ServiceInterface +export class AuthOidcService + implements ServiceInterface { - constructor(public options: AuthOidcServiceOptions) {} + constructor(public options: AuthOidcServiceOptions) {} - async find(params: ServiceParams): Promise { - const issuer = await Issuer.discover(config.get("oidc.gatewayUri")); - const client = new issuer.Client({ - client_id: config.get("oidc.clientId"), - client_secret: config.get("oidc.clientSecret"), - redirect_uris: [config.get("oidc.redirectUris")], - response_types: ["code"], - }); - const codeVerifier = generators.codeVerifier(); - const codeChallenge = generators.codeChallenge(codeVerifier); + async find(params: ServiceParams): Promise { + const issuer = await Issuer.discover(config.get('oidc.gatewayUri')); + const client = new issuer.Client({ + client_id: config.get('oidc.clientId'), + client_secret: config.get('oidc.clientSecret'), + redirect_uris: [config.get('oidc.redirectUris')], + response_types: ['code'], + }); + const codeVerifier = generators.codeVerifier(); + const codeChallenge = generators.codeChallenge(codeVerifier); - const url = client.authorizationUrl({ - redirect_uri: config.get("clientUrl") + "/auth-oidc/callback", - scope: "openid profile offline_access", - response_type: "code", - code_challenge: codeChallenge, - code_challenge_method: "S256", - }); + const url = client.authorizationUrl({ + redirect_uri: config.get('clientUrl') + '/auth-oidc/callback', + scope: 'openid profile offline_access', + response_type: 'code', + code_challenge: codeChallenge, + code_challenge_method: 'S256', + }); - params.session.codeVerifier = codeVerifier; - return url; - } + params.session.codeVerifier = codeVerifier; + return url; + } } export const getOptions = (app: Application) => { - return { app }; + return { app }; }; diff --git a/src/services/auth-oidc/auth-oidc.ts b/src/services/auth-oidc/auth-oidc.ts index fc4c484..ce6e76d 100644 --- a/src/services/auth-oidc/auth-oidc.ts +++ b/src/services/auth-oidc/auth-oidc.ts @@ -1,45 +1,45 @@ -import type { Application } from "../../declarations"; -import { AuthOidcService, getOptions } from "./auth-oidc.class"; +import type { Application } from '../../declarations'; +import { AuthOidcService, getOptions } from './auth-oidc.class'; -export const authOidcPath = "auth-oidc"; -export const authOidcMethods = ["find"] as const; +export const authOidcPath = 'auth-oidc'; +export const authOidcMethods = ['find'] as const; -export * from "./auth-oidc.class"; +export * from './auth-oidc.class'; export const authOidc = (app: Application) => { - // TODO: fix this to use the correct type - // @ts-ignore - app.use( - authOidcPath, - new AuthOidcService(getOptions(app)), - { - methods: authOidcMethods, - events: [], - }, - (req: any, res: any) => { - return res.redirect(res.data); - }, - ); + // TODO: fix this to use the correct type + // @ts-ignore + app.use( + authOidcPath, + new AuthOidcService(getOptions(app)), + { + methods: authOidcMethods, + events: [], + }, + (req: any, res: any) => { + return res.redirect(res.data); + } + ); - app.service(authOidcPath).hooks({ - around: { - all: [], - }, - before: { - all: [], - find: [], - }, - after: { - all: [], - }, - error: { - all: [], - }, - }); + app.service(authOidcPath).hooks({ + around: { + all: [], + }, + before: { + all: [], + find: [], + }, + after: { + all: [], + }, + error: { + all: [], + }, + }); }; -declare module "../../declarations" { - interface ServiceTypes { - [authOidcPath]: AuthOidcService; - } +declare module '../../declarations' { + interface ServiceTypes { + [authOidcPath]: AuthOidcService; + } } diff --git a/src/services/auth-oidc/callback/auth-oidc-callback.class.ts b/src/services/auth-oidc/callback/auth-oidc-callback.class.ts index cd4e3db..c4c9234 100644 --- a/src/services/auth-oidc/callback/auth-oidc-callback.class.ts +++ b/src/services/auth-oidc/callback/auth-oidc-callback.class.ts @@ -1,68 +1,56 @@ -import type { Params, ServiceInterface } from "@feathersjs/feathers"; -import type { Application } from "../../../declarations"; -import { Issuer } from "openid-client"; +import type { Params, ServiceInterface } from '@feathersjs/feathers'; +import type { Application } from '../../../declarations'; +import { Issuer } from 'openid-client'; -import config from "config"; +import config from 'config'; type AuthOidcCallback = string; type AuthOidcCallbackData = any; type AuthOidcCallbackPatch = any; type AuthOidcCallbackQuery = any; -export type { - AuthOidcCallback, - AuthOidcCallbackData, - AuthOidcCallbackPatch, - AuthOidcCallbackQuery, -}; +export type { AuthOidcCallback, AuthOidcCallbackData, AuthOidcCallbackPatch, AuthOidcCallbackQuery }; export interface AuthOidcCallbackServiceOptions { - app: Application; + app: Application; } export interface AuthOidcCallbackParams extends Params { - session?: any; - query: { - iss: string; - code: string; - }; + session?: any; + query: { + iss: string; + code: string; + }; } -export class AuthOidcCallbackService< - ServiceParams extends AuthOidcCallbackParams = AuthOidcCallbackParams, -> implements - ServiceInterface< - AuthOidcCallback, - AuthOidcCallbackData, - ServiceParams, - AuthOidcCallbackPatch - > +export class AuthOidcCallbackService + implements ServiceInterface { - constructor(public options: AuthOidcCallbackServiceOptions) {} + constructor(public options: AuthOidcCallbackServiceOptions) {} - async find(params: ServiceParams): Promise { - const issuer = await Issuer.discover(config.get("oidc.gatewayUri")); - const client = new issuer.Client({ - client_id: config.get("oidc.clientId"), - client_secret: config.get("oidc.clientSecret"), - redirect_uris: [config.get("oidc.redirectUris")], - response_types: ["code"], - }); + async find(params: ServiceParams): Promise { + const issuer = await Issuer.discover(config.get('oidc.gatewayUri')); + const client = new issuer.Client({ + client_id: config.get('oidc.clientId'), + client_secret: config.get('oidc.clientSecret'), + redirect_uris: [config.get('oidc.redirectUris')], + response_types: ['code'], + }); - const codeVerifier = params.session.codeVerifier; - const tokenSet = await client.callback( - config.get("clientUrl") + "/auth-oidc/callback", - { code: params.query.code, iss: params.query.iss }, - { code_verifier: codeVerifier }, - ); - const userinfo = await client.userinfo(tokenSet.access_token as string); + const codeVerifier = params.session.codeVerifier; + const tokenSet = await client.callback( + config.get('clientUrl') + '/auth-oidc/callback', + { code: params.query.code, iss: params.query.iss }, + { code_verifier: codeVerifier } + ); + const userinfo = await client.userinfo(tokenSet.access_token as string); - params.session.user = userinfo; + params.session.user = userinfo; - return "/"; - } + return '/'; + } } export const getOptions = (app: Application) => { - return { app }; + return { app }; }; diff --git a/src/services/auth-oidc/callback/auth-oidc-callback.ts b/src/services/auth-oidc/callback/auth-oidc-callback.ts index 5131b2e..9024d2c 100644 --- a/src/services/auth-oidc/callback/auth-oidc-callback.ts +++ b/src/services/auth-oidc/callback/auth-oidc-callback.ts @@ -1,49 +1,46 @@ -import { http } from "@feathersjs/transport-commons"; -import type { Application } from "../../../declarations"; -import { - AuthOidcCallbackService, - getOptions, -} from "./auth-oidc-callback.class"; +import { http } from '@feathersjs/transport-commons'; +import type { Application } from '../../../declarations'; +import { AuthOidcCallbackService, getOptions } from './auth-oidc-callback.class'; -export const authOidcCallbackPath = "auth-oidc/callback"; -export const authOidcCallbackMethods = ["find"] as const; +export const authOidcCallbackPath = 'auth-oidc/callback'; +export const authOidcCallbackMethods = ['find'] as const; -export * from "./auth-oidc-callback.class"; +export * from './auth-oidc-callback.class'; export const authOidcCallback = (app: Application) => { - // TODO: fix this to use the correct type - // @ts-ignore - app.use( - authOidcCallbackPath, - new AuthOidcCallbackService(getOptions(app)), - { - methods: authOidcCallbackMethods, - events: [], - }, - (req: any, res: any) => { - return res.redirect(res.data); - }, - ); + // TODO: fix this to use the correct type + // @ts-ignore + app.use( + authOidcCallbackPath, + new AuthOidcCallbackService(getOptions(app)), + { + methods: authOidcCallbackMethods, + events: [], + }, + (req: any, res: any) => { + return res.redirect(res.data); + } + ); - app.service(authOidcCallbackPath).hooks({ - around: { - all: [], - }, - before: { - all: [], - find: [], - }, - after: { - all: [], - }, - error: { - all: [], - }, - }); + app.service(authOidcCallbackPath).hooks({ + around: { + all: [], + }, + before: { + all: [], + find: [], + }, + after: { + all: [], + }, + error: { + all: [], + }, + }); }; -declare module "../../../declarations" { - interface ServiceTypes { - [authOidcCallbackPath]: AuthOidcCallbackService; - } +declare module '../../../declarations' { + interface ServiceTypes { + [authOidcCallbackPath]: AuthOidcCallbackService; + } } diff --git a/src/services/index.ts b/src/services/index.ts index b5e6904..9a84b7a 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,10 +1,10 @@ -import { authOidcCallback } from "./auth-oidc/callback/auth-oidc-callback"; -import { authOidc } from "./auth-oidc/auth-oidc"; -import { aliases } from "./aliases/aliases"; -import type { Application } from "../declarations"; +import { authOidcCallback } from './auth-oidc/callback/auth-oidc-callback'; +import { authOidc } from './auth-oidc/auth-oidc'; +import { aliases } from './aliases/aliases'; +import type { Application } from '../declarations'; export const services = (app: Application) => { - app.configure(authOidcCallback); - app.configure(authOidc); - app.configure(aliases); + app.configure(authOidcCallback); + app.configure(authOidc); + app.configure(aliases); }; diff --git a/src/validators.ts b/src/validators.ts index a540fc9..2a69f14 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -1,29 +1,29 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html -import { Ajv, addFormats } from "@feathersjs/schema"; -import type { FormatsPluginOptions } from "@feathersjs/schema"; +import { Ajv, addFormats } from '@feathersjs/schema'; +import type { FormatsPluginOptions } from '@feathersjs/schema'; const formats: FormatsPluginOptions = [ - "date-time", - "time", - "date", - "email", - "hostname", - "ipv4", - "ipv6", - "uri", - "uri-reference", - "uuid", - "uri-template", - "json-pointer", - "relative-json-pointer", - "regex", + 'date-time', + 'time', + 'date', + 'email', + 'hostname', + 'ipv4', + 'ipv6', + 'uri', + 'uri-reference', + 'uuid', + 'uri-template', + 'json-pointer', + 'relative-json-pointer', + 'regex', ]; export const dataValidator: Ajv = addFormats(new Ajv({}), formats); export const queryValidator: Ajv = addFormats( - new Ajv({ - coerceTypes: true, - }), - formats, + new Ajv({ + coerceTypes: true, + }), + formats ); diff --git a/test/app.test.ts b/test/app.test.ts index fb37c1b..1ca0133 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -1,40 +1,40 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/app.test.html -import assert from "assert"; -import axios from "axios"; -import type { Server } from "http"; -import { app } from "../src/app"; +import assert from 'assert'; +import axios from 'axios'; +import type { Server } from 'http'; +import { app } from '../src/app'; -const port = app.get("port"); -const appUrl = `http://${app.get("host")}:${port}`; +const port = app.get('port'); +const appUrl = `http://${app.get('host')}:${port}`; -describe("Feathers application tests", () => { - let server: Server; +describe('Feathers application tests', () => { + let server: Server; - before(async () => { - server = await app.listen(port); - }); + before(async () => { + server = await app.listen(port); + }); - after(async () => { - await app.teardown(); - }); + after(async () => { + await app.teardown(); + }); - it("starts and shows the index page", async () => { - const { data } = await axios.get(appUrl); + it('starts and shows the index page', async () => { + const { data } = await axios.get(appUrl); - assert.ok(data.indexOf('') !== -1); - }); + assert.ok(data.indexOf('') !== -1); + }); - it("shows a 404 JSON error", async () => { - try { - await axios.get(`${appUrl}/path/to/nowhere`, { - responseType: "json", - }); - assert.fail("should never get here"); - } catch (error: any) { - const { response } = error; - assert.strictEqual(response?.status, 404); - assert.strictEqual(response?.data?.code, 404); - assert.strictEqual(response?.data?.name, "NotFound"); - } - }); + it('shows a 404 JSON error', async () => { + try { + await axios.get(`${appUrl}/path/to/nowhere`, { + responseType: 'json', + }); + assert.fail('should never get here'); + } catch (error: any) { + const { response } = error; + assert.strictEqual(response?.status, 404); + assert.strictEqual(response?.data?.code, 404); + assert.strictEqual(response?.data?.name, 'NotFound'); + } + }); }); diff --git a/test/services/aliases/aliases.test.ts b/test/services/aliases/aliases.test.ts index db9f5e2..ba8cba8 100644 --- a/test/services/aliases/aliases.test.ts +++ b/test/services/aliases/aliases.test.ts @@ -1,11 +1,11 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html -import assert from "assert"; -import { app } from "../../../src/app"; +import assert from 'assert'; +import { app } from '../../../src/app'; -describe("aliases service", () => { - it("registered the service", () => { - const service = app.service("aliases"); +describe('aliases service', () => { + it('registered the service', () => { + const service = app.service('aliases'); - assert.ok(service, "Registered the service"); - }); + assert.ok(service, 'Registered the service'); + }); }); diff --git a/test/services/auth-oidc/auth-oidc.test.ts b/test/services/auth-oidc/auth-oidc.test.ts index e877a96..68dfe26 100644 --- a/test/services/auth-oidc/auth-oidc.test.ts +++ b/test/services/auth-oidc/auth-oidc.test.ts @@ -1,11 +1,11 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html -import assert from "assert"; -import { app } from "../../../src/app"; +import assert from 'assert'; +import { app } from '../../../src/app'; -describe("auth-oidc service", () => { - it("registered the service", () => { - const service = app.service("auth-oidc"); +describe('auth-oidc service', () => { + it('registered the service', () => { + const service = app.service('auth-oidc'); - assert.ok(service, "Registered the service"); - }); + assert.ok(service, 'Registered the service'); + }); }); diff --git a/test/services/auth-oidc/callback/callback.test.ts b/test/services/auth-oidc/callback/callback.test.ts index 2e53041..1c8e272 100644 --- a/test/services/auth-oidc/callback/callback.test.ts +++ b/test/services/auth-oidc/callback/callback.test.ts @@ -1,11 +1,11 @@ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html -import assert from "assert"; -import { app } from "../../../../src/app"; +import assert from 'assert'; +import { app } from '../../../../src/app'; -describe("auth-oidc/callback service", () => { - it("registered the service", () => { - const service = app.service("auth-oidc/callback"); +describe('auth-oidc/callback service', () => { + it('registered the service', () => { + const service = app.service('auth-oidc/callback'); - assert.ok(service, "Registered the service"); - }); + assert.ok(service, 'Registered the service'); + }); }); diff --git a/validators.ts b/validators.ts deleted file mode 100644 index a540fc9..0000000 --- a/validators.ts +++ /dev/null @@ -1,29 +0,0 @@ -// For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html -import { Ajv, addFormats } from "@feathersjs/schema"; -import type { FormatsPluginOptions } from "@feathersjs/schema"; - -const formats: FormatsPluginOptions = [ - "date-time", - "time", - "date", - "email", - "hostname", - "ipv4", - "ipv6", - "uri", - "uri-reference", - "uuid", - "uri-template", - "json-pointer", - "relative-json-pointer", - "regex", -]; - -export const dataValidator: Ajv = addFormats(new Ajv({}), formats); - -export const queryValidator: Ajv = addFormats( - new Ajv({ - coerceTypes: true, - }), - formats, -);