blob: d155ef5a926db473e4e8095d357b4cc278aa6757 [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';
23import { SignJWT } from 'jose';
24import { Container } from 'typedi';
25
26import { AdminConfig } from '../admin-config.js';
27import { checkAdminSetup } from '../middleware/setup.js';
28import { Vault } from '../vault.js';
29
30export const setupRouter = Router();
31
32const vault = Container.get(Vault);
33const adminConfig = Container.get(AdminConfig);
34
35setupRouter.get('/check', (_req, res, _next) => {
36 const isSetupComplete = adminConfig.get() !== undefined;
37 res.send({ isSetupComplete });
38});
39
40setupRouter.post(
41 '/admin/create',
42 asyncHandler(async (req: Request<ParamsDictionary, string, { password?: string }>, res, _next) => {
43 const isAdminCreated = adminConfig.get() !== undefined;
44 if (isAdminCreated) {
45 res.sendStatus(HttpStatusCode.BadRequest);
46 return;
47 }
48
49 const { password } = req.body;
50 if (!password) {
51 res.status(HttpStatusCode.BadRequest).send('Missing password');
52 return;
53 }
54
55 const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
56 adminConfig.set(hashedPassword);
57 await adminConfig.save();
58
59 res.sendStatus(HttpStatusCode.Created);
60 })
61);
62
63// Every request handler after this line will be submitted to this middleware
64// in order to ensure that the admin account is set up before proceeding with
65// setup related requests.
66setupRouter.use(checkAdminSetup);
67
68setupRouter.post(
69 '/admin/login',
70 asyncHandler(
71 async (req: Request<ParamsDictionary, { accessToken: string } | string, { password: string }>, res, _next) => {
72 const { password } = req.body;
73 if (!password) {
74 res.status(HttpStatusCode.BadRequest).send('Missing password');
75 return;
76 }
77
78 const hashedPassword = adminConfig.get();
79 const isPasswordVerified = await argon2.verify(hashedPassword, password);
80 if (!isPasswordVerified) {
81 res.sendStatus(HttpStatusCode.Forbidden);
82 return;
83 }
84
85 const jwt = await new SignJWT({ id: 'admin' })
86 .setProtectedHeader({ alg: 'EdDSA' })
87 .setIssuedAt()
88 // TODO: use valid issuer and audience
89 .setIssuer('urn:example:issuer')
90 .setAudience('urn:example:audience')
91 .setExpirationTime('2h')
92 .sign(vault.privateKey);
93 res.send({ accessToken: jwt });
94 }
95 )
96);