From 2b87e044e16da45d0ebfb8f06e5b778bb65abd55 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sun, 25 Dec 2022 22:23:29 -0700 Subject: [PATCH 1/5] feat: added deleting users via the API --- src/auth.ts | 23 +++++++++++++++++++++++ src/routers/api.ts | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 8b1b3921..611cd3cf 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -164,6 +164,29 @@ export const setUserPassword = (unid: string, password: string): Promise = .catch(reject); }); +/** + * Deletes a user account + * @since v0.14.1 + */ +export const deleteUser = (unid: string): Promise => new Promise((resolve, reject) => { + + // Find the user + const user = users.find((user) => user.unid === unid); + if (!user) return reject(new Error('User not found')); + + // Remove the user from the users map + users.splice(users.indexOf(user), 1); + + // 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.splice(userIndex, 1); + fs.writeJson(authPath, authData, { spaces: '\t' }) + .then(() => resolve()) + .catch(reject); +}); + /** * Called by ass.ts on startup * @since v0.14.0 diff --git a/src/routers/api.ts b/src/routers/api.ts index fb45e921..dd268678 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -5,7 +5,7 @@ */ import { Router, Request, Response, NextFunction } from 'express'; -import { findFromToken, setUserPassword, users, createNewUser, verifyCliKey } from '../auth'; +import { findFromToken, setUserPassword, users, createNewUser, deleteUser, verifyCliKey } from '../auth'; import { log } from '../utils'; import { data } from '../data'; import { User } from '../types/auth'; @@ -15,6 +15,17 @@ import { User } from '../types/auth'; */ const RouterApi = Router(); +/** + * Logs an error and sends a 500 (404 if 'User not found' error) + * @since v0.14.1 + */ +const errorHandler = (res: Response, err: Error | any) => { + log.error(err); + if (err.message === 'User not found') + return res.sendStatus(404); + res.sendStatus(500); +}; + /** * Token authentication middleware for Admins * @since v0.14.0 @@ -56,7 +67,7 @@ function buildUserRouter() { setUserPassword(id, newPassword) .then(() => res.sendStatus(200)) - .catch((err) => (log.error(err), res.sendStatus(500))); + .catch((err) => errorHandler(res, err)); }); // Create a new user @@ -73,7 +84,7 @@ function buildUserRouter() { createNewUser(username, password, admin, meta) .then((user) => res.send(user)) - .catch((err) => (log.error(err), res.sendStatus(500))); + .catch((err) => errorHandler(res, err)); }); // Get a user (must be last as it's a catch-all) @@ -81,6 +92,23 @@ function buildUserRouter() { userRouter.get('/:id', adminAuthMiddleware, (req: Request, res: Response) => userFinder(res, users.find(user => user.unid === req.params.id || user.username === req.params.id))); + // Delete a user + // Admin only + userRouter.delete('/:id', adminAuthMiddleware, (req: Request, res: Response) => { + const id = req.params.id; + + deleteUser(id) + .then(() => res.sendStatus(200)) + .catch((err) => errorHandler(res, err)); + }); + + // Update a user + // Admin only + userRouter.put('/:id', adminAuthMiddleware, (req: Request, res: Response) => { + const id = req.params.id; + //WIP + }); + return userRouter; } From 894d773e8c93a91bd0f2c72fcdb327ac2f96686a Mon Sep 17 00:00:00 2001 From: tycrek Date: Sun, 25 Dec 2022 22:53:52 -0700 Subject: [PATCH 2/5] feat: added setting and deleting user meta keys via API --- src/auth.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++ src/routers/api.ts | 31 +++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 611cd3cf..45dfea3a 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -187,6 +187,58 @@ export const deleteUser = (unid: string): Promise => new Promise((resolve, .catch(reject); }); +/** + * Sets a meta value for a user + * @since v0.14.1 + */ +export const setUserMeta = (unid: string, key: string, value: any, force = false): Promise => new Promise((resolve, reject) => { + + // Find the user + const user = users.find((user) => user.unid === unid); + if (!user) return reject(new Error('User not found')); + + // Set the meta value + if (user.meta[key] && !force) return reject(new Error('Meta key already exists')); + + user.meta[key] = value; + + // 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' }) + .then(() => log.info('Set meta value for', user.unid, `${key}=${value}`)) + .then(() => resolve(user)) + .catch(reject); +}); + +/** + * Deletes a meta value for a user + * @since v0.14.1 + */ +export const deleteUserMeta = (unid: string, key: string): Promise => new Promise((resolve, reject) => { + + // Find the user + const user = users.find((user) => user.unid === unid); + if (!user) return reject(new Error('User not found')); + + // Delete the meta value + if (!user.meta[key]) return reject(new Error('Meta key does not exist')); + + delete user.meta[key]; + + // 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' }) + .then(() => log.info('Deleted meta value for', user.unid, key)) + .then(() => resolve(user)) + .catch(reject); +}); + /** * Called by ass.ts on startup * @since v0.14.0 diff --git a/src/routers/api.ts b/src/routers/api.ts index dd268678..74b83ca7 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -5,7 +5,7 @@ */ import { Router, Request, Response, NextFunction } from 'express'; -import { findFromToken, setUserPassword, users, createNewUser, deleteUser, verifyCliKey } from '../auth'; +import { findFromToken, setUserPassword, users, createNewUser, deleteUser, setUserMeta, deleteUserMeta, verifyCliKey } from '../auth'; import { log } from '../utils'; import { data } from '../data'; import { User } from '../types/auth'; @@ -102,11 +102,34 @@ function buildUserRouter() { .catch((err) => errorHandler(res, err)); }); - // Update a user + // Update a user meta key/value // Admin only - userRouter.put('/:id', adminAuthMiddleware, (req: Request, res: Response) => { + userRouter.put('/meta/:id', adminAuthMiddleware, (req: Request, res: Response) => { const id = req.params.id; - //WIP + const key: string | undefined = req.body.key; + const value: any = req.body.value; + const force = req.body.force ?? false; + + if (key == null || key.length === 0 || value == null || value.length === 0) + return res.sendStatus(400); + + setUserMeta(id, key, value, force) + .then(() => res.sendStatus(200)) + .catch((err) => errorHandler(res, err)); + }); + + // Delete a user meta key + // Admin only + userRouter.delete('/meta/:id', adminAuthMiddleware, (req: Request, res: Response) => { + const id = req.params.id; + const key: string | undefined = req.body.key; + + if (key == null || key.length === 0) + return res.sendStatus(400); + + deleteUserMeta(id, key) + .then(() => res.sendStatus(200)) + .catch((err) => errorHandler(res, err)); }); return userRouter; From 3b00e36092c970395816e49dff3fb4b443ca7fb0 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sun, 25 Dec 2022 22:54:07 -0700 Subject: [PATCH 3/5] feat: use proper HTTP error codes for specific errors --- src/routers/api.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index 74b83ca7..b723e4fe 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -21,9 +21,14 @@ const RouterApi = Router(); */ const errorHandler = (res: Response, err: Error | any) => { log.error(err); - if (err.message === 'User not found') - return res.sendStatus(404); - res.sendStatus(500); + switch (err.message) { + case 'User not found': + return res.sendStatus(404); + case 'Meta key already exists': + return res.sendStatus(409); + default: + return res.sendStatus(500); + } }; /** From 313c8a8f0d512b124698344bf9725222f815362b Mon Sep 17 00:00:00 2001 From: tycrek Date: Sun, 25 Dec 2022 23:02:41 -0700 Subject: [PATCH 4/5] chore: make this a little bit cleaner --- src/routers/api.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index b723e4fe..dc4aea7e 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -22,12 +22,9 @@ const RouterApi = Router(); const errorHandler = (res: Response, err: Error | any) => { log.error(err); switch (err.message) { - case 'User not found': - return res.sendStatus(404); - case 'Meta key already exists': - return res.sendStatus(409); - default: - return res.sendStatus(500); + case 'User not found': return res.sendStatus(404); + case 'Meta key already exists': return res.sendStatus(409); + default: return res.sendStatus(500); } }; From 30505f8befb6328d8c74468c6d16bcf38ed67045 Mon Sep 17 00:00:00 2001 From: tycrek Date: Sun, 25 Dec 2022 23:06:16 -0700 Subject: [PATCH 5/5] feat: add API route to get all users --- src/routers/api.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routers/api.ts b/src/routers/api.ts index dc4aea7e..8fa17157 100644 --- a/src/routers/api.ts +++ b/src/routers/api.ts @@ -89,6 +89,10 @@ function buildUserRouter() { .catch((err) => errorHandler(res, err)); }); + // Get all users + // Admin only + userRouter.get('/all', adminAuthMiddleware, (req: Request, res: Response) => res.json(users)); + // Get a user (must be last as it's a catch-all) // Admin only userRouter.get('/:id', adminAuthMiddleware, (req: Request, res: Response) => @@ -104,7 +108,7 @@ function buildUserRouter() { .catch((err) => errorHandler(res, err)); }); - // Update a user meta key/value + // Update a user meta key/value (/meta can be after /:id because they are not HTTP GET) // Admin only userRouter.put('/meta/:id', adminAuthMiddleware, (req: Request, res: Response) => { const id = req.params.id;