parent
cdac3c5f68
commit
562431449b
@ -22,3 +22,8 @@ export const ADD_EMAIL_MAIL_TEMPLATE: MailTemplate = new MailTemplate(
|
||||
'add_email',
|
||||
(data) => `Add ${data.email} address to your ${config.get<string>('app.name')} account.`,
|
||||
);
|
||||
|
||||
export const REMOVE_PASSWORD_MAIL_TEMPLATE: MailTemplate = new MailTemplate(
|
||||
'remove_password',
|
||||
() => `Remove password from your ${config.get<string>('app.name')} account.`,
|
||||
);
|
||||
|
@ -10,15 +10,16 @@ import ModelFactory from "../db/ModelFactory";
|
||||
import UserEmail from "./models/UserEmail";
|
||||
import MagicLinkController from "./magic_link/MagicLinkController";
|
||||
import {MailTemplate} from "../mail/Mail";
|
||||
import {ADD_EMAIL_MAIL_TEMPLATE} from "../Mails";
|
||||
import {ADD_EMAIL_MAIL_TEMPLATE, REMOVE_PASSWORD_MAIL_TEMPLATE} from "../Mails";
|
||||
import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType";
|
||||
|
||||
export default class AccountController extends Controller {
|
||||
private readonly addEmailMailTemplate: MailTemplate;
|
||||
|
||||
public constructor(addEmailMailTemplate: MailTemplate = ADD_EMAIL_MAIL_TEMPLATE) {
|
||||
public constructor(
|
||||
private readonly addEmailMailTemplate: MailTemplate = ADD_EMAIL_MAIL_TEMPLATE,
|
||||
private readonly removePasswordMailTemplate: MailTemplate = REMOVE_PASSWORD_MAIL_TEMPLATE,
|
||||
) {
|
||||
super();
|
||||
this.addEmailMailTemplate = addEmailMailTemplate;
|
||||
}
|
||||
|
||||
public getRoutesPrefix(): string {
|
||||
@ -30,6 +31,7 @@ export default class AccountController extends Controller {
|
||||
|
||||
if (ModelFactory.get(User).hasComponent(UserPasswordComponent)) {
|
||||
this.post('/change-password', this.postChangePassword, 'change-password', RequireAuthMiddleware);
|
||||
this.post('/remove-password', this.postRemovePassword, 'remove-password', RequireAuthMiddleware);
|
||||
}
|
||||
|
||||
this.post('/add-email', this.addEmail, 'add-email', RequireAuthMiddleware);
|
||||
@ -40,11 +42,13 @@ export default class AccountController extends Controller {
|
||||
protected async getAccount(req: Request, res: Response): Promise<void> {
|
||||
const user = req.as(RequireAuthMiddleware).getUser();
|
||||
|
||||
const passwordComponent = user.asOptional(UserPasswordComponent);
|
||||
res.render('auth/account', {
|
||||
main_email: await user.mainEmail.get(),
|
||||
emails: await user.emails.get(),
|
||||
display_email_warning: config.get('app.display_email_warning'),
|
||||
has_password: user.asOptional(UserPasswordComponent)?.hasPassword(),
|
||||
has_password_component: !!passwordComponent,
|
||||
has_password: passwordComponent?.hasPassword(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -69,6 +73,31 @@ export default class AccountController extends Controller {
|
||||
res.redirect(Controller.route('account'));
|
||||
}
|
||||
|
||||
protected async postRemovePassword(req: Request, res: Response): Promise<void> {
|
||||
const user = req.as(RequireAuthMiddleware).getUser();
|
||||
const mainEmail = await user.mainEmail.get();
|
||||
if (!mainEmail || !mainEmail.email) {
|
||||
req.flash('error', 'You can\'t remove your password without adding an email address first.');
|
||||
res.redirect(Controller.route('account'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await MagicLinkController.sendMagicLink(
|
||||
this.getApp(),
|
||||
req.getSession().id,
|
||||
AuthMagicLinkActionType.REMOVE_PASSWORD,
|
||||
Controller.route('account'),
|
||||
mainEmail.email,
|
||||
this.removePasswordMailTemplate,
|
||||
{},
|
||||
);
|
||||
|
||||
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
||||
redirect_uri: Controller.route('account'),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
protected async addEmail(req: Request, res: Response): Promise<void> {
|
||||
await Validator.validate({
|
||||
|
@ -2,4 +2,5 @@ export default {
|
||||
LOGIN: 'login',
|
||||
REGISTER: 'register',
|
||||
ADD_EMAIL: 'add_email',
|
||||
REMOVE_PASSWORD: 'remove_password',
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ import {QueryVariable} from "../../db/MysqlConnectionManager";
|
||||
import UserNameComponent from "../models/UserNameComponent";
|
||||
import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent";
|
||||
import {logger} from "../../Logger";
|
||||
import UserPasswordComponent from "../password/UserPasswordComponent";
|
||||
|
||||
export default class MagicLinkController<A extends Application> extends Controller {
|
||||
public static async sendMagicLink(
|
||||
@ -233,6 +234,29 @@ export default class MagicLinkController<A extends Application> extends Controll
|
||||
res.redirect(Controller.route('account'));
|
||||
break;
|
||||
}
|
||||
|
||||
case AuthMagicLinkActionType.REMOVE_PASSWORD: {
|
||||
const session = req.getSessionOptional();
|
||||
if (!session || magicLink.session_id !== session.id) throw new BadOwnerMagicLink();
|
||||
|
||||
await magicLink.delete();
|
||||
|
||||
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
|
||||
const proofs = await authGuard.getProofsForSession(session);
|
||||
const user = await proofs[0]?.getResource();
|
||||
if (!user) return;
|
||||
|
||||
const passwordComponent = user.asOptional(UserPasswordComponent);
|
||||
if (passwordComponent) {
|
||||
passwordComponent.removePassword();
|
||||
await user.save();
|
||||
}
|
||||
|
||||
req.flash('success', `Password successfully removed.`);
|
||||
res.redirect(Controller.route('account'));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.warn('Unknown magic link action type ' + magicLink.action_type);
|
||||
break;
|
||||
|
@ -6,7 +6,7 @@ import Validator from "../../db/Validator";
|
||||
export default class UserPasswordComponent extends ModelComponent<User> {
|
||||
public static readonly PASSWORD_MIN_LENGTH = 12;
|
||||
|
||||
private password?: string = undefined;
|
||||
private password?: string | null = undefined;
|
||||
|
||||
public init(): void {
|
||||
this.setValidation('password').acceptUndefined().maxLength(128);
|
||||
@ -36,4 +36,8 @@ export default class UserPasswordComponent extends ModelComponent<User> {
|
||||
public hasPassword(): boolean {
|
||||
return typeof this.password === 'string';
|
||||
}
|
||||
|
||||
public removePassword(): void {
|
||||
this.password = null;
|
||||
}
|
||||
}
|
||||
|
@ -22,21 +22,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<h2><i data-feather="key"></i> {% if has_password %}Change{% else %}Set{% endif %} password</h2>
|
||||
|
||||
<form action="{{ route('change-password') }}" method="POST">
|
||||
{% if has_password %}
|
||||
{{ macros.field(_locals, 'password', 'current_password', null, 'Current password') }}
|
||||
{% endif %}
|
||||
{{ macros.field(_locals, 'password', 'new_password', null, 'New password') }}
|
||||
{{ macros.field(_locals, 'password', 'new_password_confirmation', null, 'New password confirmation') }}
|
||||
|
||||
<button type="submit"><i data-feather="save"></i> Save</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
</section>
|
||||
{% if has_password_component %}
|
||||
{% include './password_panel.njk' %}
|
||||
{% endif %}
|
||||
|
||||
<section class="panel">
|
||||
<h2 id="emails"><i data-feather="shield"></i> Email addresses</h2>
|
||||
|
45
views/auth/password_panel.njk
Normal file
45
views/auth/password_panel.njk
Normal file
@ -0,0 +1,45 @@
|
||||
<section class="panel">
|
||||
<h2><i data-feather="key"></i> {% if has_password %}Change{% else %}Set{% endif %} password</h2>
|
||||
|
||||
<form action="{{ route('change-password') }}" method="POST" id="change-password-form">
|
||||
{% if has_password %}
|
||||
{{ macros.field(_locals, 'password', 'current_password', null, 'Current password') }}
|
||||
<p><a href="javascript: void(0);" class="switch-form-link">Forgot your password?</a></p>
|
||||
{% endif %}
|
||||
{{ macros.field(_locals, 'password', 'new_password', null, 'New password') }}
|
||||
{{ macros.field(_locals, 'password', 'new_password_confirmation', null, 'New password confirmation') }}
|
||||
|
||||
<button type="submit"><i data-feather="save"></i> Save</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
|
||||
{% if has_password %}
|
||||
<form action="{{ route('remove-password') }}" method="POST" id="remove-password-form" class="hidden">
|
||||
<p><a href="javascript: void(0);" class="switch-form-link">Go back</a></p>
|
||||
|
||||
<button type="submit" class="danger"><i data-feather="trash"></i> Remove password</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const changePasswordForm = document.getElementById('change-password-form');
|
||||
const removePasswordLink = changePasswordForm.querySelector('a.switch-form-link');
|
||||
const removePasswordForm = document.getElementById('remove-password-form');
|
||||
const changePasswordLink = removePasswordForm.querySelector('a.switch-form-link');
|
||||
|
||||
removePasswordLink.addEventListener('click', () => {
|
||||
changePasswordForm.classList.add('hidden');
|
||||
removePasswordForm.classList.remove('hidden');
|
||||
});
|
||||
|
||||
changePasswordLink.addEventListener('click', () => {
|
||||
removePasswordForm.classList.add('hidden');
|
||||
changePasswordForm.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</section>
|
27
views/mails/remove_password.mjml.njk
Normal file
27
views/mails/remove_password.mjml.njk
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends 'mails/base_layout.mjml.njk' %}
|
||||
|
||||
{% block body %}
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text mj-class="title">
|
||||
Remove your password on your {{ app.name }} account
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Someone wants to remove your password from your account.
|
||||
<br><br>
|
||||
<strong>Do not click on this if this is not you!</strong>
|
||||
</mj-text>
|
||||
|
||||
<mj-button href="{{ link | safe }}">
|
||||
Remove my {{ app.name }} password
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
{% endblock %}
|
||||
|
||||
{% block text %}
|
||||
Hi!
|
||||
Someone wants to remove your password from your {{ app.name }} account.
|
||||
|
||||
To confirm this action and remove your password, please follow this link: {{ link|safe }}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user