diff --git a/src/command/resetCommand.ts b/src/command/resetCommand.ts deleted file mode 100644 index 0382771..0000000 --- a/src/command/resetCommand.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface CreateResetCommand { - mail: string; - username: string; - secret: string; -} - -export interface DeleteResetCommand { - token: string; - mail: string; -} diff --git a/src/command/resetCommandHandler.ts b/src/command/resetCommandHandler.ts deleted file mode 100644 index 6ef1d00..0000000 --- a/src/command/resetCommandHandler.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { dataSource } from "../data-source"; -import { reset } from "../entity/reset"; -import InternalException from "../exceptions/internalException"; -import { StringHelper } from "../helpers/stringHelper"; -import { CreateResetCommand, DeleteResetCommand } from "./resetCommand"; - -export default abstract class ResetCommandHandler { - /** - * @description create user - * @param CreateResetCommand - * @returns {Promise} - */ - static async create(createReset: CreateResetCommand): Promise { - const token = StringHelper.random(32); - - return await dataSource - .createQueryBuilder() - .insert() - .into(reset) - .values({ - token: token, - mail: createReset.mail, - username: createReset.username, - secret: createReset.secret, - }) - .orUpdate(["token", "secret"], ["mail"]) - .execute() - .then((result) => { - return token; - }) - .catch((err) => { - throw new InternalException("Failed saving reset", err); - }); - } - - /** - * @description delete reset by mail and token - * @param DeleteRefreshCommand - * @returns {Promise} - */ - static async deleteByTokenAndMail(deleteReset: DeleteResetCommand): Promise { - return await dataSource - .createQueryBuilder() - .delete() - .from(reset) - .where("reset.token = :token", { token: deleteReset.token }) - .andWhere("reset.mail = :mail", { mail: deleteReset.mail }) - .execute() - .then((res) => {}) - .catch((err) => { - throw new InternalException("failed reset removal", err); - }); - } -} diff --git a/src/command/userCommand.ts b/src/command/userCommand.ts index cf23bb8..b61b578 100644 --- a/src/command/userCommand.ts +++ b/src/command/userCommand.ts @@ -15,11 +15,6 @@ export interface UpdateUserCommand { lastname: string; } -export interface UpdateUserSecretCommand { - id: number; - secret: string; -} - export interface TransferUserOwnerCommand { fromId: number; toId: number; diff --git a/src/command/userCommandHandler.ts b/src/command/userCommandHandler.ts index 6d1c616..a88979b 100644 --- a/src/command/userCommandHandler.ts +++ b/src/command/userCommandHandler.ts @@ -8,7 +8,6 @@ import { TransferUserOwnerCommand, UpdateUserCommand, UpdateUserRolesCommand, - UpdateUserSecretCommand, } from "./userCommand"; import UserService from "../service/userService"; @@ -63,26 +62,6 @@ export default abstract class UserCommandHandler { }); } - /** - * @description update user - * @param UpdateUserSecretCommand - * @returns {Promise} - */ - static async updateSecret(updateUser: UpdateUserSecretCommand): Promise { - return await dataSource - .createQueryBuilder() - .update(user) - .set({ - secret: updateUser.secret, - }) - .where("id = :id", { id: updateUser.id }) - .execute() - .then(() => {}) - .catch((err) => { - throw new InternalException("Failed updating user secret", err); - }); - } - /** * @description update user roles * @param UpdateUserRolesCommand diff --git a/src/controller/authController.ts b/src/controller/authController.ts index 7b2494b..f68aab2 100644 --- a/src/controller/authController.ts +++ b/src/controller/authController.ts @@ -22,7 +22,7 @@ export async function login(req: Request, res: Response): Promise { let username = req.body.username; let totp = req.body.totp; - let { id, secret } = await UserService.getByUsername(username); + let { id, secret, mail, firstname, lastname, isOwner } = await UserService.getByUsername(username); let valid = speakeasy.totp.verify({ secret: secret, @@ -35,12 +35,39 @@ export async function login(req: Request, res: Response): Promise { throw new UnauthorizedRequestException("Token not valid or expired"); } - let accessToken = await JWTHelper.buildToken(id); + let userPermissions = await UserPermissionService.getByUser(id); + let userPermissionStrings = userPermissions.map((e) => e.permission); + let userRoles = await UserService.getAssignedRolesByUserId(id); + let rolePermissions = userRoles.length != 0 ? await RolePermissionService.getByRoles(userRoles.map((e) => e.id)) : []; + let rolePermissionStrings = rolePermissions.map((e) => e.permission); + let permissionObject = PermissionHelper.convertToObject([...userPermissionStrings, ...rolePermissionStrings]); + + let jwtData: JWTToken = { + userId: id, + mail: mail, + username: username, + firstname: firstname, + lastname: lastname, + isOwner: isOwner, + permissions: permissionObject, + }; + + let accessToken: string; + let refreshToken: string; + + JWTHelper.create(jwtData) + .then((result) => { + accessToken = result; + }) + .catch((err) => { + console.log(err); + throw new InternalException("Failed accessToken creation", err); + }); let refreshCommand: CreateRefreshCommand = { userId: id, }; - let refreshToken = await RefreshCommandHandler.create(refreshCommand); + refreshToken = await RefreshCommandHandler.create(refreshCommand); res.json({ accessToken, @@ -79,15 +106,40 @@ export async function refresh(req: Request, res: Response): Promise { throw new UnauthorizedRequestException("user not identified with token and refresh"); } - let accessToken = await JWTHelper.buildToken(tokenUserId); + let { id, username, mail, firstname, lastname, isOwner } = await UserService.getById(tokenUserId); + + let permissions = await UserPermissionService.getByUser(id); + let permissionStrings = permissions.map((e) => e.permission); + let permissionObject = PermissionHelper.convertToObject(permissionStrings); + + let jwtData: JWTToken = { + userId: id, + mail: mail, + username: username, + firstname: firstname, + lastname: lastname, + isOwner: isOwner, + permissions: permissionObject, + }; + + let accessToken: string; + let refreshToken: string; + + JWTHelper.create(jwtData) + .then((result) => { + accessToken = result; + }) + .catch((err) => { + throw new InternalException("Failed accessToken creation", err); + }); let refreshCommand: CreateRefreshCommand = { - userId: tokenUserId, + userId: id, }; - let refreshToken = await RefreshCommandHandler.create(refreshCommand); + refreshToken = await RefreshCommandHandler.create(refreshCommand); let removeToken: DeleteRefreshCommand = { - userId: tokenUserId, + userId: id, token: refresh, }; await RefreshCommandHandler.deleteByToken(removeToken); diff --git a/src/controller/inviteController.ts b/src/controller/inviteController.ts index a6ff7eb..0b2b207 100644 --- a/src/controller/inviteController.ts +++ b/src/controller/inviteController.ts @@ -128,12 +128,33 @@ export async function finishInvite(req: Request, res: Response, grantAdmin: bool }; let id = await UserCommandHandler.create(createUser); - let accessToken = await JWTHelper.buildToken(id); + let jwtData: JWTToken = { + userId: id, + mail: mail, + username: username, + firstname: firstname, + lastname: lastname, + isOwner: grantAdmin, + permissions: { + ...(grantAdmin ? { admin: true } : {}), + }, + }; + + let accessToken: string; + let refreshToken: string; + + JWTHelper.create(jwtData) + .then((result) => { + accessToken = result; + }) + .catch((err) => { + throw new InternalException("Failed accessToken creation", err); + }); let refreshCommand: CreateRefreshCommand = { userId: id, }; - let refreshToken = await RefreshCommandHandler.create(refreshCommand); + refreshToken = await RefreshCommandHandler.create(refreshCommand); let deleteInvite: DeleteInviteCommand = { mail: mail, diff --git a/src/controller/resetController.ts b/src/controller/resetController.ts deleted file mode 100644 index 32f2004..0000000 --- a/src/controller/resetController.ts +++ /dev/null @@ -1,129 +0,0 @@ -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 } from "../command/refreshCommand"; -import speakeasy from "speakeasy"; -import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException"; -import QRCode from "qrcode"; -import { CreateResetCommand, DeleteResetCommand } from "../command/resetCommand"; -import ResetCommandHandler from "../command/resetCommandHandler"; -import MailHelper from "../helpers/mailHelper"; -import ResetService from "../service/resetService"; -import UserService from "../service/userService"; -import { CLUB_NAME } from "../env.defaults"; -import PermissionHelper from "../helpers/permissionHelper"; -import RolePermissionService from "../service/rolePermissionService"; -import UserPermissionService from "../service/userPermissionService"; -import { UpdateUserSecretCommand } from "../command/userCommand"; -import UserCommandHandler from "../command/userCommandHandler"; - -/** - * @description request totp reset - * @param req {Request} Express req object - * @param res {Response} Express res object - * @returns {Promise<*>} - */ -export async function startReset(req: Request, res: Response): Promise { - let origin = req.headers.origin; - let username = req.body.username; - - let { mail } = await UserService.getByUsername(username); - - var secret = speakeasy.generateSecret({ length: 20, name: `Mitgliederverwaltung ${CLUB_NAME}` }); - - let createReset: CreateResetCommand = { - username: username, - mail: mail, - secret: secret.base32, - }; - let token = await ResetCommandHandler.create(createReset); - - // sendmail - let mailhelper = new MailHelper(); - await mailhelper.sendMail( - mail, - `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`, - `Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}` - ); - - res.sendStatus(204); -} - -/** - * @description verify reset link - * @param req {Request} Express req object - * @param res {Response} Express res object - * @returns {Promise<*>} - */ -export async function verifyReset(req: Request, res: Response): Promise { - let mail = req.body.mail; - let token = req.body.token; - - let { secret } = await ResetService.getByMailAndToken(mail, token); - - const url = `otpauth://totp/Mitgliederverwaltung ${CLUB_NAME}?secret=${secret}`; - - QRCode.toDataURL(url) - .then((result) => { - res.json({ - dataUrl: result, - otp: secret, - }); - }) - .catch((err) => { - throw new InternalException("QRCode not created", err); - }); -} - -/** - * @description finishReset - * @param req {Request} Express req object - * @param res {Response} Express res object - * @returns {Promise<*>} - */ -export async function finishReset(req: Request, res: Response): Promise { - let mail = req.body.mail; - let token = req.body.token; - let totp = req.body.totp; - - let { secret, username } = await ResetService.getByMailAndToken(mail, token); - - let valid = speakeasy.totp.verify({ - secret: secret, - encoding: "base32", - token: totp, - window: 2, - }); - - if (!valid) { - throw new UnauthorizedRequestException("Token not valid or expired"); - } - - let { id } = await UserService.getByUsername(username); - - let updateUserSecret: UpdateUserSecretCommand = { - id, - secret, - }; - await UserCommandHandler.updateSecret(updateUserSecret); - - let accessToken = await JWTHelper.buildToken(id); - - let refreshCommand: CreateRefreshCommand = { - userId: id, - }; - let refreshToken = await RefreshCommandHandler.create(refreshCommand); - - let deleteReset: DeleteResetCommand = { - mail: mail, - token: token, - }; - await ResetCommandHandler.deleteByTokenAndMail(deleteReset); - - res.json({ - accessToken, - refreshToken, - }); -} diff --git a/src/data-source.ts b/src/data-source.ts index 39a56c7..3801455 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -40,8 +40,6 @@ import { Protocol1729347911107 } from "./migrations/1729347911107-protocol"; import { calendar } from "./entity/calendar"; import { calendarType } from "./entity/calendarType"; import { Calendar1729947763295 } from "./migrations/1729947763295-calendar"; -import { reset } from "./entity/reset"; -import { ResetToken1732358596823 } from "./migrations/1732358596823-resetToken"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -57,7 +55,6 @@ const dataSource = new DataSource({ user, refresh, invite, - reset, userPermission, role, rolePermission, @@ -93,7 +90,6 @@ const dataSource = new DataSource({ Ownership1728313041449, Protocol1729347911107, Calendar1729947763295, - ResetToken1732358596823, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/reset.ts b/src/entity/reset.ts deleted file mode 100644 index a7e2b92..0000000 --- a/src/entity/reset.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Column, Entity, PrimaryColumn } from "typeorm"; - -@Entity() -export class reset { - @PrimaryColumn({ type: "varchar", length: 255 }) - mail: string; - - @Column({ type: "varchar", length: 255 }) - token: string; - - @Column({ type: "varchar", length: 255 }) - username: string; - - @Column({ type: "varchar", length: 255 }) - secret: string; -} diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts index 993f41f..5d79921 100644 --- a/src/helpers/jwtHelper.ts +++ b/src/helpers/jwtHelper.ts @@ -1,11 +1,6 @@ import jwt from "jsonwebtoken"; -import { JWTData, JWTToken } from "../type/jwtTypes"; +import { JWTData } from "../type/jwtTypes"; import { JWT_SECRET, JWT_EXPIRATION } from "../env.defaults"; -import InternalException from "../exceptions/internalException"; -import RolePermissionService from "../service/rolePermissionService"; -import UserPermissionService from "../service/userPermissionService"; -import UserService from "../service/userService"; -import PermissionHelper from "./permissionHelper"; export abstract class JWTHelper { static validate(token: string): Promise { @@ -43,33 +38,4 @@ export abstract class JWTHelper { } }); } - - static async buildToken(id: number): Promise { - let { firstname, lastname, mail, username, isOwner } = await UserService.getById(id); - let userPermissions = await UserPermissionService.getByUser(id); - let userPermissionStrings = userPermissions.map((e) => e.permission); - let userRoles = await UserService.getAssignedRolesByUserId(id); - let rolePermissions = - userRoles.length != 0 ? await RolePermissionService.getByRoles(userRoles.map((e) => e.id)) : []; - let rolePermissionStrings = rolePermissions.map((e) => e.permission); - let permissionObject = PermissionHelper.convertToObject([...userPermissionStrings, ...rolePermissionStrings]); - - let jwtData: JWTToken = { - userId: id, - mail: mail, - username: username, - firstname: firstname, - lastname: lastname, - isOwner: isOwner, - permissions: permissionObject, - }; - - return await JWTHelper.create(jwtData) - .then((result) => { - return result; - }) - .catch((err) => { - throw new InternalException("Failed accessToken creation", err); - }); - } } diff --git a/src/migrations/1732358596823-resetToken.ts b/src/migrations/1732358596823-resetToken.ts deleted file mode 100644 index ba4b4d8..0000000 --- a/src/migrations/1732358596823-resetToken.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MigrationInterface, QueryRunner, Table } from "typeorm"; - -export class ResetToken1732358596823 implements MigrationInterface { - name = "ResetToken1732358596823"; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.createTable( - new Table({ - name: "reset", - columns: [ - { name: "mail", type: "varchar", length: "255", isPrimary: true, isNullable: false }, - { name: "token", type: "varchar", length: "255", isNullable: false }, - { name: "username", type: "varchar", length: "255", isNullable: false }, - { name: "secret", type: "varchar", length: "255", isNullable: false }, - ], - }), - true - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropTable("reset"); - } -} diff --git a/src/routes/index.ts b/src/routes/index.ts index 5025c17..b9281d0 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -8,7 +8,6 @@ import errorHandler from "../middleware/errorHandler"; import publicAvailable from "./public"; import setup from "./setup"; -import reset from "./reset"; import auth from "./auth"; import admin from "./admin/index"; import user from "./user"; @@ -26,7 +25,6 @@ export default (app: Express) => { app.use("/public", publicAvailable); app.use("/setup", allowSetup, setup); - app.use("/reset", reset); app.use("/auth", auth); app.use(authenticate); app.use("/admin", admin); diff --git a/src/routes/reset.ts b/src/routes/reset.ts deleted file mode 100644 index acb1516..0000000 --- a/src/routes/reset.ts +++ /dev/null @@ -1,19 +0,0 @@ -import express from "express"; -import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper"; -import { finishReset, startReset, verifyReset } from "../controller/resetController"; - -var router = express.Router({ mergeParams: true }); - -router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token"]), async (req, res) => { - await verifyReset(req, res); -}); - -router.post("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["username"]), async (req, res) => { - await startReset(req, res); -}); - -router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => { - await finishReset(req, res); -}); - -export default router; diff --git a/src/service/resetService.ts b/src/service/resetService.ts deleted file mode 100644 index 802548e..0000000 --- a/src/service/resetService.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { dataSource } from "../data-source"; -import { reset } from "../entity/reset"; -import InternalException from "../exceptions/internalException"; - -export default abstract class ResetService { - /** - * @description get reset by id - * @param mail string - * @param token string - * @returns {Promise} - */ - static async getByMailAndToken(mail: string, token: string): Promise { - return await dataSource - .getRepository(reset) - .createQueryBuilder("reset") - .where("reset.mail = :mail", { mail: mail }) - .andWhere("reset.token = :token", { token: token }) - .getOneOrFail() - .then((res) => { - return res; - }) - .catch((err) => { - throw new InternalException("reset not found by mail and token", err); - }); - } -}