From ddb460f8d0a61067741c9821b30be7085c33f5e3 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Mon, 5 May 2025 17:43:57 +0200 Subject: [PATCH] enable switch to pw totp in account settings --- src/command/management/user/userCommand.ts | 3 + .../management/user/userCommandHandler.ts | 1 + src/controller/authController.ts | 1 + src/controller/resetController.ts | 2 + src/controller/userController.ts | 129 +++++++++++++++++- src/routes/user.ts | 33 ++++- 6 files changed, 165 insertions(+), 4 deletions(-) diff --git a/src/command/management/user/userCommand.ts b/src/command/management/user/userCommand.ts index 90f9872..66bab37 100644 --- a/src/command/management/user/userCommand.ts +++ b/src/command/management/user/userCommand.ts @@ -1,3 +1,5 @@ +import { LoginRoutineEnum } from "../../../enums/loginRoutineEnum"; + export interface CreateUserCommand { mail: string; username: string; @@ -18,6 +20,7 @@ export interface UpdateUserCommand { export interface UpdateUserSecretCommand { id: string; secret: string; + routine: LoginRoutineEnum; } export interface TransferUserOwnerCommand { diff --git a/src/command/management/user/userCommandHandler.ts b/src/command/management/user/userCommandHandler.ts index 590b2de..0f89ef5 100644 --- a/src/command/management/user/userCommandHandler.ts +++ b/src/command/management/user/userCommandHandler.ts @@ -75,6 +75,7 @@ export default abstract class UserCommandHandler { .update(user) .set({ secret: updateUser.secret, + routine: updateUser.routine, }) .where("id = :id", { id: updateUser.id }) .execute() diff --git a/src/controller/authController.ts b/src/controller/authController.ts index ce674ab..77a6717 100644 --- a/src/controller/authController.ts +++ b/src/controller/authController.ts @@ -50,6 +50,7 @@ export async function login(req: Request, res: Response): Promise { window: 2, }); } else { + console.log(passedSecret, secret, passedSecret == secret); valid = passedSecret == secret; } diff --git a/src/controller/resetController.ts b/src/controller/resetController.ts index 3592639..1a12b3f 100644 --- a/src/controller/resetController.ts +++ b/src/controller/resetController.ts @@ -14,6 +14,7 @@ import UserService from "../service/management/userService"; import { UpdateUserSecretCommand } from "../command/management/user/userCommand"; import UserCommandHandler from "../command/management/user/userCommandHandler"; import SettingHelper from "../helpers/settingsHelper"; +import { LoginRoutineEnum } from "../enums/loginRoutineEnum"; /** * @description request totp reset @@ -101,6 +102,7 @@ export async function finishReset(req: Request, res: Response): Promise { let updateUserSecret: UpdateUserSecretCommand = { id, secret, + routine: LoginRoutineEnum.totp, }; await UserCommandHandler.updateSecret(updateUserSecret); diff --git a/src/controller/userController.ts b/src/controller/userController.ts index c8001a7..1fe92c5 100644 --- a/src/controller/userController.ts +++ b/src/controller/userController.ts @@ -4,10 +4,15 @@ import QRCode from "qrcode"; import InternalException from "../exceptions/internalException"; import UserService from "../service/management/userService"; import UserFactory from "../factory/admin/management/user"; -import { TransferUserOwnerCommand, UpdateUserCommand } from "../command/management/user/userCommand"; +import { + TransferUserOwnerCommand, + UpdateUserCommand, + UpdateUserSecretCommand, +} from "../command/management/user/userCommand"; import UserCommandHandler from "../command/management/user/userCommandHandler"; import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; import SettingHelper from "../helpers/settingsHelper"; +import { LoginRoutineEnum } from "../enums/loginRoutineEnum"; /** * @description get my by id @@ -22,6 +27,21 @@ export async function getMeById(req: Request, res: Response): Promise { res.json(UserFactory.mapToSingle(user)); } +/** + * @description get my routine by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getMyRoutine(req: Request, res: Response): Promise { + const id = req.userId; + let user = await UserService.getById(id); + + res.json({ + routine: user.routine, + }); +} + /** * @description get my totp * @param req {Request} Express req object @@ -33,8 +53,6 @@ export async function getMyTotp(req: Request, res: Response): Promise { let { secret, routine } = await UserService.getUserSecretAndRoutine(userId); - console.log(secret); - const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`; QRCode.toDataURL(url) @@ -60,6 +78,11 @@ export async function verifyMyTotp(req: Request, res: Response): Promise { let totp = req.body.totp; let { secret, routine } = await UserService.getUserSecretAndRoutine(userId); + + if (routine != LoginRoutineEnum.totp) { + throw new ForbiddenRequestException("only allowed for totp login"); + } + let valid = speakeasy.totp.verify({ secret: secret, encoding: "base32", @@ -73,6 +96,106 @@ export async function verifyMyTotp(req: Request, res: Response): Promise { res.sendStatus(204); } +/** + * @description change my password + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function changeMyPassword(req: Request, res: Response): Promise { + const userId = req.userId; + let current = req.body.current; + let newpassword = req.body.newpassword; + + let { secret, routine } = await UserService.getUserSecretAndRoutine(userId); + + if (routine == LoginRoutineEnum.password && current != secret) { + throw new ForbiddenRequestException("passwords do not match"); + } + + let updateUser: UpdateUserSecretCommand = { + id: userId, + secret: newpassword, + routine: LoginRoutineEnum.password, + }; + await UserCommandHandler.updateSecret(updateUser); + + res.sendStatus(204); +} + +/** + * @description get change to totp + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getChangeToTOTP(req: Request, res: Response): Promise { + var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` }); + + QRCode.toDataURL(secret.otpauth_url) + .then((result) => { + res.json({ + dataUrl: result, + otp: secret.base32, + }); + }) + .catch((err) => { + throw new InternalException("QRCode not created", err); + }); +} + +/** + * @description change to totp + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function changeToTOTP(req: Request, res: Response): Promise { + const userId = req.userId; + let otp = req.body.otp; + let totp = req.body.totp; + + let valid = speakeasy.totp.verify({ + secret: otp, + encoding: "base32", + token: totp, + window: 2, + }); + + if (!valid) { + throw new InternalException("Token not valid or expired"); + } + + let updateUser: UpdateUserSecretCommand = { + id: userId, + secret: otp, + routine: LoginRoutineEnum.totp, + }; + await UserCommandHandler.updateSecret(updateUser); + + res.sendStatus(204); +} + +/** + * @description change to password + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function changeToPW(req: Request, res: Response): Promise { + const userId = req.userId; + let newpassword = req.body.newpassword; + + let updateUser: UpdateUserSecretCommand = { + id: userId, + secret: newpassword, + routine: LoginRoutineEnum.password, + }; + await UserCommandHandler.updateSecret(updateUser); + + res.sendStatus(204); +} + /** * @description transferOwnership * @param req {Request} Express req object diff --git a/src/routes/user.ts b/src/routes/user.ts index d196e16..90ba489 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,5 +1,16 @@ import express from "express"; -import { getMeById, getMyTotp, transferOwnership, updateMe, verifyMyTotp } from "../controller/userController"; +import { + changeMyPassword, + changeToPW, + changeToTOTP, + getChangeToTOTP, + getMeById, + getMyRoutine, + getMyTotp, + transferOwnership, + updateMe, + verifyMyTotp, +} from "../controller/userController"; var router = express.Router({ mergeParams: true }); @@ -7,14 +18,34 @@ router.get("/me", async (req, res) => { await getMeById(req, res); }); +router.get("/routine", async (req, res) => { + await getMyRoutine(req, res); +}); + router.get("/totp", async (req, res) => { await getMyTotp(req, res); }); +router.get("/changeToTOTP", async (req, res) => { + await getChangeToTOTP(req, res); +}); + router.post("/verify", async (req, res) => { await verifyMyTotp(req, res); }); +router.post("/changepw", async (req, res) => { + await changeMyPassword(req, res); +}); + +router.post("/changeToTOTP", async (req, res) => { + await changeToTOTP(req, res); +}); + +router.post("/changeToPW", async (req, res) => { + await changeToPW(req, res); +}); + router.put("/transferOwner", async (req, res) => { await transferOwnership(req, res); });