diff --git a/src/app.controller.ts b/src/app.controller.ts index 5da3a59..86b3312 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,23 +1,10 @@ -import { Controller, Get, Req, Res, Session } from '@nestjs/common'; -import { Request, Response } from 'express'; -import { SessionData } from 'express-session'; -import { AppService } from './app.service'; +import { Controller, Get, Redirect } from '@nestjs/common'; @Controller() export class AppController { - constructor(private readonly appService: AppService) {} - @Get() - getHello( - @Session() session: SessionData, - @Res() res: Response, - @Req() req: Request, - ): Record { - if (!session.user) { - res.redirect('/login'); - return; - } - - res.render('index', { user: req.user }); + @Redirect('/account/general') + getHello() { + return; } } diff --git a/src/fe/scss/_button.scss b/src/fe/scss/_button.scss index 7de3031..fd3c8e0 100644 --- a/src/fe/scss/_button.scss +++ b/src/fe/scss/_button.scss @@ -8,6 +8,9 @@ outline: 0px solid var(--focus-outline); background-color: var(--btn-background); color: var(--btn-color); + text-shadow: none; + text-decoration: none; + text-align: center; min-width: 120px; transition: background-color 0.35s linear, outline 0.15s linear; diff --git a/src/fe/scss/_flex.scss b/src/fe/scss/_flex.scss index b7856bd..8df8c6e 100644 --- a/src/fe/scss/_flex.scss +++ b/src/fe/scss/_flex.scss @@ -7,6 +7,9 @@ .align-self-center { align-self: center; } +.align-items-start { + align-items: flex-start; +} .d-flex { display: flex; } diff --git a/src/modules/features/settings/settings.controller.ts b/src/modules/features/settings/settings.controller.ts index 56e71ff..729dc74 100644 --- a/src/modules/features/settings/settings.controller.ts +++ b/src/modules/features/settings/settings.controller.ts @@ -18,6 +18,7 @@ import { Request, Response } from 'express'; import { unlink } from 'fs/promises'; import { OAuth2ClientService } from 'src/modules/objects/oauth2-client/oauth2-client.service'; import { UploadService } from 'src/modules/objects/upload/upload.service'; +import { UserTOTPService } from 'src/modules/objects/user-token/user-totp-token.service'; import { UserService } from 'src/modules/objects/user/user.service'; import { FormUtilityService } from 'src/modules/utility/services/form-utility.service'; import { TokenService } from 'src/modules/utility/services/token.service'; @@ -31,6 +32,7 @@ export class SettingsController { private readonly _upload: UploadService, private readonly _token: TokenService, private readonly _user: UserService, + private readonly _totp: UserTOTPService, private readonly _client: OAuth2ClientService, ) {} @@ -163,4 +165,116 @@ export class SettingsController { } res.redirect('/account/oauth2'); } + + @Get('security') + @Render('settings/security') + public async security(@Req() req: Request) { + const mailSplit = req.user.email.split('@'); + const emailHint = `${mailSplit[0].substring(0, 1)}***@${mailSplit[1]}`; + const twofactor = await this._totp.userHasTOTP(req.user); + return this._form.populateTemplate(req, { + user: req.user, + emailHint, + twofactor, + }); + } + + @Post('security/password') + public async setPassword( + @Req() req: Request, + @Res() res: Response, + @Body() + body: { + password: string; + new_password: string; + password_repeat: string; + }, + ) { + const { password, new_password, password_repeat } = body; + try { + if (!password || !new_password || !password_repeat) { + throw new Error('Please fill out all of the fields.'); + } + + if (!(await this._user.comparePasswords(req.user.password, password))) { + throw new Error('Current password is invalid.'); + } + + if (!new_password.match(this._form.passwordRegex)) { + throw new Error( + 'Password must be at least 8 characters long, contain a capital and lowercase letter and a number', + ); + } + + if (new_password !== password_repeat) { + throw new Error('The passwords do not match.'); + } + } catch (e: any) { + req.flash('message', { + error: true, + text: e.message, + }); + res.redirect('/account/security'); + return; + } + + const newPassword = await this._user.hashPassword(new_password); + req.user.password = newPassword; + await this._user.updateUser(req.user); + + req.flash('message', { + error: false, + text: 'Password changed successfully.', + }); + res.redirect('/account/security'); + } + + @Post('security/email') + public async setEmail( + @Req() req: Request, + @Res() res: Response, + @Body() + body: { + current_email: string; + email: string; + }, + ) { + const { current_email, email } = body; + try { + if (!current_email || !email) { + throw new Error('Please fill out all of the fields.'); + } + + if (current_email !== req.user.email) { + throw new Error('The current email address is invalid.'); + } + + if (!email.match(this._form.emailRegex)) { + throw new Error('The new email address is invalid.'); + } + + const existing = await this._user.getByEmail(email); + if (existing) { + throw new Error( + 'There is already an existing user with this email address.', + ); + } + } catch (e: any) { + req.flash('message', { + error: true, + text: e.message, + }); + res.redirect('/account/security'); + return; + } + + req.user.email = email; + await this._user.updateUser(req.user); + + req.flash('message', { + error: false, + text: 'Email address changed successfully.', + }); + res.redirect('/account/security'); + } } diff --git a/src/modules/features/settings/settings.module.ts b/src/modules/features/settings/settings.module.ts index 59b2073..3428a3b 100644 --- a/src/modules/features/settings/settings.module.ts +++ b/src/modules/features/settings/settings.module.ts @@ -19,6 +19,7 @@ import { OAuth2Module } from '../oauth2/oauth2.module'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module'; +import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module'; @Module({ controllers: [SettingsController], @@ -26,6 +27,7 @@ import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-cli ConfigurationModule, UploadModule, UserModule, + UserTokenModule, OAuth2Module, OAuth2ClientModule, MulterModule.registerAsync({