From e425425749122033f074973cefc4216f9603db73 Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 21:48:21 -0700 Subject: [PATCH 01/31] build: added nanoid package --- package-lock.json | 1 + package.json | 1 + src/generators/nanoid.ts | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 src/generators/nanoid.ts diff --git a/package-lock.json b/package-lock.json index 6ed19be..c9c5f99 100755 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "fs-extra": "^10.0.1", "helmet": "^4.6.0", "luxon": "^2.3.1", + "nanoid": "^3.3.4", "node-fetch": "^2.6.7", "node-vibrant": "^3.1.6", "postcss-font-magician": "^3.0.0", diff --git a/package.json b/package.json index d74eb31..def2330 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "fs-extra": "^10.0.1", "helmet": "^4.6.0", "luxon": "^2.3.1", + "nanoid": "^3.3.4", "node-fetch": "^2.6.7", "node-vibrant": "^3.1.6", "postcss-font-magician": "^3.0.0", diff --git a/src/generators/nanoid.ts b/src/generators/nanoid.ts new file mode 100644 index 0000000..cd87e7e --- /dev/null +++ b/src/generators/nanoid.ts @@ -0,0 +1,2 @@ +import { nanoid } from 'nanoid'; +export default ({ length }: { length?: number }) => nanoid(length); From 20b907d676dcd881fc5908bea979029c2e3d8ed7 Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 21:49:58 -0700 Subject: [PATCH 02/31] build: target ES2022 --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 5cec8f8..6b63df6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "extends": "@tsconfig/node16/tsconfig.json", "compilerOptions": { "outDir": "./dist", - "target": "ES2021", + "target": "ES2022", "lib": [ - "ES2021", + "ES2022", "DOM" ], "allowJs": true, From 7b88ee9638fb8e30789d0433c06e411bc3bd16f7 Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 21:54:50 -0700 Subject: [PATCH 03/31] feat: added type definitions for auth --- src/types/auth.d.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/types/auth.d.ts diff --git a/src/types/auth.d.ts b/src/types/auth.d.ts new file mode 100644 index 0000000..a42e2f6 --- /dev/null +++ b/src/types/auth.d.ts @@ -0,0 +1,64 @@ +/** + * Defines the structure of a user + */ +export interface User { + /** + * Name of the user + */ + username: string + + /** + * Hashed password. Passwords are hashed using bcrypt. + */ + passhash: string + + /** + * Token used for upload authentication + */ + token: string + + /** + * Indicates whether the user is an admin + */ + admin: boolean + + /** + * Extra metadata. Frontends can use this to store extra data. + */ + meta: { + [key: string]: any + } +} + +/** + * Defines the structure of the users.json file + */ +export interface Users { + /** + * List of users. The key is the user's unique ID. + */ + users: { + [key: string]: User + } + + /** + * Indicates whether auth.json has been migrated + */ + migrated?: boolean + + /** + * Extra metadata. Frontends can use this to store extra data. + */ + meta: { + [key: string]: any + } +} + +export interface OldUser { + username: string + count: number +} + +export interface OldUsers { + [key: string]: OldUser +} From 1a9fad707633857cb8f3f28c15d503cc5442e04b Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 21:58:28 -0700 Subject: [PATCH 04/31] feat: BREAKING: overhaul auth file format and handling (etc, expand) BREAKING CHANGE: any hosts with modified deployments of ass utilizing the auth file in its current state will need to fix their modifications. --- src/ass.ts | 8 +- src/auth.ts | 145 ++++++++++++++++++++++++++++++++++++- src/routers/resource.ts | 4 +- src/routers/upload.ts | 26 ++----- src/types/definitions.d.ts | 5 -- src/utils.ts | 5 -- 6 files changed, 156 insertions(+), 37 deletions(-) diff --git a/src/ass.ts b/src/ass.ts index 445e893..b075ad9 100644 --- a/src/ass.ts +++ b/src/ass.ts @@ -44,7 +44,7 @@ const ROUTERS = { }; // Read users and data -import { users } from './auth'; +import { onStart as AuthOnStart, users } from './auth'; import { data } from './data'; //#endregion @@ -127,10 +127,12 @@ app.use('/:resourceId', (req, _res, next) => (req.resourceId = req.params.resour // Error handler app.use((err: ErrWrap, _req: Request, res: Response) => log.error(err.message).err(err).callback(() => res.sendStatus(CODE_INTERNAL_SERVER_ERROR))); // skipcq: JS-0128 -(function start() { +(async function start() { + await AuthOnStart(); + if (data() == null) setTimeout(start, 100); else log - .info('Users', `${Object.keys(users).length}`) + .info('Users', `${users.size}`) .info('Files', `${data().size}`) .info('Data engine', data().name, data().type) .info('Frontend', ASS_FRONTEND.enabled ? ASS_FRONTEND.brand : 'disabled', `${ASS_FRONTEND.enabled ? `${getTrueHttp()}${getTrueDomain()}${ASS_FRONTEND.endpoint}` : ''}`) diff --git a/src/auth.ts b/src/auth.ts index 304d7ba..607de7d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -4,9 +4,152 @@ import fs from 'fs-extra'; import { log, path, arrayEquals } from './utils'; +import { nanoid } from 'nanoid'; +import { User, Users, OldUsers } from './types/auth'; +import { Request } from 'express'; -export const users = require('../auth.json').users || {}; +/** + * !!!!! + * Things for tycrek to do: + * - [ ] Add a way to configure passwords + * - [ ] Create new users + * - [ ] Modify user (admin, meta, replace token/token history) + * - [ ] Delete user + * - [x] Get user + * - [x] Get users + * - [x] Get user by token + */ + +/** + * Map of users + */ +export const users = new Map(); + +/** + * Migrates the old auth.json format to the new one + * @since v0.14.0 + */ +const migrate = (): Promise => new Promise(async (resolve, reject) => { + + // Get ready to read the old auth.json file + const authPath = path('auth.json'); + const oldUsers = fs.readJsonSync(authPath).users as OldUsers; + + // Create a new users object + const newUsers: Users = { users: {}, meta: {} }; + newUsers.migrated = true; + + // Loop through each user + Object.entries(oldUsers).forEach(([token, { username }]) => { + + // Create a new user object + const id = nanoid(); + const newUser: User = { + username: username, + passhash: '', // TODO: Figure out how to configure passwords + token, + admin: Object.keys(oldUsers).indexOf(token) === 0, + meta: {} + }; + + newUsers.users[id] = newUser; + }); + + // Save the new users object to auth.json + fs.writeJson(authPath, newUsers, { spaces: '\t' }) + .then(() => resolve(newUsers)) + .catch(reject); +}); + +/** + * This is a WIP + */ +export const createNewUser = (username: string, passhash: string, admin: boolean, meta?: { [key: string]: User }): Promise => new Promise(async (resolve, reject) => { + + // todo: finish this + + // Create a new user object + const id = nanoid(); + const newUser: User = { + username, + passhash, + token: nanoid(32), + admin, + meta: meta || {} + }; + + // Add the user to the users map + users.set(id, newUser); + + // Save the new user to auth.json + const authPath = path('auth.json'); + const auth = fs.readJsonSync(authPath) as Users; + auth.users[id] = newUser; + fs.writeJson(authPath, auth, { spaces: '\t' }); +}); + + +/** + * Called by ass.ts on startup + */ +export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) => { + const file = path(authFile); + + log.debug('Reading', file); + + // Check if the file exists + fs.stat(file) + + // Create the file if it doesn't exist + .catch((_errStat) => { + log.debug('File does not exist', authFile, 'will be created automatically'); + return fs.writeJson(file, { migrated: true }); + }) + .catch((errWriteJson) => log.error('Failed to create auth.json').callback(reject, errWriteJson)) + + // File exists or was created + .then(() => fs.readJson(file)) + .then((json: Users) => { + + // Check if the file is the old format + if (json.migrated === undefined || !json.migrated) return ( + log.debug('auth.json is in old format, migrating'), + migrate()); + else return json; + }) + .then((json: Users) => { + + // Check if the file is empty + if (Object.keys(json).length === 0) { + log.debug('auth.json is empty, creating default user'); + //return createDefaultUser(); // todo: need to do this + } + + // Add users to the map + Object.entries(json.users).forEach(([uuid, user]) => users.set(uuid, user)); + }) + .catch((errReadJson) => log.error('Failed to read auth.json').callback(reject, errReadJson)) + .then(resolve); +}); + +/** + * Retrieves a user using their upload token. + */ +export const findFromToken = (token: string) => { + for (const [uuid, user] of users) + if (user.token === token) + return { uuid, user }; + return null; +}; + +/** + * Verifies that the upload token in the request exists in the user map + */ +export const verify = (req: Request) => { + return req.headers.authorization && findFromToken(req.headers.authorization); +}; +// todo: This is definitely broken // Monitor auth.json for changes (triggered by running 'npm run new-token') fs.watch(path('auth.json'), { persistent: false }, (eventType: String) => eventType === 'change' && fs.readJson(path('auth.json')) diff --git a/src/routers/resource.ts b/src/routers/resource.ts index 9bcc2d3..068dd87 100644 --- a/src/routers/resource.ts +++ b/src/routers/resource.ts @@ -11,7 +11,7 @@ import { path, log, getTrueHttp, getTrueDomain, formatBytes, formatTimestamp, ge const { diskFilePath, s3enabled, viewDirect, useSia }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_NOT_FOUND, }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); import { data } from '../data'; -import { users } from '../auth'; +import { findFromToken } from '../auth'; import express from 'express'; const router = express.Router(); @@ -49,7 +49,7 @@ router.get('/', (req: Request, res: Response, next) => data().get(req.ass.resour fileIs: fileData.is, title: escape(fileData.originalname), mimetype: fileData.mimetype, - uploader: users[fileData.token].username, + uploader: findFromToken(fileData.token)?.user.username || 'Unknown', timestamp: formatTimestamp(fileData.timestamp, fileData.timeoffset), size: formatBytes(fileData.size), // todo: figure out how to not ignore this diff --git a/src/routers/upload.ts b/src/routers/upload.ts index bcdfa7d..5295b1e 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -1,4 +1,4 @@ -import { ErrWrap, User } from '../types/definitions'; +import { ErrWrap } from '../types/definitions'; import { Config, MagicNumbers } from 'ass-json'; import fs from 'fs-extra'; @@ -7,9 +7,9 @@ import bb from 'express-busboy'; import { DateTime } from 'luxon'; import { Webhook, MessageBuilder } from 'discord-webhook-node'; import { processUploaded } from '../storage'; -import { path, log, verify, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; +import { path, log, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; import { data } from '../data'; -import { users } from '../auth'; +import { findFromToken, verify } from '../auth'; const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType, spaceReplace, adminWebhookEnabled, adminWebhookUrl, adminWebhookUsername, adminWebhookAvatar }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_PAYLOAD_TOO_LARGE }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); @@ -35,7 +35,7 @@ bb.extend(router, { router.post('/', (req: Request, res: Response, next: Function) => { req.headers.authorization = req.headers.authorization || ''; req.token = req.headers.authorization.replace(/[^\da-z]/gi, ''); // Strip anything that isn't a digit or ASCII letter - !verify(req, users) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 + !verify(req) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 }); // Upload file @@ -110,7 +110,7 @@ router.post('/', (req: Request, res: Response, next: Function) => { .then(() => { // Log the upload const logInfo = `${req.file!.originalname} (${req.file!.mimetype}, ${formatBytes(req.file.size)})`; - const uploader = users[req.token ?? ''] ? users[req.token ?? ''].username : ''; + const uploader = findFromToken(req.token)?.user.username ?? 'Unknown'; log.success('File uploaded', logInfo, `uploaded by ${uploader}`); // Build the URLs @@ -163,22 +163,6 @@ router.post('/', (req: Request, res: Response, next: Function) => { adminWebhookUsername.trim().length === 0 ? 'ass admin logs' : adminWebhookUsername, adminWebhookAvatar.trim().length === 0 ? ASS_LOGO : adminWebhookAvatar, true); - - // Also update the users upload count - if (!users[req.token ?? '']) { - const generateUsername = () => generateId('random', 20, 0, req.file.size.toString()); // skipcq: JS-0074 - let username: string = generateUsername(); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - while (Object.values(users).findIndex((user: User) => user.username === username) !== -1) // skipcq: JS-0073 - username = generateUsername(); - - users[req.token ?? ''] = { username, count: 0 }; - } - users[req.token ?? ''].count += 1; - fs.writeJsonSync(path('auth.json'), { users }, { spaces: 4 }); - log.debug('Upload request flow completed', ''); }); }) diff --git a/src/types/definitions.d.ts b/src/types/definitions.d.ts index 75c5021..5628869 100644 --- a/src/types/definitions.d.ts +++ b/src/types/definitions.d.ts @@ -12,11 +12,6 @@ declare global { } } -export interface User { - token: string - username: string -} - export interface FileData { // Data from request file object uuid?: string diff --git a/src/utils.ts b/src/utils.ts index 0748fb1..934f828 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -70,10 +70,6 @@ export function replaceholder(data: string, size: number, timestamp: number, tim export function arrayEquals(arr1: any[], arr2: any[]) { return arr1.length === arr2.length && arr1.slice().sort().every((value: string, index: number) => value === arr2.slice().sort()[index]) -}; - -export function verify(req: Request, users: JSON) { - return req.headers.authorization && Object.prototype.hasOwnProperty.call(users, req.headers.authorization); } const idModes = { @@ -108,7 +104,6 @@ module.exports = { replaceholder, randomHexColour, sanitize, - verify, renameFile: (req: Request, newName: string) => new Promise((resolve: Function, reject) => { try { const paths = [req.file.destination, newName]; From 340b44a3000c64ad9bad56c73cdfa74b59cc9ead Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 21:58:57 -0700 Subject: [PATCH 05/31] fix: use `setURL` instead of `setUrl`, package seems unmaintained --- src/routers/upload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/upload.ts b/src/routers/upload.ts index 5295b1e..a64a509 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -130,7 +130,7 @@ router.post('/', (req: Request, res: Response, next: Function) => { const embed = new MessageBuilder() .setTitle(logInfo) // @ts-ignore - .setUrl(resourceUrl) + .setURL(resourceUrl) // I don't know why this is throwing an error when `setUrl` is used but it does. This is a workaround. .setDescription(`${admin ? `**User:** \`${uploader}\`\n` : ''}**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) .setThumbnail(thumbnailUrl) // @ts-ignore From 499aac0f5b20bc27dfb30b9360f96d60eb09149b Mon Sep 17 00:00:00 2001 From: tycrek Date: Tue, 29 Nov 2022 22:00:01 -0700 Subject: [PATCH 06/31] chore: indicate this will become v0.14.0 eventually --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index def2330..74aca19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "description": "The superior self-hosted ShareX server", "main": "ass.js", "engines": { From 27aa264d376451a59b1cd4dafa9af1b33afe6957 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 30 Nov 2022 13:16:44 -0700 Subject: [PATCH 07/31] refactor: switch to arrays instead of Objects/maps --- src/ass.ts | 2 +- src/auth.ts | 27 ++++++++++++--------------- src/routers/resource.ts | 2 +- src/routers/upload.ts | 2 +- src/types/auth.d.ts | 9 ++++++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ass.ts b/src/ass.ts index b075ad9..b5b38d4 100644 --- a/src/ass.ts +++ b/src/ass.ts @@ -132,7 +132,7 @@ app.use((err: ErrWrap, _req: Request, res: Response) => log.error(err.message).e if (data() == null) setTimeout(start, 100); else log - .info('Users', `${users.size}`) + .info('Users', `${users.length}`) .info('Files', `${data().size}`) .info('Data engine', data().name, data().type) .info('Frontend', ASS_FRONTEND.enabled ? ASS_FRONTEND.brand : 'disabled', `${ASS_FRONTEND.enabled ? `${getTrueHttp()}${getTrueDomain()}${ASS_FRONTEND.endpoint}` : ''}`) diff --git a/src/auth.ts b/src/auth.ts index 607de7d..80560e3 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -23,7 +23,7 @@ import { Request } from 'express'; /** * Map of users */ -export const users = new Map(); +export const users = [] as User[]; /** * Migrates the old auth.json format to the new one @@ -36,15 +36,15 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { const oldUsers = fs.readJsonSync(authPath).users as OldUsers; // Create a new users object - const newUsers: Users = { users: {}, meta: {} }; + const newUsers: Users = { users: [], meta: {} }; newUsers.migrated = true; // Loop through each user Object.entries(oldUsers).forEach(([token, { username }]) => { // Create a new user object - const id = nanoid(); const newUser: User = { + unid: nanoid(), username: username, passhash: '', // TODO: Figure out how to configure passwords token, @@ -52,7 +52,7 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { meta: {} }; - newUsers.users[id] = newUser; + newUsers.users.push(newUser); }); // Save the new users object to auth.json @@ -69,8 +69,8 @@ export const createNewUser = (username: string, passhash: string, admin: boolean // todo: finish this // Create a new user object - const id = nanoid(); const newUser: User = { + unid: nanoid(), username, passhash, token: nanoid(32), @@ -79,13 +79,13 @@ export const createNewUser = (username: string, passhash: string, admin: boolean }; // Add the user to the users map - users.set(id, newUser); + users.push(newUser); // Save the new user to auth.json const authPath = path('auth.json'); - const auth = fs.readJsonSync(authPath) as Users; - auth.users[id] = newUser; - fs.writeJson(authPath, auth, { spaces: '\t' }); + const authData = fs.readJsonSync(authPath) as Users; + authData.users.push(newUser); + fs.writeJson(authPath, authData, { spaces: '\t' }); }); @@ -126,20 +126,17 @@ export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) } // Add users to the map - Object.entries(json.users).forEach(([uuid, user]) => users.set(uuid, user)); + json.users.forEach((user) => users.push(user)); }) .catch((errReadJson) => log.error('Failed to read auth.json').callback(reject, errReadJson)) .then(resolve); }); /** - * Retrieves a user using their upload token. + * Retrieves a user using their upload token. Returns `null` if the user does not exist. */ export const findFromToken = (token: string) => { - for (const [uuid, user] of users) - if (user.token === token) - return { uuid, user }; - return null; + return users.find((user) => user.token === token) || null; }; /** diff --git a/src/routers/resource.ts b/src/routers/resource.ts index 068dd87..d42c7b0 100644 --- a/src/routers/resource.ts +++ b/src/routers/resource.ts @@ -49,7 +49,7 @@ router.get('/', (req: Request, res: Response, next) => data().get(req.ass.resour fileIs: fileData.is, title: escape(fileData.originalname), mimetype: fileData.mimetype, - uploader: findFromToken(fileData.token)?.user.username || 'Unknown', + uploader: findFromToken(fileData.token)?.username ?? 'Unknown', timestamp: formatTimestamp(fileData.timestamp, fileData.timeoffset), size: formatBytes(fileData.size), // todo: figure out how to not ignore this diff --git a/src/routers/upload.ts b/src/routers/upload.ts index a64a509..904d90e 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -110,7 +110,7 @@ router.post('/', (req: Request, res: Response, next: Function) => { .then(() => { // Log the upload const logInfo = `${req.file!.originalname} (${req.file!.mimetype}, ${formatBytes(req.file.size)})`; - const uploader = findFromToken(req.token)?.user.username ?? 'Unknown'; + const uploader = findFromToken(req.token)?.username ?? 'Unknown'; log.success('File uploaded', logInfo, `uploaded by ${uploader}`); // Build the URLs diff --git a/src/types/auth.d.ts b/src/types/auth.d.ts index a42e2f6..6234ac8 100644 --- a/src/types/auth.d.ts +++ b/src/types/auth.d.ts @@ -2,6 +2,11 @@ * Defines the structure of a user */ export interface User { + /** + * Unique ID, provided by Nano ID + */ + unid: string + /** * Name of the user */ @@ -37,9 +42,7 @@ export interface Users { /** * List of users. The key is the user's unique ID. */ - users: { - [key: string]: User - } + users: User[] /** * Indicates whether auth.json has been migrated From 42a7b4758fa8a6a2c7e6f4d23e385430840176ba Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 30 Nov 2022 14:15:23 -0700 Subject: [PATCH 08/31] chore: rename this to improve understanding --- src/auth.ts | 2 +- src/routers/upload.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 80560e3..10b9d62 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -142,7 +142,7 @@ export const findFromToken = (token: string) => { /** * Verifies that the upload token in the request exists in the user map */ -export const verify = (req: Request) => { +export const verifyValidToken = (req: Request) => { return req.headers.authorization && findFromToken(req.headers.authorization); }; diff --git a/src/routers/upload.ts b/src/routers/upload.ts index 904d90e..01632d6 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -9,7 +9,7 @@ import { Webhook, MessageBuilder } from 'discord-webhook-node'; import { processUploaded } from '../storage'; import { path, log, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; import { data } from '../data'; -import { findFromToken, verify } from '../auth'; +import { findFromToken, verifyValidToken } from '../auth'; const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType, spaceReplace, adminWebhookEnabled, adminWebhookUrl, adminWebhookUsername, adminWebhookAvatar }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_PAYLOAD_TOO_LARGE }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); @@ -35,7 +35,7 @@ bb.extend(router, { router.post('/', (req: Request, res: Response, next: Function) => { req.headers.authorization = req.headers.authorization || ''; req.token = req.headers.authorization.replace(/[^\da-z]/gi, ''); // Strip anything that isn't a digit or ASCII letter - !verify(req) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 + !verifyValidToken(req) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 }); // Upload file From 6be768d655087482a8a341462274e3df3c88df47 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:23:31 -0700 Subject: [PATCH 09/31] feat: auto-gen random unknown admin password during migration --- src/auth.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 10b9d62..69c3b26 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -7,6 +7,7 @@ import { log, path, arrayEquals } from './utils'; import { nanoid } from 'nanoid'; import { User, Users, OldUsers } from './types/auth'; import { Request } from 'express'; +import bcrypt from 'bcrypt'; /** * !!!!! @@ -40,15 +41,18 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { newUsers.migrated = true; // Loop through each user - Object.entries(oldUsers).forEach(([token, { username }]) => { + Object.entries(oldUsers).forEach(async ([token, { username }]) => { + + // Determine if this user is the admin + const admin = Object.keys(oldUsers).indexOf(token) === 0; // Create a new user object const newUser: User = { unid: nanoid(), username: username, - passhash: '', // TODO: Figure out how to configure passwords + passhash: admin ? await bcrypt.hash(nanoid(32), 10) : '', token, - admin: Object.keys(oldUsers).indexOf(token) === 0, + admin, meta: {} }; From 6cfd353a54a8d0f66fe5271351c3b44deca1287e Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:24:03 -0700 Subject: [PATCH 10/31] feat: added export for setting passwords --- src/auth.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/auth.ts b/src/auth.ts index 69c3b26..7bb8df1 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -92,6 +92,22 @@ export const createNewUser = (username: string, passhash: string, admin: boolean fs.writeJson(authPath, authData, { spaces: '\t' }); }); +export const setUserPassword = (unid: string, password: string): Promise => new Promise(async (resolve, reject) => { + + // Find the user + const user = users.find((user) => user.unid === unid); + if (!user) return reject(new Error('User not found')); + + // Set the password + user.passhash = await bcrypt.hash(password, 10); + + // Save the new user to auth.json + const authPath = path('auth.json'); + const authData = fs.readJsonSync(authPath) as Users; + const userIndex = authData.users.findIndex((user) => user.unid === unid); + authData.users[userIndex] = user; + fs.writeJson(authPath, authData, { spaces: '\t' }); +}); /** * Called by ass.ts on startup From 6338628739cfc4bb071b20027f7247d4b9b4d092 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:24:54 -0700 Subject: [PATCH 11/31] build: forgot to commit packages for bcrypt --- package-lock.json | 641 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 2 files changed, 642 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9c5f99..cc68990 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "license": "ISC", "dependencies": { "@skynetlabs/skynet-nodejs": "^2.3.0", @@ -20,6 +20,7 @@ "any-shell-escape": "^0.1.1", "autoprefixer": "^10.4.4", "aws-sdk": "^2.1115.0", + "bcrypt": "^5.1.0", "check-node-version": "^4.2.1", "crypto-random-string": "3.3.1", "cssnano": "^5.1.7", @@ -47,6 +48,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/express-brute": "^1.0.1", @@ -760,6 +762,39 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "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/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -918,6 +953,15 @@ "url": "https://patreon.com/tycrek" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -1138,6 +1182,11 @@ "jdataview": "^2.5.0" } }, + "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/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1247,6 +1296,23 @@ "node": ">= 8" } }, + "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/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1381,6 +1447,11 @@ "node": ">= 10.0.0" } }, + "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==" + }, "node_modules/base32-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base32-decode/-/base32-decode-1.0.0.tgz", @@ -1418,6 +1489,19 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1577,6 +1661,15 @@ "node": ">=0.8.0" } }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -1875,6 +1968,14 @@ "simple-swizzle": "^0.2.2" } }, + "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/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -1916,6 +2017,11 @@ "node": ">= 10" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -1941,6 +2047,11 @@ "node": ">=0.8.0" } }, + "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/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -2261,6 +2372,11 @@ "node": ">=0.4.0" } }, + "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2429,6 +2545,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "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/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2883,6 +3004,22 @@ "node": ">=12" } }, + "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.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2926,6 +3063,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/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/gauge/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" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2993,6 +3189,25 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3118,6 +3333,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/helmet": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", @@ -3195,6 +3415,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" }, + "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==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3801,6 +4030,20 @@ "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": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -3933,6 +4176,17 @@ "dom-walk": "^0.1.0" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/minimist": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", @@ -3941,6 +4195,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/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/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4078,6 +4355,20 @@ "querystring": "0.2.0" } }, + "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/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4116,6 +4407,17 @@ "node": ">=4" } }, + "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/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -4338,6 +4640,14 @@ "node": ">=4" } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -5362,6 +5672,20 @@ "node": ">= 0.4.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5965,6 +6289,22 @@ "postcss": "^8.0.9" } }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -5991,6 +6331,14 @@ "node": ">=6" } }, + "node_modules/tar/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/timm": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", @@ -6322,6 +6670,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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" + } + }, "node_modules/winston": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", @@ -7021,6 +7377,32 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "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" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7137,6 +7519,15 @@ "fs-extra": "^10.0.0" } }, + "@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -7355,6 +7746,11 @@ "jdataview": "^2.5.0" } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7434,6 +7830,20 @@ "picomatch": "^2.0.4" } }, + "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==" + }, + "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==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -7536,6 +7946,11 @@ "@babel/types": "^7.9.6" } }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "base32-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base32-decode/-/base32-decode-1.0.0.tgz", @@ -7559,6 +7974,15 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -7699,6 +8123,15 @@ } } }, + "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" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -7916,6 +8349,11 @@ "simple-swizzle": "^0.2.2" } }, + "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==" + }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -7948,6 +8386,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -7967,6 +8410,11 @@ "busboy": "~0.3.1" } }, + "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==" + }, "constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -8189,6 +8637,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8319,6 +8772,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "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==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8675,6 +9133,19 @@ "universalify": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -8702,6 +9173,52 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8754,6 +9271,19 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "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" + } + }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -8844,6 +9374,11 @@ "has-symbols": "^1.0.2" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "helmet": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", @@ -8913,6 +9448,15 @@ } } }, + "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" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -9360,6 +9904,14 @@ "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.0.tgz", "integrity": "sha512-IDkEPB80Rb6gCAU+FEib0t4FeJ4uVOuX1CQ9GsvU3O+JAGIgu0J7sf1OarXKaKDygTZIoJyU6YdZzTFRu+YR0A==" }, + "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==", + "requires": { + "semver": "^6.0.0" + } + }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -9453,11 +10005,36 @@ "dom-walk": "^0.1.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" + } + }, "minimist": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9564,6 +10141,14 @@ } } }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9587,6 +10172,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -9752,6 +10348,11 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" }, + "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==" + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -10443,6 +11044,14 @@ "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==" }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10862,6 +11471,26 @@ "resolve": "^1.22.1" } }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + } + } + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -11139,6 +11768,14 @@ "is-typed-array": "^1.1.9" } }, + "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==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "winston": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", diff --git a/package.json b/package.json index 74aca19..5510cdd 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "any-shell-escape": "^0.1.1", "autoprefixer": "^10.4.4", "aws-sdk": "^2.1115.0", + "bcrypt": "^5.1.0", "check-node-version": "^4.2.1", "crypto-random-string": "3.3.1", "cssnano": "^5.1.7", @@ -77,6 +78,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/express-brute": "^1.0.1", @@ -93,4 +95,4 @@ "@types/uuid": "^8.3.1", "@types/ws": "^7.4.7" } -} +} \ No newline at end of file From dc2f3937b2dd0b1f36f9d8422c3219cf1b59a8d9 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:25:24 -0700 Subject: [PATCH 12/31] feat: added CLI scripts for setting & testing hashed passwords --- package.json | 4 +++- src/tools/script.setpassword.ts | 19 +++++++++++++++++++ src/tools/script.testpassword.ts | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/tools/script.setpassword.ts create mode 100644 src/tools/script.testpassword.ts diff --git a/package.json b/package.json index 5510cdd..478c2c6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "docker-update": "git pull && npm run docker-uplite", "docker-uplite": "docker-compose up --force-recreate --build -d && docker image prune -f", "docker-upfull": "npm run docker-update && npm run docker-resetup", - "docker-resetup": "docker-compose exec ass npm run setup && docker-compose restart" + "docker-resetup": "docker-compose exec ass npm run setup && docker-compose restart", + "cli-setpassword": "node dist/tools/script.setpassword.js", + "cli-testpassword": "node dist/tools/script.testpassword.js" }, "repository": "github:tycrek/ass", "keywords": [ diff --git a/src/tools/script.setpassword.ts b/src/tools/script.setpassword.ts new file mode 100644 index 0000000..ebbd55a --- /dev/null +++ b/src/tools/script.setpassword.ts @@ -0,0 +1,19 @@ +import logger from '../logger'; +import { onStart, users, setUserPassword } from '../auth'; + +if (process.argv.length < 4) { + logger.error('Missing username/unid or password'); + process.exit(1); +} else { + const id = process.argv[2]; + const password = process.argv[3]; + + onStart(process.argv[4] || 'auth.json') + .then(() => { + const user = users.find((user) => user.unid === id || user.username === id); + if (!user) throw new Error('User not found'); + else return setUserPassword(user.unid, password); + }) + .then(() => logger.info('Password changed successfully').callback(() => process.exit(0))) + .catch((err) => logger.error(err).callback(() => process.exit(1))); +} diff --git a/src/tools/script.testpassword.ts b/src/tools/script.testpassword.ts new file mode 100644 index 0000000..699ec9a --- /dev/null +++ b/src/tools/script.testpassword.ts @@ -0,0 +1,20 @@ +import logger from '../logger'; +import { onStart, users } from '../auth'; +import { compare } from 'bcrypt'; + +if (process.argv.length < 4) { + logger.error('Missing username/unid or password'); + process.exit(1); +} else { + const id = process.argv[2]; + const password = process.argv[3]; + + onStart(process.argv[4] || 'auth.json') + .then(() => { + const user = users.find((user) => user.unid === id || user.username === id); + if (!user) throw new Error('User not found'); + else return compare(password, user.passhash); + }) + .then((result) => logger.info('Matches', `${result}`).callback(() => process.exit(0))) + .catch((err) => logger.error(err).callback(() => process.exit(1))); +} From 2dad6f13cea0bf82c85553af85d7edd6e81b8805 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:25:48 -0700 Subject: [PATCH 13/31] feat: `createNewUser` now accepts a password and handles hashing --- src/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 7bb8df1..5d5dccc 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -68,7 +68,7 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { /** * This is a WIP */ -export const createNewUser = (username: string, passhash: string, admin: boolean, meta?: { [key: string]: User }): Promise => new Promise(async (resolve, reject) => { +export const createNewUser = (username: string, password: string, admin: boolean, meta?: { [key: string]: User }): Promise => new Promise(async (resolve, reject) => { // todo: finish this @@ -76,7 +76,7 @@ export const createNewUser = (username: string, passhash: string, admin: boolean const newUser: User = { unid: nanoid(), username, - passhash, + passhash: await bcrypt.hash(password, 10), token: nanoid(32), admin, meta: meta || {} From 1887409eeb10adae2d0071ac2ab17a30968ce24c Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 13:26:56 -0700 Subject: [PATCH 14/31] fix: put salt rounds in a constant --- src/auth.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 5d5dccc..c660629 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -9,6 +9,8 @@ import { User, Users, OldUsers } from './types/auth'; import { Request } from 'express'; import bcrypt from 'bcrypt'; +const SALT_ROUNDS = 10; + /** * !!!!! * Things for tycrek to do: @@ -50,7 +52,7 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { const newUser: User = { unid: nanoid(), username: username, - passhash: admin ? await bcrypt.hash(nanoid(32), 10) : '', + passhash: admin ? await bcrypt.hash(nanoid(32), SALT_ROUNDS) : '', token, admin, meta: {} @@ -76,7 +78,7 @@ export const createNewUser = (username: string, password: string, admin: boolean const newUser: User = { unid: nanoid(), username, - passhash: await bcrypt.hash(password, 10), + passhash: await bcrypt.hash(password, SALT_ROUNDS), token: nanoid(32), admin, meta: meta || {} @@ -99,7 +101,7 @@ export const setUserPassword = (unid: string, password: string): Promise = if (!user) return reject(new Error('User not found')); // Set the password - user.passhash = await bcrypt.hash(password, 10); + user.passhash = await bcrypt.hash(password, SALT_ROUNDS); // Save the new user to auth.json const authPath = path('auth.json'); From 619a30d6850f48507e845395111b2740e84e8cc1 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:14:11 -0700 Subject: [PATCH 15/31] fix: auth file written before bcrypt Promise resolved --- src/auth.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index c660629..4241e2d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -43,23 +43,24 @@ const migrate = (): Promise => new Promise(async (resolve, reject) => { newUsers.migrated = true; // Loop through each user - Object.entries(oldUsers).forEach(async ([token, { username }]) => { + await Promise.all(Object.entries(oldUsers).map(async ([token, { username }]) => { // Determine if this user is the admin const admin = Object.keys(oldUsers).indexOf(token) === 0; + const passhash = admin ? await bcrypt.hash(nanoid(32), SALT_ROUNDS) : ''; // Create a new user object const newUser: User = { unid: nanoid(), - username: username, - passhash: admin ? await bcrypt.hash(nanoid(32), SALT_ROUNDS) : '', + username, + passhash, token, admin, meta: {} }; newUsers.users.push(newUser); - }); + })); // Save the new users object to auth.json fs.writeJson(authPath, newUsers, { spaces: '\t' }) From 76dc68405aff212f01b0b251e71d6d130e0c179f Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:29:39 -0700 Subject: [PATCH 16/31] feat: deprecate `token` in FileData, switch to `uploader` --- src/auth.ts | 6 ++++-- src/routers/resource.ts | 4 ++-- src/routers/upload.ts | 2 +- src/types/definitions.d.ts | 6 +++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 4241e2d..2c83dc8 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -3,11 +3,13 @@ */ import fs from 'fs-extra'; -import { log, path, arrayEquals } from './utils'; import { nanoid } from 'nanoid'; -import { User, Users, OldUsers } from './types/auth'; import { Request } from 'express'; import bcrypt from 'bcrypt'; +import { log, path, arrayEquals } from './utils'; +import { data } from './data'; +import { User, Users, OldUsers } from './types/auth'; +import { FileData } from './types/definitions'; const SALT_ROUNDS = 10; diff --git a/src/routers/resource.ts b/src/routers/resource.ts index d42c7b0..aee06d3 100644 --- a/src/routers/resource.ts +++ b/src/routers/resource.ts @@ -11,7 +11,7 @@ import { path, log, getTrueHttp, getTrueDomain, formatBytes, formatTimestamp, ge const { diskFilePath, s3enabled, viewDirect, useSia }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_NOT_FOUND, }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); import { data } from '../data'; -import { findFromToken } from '../auth'; +import { users } from '../auth'; import express from 'express'; const router = express.Router(); @@ -49,7 +49,7 @@ router.get('/', (req: Request, res: Response, next) => data().get(req.ass.resour fileIs: fileData.is, title: escape(fileData.originalname), mimetype: fileData.mimetype, - uploader: findFromToken(fileData.token)?.username ?? 'Unknown', + uploader: users.find(user => user.unid === fileData.uploader)?.username || 'Unknown', timestamp: formatTimestamp(fileData.timestamp, fileData.timeoffset), size: formatBytes(fileData.size), // todo: figure out how to not ignore this diff --git a/src/routers/upload.ts b/src/routers/upload.ts index 01632d6..be89389 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -60,7 +60,7 @@ router.post('/', (req: Request, res: Response, next: Function) => { req.file!.timeoffset = req.headers['x-ass-timeoffset']?.toString() || 'UTC+0'; // Keep track of the token that uploaded the resource - req.file.token = req.token ?? ''; + req.file.uploader = findFromToken(req.token)?.unid ?? ''; // Attach any embed overrides, if necessary req.file.opengraph = { diff --git a/src/types/definitions.d.ts b/src/types/definitions.d.ts index 5628869..cbbdfbf 100644 --- a/src/types/definitions.d.ts +++ b/src/types/definitions.d.ts @@ -38,7 +38,11 @@ export interface FileData { domain: string timestamp: number timeoffset: string - token: string + /** + * @deprecated + */ + token?: string + uploader: string opengraph: OpenGraphData // I found this in utils and idk where it comes from From 6cc0cb3ffae62daf4854e40ad4e6a769d83802a0 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:30:36 -0700 Subject: [PATCH 17/31] feat: use vars for filenames instead of direct strings --- src/auth.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 2c83dc8..e3f7337 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -34,10 +34,10 @@ export const users = [] as User[]; * Migrates the old auth.json format to the new one * @since v0.14.0 */ -const migrate = (): Promise => new Promise(async (resolve, reject) => { +const migrate = (authFileName = 'auth.json'): Promise => new Promise(async (resolve, reject) => { // Get ready to read the old auth.json file - const authPath = path('auth.json'); + const authPath = path(authFileName); const oldUsers = fs.readJsonSync(authPath).users as OldUsers; // Create a new users object @@ -118,8 +118,8 @@ export const setUserPassword = (unid: string, password: string): Promise = * Called by ass.ts on startup */ export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) => { - const file = path(authFile); + const file = path(authFile); log.debug('Reading', file); // Check if the file exists @@ -139,7 +139,7 @@ export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) // Check if the file is the old format if (json.migrated === undefined || !json.migrated) return ( log.debug('auth.json is in old format, migrating'), - migrate()); + migrate(authFile)); else return json; }) .then((json: Users) => { From 4c4009207ff2d212e93245ee226ca88b193bd82a Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:31:06 -0700 Subject: [PATCH 18/31] feat: migrate datafile (token => uploader unid) --- src/auth.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/auth.ts b/src/auth.ts index e3f7337..a2732db 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -66,6 +66,27 @@ const migrate = (authFileName = 'auth.json'): Promise => new Promise(asyn // Save the new users object to auth.json fs.writeJson(authPath, newUsers, { spaces: '\t' }) + .catch(reject) + + // Migrate the datafile (token => uploader) + .then(() => data().get()) + .then((fileData: [string, FileData][]) => + + // Wait for all the deletions and puts to finish + Promise.all(fileData.map(async ([key, file]) => { + + // We need to use `newUsers` because `users` hasn't been re-assigned yet + const user = newUsers.users.find((user) => user.token === file.token!)?.unid ?? ''; // ? This is probably fine + + // Because of the stupid way I wrote papito, we need to DEL before we can PUT + await data().del(key); + + // PUT the new data + return data().put(key, { ...file, uploader: user }); + }))) + + // We did it hoofuckingray + .then(() => log.success('Migrated all auth & file data to new auth system')) .then(() => resolve(newUsers)) .catch(reject); }); From a774ab311b15fc2cac6aa9c43cd70ae474d29ca4 Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:31:28 -0700 Subject: [PATCH 19/31] feat: try to reset user array at start of onStart* I don't think it works --- src/auth.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/auth.ts b/src/auth.ts index a2732db..85b310f 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -139,6 +139,9 @@ export const setUserPassword = (unid: string, password: string): Promise = * Called by ass.ts on startup */ export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) => { + // Reset user array (https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript#1232046) + // ! I don't think this works properly..? + users.splice(0, users.length); const file = path(authFile); log.debug('Reading', file); From 87c8e64698cb046df0cee7f75567a5afb50d98af Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:40:18 -0700 Subject: [PATCH 20/31] fix: remove this method for duplicating the list (ironically correctly) --- src/auth.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 85b310f..0afac37 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -195,9 +195,9 @@ export const verifyValidToken = (req: Request) => { return req.headers.authorization && findFromToken(req.headers.authorization); }; -// todo: This is definitely broken +// todo: move inside of onStart (currently broken) // Monitor auth.json for changes (triggered by running 'npm run new-token') -fs.watch(path('auth.json'), { persistent: false }, +/* fs.watch(path('auth.json'), { persistent: false }, (eventType: String) => eventType === 'change' && fs.readJson(path('auth.json')) .then((json: { users: JSON[] }) => { if (!(arrayEquals(Object.keys(users), Object.keys(json.users)))) { @@ -207,3 +207,4 @@ fs.watch(path('auth.json'), { persistent: false }, } }) .catch(console.error)); + */ \ No newline at end of file From 0b5b7d703875ed74dac7854809306d7e280985cc Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 14:43:01 -0700 Subject: [PATCH 21/31] chore: hide backup files --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d339572..57c63b1 100755 --- a/.gitignore +++ b/.gitignore @@ -104,11 +104,11 @@ dist .tern-port # tokens -auth.json +auth.json* auth.*.json # data -data.json +data.json* # uploads uploads/ From fd06e921915794f02df3d9000b327c9a0236142f Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 16:03:26 -0700 Subject: [PATCH 22/31] fix: API auth middleware for new auth system --- src/routers/api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index c028130..f99f06c 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -5,7 +5,7 @@ */ import { Router, Request, Response, NextFunction } from 'express'; -import { users } from '../auth'; +import { findFromToken, users } from '../auth'; import { data } from '../data'; const RouterApi = Router(); @@ -16,8 +16,8 @@ const RouterResource = Router(); * Token authentication middleware */ const authMiddleware = (req: Request, res: Response, next: NextFunction) => { - const token = req.headers.authorization; - (token && users[token]) + const user = findFromToken(req.headers.authorization ?? ''); + (user && user.admin) ? next() : res.sendStatus(401); }; From 6c9b8166e95b9969ea5c039d4dd40d04083fafdf Mon Sep 17 00:00:00 2001 From: tycrek Date: Wed, 7 Dec 2022 16:05:18 -0700 Subject: [PATCH 23/31] feat: use functions instead because why not --- src/routers/api.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index f99f06c..5fdc92d 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -9,8 +9,17 @@ import { findFromToken, users } from '../auth'; import { data } from '../data'; const RouterApi = Router(); -const RouterUser = Router(); -const RouterResource = Router(); + +function buildUserRouter() { + const RouterUser = Router(); + + return RouterUser; +} +function buildResourceRouter() { + const RouterResource = Router(); + + return RouterResource; +} /** * Token authentication middleware @@ -23,8 +32,8 @@ const authMiddleware = (req: Request, res: Response, next: NextFunction) => { }; export const onStart = () => { - RouterApi.use('/user', RouterUser); - RouterApi.use('/resource', RouterResource); + RouterApi.use('/user', buildUserRouter()); + RouterApi.use('/resource', buildResourceRouter()); return RouterApi; }; From ac8c861bb100805b7850e680ef56e3a54fec4e08 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:51:37 -0700 Subject: [PATCH 24/31] feat: add middleware function for verifying admin users --- src/routers/api.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/routers/api.ts b/src/routers/api.ts index 5fdc92d..19159a2 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -7,9 +7,20 @@ import { Router, Request, Response, NextFunction } from 'express'; import { findFromToken, users } from '../auth'; import { data } from '../data'; +import { User } from '../types/auth'; +/** + * The primary API router + */ const RouterApi = Router(); +/** + * Token authentication middleware for Admins + */ +const adminAuthMiddleware = (req: Request, res: Response, next: NextFunction) => { + const user = findFromToken(req.headers.authorization ?? ''); + (user && user.admin) ? next() : res.sendStatus(401); +}; function buildUserRouter() { const RouterUser = Router(); From 118039f84997b7eccb7c0ea62f00f12f764d9ca8 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:52:29 -0700 Subject: [PATCH 25/31] fix: user names that make sense --- src/routers/api.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index 19159a2..b4fd4c5 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -22,14 +22,15 @@ const adminAuthMiddleware = (req: Request, res: Response, next: NextFunction) => (user && user.admin) ? next() : res.sendStatus(401); }; function buildUserRouter() { - const RouterUser = Router(); + const userRouter = Router(); - return RouterUser; + return userRouter; } + function buildResourceRouter() { - const RouterResource = Router(); + const resourceRouter = Router(); - return RouterResource; + return resourceRouter; } /** From 55401441fa644727933166d879c74f5e6ca5fa8e Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:52:47 -0700 Subject: [PATCH 26/31] fix: remove duplicate function --- src/routers/api.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index b4fd4c5..246cce6 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -33,16 +33,6 @@ function buildResourceRouter() { return resourceRouter; } -/** - * Token authentication middleware - */ -const authMiddleware = (req: Request, res: Response, next: NextFunction) => { - const user = findFromToken(req.headers.authorization ?? ''); - (user && user.admin) - ? next() - : res.sendStatus(401); -}; - export const onStart = () => { RouterApi.use('/user', buildUserRouter()); RouterApi.use('/resource', buildResourceRouter()); From cb1d75ff1d15a66bfe7fa9692613560236eb0847 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:53:39 -0700 Subject: [PATCH 27/31] feat: added user API routes --- src/routers/api.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/routers/api.ts b/src/routers/api.ts index 246cce6..ce086a2 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -21,9 +21,35 @@ const adminAuthMiddleware = (req: Request, res: Response, next: NextFunction) => const user = findFromToken(req.headers.authorization ?? ''); (user && user.admin) ? next() : res.sendStatus(401); }; + +/** + * Simple function to either return JSON or a 404, so I don't have to write it 40 times. + */ +const userFinder = (res: Response, user: User | undefined) => user ? res.json(user) : res.sendStatus(404); + function buildUserRouter() { const userRouter = Router(); + // Index + userRouter.get('/', (_req: Request, res: Response) => res.sendStatus(200)); + + // Get all users + // Admin only + userRouter.get('/all', adminAuthMiddleware, (req: Request, res: Response) => res.json(users)); + + // Get self + userRouter.get('/self', (req: Request, res: Response) => + userFinder(res, findFromToken(req.headers['authorization'] ?? '') ?? undefined)); + + // Get user by token + userRouter.get('/token/:token', (req: Request, res: Response) => + userFinder(res, users.find(user => user.token === req.params.token))); + + // Get a user (must be last as it's a catch-all) + // Admin only + userRouter.get('/:id', adminAuthMiddleware, (req: Request, res: Response) => + userFinder(res, users.find(user => user.unid === req.params.id || user.username === req.params.id))); + return userRouter; } From 16ba3ca438ab26789be4d6dfec3ed2d9168bd2d9 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:54:46 -0700 Subject: [PATCH 28/31] feat: allow password resets over the API --- src/ass.ts | 6 +++++- src/routers/api.ts | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ass.ts b/src/ass.ts index b5b38d4..097812f 100644 --- a/src/ass.ts +++ b/src/ass.ts @@ -3,7 +3,7 @@ import { Config, MagicNumbers, Package } from 'ass-json'; //#region Imports import fs from 'fs-extra'; -import express, { Request, Response } from 'express'; +import express, { Request, Response, json as BodyParserJson } from 'express'; import nofavicon from '@tycrek/express-nofavicon'; import { epcss } from '@tycrek/express-postcss'; import tailwindcss from 'tailwindcss'; @@ -80,6 +80,10 @@ app.get(['/'], bruteforce.prevent, (_req, _res, next) => next()); // Express logger middleware app.use(log.middleware()); +// Body parser for API POST requests +// (I really don't like this being top level but it does not work inside the API Router as of 2022-12-24) +app.use(BodyParserJson()); + // Helmet security middleware app.use(helmet.noSniff()); app.use(helmet.ieNoOpen()); diff --git a/src/routers/api.ts b/src/routers/api.ts index ce086a2..a7a0675 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -5,7 +5,7 @@ */ import { Router, Request, Response, NextFunction } from 'express'; -import { findFromToken, users } from '../auth'; +import { findFromToken, setUserPassword, users } from '../auth'; import { data } from '../data'; import { User } from '../types/auth'; @@ -45,6 +45,17 @@ function buildUserRouter() { userRouter.get('/token/:token', (req: Request, res: Response) => userFinder(res, users.find(user => user.token === req.params.token))); + // Reset password (new plaintext password in form data; HOST SHOULD BE USING HTTPS) + // Admin only + userRouter.post('/reset', adminAuthMiddleware, (req: Request, res: Response) => { + const id = req.body.id; + const newPassword = req.body.password; + + setUserPassword(id, newPassword) + .then(() => res.sendStatus(200)) + .catch(() => res.sendStatus(500)); + }); + // Get a user (must be last as it's a catch-all) // Admin only userRouter.get('/:id', adminAuthMiddleware, (req: Request, res: Response) => From 0af13589ff56d54a4053a729583b408d951b2bb8 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:55:03 -0700 Subject: [PATCH 29/31] docs: add new user system to README --- .github/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/README.md b/.github/README.md index e179b44..a75a75d 100644 --- a/.github/README.md +++ b/.github/README.md @@ -337,6 +337,17 @@ S3 servers are generally very fast & have very good uptime, though this will dep [Amazon S3]: https://en.wikipedia.org/wiki/Amazon_S3 [Skynet Labs]: https://github.com/SkynetLabs +## New user system (v0.14.0) + +The user system was overhauled in v0.14.0 to allow more features and flexibility. New fields on users include `admin`, `passhash`, `unid`, and `meta` (these will be documented more once the system is finalized). + +ass will automatically convert your old `auth.json` to the new format. **Always backup your `auth.json` and `data.json` before updating**. By default, the original user (named `ass`) will be marked as an admin. Adding new users via `npm run new-token ` should work as expected, though you'll need to re-launch ass to load the new file. + +**Things still borked:** + +- Creating a default user on new installs +- Creating/modifying/deleting users via the API +- The filewatcher that reloads `auth.json` when modified on CLI (to be changed in the future) ## Custom frontends - OUTDATED **Please be aware that this section is outdated (marked as of 2022-04-15). It will be updated when I overhaul the frontend system.** From ad3b7435b6a5ff2c36dbca37a74d86e13db7bac7 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:55:20 -0700 Subject: [PATCH 30/31] docs: add API to README --- .github/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/README.md b/.github/README.md index a75a75d..7661001 100644 --- a/.github/README.md +++ b/.github/README.md @@ -348,6 +348,29 @@ ass will automatically convert your old `auth.json` to the new format. **Always - Creating a default user on new installs - Creating/modifying/deleting users via the API - The filewatcher that reloads `auth.json` when modified on CLI (to be changed in the future) + +## Developer API + +ass includes an API (v0.14.0) for frontend developers to easily integrate with. Right now the API is pretty limited but I will expand on it in the future, with frontend developer feedback. + +Any endpoints requiring authorization will require an `Authorization` header with the value being the user's upload token. Admin users are a new feature introduced in v0.14.0. Admin users can access all endpoints, while non-admin users can only access those relevant to them. + +Other things to note: + +- **All endpoints are prefixed with `/api/`**. +- All endpoints will return a JSON object unless otherwise specified. +- Successful endpoints *should* return a `200` status code. Any errors will use the corresponding `4xx` or `5xx` status code (such as `401 Unauthorized`). + +### API endpoints + +| Endpoint | Purpose | Admin? | +| -------- | ------- | ------ | +| **`GET /user/all`** | Returns a list of all users | Yes | +| **`GET /user/self`** | Returns the current user | No | +| **`GET /user/token/:token`** | Returns the user with the given token | No | +| **`POST /user/reset`** | Resets the current user's **password** (token resets coming soon) | No | +| **`GET /user/:id`** | Returns the user with the given ID | Yes | + ## Custom frontends - OUTDATED **Please be aware that this section is outdated (marked as of 2022-04-15). It will be updated when I overhaul the frontend system.** From 45f77001a0e76f88f76a7232a9bbfa2a81a5cb00 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sat, 24 Dec 2022 16:55:27 -0700 Subject: [PATCH 31/31] docs: small note --- .github/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/README.md b/.github/README.md index 7661001..5acbfcc 100644 --- a/.github/README.md +++ b/.github/README.md @@ -375,6 +375,8 @@ Other things to note: **Please be aware that this section is outdated (marked as of 2022-04-15). It will be updated when I overhaul the frontend system.** +**Update 2022-12-24: I plan to overhaul this early in 2023.** + ass is intended to provide a strong backend for developers to build their own frontends around. [Git Submodules] make it easy to create custom frontends. Submodules are their own projects, which means you are free to build the router however you wish, as long as it exports the required items. A custom frontend is really just an [Express.js router]. **For a detailed walkthrough on developing your first frontend, [consult the wiki][ctw1].**