Add users list backend and allow admins to change a user's password

This commit is contained in:
Alice Gaudon 2020-11-10 14:53:58 +01:00
parent d4ff831184
commit 0fb544d88b
6 changed files with 145 additions and 8 deletions

View File

@ -19,7 +19,6 @@ import AuthGuard from "wms-core/auth/AuthGuard";
import {PasswordAuthProof} from "./models/UserPasswordComponent";
import LDAPServerComponent from "./LDAPServerComponent";
import AutoUpdateComponent from "wms-core/components/AutoUpdateComponent";
import packageJson = require('../package.json');
import DummyMigration from "wms-core/migrations/DummyMigration";
import DropLegacyLogsTable from "wms-core/migrations/DropLegacyLogsTable";
import AccountController from "./controllers/AccountController";
@ -37,9 +36,11 @@ import DropNameFromUsers from "wms-core/auth/migrations/DropNameFromUsers";
import MagicLink from "wms-core/auth/models/MagicLink";
import AddNameToUsers from "./migrations/AddNameToUsers";
import CreateMailTables from "./migrations/CreateMailTables";
import MailboxBackendController from "./controllers/MailboxBackendController";
import MailboxBackendController from "./controllers/backend/MailboxBackendController";
import RedirectBackComponent from "wms-core/components/RedirectBackComponent";
import MailAutoConfigController from "./controllers/MailAutoConfigController";
import AccountBackendController from "./controllers/backend/AccountBackendController";
import packageJson = require('../package.json');
export default class App extends Application {
private magicLinkWebSocketListener?: MagicLinkWebSocketListener<this>;
@ -136,6 +137,7 @@ export default class App extends Application {
this.use(new MagicLinkController(this.as(MagicLinkWebSocketListener)));
this.use(new BackendController());
this.use(new MailboxBackendController());
this.use(new AccountBackendController());
this.use(new AuthController());
this.use(new MailAutoConfigController()); // Needs to override MailController

View File

@ -0,0 +1,65 @@
import Controller from "wms-core/Controller";
import BackendController from "wms-core/helpers/BackendController";
import {RequireAdminMiddleware, RequireAuthMiddleware} from "wms-core/auth/AuthComponent";
import {Request, Response} from "express";
import User from "wms-core/auth/models/User";
import {NotFoundHttpError} from "wms-core/HttpError";
import Validator from "wms-core/db/Validator";
import UserPasswordComponent from "../../models/UserPasswordComponent";
import UserNameComponent from "../../models/UserNameComponent";
export default class AccountBackendController extends Controller {
public constructor() {
super();
BackendController.registerMenuElement({
getLink: async () => Controller.route('backend-list-users'),
getDisplayString: async () => 'Users',
getDisplayIcon: async () => 'user',
});
}
public getRoutesPrefix(): string {
return '/backend/users';
}
public routes(): void {
this.get('/', this.getListUsers, 'backend-list-users', RequireAuthMiddleware, RequireAdminMiddleware);
this.get('/:user_id/change-password', this.getChangeUserPassword, 'backend-change-user-password', RequireAuthMiddleware, RequireAdminMiddleware);
this.post('/:user_id/change-password', this.postChangeUserPassword, 'backend-change-user-password', RequireAuthMiddleware, RequireAdminMiddleware);
}
protected async getListUsers(req: Request, res: Response): Promise<void> {
res.render('backend/users', {
accounts: await User.select()
.with('mainEmail')
.get(),
});
}
protected async getChangeUserPassword(req: Request, res: Response): Promise<void> {
const user = await User.getById(req.params.user_id);
if (!user) throw new NotFoundHttpError('user', req.url);
res.render('backend/users-change-password', {
user: user,
});
}
protected async postChangeUserPassword(req: Request, res: Response): Promise<void> {
const user = await User.getById(req.params.user_id);
if (!user) throw new NotFoundHttpError('user', req.url);
await this.validate({
'new_password': new Validator().defined(),
'new_password_confirmation': new Validator().sameAs('new_password', req.body.new_password),
}, req.body);
await user.as(UserPasswordComponent).setPassword(req.body.new_password, 'new_password');
await user.save();
req.flash('success', `New password set for ${user.as(UserNameComponent).name}`);
res.redirect(Controller.route('backend-list-users'));
}
}

View File

@ -2,12 +2,12 @@ import Controller from "wms-core/Controller";
import {Request, Response} from "express";
import User from "wms-core/auth/models/User";
import {WhereTest} from "wms-core/db/ModelQuery";
import UserNameComponent from "../models/UserNameComponent";
import UserMailIdentityComponent from "../models/UserMailIdentityComponent";
import UserNameComponent from "../../models/UserNameComponent";
import UserMailIdentityComponent from "../../models/UserMailIdentityComponent";
import {NotFoundHttpError} from "wms-core/HttpError";
import MailDomain from "../models/MailDomain";
import MailDomain from "../../models/MailDomain";
import BackendController from "wms-core/helpers/BackendController";
import MailIdentity from "../models/MailIdentity";
import MailIdentity from "../../models/MailIdentity";
import {RequireAdminMiddleware, RequireAuthMiddleware} from "wms-core/auth/AuthComponent";
export default class MailboxBackendController extends Controller {

View File

@ -11,9 +11,9 @@ export default class UserPasswordComponent extends ModelComponent<User> {
this.setValidation('password').acceptUndefined().maxLength(128);
}
public async setPassword(rawPassword: string): Promise<void> {
public async setPassword(rawPassword: string, fieldName: string = 'password'): Promise<void> {
await new Validator<string>().defined().minLength(8).maxLength(512)
.execute('password', rawPassword, true);
.execute(fieldName, rawPassword, true);
this.password = await argon2.hash(rawPassword, {
timeCost: 10,
memoryCost: 65536,

View File

@ -0,0 +1,27 @@
{% extends 'layouts/base.njk' %}
{% set title = app.name + ' - Backend' %}
{% block body %}
<div class="container">
{{ macros.breadcrumb('Change ' + user.name + '\'s password', [
{title: 'Backend', link: route('backend')},
{title: 'Users', link: route('backend-list-users')}
]) }}
<h1>Accounts manager</h1>
<section class="panel">
<h2><i data-feather="key"></i> Change {{ user.name }}'s password</h2>
<form action="{{ route('backend-change-user-password', user.id) }}" method="POST">
{{ 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>
</div>
{% endblock %}

43
views/backend/users.njk Normal file
View File

@ -0,0 +1,43 @@
{% extends 'layouts/base.njk' %}
{% set title = app.name + ' - Backend' %}
{% block body %}
<div class="container">
{{ macros.breadcrumb('Users', [
{title: 'Backend', link: route('backend')}
]) }}
<h1>Accounts manager</h1>
<section class="panel">
<h2><i data-feather="user"></i> Accounts</h2>
<table class="data-table">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr>
<td>{{ account.id }}</td>
<td>{{ account.name }}</td>
<td>{{ account.mainEmail.getOrFail().email | default('-') }}</td>
<td class="actions">
<a href="{{ route('backend-change-user-password', account.id) }}" class="button">
<i data-feather="key"></i> <span class="tip">Change password</span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
</div>
{% endblock %}