blob: 4255655fbfaf098b7ebbeeaa7c9e3a54b65c6a28 [file] [log] [blame]
Issam E. Maghni0ef4a362022-10-05 23:20:16 +00001/*
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 */
Issam E. Maghnif796a092022-10-09 20:25:26 +000018import argon2 from 'argon2';
Misha Krieger-Raynauld708a9632022-10-14 22:55:59 -040019import { Router } from 'express';
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000020import asyncHandler from 'express-async-handler';
Issam E. Maghnif796a092022-10-09 20:25:26 +000021import { ParamsDictionary, Request } from 'express-serve-static-core';
Misha Krieger-Raynauld2f5d1ce2022-10-23 21:13:33 -040022import { HttpStatusCode } from 'jami-web-common';
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040023import { Container } from 'typedi';
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000024
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040025import { Jamid } from '../jamid/jamid.js';
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050026import { Accounts } from '../storage/accounts.js';
27import { signJwt } from '../utils/jwt.js';
Issam E. Maghnif796a092022-10-09 20:25:26 +000028
29interface Credentials {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050030 username: string;
31 password: string;
Issam E. Maghnif796a092022-10-09 20:25:26 +000032}
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000033
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040034const jamid = Container.get(Jamid);
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050035const accounts = Container.get(Accounts);
Issam E. Maghnif796a092022-10-09 20:25:26 +000036
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040037export const authRouter = Router();
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000038
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040039authRouter.post(
40 '/new-account',
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050041 asyncHandler(async (req: Request<ParamsDictionary, string, Partial<Credentials>>, res, _next) => {
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040042 const { username, password } = req.body;
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050043 if (username === undefined || password === undefined) {
44 res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
45 return;
46 }
47
48 if (password === '') {
49 res.status(HttpStatusCode.BadRequest).send('Password may not be empty');
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040050 return;
51 }
Issam E. Maghnif796a092022-10-09 20:25:26 +000052
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040053 const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
Issam E. Maghnif796a092022-10-09 20:25:26 +000054
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040055 // TODO: add JAMS support
56 // managerUri: 'https://jams.savoirfairelinux.com',
57 // managerUsername: data.username,
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040058 const accountId = await jamid.addAccount(new Map());
Issam E. Maghnif796a092022-10-09 20:25:26 +000059
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040060 const state = await jamid.registerUsername(accountId, username, '');
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040061 if (state !== 0) {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -040062 jamid.removeAccount(accountId);
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040063 if (state === 2) {
Misha Krieger-Raynauld2f5d1ce2022-10-23 21:13:33 -040064 res.status(HttpStatusCode.BadRequest).send('Invalid username or password');
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040065 } else if (state === 3) {
Misha Krieger-Raynauld2f5d1ce2022-10-23 21:13:33 -040066 res.status(HttpStatusCode.Conflict).send('Username already exists');
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040067 } else {
68 throw new Error(`Unhandled state ${state}`);
69 }
70 return;
71 }
Issam E. Maghnif796a092022-10-09 20:25:26 +000072
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050073 accounts.set(username, hashedPassword);
74 await accounts.save();
Issam E. Maghnif796a092022-10-09 20:25:26 +000075
Misha Krieger-Raynauld2f5d1ce2022-10-23 21:13:33 -040076 res.sendStatus(HttpStatusCode.Created);
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040077 })
78);
Issam E. Maghnif796a092022-10-09 20:25:26 +000079
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040080authRouter.post(
81 '/login',
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050082 asyncHandler(
83 async (req: Request<ParamsDictionary, { accessToken: string } | string, Partial<Credentials>>, res, _next) => {
84 const { username, password } = req.body;
85 if (username === undefined || password === undefined) {
86 res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
87 return;
88 }
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000089
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050090 // Check if the account is stored stored on this daemon instance
91 const accountId = jamid.getAccountIdFromUsername(username);
92 if (accountId === undefined) {
93 res.status(HttpStatusCode.NotFound).send('Username not found');
94 return;
95 }
Issam E. Maghnif796a092022-10-09 20:25:26 +000096
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050097 const hashedPassword = accounts.get(username);
98 if (hashedPassword === undefined) {
99 res
100 .status(HttpStatusCode.NotFound)
101 .send('Password not found (the account does not have a password set on the server)');
102 return;
103 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000104
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500105 const isPasswordVerified = await argon2.verify(hashedPassword, password);
106 if (!isPasswordVerified) {
107 res.status(HttpStatusCode.Unauthorized).send('Incorrect password');
108 return;
109 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000110
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500111 const jwt = await signJwt(accountId);
112 res.send({ accessToken: jwt });
113 }
114 )
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -0400115);