enable switch to pw totp in account settings

This commit is contained in:
Julian Krauser 2025-05-05 17:43:57 +02:00
parent be22c78372
commit ddb460f8d0
6 changed files with 165 additions and 4 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -50,6 +50,7 @@ export async function login(req: Request, res: Response): Promise<any> {
window: 2,
});
} else {
console.log(passedSecret, secret, passedSecret == secret);
valid = passedSecret == secret;
}

View file

@ -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<any> {
let updateUserSecret: UpdateUserSecretCommand = {
id,
secret,
routine: LoginRoutineEnum.totp,
};
await UserCommandHandler.updateSecret(updateUserSecret);

View file

@ -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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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

View file

@ -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);
});