From 8c86ad6cfc75f77e6a8b6f7714ffcb7091c5c208 Mon Sep 17 00:00:00 2001 From: Roland Bernard <rolbernard@unibz.it> Date: Sat, 17 Apr 2021 14:17:30 +0200 Subject: [PATCH] Added options to update user data --- server/src/v1/auth.ts | 133 +++++++++++++++++++++++++++++++++--------- server/src/v1/user.ts | 85 ++++++++++++++++++++++++++- 2 files changed, 186 insertions(+), 32 deletions(-) diff --git a/server/src/v1/auth.ts b/server/src/v1/auth.ts index 89272dd..01e3f0f 100644 --- a/server/src/v1/auth.ts +++ b/server/src/v1/auth.ts @@ -10,9 +10,54 @@ import { getPublicKey, getPrivateKey } from '../keys'; const auth = express(); +export interface Token { + id: string; +} + +export async function tokenVerification(req: Request, _res: Response, next: NextFunction) { + const header = req.headers?.authorization; + let token: string | null = null; + if (header) { + const bearer = header.split(' '); + token = bearer[1]; + } else if (!req.body) { + req.body = {}; + } else if (req.body.token) { + token = req.body.token; + } + if (token) { + try { + const decoded = await asyncify(verify, token, await getPublicKey(), { algorithms: ["ES384"] }); + req.body.token = decoded; + } catch (err) { + delete req.body.token; + } + next(); + } else { + next(); + } +} + +export function requireVerification(req: Request, res: Response, next: NextFunction) { + if (req.body.token) { + next(); + } else { + res.status(403).json({ + status: 'error', + message: 'authentication failed', + }); + } +} + +async function generateToken(token: Token) { + return asyncify(sign, token, await getPrivateKey(), { algorithm: "ES384", expiresIn: 60 * 60 }); +} + interface RegisterBody { username: string; password: string; + email?: string; + realname?: string; } auth.post('/register', async (req, res) => { @@ -21,13 +66,17 @@ auth.post('/register', async (req, res) => { const id = uuid(); const passwdHash = await hash(body.password, 10); try { + const token = await generateToken({ id: id }); await database('users').insert({ id: id, user_name: body.username, - passwd_hash: passwdHash + passwd_hash: passwdHash, + email: body.email ?? null, + real_name: body.realname ?? null, }); res.status(200).json({ status: 'success', + token: token, }); } catch (e) { // Fails if unique constraint for username is not met @@ -56,9 +105,7 @@ auth.post('/token', async (req, res) => { const user = await database('users').where({ user_name: body.username }); if (user.length === 1) { if (await compare(body.password, user[0].passwd_hash)) { - const token = await asyncify(sign, { - id: user[0].id, - }, await getPrivateKey(), { algorithm: "ES384", expiresIn: 60 * 60 }); + const token = await generateToken({ id: user[0].id }); res.status(200).json({ status: 'success', token: token, @@ -76,7 +123,6 @@ auth.post('/token', async (req, res) => { }); } } catch (e) { - console.error(e); res.status(400).json({ status: 'error', message: 'failed to acquire token', @@ -109,40 +155,69 @@ auth.get("/extend", async function (req, res) { } }); -export async function tokenVerification(req: Request, _res: Response, next: NextFunction) { - const header = req.headers?.authorization; - let token: string | null = null; - if (header) { - const bearer = header.split(' '); - token = bearer[1]; - } else if (!req.body) { - req.body = {}; - } else if (req.body.token) { - token = req.body.token; - } - if (token) { +interface UsernameBody { + token: Token; + username: string; +} + +auth.put("/username", async function (req, res) { + if (isOfType<UsernameBody>(req.body, [['username', 'string']])) { + const body: UsernameBody = req.body; try { - const decoded = await asyncify(verify, token, await getPublicKey(), { algorithms: ["ES384"] }); - req.body.token = decoded; - } catch (err) { - delete req.body.token; + await database('users').update({ + user_name: body.username, + }).where({ + id: body.token.id, + }); + res.status(200).json({ + status: 'success', + }); + } catch (e) { + // Fails if unique constraint for username is not met + res.status(400).json({ + status: 'error', + message: 'failed to update username', + }); } - next(); } else { - next(); + res.status(400).json({ + status: 'error', + message: 'missing request fields', + }); } +}); + +interface PasswordBody { + token: Token; + password: string; } -export function requireVerification(req: Request, res: Response, next: NextFunction) { - if (req.body.token) { - next(); +auth.put("/password", async function (req, res) { + if (isOfType<PasswordBody>(req.body, [['password', 'string']])) { + const body: PasswordBody = req.body; + try { + const passwdHash = await hash(body.password, 10); + await database('users').update({ + passwd_hash: passwdHash + }).where({ + id: body.token.id, + }); + res.status(200).json({ + status: 'success', + }); + } catch (e) { + res.status(400).json({ + status: 'error', + message: 'failed to update password', + }); + } } else { - res.status(403).json({ + res.status(400).json({ status: 'error', - message: 'authentication failed', + message: 'missing request fields', }); } -} +}); export default auth; diff --git a/server/src/v1/user.ts b/server/src/v1/user.ts index f23b5b8..b933d6c 100644 --- a/server/src/v1/user.ts +++ b/server/src/v1/user.ts @@ -1,8 +1,10 @@ import express from 'express'; +import { validate } from 'uuid'; import database from '../database'; -import { requireVerification } from './auth'; +import { isOfType } from '../util'; +import { requireVerification, Token } from './auth'; const user = express(); @@ -12,8 +14,6 @@ user.get('/name/:username', async (req, res) => { .select({ id: 'id', username: 'user_name', - email: 'email', - realname: 'real_name', }) .where({ username: req.params.username }); if (user.length === 1) { @@ -66,4 +66,83 @@ user.get('/', async (req, res) => { } }); +interface UserUpdateBody { + token: Token; + realname?: string; + email?: string; +} + +user.put('/', async (req, res) => { + if ( + isOfType<UserUpdateBody>(req.body, [['realname', 'string']]) + || isOfType<UserUpdateBody>(req.body, [['email', 'string']]) + ) { + try { + const updated = await database('users') + .update({ + email: req.body.email, + real_name: req.body.realname, + }) + .where({ id: req.body.token.id }); + if (updated === 1) { + res.status(200).json({ + status: 'success', + }); + } else { + res.status(404).json({ + status: 'error', + message: 'user not found', + }); + } + } catch (e) { + res.status(400).json({ + status: 'error', + message: 'failed to update user', + }); + } + } else { + res.status(400).json({ + status: 'error', + message: 'missing request fields', + }); + } +}); + +user.get('/:uuid', async (req, res) => { + try { + const id = req.params.uuid; + if (validate(id)) { + const user = await database('users') + .select({ + id: 'id', + username: 'user_name', + email: 'email', + realname: 'real_name', + }) + .where({ id: id }); + if (user.length === 1) { + res.status(200).json({ + status: 'success', + user: user[0], + }); + } else { + res.status(404).json({ + status: 'error', + message: 'user not found', + }); + } + } else { + res.status(400).json({ + status: 'error', + message: 'malformed uuid', + }); + } + } catch (e) { + res.status(400).json({ + status: 'error', + message: 'failed get user', + }); + } +}); + export default user; -- GitLab