Merge pull request 'feature/#70-static-user-login' (#97) from feature/#70-static-user-login into develop

Reviewed-on: #97
This commit is contained in:
Julian Krauser 2025-05-06 07:20:15 +00:00
commit a64567ce4e
21 changed files with 395 additions and 79 deletions

View file

@ -1,3 +1,5 @@
import { LoginRoutineEnum } from "../../../enums/loginRoutineEnum";
export interface CreateUserCommand {
mail: string;
username: string;
@ -5,6 +7,7 @@ export interface CreateUserCommand {
lastname: string;
secret: string;
isOwner: boolean;
routine: LoginRoutineEnum;
}
export interface UpdateUserCommand {
@ -18,6 +21,7 @@ export interface UpdateUserCommand {
export interface UpdateUserSecretCommand {
id: string;
secret: string;
routine: LoginRoutineEnum;
}
export interface TransferUserOwnerCommand {

View file

@ -31,6 +31,7 @@ export default abstract class UserCommandHandler {
lastname: createUser.lastname,
secret: createUser.secret,
isOwner: createUser.isOwner,
routine: createUser.routine,
})
.execute()
.then((result) => {
@ -75,6 +76,7 @@ export default abstract class UserCommandHandler {
.update(user)
.set({
secret: updateUser.secret,
routine: updateUser.routine,
})
.where("id = :id", { id: updateUser.id })
.execute()

View file

@ -8,6 +8,25 @@ import UserService from "../service/management/userService";
import speakeasy from "speakeasy";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
import RefreshService from "../service/refreshService";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
/**
* @description Check authentication status by token
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function kickof(req: Request, res: Response): Promise<any> {
let username = req.body.username;
let { routine } = await UserService.getByUsername(username).catch(() => {
throw new UnauthorizedRequestException("Username not found");
});
res.json({
routine,
});
}
/**
* @description Check authentication status by token
@ -17,19 +36,27 @@ import RefreshService from "../service/refreshService";
*/
export async function login(req: Request, res: Response): Promise<any> {
let username = req.body.username;
let totp = req.body.totp;
let passedSecret = req.body.secret;
let { id, secret } = await UserService.getByUsername(username);
let { id } = await UserService.getByUsername(username);
let { secret, routine } = await UserService.getUserSecretAndRoutine(id);
let valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: totp,
window: 2,
});
console.log(secret, passedSecret);
let valid = false;
if (routine == LoginRoutineEnum.totp) {
valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: passedSecret,
window: 2,
});
} else {
valid = passedSecret == secret;
}
if (!valid) {
throw new UnauthorizedRequestException("Token not valid or expired");
throw new UnauthorizedRequestException("Credentials not valid or expired");
}
let accessToken = await JWTHelper.buildToken(id);

View file

@ -16,6 +16,7 @@ import UserService from "../service/management/userService";
import CustomRequestException from "../exceptions/customRequestException";
import InviteFactory from "../factory/admin/management/invite";
import SettingHelper from "../helpers/settingsHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
/**
* @description get all invites
@ -112,20 +113,26 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
*/
export async function finishInvite(req: Request, res: Response, grantAdmin: boolean = false): Promise<any> {
let mail = req.body.mail;
let routine = req.body.routine;
let token = req.body.token;
let totp = req.body.totp;
let passedSecret = req.body.secret;
let { secret, username, firstname, lastname } = await InviteService.getByMailAndToken(mail, token);
let valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: totp,
window: 2,
});
let valid = false;
if (routine == LoginRoutineEnum.totp) {
valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: passedSecret,
window: 2,
});
} else {
valid = passedSecret != "";
}
if (!valid) {
throw new UnauthorizedRequestException("Token not valid or expired");
throw new UnauthorizedRequestException("Credentials not valid or expired");
}
let createUser: CreateUserCommand = {
@ -133,8 +140,9 @@ export async function finishInvite(req: Request, res: Response, grantAdmin: bool
firstname: firstname,
lastname: lastname,
mail: mail,
secret: secret,
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
isOwner: grantAdmin,
routine,
};
let id = await UserCommandHandler.create(createUser);

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
@ -80,27 +81,34 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
*/
export async function finishReset(req: Request, res: Response): Promise<any> {
let mail = req.body.mail;
let routine = req.body.routine;
let token = req.body.token;
let totp = req.body.totp;
let passedSecret = req.body.secret;
let { secret, username } = await ResetService.getByMailAndToken(mail, token);
let valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: totp,
window: 2,
});
let valid = false;
if (routine == LoginRoutineEnum.totp) {
valid = speakeasy.totp.verify({
secret: secret,
encoding: "base32",
token: passedSecret,
window: 2,
});
} else {
valid = passedSecret != "";
}
if (!valid) {
throw new UnauthorizedRequestException("Token not valid or expired");
throw new UnauthorizedRequestException("Credentials not valid or expired");
}
let { id } = await UserService.getByUsername(username);
let updateUserSecret: UpdateUserSecretCommand = {
id,
secret,
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
routine,
};
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
@ -31,7 +51,7 @@ export async function getMeById(req: Request, res: Response): Promise<any> {
export async function getMyTotp(req: Request, res: Response): Promise<any> {
const userId = req.userId;
let { secret } = await UserService.getById(userId);
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
@ -57,7 +77,12 @@ export async function verifyMyTotp(req: Request, res: Response): Promise<any> {
const userId = req.userId;
let totp = req.body.totp;
let { secret } = await UserService.getById(userId);
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",
@ -71,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,13 +1,5 @@
import { Request, Response } from "express";
import { JWTHelper } from "../helpers/jwtHelper";
import { JWTToken } from "../type/jwtTypes";
import InternalException from "../exceptions/internalException";
import RefreshCommandHandler from "../command/refreshCommandHandler";
import { CreateRefreshCommand, DeleteRefreshCommand } from "../command/refreshCommand";
import UserService from "../service/management/userService";
import speakeasy from "speakeasy";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
import RefreshService from "../service/refreshService";
import WebapiService from "../service/management/webapiService";
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
import WebapiCommandHandler from "../command/management/webapi/webapiCommandHandler";

View file

@ -1,7 +1,7 @@
import "dotenv/config";
import "reflect-metadata";
import { DataSource } from "typeorm";
import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";
import { configCheck, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";
import { user } from "./entity/management/user";
import { refresh } from "./entity/refresh";
@ -55,6 +55,9 @@ import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpd
import { SettingsFromEnv1745059495808 } from "./migrations/1745059495808-settingsFromEnv";
import { MemberCreatedAt1746006549262 } from "./migrations/1746006549262-memberCreatedAt";
import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine";
import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set";
configCheck();
const dataSource = new DataSource({
type: DB_TYPE as any,
@ -119,6 +122,7 @@ const dataSource = new DataSource({
SettingsFromEnv1745059495808,
SettingsFromEnv_SET1745059495808,
MemberCreatedAt1746006549262,
UserLoginRoutine1746252454922,
],
migrationsRun: true,
migrationsTransactionMode: "each",

View file

@ -1,6 +1,9 @@
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn } from "typeorm";
import { role } from "./role";
import { userPermission } from "./user_permission";
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
import { CodingHelper } from "../../helpers/codingHelper";
import { APPLICATION_SECRET } from "../../env.defaults";
@Entity()
export class user {
@ -19,11 +22,27 @@ export class user {
@Column({ type: "varchar", length: 255 })
lastname: string;
@Column({ type: "varchar", length: 255 })
@Column({
type: "text",
select: false,
transformer: CodingHelper.entityBaseCoding(APPLICATION_SECRET, "<self>"),
})
secret: string;
@Column({ type: "boolean", default: false })
static: boolean;
@Column({
type: "varchar",
length: "255",
default: LoginRoutineEnum.totp,
transformer: {
to(value: LoginRoutineEnum) {
return value.toString();
},
from(value: string) {
return LoginRoutineEnum[value as keyof typeof LoginRoutineEnum];
},
},
})
routine: LoginRoutineEnum;
@Column({ type: "boolean", default: false })
isOwner: boolean;

View file

@ -0,0 +1,4 @@
export enum LoginRoutineEnum {
password = "password", // login with self defined password
totp = "totp", // login with totp by auth apps
}

View file

@ -11,7 +11,7 @@ export const DB_PASSWORD = process.env.DB_PASSWORD ?? "";
export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
export const APPLICATION_SECRET = process.env.APPLICATION_SECRET;
export const APPLICATION_SECRET = process.env.APPLICATION_SECRET ?? "";
export const USE_SECURITY_STRICT_LIMIT = process.env.USE_SECURITY_STRICT_LIMIT ?? "true";
export const SECURITY_STRICT_LIMIT_WINDOW = (process.env.SECURITY_STRICT_LIMIT_WINDOW ?? "15m") as ms.StringValue;
@ -45,6 +45,8 @@ export function configCheck() {
if (DB_USERNAME == "" || typeof DB_USERNAME != "string") throw new Error("set valid value to DB_USERNAME");
if (DB_PASSWORD == "" || typeof DB_PASSWORD != "string") throw new Error("set valid value to DB_PASSWORD");
if (APPLICATION_SECRET == "") throw new Error("set valid APPLICATION_SECRET");
if (isNaN(SERVER_PORT)) throw new Error("set valid numeric value to SERVER_PORT");
if (USE_SECURITY_STRICT_LIMIT != "true" && USE_SECURITY_STRICT_LIMIT != "false")

View file

@ -2,7 +2,7 @@ import CustomRequestException from "./customRequestException";
export default class DatabaseActionException extends CustomRequestException {
constructor(action: string, table: string, err: any) {
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? "XX"}`;
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? err?.message ?? "XX"}`;
super(500, errstring, err);
}
}

View file

@ -7,6 +7,7 @@ import UserService from "../service/management/userService";
import DatabaseActionException from "../exceptions/databaseActionException";
import { availableTemplates } from "../type/templateTypes";
import SettingHelper from "./settingsHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
export type BackupSection =
| "member"
@ -440,6 +441,7 @@ export default abstract class BackupHelper {
"user.firstname",
"user.lastname",
"user.secret",
"user.routine",
"user.isOwner",
])
.addSelect(["permissions.permission"])
@ -806,6 +808,7 @@ export default abstract class BackupHelper {
let roles = await this.transactionManager.getRepository("role").find();
let dataWithMappedIds = (data?.["user"] ?? []).map((u) => ({
...u,
routine: u.routine ?? LoginRoutineEnum.totp,
roles: u.roles.map((r: any) => ({
...r,
id: roles.find((role) => role.role == r.role)?.id ?? undefined,

View file

@ -9,12 +9,13 @@ export abstract class CodingHelper {
static entityBaseCoding(key: string = "", fallback: string = ""): ValueTransformer {
return {
from(val: string | null | undefined): string {
if (!val) return fallback;
if (!val || val == "") return fallback;
try {
return CodingHelper.decrypt(key, val) || fallback;
return CodingHelper.decrypt(key, val, true);
} catch (error) {
console.error("Decryption error:", error);
return fallback;
if (fallback == "<self>") return val;
else return fallback;
}
},
to(val: string | null | undefined): string {
@ -22,40 +23,47 @@ export abstract class CodingHelper {
if (valueToEncrypt === "") return "";
try {
return CodingHelper.encrypt(key, valueToEncrypt);
return CodingHelper.encrypt(key, valueToEncrypt, true);
} catch (error) {
console.error("Encryption error:", error);
if (fallback == "<self>") return val;
return "";
}
},
};
}
public static encrypt(phrase: string, content: string): string {
public static encrypt(phrase: string, content: string, passError = false): string {
if (!content) return "";
// Generiere zufälligen IV für jede Verschlüsselung (sicherer als statischer IV)
const iv = randomBytes(this.ivLength);
const key = scryptSync(phrase, "salt", 32);
try {
// Generiere zufälligen IV für jede Verschlüsselung (sicherer als statischer IV)
const iv = randomBytes(this.ivLength);
const key = scryptSync(phrase, "salt", 32);
const cipher = createCipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
const cipher = createCipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
// Verschlüssele den Inhalt
let encrypted = cipher.update(content, "utf8", "hex");
encrypted += cipher.final("hex");
// Verschlüssele den Inhalt
let encrypted = cipher.update(content, "utf8", "hex");
encrypted += cipher.final("hex");
// Speichere das Auth-Tag für GCM (wichtig für die Entschlüsselung)
const authTag = cipher.getAuthTag();
// Speichere das Auth-Tag für GCM (wichtig für die Entschlüsselung)
const authTag = cipher.getAuthTag();
// Gib das Format: iv:verschlüsselter_text:authTag zurück
return Buffer.concat([
Uint8Array.from(iv),
Uint8Array.from(Buffer.from(encrypted, "hex")),
Uint8Array.from(authTag),
]).toString("base64");
// Gib das Format: iv:verschlüsselter_text:authTag zurück
return Buffer.concat([
Uint8Array.from(iv),
Uint8Array.from(Buffer.from(encrypted, "hex")),
Uint8Array.from(authTag),
]).toString("base64");
} catch (error) {
if (passError) throw error;
console.error("Encryption failed:", error);
return "";
}
}
public static decrypt(phrase: string, content: string): string {
public static decrypt(phrase: string, content: string, passError = false): string {
if (!content) return "";
try {
@ -79,6 +87,7 @@ export abstract class CodingHelper {
return decrypted;
} catch (error) {
if (passError) throw error;
console.error("Decryption failed:", error);
return "";
}

View file

@ -56,12 +56,17 @@ export default abstract class SettingHelper {
return rawValue as unknown as SettingValueMapping[K];
}
let processedValue = rawValue;
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
processedValue = CodingHelper.decrypt(APPLICATION_SECRET, processedValue);
}
const baseType =
typeof settingType.type === "string"
? (settingType.type.split("/")[0] as SettingTypeAtom)
: (settingType.type as SettingTypeAtom);
return this.converters[baseType].fromString(rawValue) as unknown as SettingValueMapping[K];
return this.converters[baseType].fromString(processedValue) as unknown as SettingValueMapping[K];
}
/**
@ -81,11 +86,11 @@ export default abstract class SettingHelper {
const settingType = settingsType[key];
this.validateSetting(key, stringValue);
const oldValue = this.getSetting(key);
let finalValue = stringValue;
const oldValue = cloneDeep(this.settings[key]);
let newValue = stringValue;
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
finalValue = CodingHelper.encrypt(APPLICATION_SECRET, stringValue);
newValue = CodingHelper.encrypt(APPLICATION_SECRET, stringValue);
}
this.settings[key] = stringValue;
@ -94,10 +99,9 @@ export default abstract class SettingHelper {
await SettingCommandHandler.create({
topic,
key: settingKey,
value: finalValue,
value: newValue,
});
const newValue = this.getSetting(key);
this.notifyListeners(key, newValue, oldValue);
}

View file

@ -0,0 +1,39 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
import { CodingHelper } from "../helpers/codingHelper";
import { APPLICATION_SECRET } from "../env.defaults";
export class UserLoginRoutine1746252454922 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
await queryRunner.dropColumns("user", ["secret", "static"]);
await queryRunner.addColumns("user", [
new TableColumn({ name: "secret", ...getTypeByORM("text"), default: getDefaultByORM("string") }),
new TableColumn({
name: "routine",
...getTypeByORM("varchar"),
default: getDefaultByORM("string", LoginRoutineEnum.totp),
}),
]);
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
}
public async down(queryRunner: QueryRunner): Promise<void> {
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
await queryRunner.dropColumn("user", "secret");
await queryRunner.addColumns("user", [
new TableColumn({ name: "secret", ...getTypeByORM("varchar"), default: getDefaultByORM("string") }),
new TableColumn({ name: "static", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) }),
]);
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
await queryRunner.dropColumn("user", "routine");
}
}

View file

@ -1,8 +1,12 @@
import express from "express";
import { login, logout, refresh } from "../controller/authController";
import { kickof, login, logout, refresh } from "../controller/authController";
var router = express.Router({ mergeParams: true });
router.post("/kickof", async (req, res) => {
await kickof(req, res);
});
router.post("/login", async (req, res) => {
await login(req, res);
});

View file

@ -8,8 +8,12 @@ router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mai
await verifyInvite(req, res);
});
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
await finishInvite(req, res);
});
router.put(
"/",
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine "]),
async (req, res) => {
await finishInvite(req, res);
}
);
export default router;

View file

@ -12,8 +12,12 @@ router.post("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["username"
await startReset(req, res);
});
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
await finishReset(req, res);
});
router.put(
"/",
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine"]),
async (req, res) => {
await finishReset(req, res);
}
);
export default router;

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

View file

@ -129,4 +129,27 @@ export default abstract class UserService {
throw new DatabaseActionException("SELECT", "userRoles", err);
});
}
/**
* @description get secret and routine by iser
* @param userId string
* @returns {Promise<user>}
*/
static async getUserSecretAndRoutine(userId: string): Promise<user> {
return await dataSource
.getRepository(user)
.createQueryBuilder("user")
.select("user.id")
.addSelect("user.secret")
.addSelect("user.routine")
.where("user.id = :id", { id: userId })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
console.log(err);
throw new DatabaseActionException("SELECT", "user credentials", err);
});
}
}