blob: 01e0ad1fd0298c08d1dfbefcd7f659cc9375840d [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';
Issam E. Maghnib05ad992022-11-20 23:50:48 +000022import { AccountDetails, 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. Maghnib05ad992022-11-20 23:50:48 +000032 isJams?: boolean;
Issam E. Maghnif796a092022-10-09 20:25:26 +000033}
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000034
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040035const jamid = Container.get(Jamid);
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050036const accounts = Container.get(Accounts);
Issam E. Maghnif796a092022-10-09 20:25:26 +000037
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040038export const authRouter = Router();
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000039
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040040authRouter.post(
41 '/new-account',
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050042 asyncHandler(async (req: Request<ParamsDictionary, string, Partial<Credentials>>, res, _next) => {
Issam E. Maghnib05ad992022-11-20 23:50:48 +000043 const { username, password, isJams } = req.body;
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050044 if (username === undefined || password === undefined) {
45 res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
46 return;
47 }
48
Issam E. Maghnib05ad992022-11-20 23:50:48 +000049 const isAccountAlreadyCreated = accounts.get(username, isJams) !== undefined;
50 if (isAccountAlreadyCreated) {
51 res.status(HttpStatusCode.Conflict).send('Username already exists locally');
52 return;
53 }
54
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050055 if (password === '') {
56 res.status(HttpStatusCode.BadRequest).send('Password may not be empty');
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040057 return;
58 }
Issam E. Maghnif796a092022-10-09 20:25:26 +000059
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040060 const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
Issam E. Maghnif796a092022-10-09 20:25:26 +000061
Issam E. Maghnib05ad992022-11-20 23:50:48 +000062 const accountDetails: Partial<AccountDetails> = { 'Account.archivePassword': password };
63 if (isJams) {
64 accountDetails['Account.managerUri'] = 'https://jams.savoirfairelinux.com/';
65 accountDetails['Account.managerUsername'] = username;
66 }
67 const { accountId, state } = await jamid.addAccount(accountDetails);
Issam E. Maghnif796a092022-10-09 20:25:26 +000068
Issam E. Maghnib05ad992022-11-20 23:50:48 +000069 if (isJams) {
70 if (state === 'ERROR_GENERIC') {
71 jamid.removeAccount(accountId);
72 res.status(HttpStatusCode.Unauthorized).send('Invalid JAMS credentials');
73 return;
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040074 }
Issam E. Maghnib05ad992022-11-20 23:50:48 +000075 } else {
76 const state = await jamid.registerUsername(accountId, username, '');
77 if (state !== 0) {
78 jamid.removeAccount(accountId);
79 if (state === 2) {
80 res.status(HttpStatusCode.BadRequest).send('Invalid username or password');
81 } else if (state === 3) {
82 res.status(HttpStatusCode.Conflict).send('Username already exists');
83 } else {
84 throw new Error(`Unhandled state ${state}`);
85 }
86 return;
87 }
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040088 }
Issam E. Maghnif796a092022-10-09 20:25:26 +000089
Issam E. Maghnib05ad992022-11-20 23:50:48 +000090 accounts.set(username, hashedPassword, isJams);
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050091 await accounts.save();
Issam E. Maghnif796a092022-10-09 20:25:26 +000092
Misha Krieger-Raynauld2f5d1ce2022-10-23 21:13:33 -040093 res.sendStatus(HttpStatusCode.Created);
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040094 })
95);
Issam E. Maghnif796a092022-10-09 20:25:26 +000096
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -040097authRouter.post(
98 '/login',
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050099 asyncHandler(
100 async (req: Request<ParamsDictionary, { accessToken: string } | string, Partial<Credentials>>, res, _next) => {
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000101 const { username, password, isJams } = req.body;
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500102 if (username === undefined || password === undefined) {
103 res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
104 return;
105 }
Issam E. Maghni0ef4a362022-10-05 23:20:16 +0000106
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500107 // Check if the account is stored stored on this daemon instance
108 const accountId = jamid.getAccountIdFromUsername(username);
109 if (accountId === undefined) {
110 res.status(HttpStatusCode.NotFound).send('Username not found');
111 return;
112 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000113
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000114 const hashedPassword = accounts.get(username, isJams);
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500115 if (hashedPassword === undefined) {
116 res
117 .status(HttpStatusCode.NotFound)
118 .send('Password not found (the account does not have a password set on the server)');
119 return;
120 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000121
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500122 const isPasswordVerified = await argon2.verify(hashedPassword, password);
123 if (!isPasswordVerified) {
124 res.status(HttpStatusCode.Unauthorized).send('Incorrect password');
125 return;
126 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000127
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500128 const jwt = await signJwt(accountId);
129 res.send({ accessToken: jwt });
130 }
131 )
Misha Krieger-Raynauld242560f2022-10-16 19:59:58 -0400132);