blob: 3bc3088523a6af400db09b6181cf82023f1ae240 [file] [log] [blame]
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -05001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import argon2 from 'argon2';
19import { Router } from 'express';
20import asyncHandler from 'express-async-handler';
21import { ParamsDictionary, Request } from 'express-serve-static-core';
22import { HttpStatusCode } from 'jami-web-common';
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050023import { Container } from 'typedi';
24
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050025import { checkAdminSetup } from '../middleware/setup.js';
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050026import { AdminAccount } from '../storage/admin-account.js';
27import { signJwt } from '../utils/jwt.js';
28
29const adminAccount = Container.get(AdminAccount);
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050030
31export const setupRouter = Router();
32
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050033setupRouter.get('/check', (_req, res, _next) => {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050034 const isSetupComplete = adminAccount.get() !== undefined;
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050035 res.send({ isSetupComplete });
36});
37
38setupRouter.post(
39 '/admin/create',
40 asyncHandler(async (req: Request<ParamsDictionary, string, { password?: string }>, res, _next) => {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050041 const { password } = req.body;
42 if (password === undefined) {
43 res.status(HttpStatusCode.BadRequest).send('Missing password in body');
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050044 return;
45 }
46
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050047 if (password === '') {
48 res.status(HttpStatusCode.BadRequest).send('Password may not be empty');
49 return;
50 }
51
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050052 const isAdminCreated = adminAccount.get() !== undefined;
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050053 if (isAdminCreated) {
54 res.status(HttpStatusCode.Conflict).send('Admin already exists');
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050055 return;
56 }
57
58 const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050059
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050060 adminAccount.set(hashedPassword);
61 await adminAccount.save();
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050062
63 res.sendStatus(HttpStatusCode.Created);
64 })
65);
66
67// Every request handler after this line will be submitted to this middleware
68// in order to ensure that the admin account is set up before proceeding with
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050069// setup-related requests
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050070setupRouter.use(checkAdminSetup);
71
72setupRouter.post(
73 '/admin/login',
74 asyncHandler(
75 async (req: Request<ParamsDictionary, { accessToken: string } | string, { password: string }>, res, _next) => {
76 const { password } = req.body;
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050077 if (password === undefined) {
78 res.status(HttpStatusCode.BadRequest).send('Missing password in body');
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050079 return;
80 }
81
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050082 const hashedPassword = adminAccount.get();
83 if (hashedPassword === undefined) {
84 res.status(HttpStatusCode.InternalServerError).send('Admin password not found');
85 return;
86 }
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050087
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050088 const isPasswordVerified = await argon2.verify(hashedPassword, password);
89 if (!isPasswordVerified) {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050090 res.status(HttpStatusCode.Forbidden).send('Incorrect password');
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050091 return;
92 }
93
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050094 const jwt = await signJwt('admin');
Michelle Sepkap Sime6967fb92022-11-08 08:39:36 -050095 res.send({ accessToken: jwt });
96 }
97 )
98);