diff --git a/server/src/v1/auth.ts b/server/src/v1/auth.ts
index 89272dd644fb79bf8c0800e863670db972ebc0e4..01e3f0fa18bee2dc932756dad58c010c92d326b1 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 f23b5b8cdf7a93de07b2bf6b98af8b65370e18ce..b933d6c1ef8265e1031c8dedc7727baa8a837e88 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;