blob: 7dfa24f6d5fb26a82deaa43252260c5e1483c46e [file] [log] [blame]
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
import argon2 from 'argon2';
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { ParamsDictionary, Request } from 'express-serve-static-core';
import { HttpStatusCode } from 'jami-web-common';
import { SignJWT } from 'jose';
import { Container } from 'typedi';
import { AdminConfig } from '../admin-config.js';
import { checkAdminSetup } from '../middleware/setup.js';
import { Vault } from '../vault.js';
export const setupRouter = Router();
const vault = Container.get(Vault);
const adminConfig = Container.get(AdminConfig);
setupRouter.get('/check', (_req, res, _next) => {
const isSetupComplete = adminConfig.get() !== undefined;
res.send({ isSetupComplete });
});
setupRouter.post(
'/admin/create',
asyncHandler(async (req: Request<ParamsDictionary, string, { password?: string }>, res, _next) => {
const { password } = req.body;
if (password === undefined) {
res.status(HttpStatusCode.BadRequest).send('Missing password in body');
return;
}
if (password === '') {
res.status(HttpStatusCode.BadRequest).send('Password may not be empty');
return;
}
const isAdminCreated = adminConfig.get() !== undefined;
if (isAdminCreated) {
res.status(HttpStatusCode.Conflict).send('Admin already exists');
return;
}
const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
adminConfig.set(hashedPassword);
await adminConfig.save();
res.sendStatus(HttpStatusCode.Created);
})
);
// Every request handler after this line will be submitted to this middleware
// in order to ensure that the admin account is set up before proceeding with
// setup related requests
setupRouter.use(checkAdminSetup);
setupRouter.post(
'/admin/login',
asyncHandler(
async (req: Request<ParamsDictionary, { accessToken: string } | string, { password: string }>, res, _next) => {
const { password } = req.body;
if (password === undefined) {
res.status(HttpStatusCode.BadRequest).send('Missing password in body');
return;
}
const hashedPassword = adminConfig.get();
const isPasswordVerified = await argon2.verify(hashedPassword, password);
if (!isPasswordVerified) {
res.status(HttpStatusCode.Forbidden).send('Incorrect password');
return;
}
const jwt = await new SignJWT({ id: 'admin' })
.setProtectedHeader({ alg: 'EdDSA' })
.setIssuedAt()
// TODO: use valid issuer and audience
.setIssuer('urn:example:issuer')
.setAudience('urn:example:audience')
.setExpirationTime('2h')
.sign(vault.privateKey);
res.send({ accessToken: jwt });
}
)
);