🤖 Tests: Authentication
This commit is contained in:
parent
fa0a7d0764
commit
371c6dde36
10 changed files with 477 additions and 97 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -6,9 +6,8 @@ env:
|
|||
ROOT_FOLDER: /test
|
||||
|
||||
jobs:
|
||||
cache-and-install:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
|
36
package.json
Normal file
36
package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "runtipi",
|
||||
"version": "0.0.1",
|
||||
"description": "A homeserver for everyone",
|
||||
"dependencies": {
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-next": "^12.1.4",
|
||||
"eslint-import-resolver-node": "^0.3.4",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-module-utils": "^2.7.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.29.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-scope": "^7.1.1",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"eslint-visitor-keys": "^3.3.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"eslint-import-resolver-typescript": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/meienberger/runtipi.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GNU General Public License v3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/meienberger/runtipi/issues"
|
||||
},
|
||||
"homepage": "https://github.com/meienberger/runtipi#readme"
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
"clean": "rimraf dist",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"build-prod": "esbuild --bundle src/server.ts --outdir=dist --allow-overwrite --sourcemap --platform=node --minify --analyze=verbose --external:./node_modules/* --format=esm",
|
||||
"build:watch": "esbuild --bundle src/server.ts --outdir=dist --allow-overwrite --sourcemap --platform=node --external:./node_modules/* --format=esm --watch",
|
||||
"start:dev": "NODE_ENV=development nodemon --trace-deprecation --trace-warnings --watch dist dist/server.js",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"argon2": "^0.28.5",
|
||||
"bcrypt": "^5.0.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"helmet": "^5.0.2",
|
||||
"internal-ip": "^6.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mock-fs": "^5.1.2",
|
||||
"node-port-scanner": "^3.0.1",
|
||||
"p-iteration": "^1.1.8",
|
||||
"passport": "^0.5.2",
|
||||
|
@ -36,8 +39,7 @@
|
|||
"passport-http-bearer": "^1.0.1",
|
||||
"public-ip": "^5.0.0",
|
||||
"systeminformation": "^5.11.9",
|
||||
"tcp-port-used": "^1.0.2",
|
||||
"mock-fs": "^5.1.2"
|
||||
"tcp-port-used": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
import { Request, Response } from 'express';
|
||||
import fs from 'fs';
|
||||
import * as argon2 from 'argon2';
|
||||
import config from '../../../config';
|
||||
import AuthController from '../auth.controller';
|
||||
|
||||
let user: any;
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const next = jest.fn();
|
||||
|
||||
const MOCK_USER_REGISTERED = () => ({
|
||||
[`${config.ROOT_FOLDER}/state/users.json`]: `[${user}]`,
|
||||
});
|
||||
|
||||
const MOCK_NO_USER = {
|
||||
[`${config.ROOT_FOLDER}/state/users.json`]: '[]',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const hash = await argon2.hash('password');
|
||||
user = JSON.stringify({
|
||||
email: 'username',
|
||||
password: hash,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Login', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should put cookie in response after login', async () => {
|
||||
const json = jest.fn();
|
||||
const res = { cookie: jest.fn(), status: jest.fn(() => ({ json })), json: jest.fn() } as unknown as Response;
|
||||
const req = { body: { email: 'username', password: 'password' } } as Request;
|
||||
|
||||
await AuthController.login(req, res, next);
|
||||
|
||||
expect(res.cookie).toHaveBeenCalledWith('tipi_token', expect.any(String), expect.any(Object));
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ token: expect.any(String) });
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should throw if username is not provided in request', async () => {
|
||||
const res = { cookie: jest.fn(), status: jest.fn(), json: jest.fn() } as unknown as Response;
|
||||
const req = { body: { password: 'password' } } as Request;
|
||||
|
||||
await AuthController.login(req, res, next);
|
||||
|
||||
expect(res.cookie).not.toHaveBeenCalled();
|
||||
expect(next).toHaveBeenCalledWith(expect.any(Error));
|
||||
});
|
||||
|
||||
it('Should throw if password is not provided in request', async () => {
|
||||
const res = { cookie: jest.fn(), status: jest.fn(), json: jest.fn() } as unknown as Response;
|
||||
const req = { body: { email: 'username' } } as Request;
|
||||
|
||||
await AuthController.login(req, res, next);
|
||||
|
||||
expect(res.cookie).not.toHaveBeenCalled();
|
||||
expect(next).toHaveBeenCalledWith(expect.any(Error));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Register', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_NO_USER);
|
||||
});
|
||||
|
||||
it('Should put cookie in response after register', async () => {
|
||||
const json = jest.fn();
|
||||
const res = { cookie: jest.fn(), status: jest.fn(() => ({ json })), json: jest.fn() } as unknown as Response;
|
||||
const req = { body: { email: 'username', password: 'password', name: 'name' } } as Request;
|
||||
|
||||
await AuthController.register(req, res, next);
|
||||
|
||||
expect(res.cookie).toHaveBeenCalledWith('tipi_token', expect.any(String), expect.any(Object));
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ token: expect.any(String) });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Me', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should return user if present in request', async () => {
|
||||
const json = jest.fn();
|
||||
const res = { status: jest.fn(() => ({ json })) } as unknown as Response;
|
||||
const req = { user } as Request;
|
||||
|
||||
await AuthController.me(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ user });
|
||||
});
|
||||
|
||||
it('Should return null if user is not present in request', async () => {
|
||||
const json = jest.fn();
|
||||
const res = { status: jest.fn(() => ({ json })) } as unknown as Response;
|
||||
const req = {} as Request;
|
||||
|
||||
await AuthController.me(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ user: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('isConfigured', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_NO_USER);
|
||||
});
|
||||
|
||||
it('Should return false if no user is registered', async () => {
|
||||
const json = jest.fn();
|
||||
const res = { status: jest.fn(() => ({ json })) } as unknown as Response;
|
||||
const req = {} as Request;
|
||||
|
||||
await AuthController.isConfigured(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ configured: false });
|
||||
});
|
||||
|
||||
it('Should return true if user is registered', async () => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
|
||||
const json = jest.fn();
|
||||
const res = { status: jest.fn(() => ({ json })) } as unknown as Response;
|
||||
const req = { user } as Request;
|
||||
|
||||
await AuthController.isConfigured(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(json).toHaveBeenCalledWith({ configured: true });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
import * as argon2 from 'argon2';
|
||||
import fs from 'fs';
|
||||
import config from '../../../config';
|
||||
import { IUser } from '../../../config/types';
|
||||
import AuthHelpers from '../auth.helpers';
|
||||
|
||||
let user: IUser;
|
||||
|
||||
beforeAll(async () => {
|
||||
const hash = await argon2.hash('password');
|
||||
user = { email: 'username', password: hash, name: 'name' };
|
||||
});
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const MOCK_USER_REGISTERED = () => ({
|
||||
[`${config.ROOT_FOLDER}/state/users.json`]: `[${JSON.stringify(user)}]`,
|
||||
});
|
||||
|
||||
describe('TradeTokenForUser', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should return null if token is invalid', () => {
|
||||
const result = AuthHelpers.tradeTokenForUser('invalid token');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('Should return user if token is valid', async () => {
|
||||
const token = await AuthHelpers.getJwtToken(user, 'password');
|
||||
const result = AuthHelpers.tradeTokenForUser(token);
|
||||
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetJwtToken', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should return token if user and password are valid', async () => {
|
||||
const token = await AuthHelpers.getJwtToken(user, 'password');
|
||||
expect(token).toBeDefined();
|
||||
});
|
||||
|
||||
it('Should throw if password is invalid', async () => {
|
||||
await expect(AuthHelpers.getJwtToken(user, 'invalid password')).rejects.toThrow('Wrong password');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUser', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should return null if user is not found', () => {
|
||||
const result = AuthHelpers.getUser('invalid token');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should return user if token is valid', async () => {
|
||||
const result = AuthHelpers.getUser('username');
|
||||
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
import fs from 'fs';
|
||||
// import bcrypt from 'bcrypt';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import * as argon2 from 'argon2';
|
||||
import config from '../../../config';
|
||||
import AuthService from '../auth.service';
|
||||
import { IUser } from '../../../config/types';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
let user: any;
|
||||
|
||||
const MOCK_USER_REGISTERED = () => ({
|
||||
[`${config.ROOT_FOLDER}/state/users.json`]: `[${user}]`,
|
||||
});
|
||||
|
||||
const MOCK_NO_USER = {
|
||||
[`${config.ROOT_FOLDER}/state/users.json`]: '[]',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const hash = await argon2.hash('password');
|
||||
user = JSON.stringify({
|
||||
email: 'username',
|
||||
password: hash,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Login', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_USER_REGISTERED());
|
||||
});
|
||||
|
||||
it('Should return token after login', async () => {
|
||||
const token = await AuthService.login('username', 'password');
|
||||
|
||||
const { email } = jsonwebtoken.verify(token, config.JWT_SECRET) as { email: string };
|
||||
|
||||
expect(token).toBeDefined();
|
||||
expect(email).toBe('username');
|
||||
});
|
||||
|
||||
it('Should throw if user does not exist', async () => {
|
||||
await expect(AuthService.login('username1', 'password')).rejects.toThrowError('User not found');
|
||||
});
|
||||
|
||||
it('Should throw if password is incorrect', async () => {
|
||||
await expect(AuthService.login('username', 'password1')).rejects.toThrowError('Wrong password');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Register', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MOCK_NO_USER);
|
||||
});
|
||||
|
||||
it('Should return token after register', async () => {
|
||||
const token = await AuthService.register('username', 'password', 'name');
|
||||
|
||||
const { email } = jsonwebtoken.verify(token, config.JWT_SECRET) as { email: string };
|
||||
|
||||
expect(token).toBeDefined();
|
||||
expect(email).toBe('username');
|
||||
});
|
||||
|
||||
it('Should correctly write user to file', async () => {
|
||||
await AuthService.register('username', 'password', 'name');
|
||||
|
||||
const users: IUser[] = JSON.parse(fs.readFileSync(`${config.ROOT_FOLDER}/state/users.json`, 'utf8'));
|
||||
|
||||
expect(users.length).toBe(1);
|
||||
expect(users[0].email).toBe('username');
|
||||
expect(users[0].name).toBe('name');
|
||||
|
||||
const valid = await argon2.verify(users[0].password, 'password');
|
||||
|
||||
expect(valid).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should throw if user already exists', async () => {
|
||||
await AuthService.register('username', 'password', 'name');
|
||||
|
||||
await expect(AuthService.register('username', 'password', 'name')).rejects.toThrowError('There is already an admin user');
|
||||
});
|
||||
|
||||
it('Should throw if email is not provided', async () => {
|
||||
await expect(AuthService.register('', 'password', 'name')).rejects.toThrowError('Missing email or password');
|
||||
});
|
||||
|
||||
it('Should throw if password is not provided', async () => {
|
||||
await expect(AuthService.register('username', '', 'name')).rejects.toThrowError('Missing email or password');
|
||||
});
|
||||
|
||||
it('Does not throw if name is not provided', async () => {
|
||||
await AuthService.register('username', 'password', '');
|
||||
|
||||
const users: IUser[] = JSON.parse(fs.readFileSync(`${config.ROOT_FOLDER}/state/users.json`, 'utf8'));
|
||||
|
||||
expect(users.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1,8 +1,7 @@
|
|||
import { NextFunction, Request, Response } from 'express';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { IUser } from '../../config/types';
|
||||
import { readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import { getJwtToken, getUser } from './auth.helpers';
|
||||
import { readJsonFile } from '../fs/fs.helpers';
|
||||
import AuthService from './auth.service';
|
||||
|
||||
const login = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
@ -12,13 +11,7 @@ const login = async (req: Request, res: Response, next: NextFunction) => {
|
|||
throw new Error('Missing id or password');
|
||||
}
|
||||
|
||||
const user = getUser(email);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const token = await getJwtToken(user, password);
|
||||
const token = await AuthService.login(email, password);
|
||||
|
||||
res.cookie('tipi_token', token, {
|
||||
httpOnly: false,
|
||||
|
@ -34,26 +27,9 @@ const login = async (req: Request, res: Response, next: NextFunction) => {
|
|||
|
||||
const register = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const users: IUser[] = readJsonFile('/state/users.json');
|
||||
|
||||
if (users.length > 0) {
|
||||
throw new Error('There is already an admin user');
|
||||
}
|
||||
|
||||
const { email, password, name } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
throw new Error('Missing email or password');
|
||||
}
|
||||
|
||||
if (users.find((user) => user.email === email)) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
const newuser: IUser = { email, name, password: hash };
|
||||
|
||||
const token = await getJwtToken(newuser, password);
|
||||
const token = await AuthService.register(email, password, name);
|
||||
|
||||
res.cookie('tipi_token', token, {
|
||||
httpOnly: false,
|
||||
|
@ -61,28 +37,34 @@ const register = async (req: Request, res: Response, next: NextFunction) => {
|
|||
maxAge: 1000 * 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
||||
writeFile('/state/users.json', JSON.stringify([newuser]));
|
||||
|
||||
res.status(200).json({ token });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
const me = async (req: Request, res: Response) => {
|
||||
const { user } = req;
|
||||
const me = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { user } = req;
|
||||
|
||||
if (user) {
|
||||
res.status(200).json({ user });
|
||||
} else {
|
||||
res.status(200).json({ user: null });
|
||||
if (user) {
|
||||
res.status(200).json({ user });
|
||||
} else {
|
||||
res.status(200).json({ user: null });
|
||||
}
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
const isConfigured = async (req: Request, res: Response) => {
|
||||
const users: IUser[] = readJsonFile('/state/users.json');
|
||||
const isConfigured = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const users: IUser[] = readJsonFile('/state/users.json');
|
||||
|
||||
res.status(200).json({ configured: users.length > 0 });
|
||||
res.status(200).json({ configured: users.length > 0 });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
export default { login, me, register, isConfigured };
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import bcrypt from 'bcrypt';
|
||||
import * as argon2 from 'argon2';
|
||||
import { IUser, Maybe } from '../../config/types';
|
||||
import { readJsonFile } from '../fs/fs.helpers';
|
||||
import config from '../../config';
|
||||
|
||||
export const getUser = (email: string): Maybe<IUser> => {
|
||||
const getUser = (email: string): Maybe<IUser> => {
|
||||
const savedUser: IUser[] = readJsonFile('/state/users.json');
|
||||
|
||||
const user = savedUser.find((u) => u.email === email);
|
||||
|
@ -13,17 +13,19 @@ export const getUser = (email: string): Maybe<IUser> => {
|
|||
};
|
||||
|
||||
const compareHashPassword = (password: string, hash = ''): Promise<boolean> => {
|
||||
return bcrypt.compare(password, hash || '');
|
||||
return argon2.verify(hash, password);
|
||||
};
|
||||
|
||||
const getJwtToken = async (user: IUser, password: string) => {
|
||||
const validPassword = await compareHashPassword(password, user.password || '');
|
||||
const validPassword = await compareHashPassword(password, user.password);
|
||||
|
||||
if (validPassword) {
|
||||
if (config.JWT_SECRET) {
|
||||
return jsonwebtoken.sign({ email: user.email }, config.JWT_SECRET, {
|
||||
expiresIn: '7d',
|
||||
});
|
||||
} else {
|
||||
throw new Error('JWT_SECRET is not set');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +35,7 @@ const getJwtToken = async (user: IUser, password: string) => {
|
|||
const tradeTokenForUser = (token: string): Maybe<IUser> => {
|
||||
try {
|
||||
const { email } = jsonwebtoken.verify(token, config.JWT_SECRET) as { email: string };
|
||||
|
||||
const users: IUser[] = readJsonFile('/state/users.json');
|
||||
|
||||
return users.find((user) => user.email === email);
|
||||
|
@ -41,4 +44,4 @@ const tradeTokenForUser = (token: string): Maybe<IUser> => {
|
|||
}
|
||||
};
|
||||
|
||||
export { tradeTokenForUser, getJwtToken };
|
||||
export default { tradeTokenForUser, getJwtToken, getUser };
|
||||
|
|
48
packages/system-api/src/modules/auth/auth.service.ts
Normal file
48
packages/system-api/src/modules/auth/auth.service.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import * as argon2 from 'argon2';
|
||||
import { IUser } from '../../config/types';
|
||||
import { readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import AuthHelpers from './auth.helpers';
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
const user = AuthHelpers.getUser(email);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const token = await AuthHelpers.getJwtToken(user, password);
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
const register = async (email: string, password: string, name: string) => {
|
||||
const users: IUser[] = readJsonFile('/state/users.json');
|
||||
|
||||
if (users.length > 0) {
|
||||
throw new Error('There is already an admin user');
|
||||
}
|
||||
|
||||
if (!email || !password) {
|
||||
throw new Error('Missing email or password');
|
||||
}
|
||||
|
||||
if (users.find((user) => user.email === email)) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
|
||||
const hash = await argon2.hash(password); // bcrypt.hash(password, 10);
|
||||
const newuser: IUser = { email, name, password: hash };
|
||||
|
||||
const token = await AuthHelpers.getJwtToken(newuser, password);
|
||||
|
||||
writeFile('/state/users.json', JSON.stringify([newuser]));
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
const AuthService = {
|
||||
login,
|
||||
register,
|
||||
};
|
||||
|
||||
export default AuthService;
|
|
@ -93,6 +93,7 @@ importers:
|
|||
'@types/validator': ^13.7.2
|
||||
'@typescript-eslint/eslint-plugin': ^5.18.0
|
||||
'@typescript-eslint/parser': ^5.22.0
|
||||
argon2: ^0.28.5
|
||||
bcrypt: ^5.0.1
|
||||
compression: ^1.7.4
|
||||
concurrently: ^7.1.0
|
||||
|
@ -124,6 +125,7 @@ importers:
|
|||
ts-jest: ^28.0.2
|
||||
typescript: 4.6.4
|
||||
dependencies:
|
||||
argon2: 0.28.5
|
||||
bcrypt: 5.0.1
|
||||
compression: 1.7.4
|
||||
cookie-parser: 1.4.6
|
||||
|
@ -162,7 +164,7 @@ importers:
|
|||
eslint: 8.15.0
|
||||
eslint-config-airbnb-typescript: 17.0.0_c2ouaf3l4ivgkc6ae4nebvztom
|
||||
eslint-config-prettier: 8.5.0_eslint@8.15.0
|
||||
eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
|
||||
eslint-plugin-import: 2.26.0_eslint@8.15.0
|
||||
eslint-plugin-prettier: 4.0.0_iqftbjqlxzn3ny5nablrkczhqi
|
||||
jest: 28.1.0
|
||||
nodemon: 2.0.16
|
||||
|
@ -1891,6 +1893,11 @@ packages:
|
|||
fastq: 1.13.0
|
||||
dev: true
|
||||
|
||||
/@phc/format/1.0.0:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/@popperjs/core/2.11.5:
|
||||
resolution: {integrity: sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==}
|
||||
dev: false
|
||||
|
@ -2684,6 +2691,19 @@ packages:
|
|||
resolution: {integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==}
|
||||
dev: true
|
||||
|
||||
/argon2/0.28.5:
|
||||
resolution: {integrity: sha512-kGFCctzc3VWmR1aCOYjNgvoTmVF5uVBUtWlXCKKO54d1K+31zRz45KAcDIqMo2746ozv/52d25nfEekitaXP0w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.9
|
||||
'@phc/format': 1.0.0
|
||||
node-addon-api: 4.3.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/argparse/1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
dependencies:
|
||||
|
@ -2902,8 +2922,6 @@ packages:
|
|||
raw-body: 2.5.1
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/boxen/5.1.2:
|
||||
|
@ -3170,8 +3188,6 @@ packages:
|
|||
on-headers: 1.0.2
|
||||
safe-buffer: 5.1.2
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/compute-scroll-into-view/1.0.14:
|
||||
|
@ -3328,37 +3344,15 @@ packages:
|
|||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
|
||||
/debug/3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/debug/3.2.7_supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/debug/4.3.1:
|
||||
resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
@ -3877,7 +3871,7 @@ packages:
|
|||
dependencies:
|
||||
confusing-browser-globals: 1.0.11
|
||||
eslint: 8.15.0
|
||||
eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
|
||||
eslint-plugin-import: 2.26.0_eslint@8.15.0
|
||||
object.assign: 4.1.2
|
||||
object.entries: 1.1.5
|
||||
semver: 6.3.0
|
||||
|
@ -3910,7 +3904,7 @@ packages:
|
|||
'@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
|
||||
eslint: 8.15.0
|
||||
eslint-config-airbnb-base: 15.0.0_gwd37gqv3vjv3xlpl7ju3ag2qu
|
||||
eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
|
||||
eslint-plugin-import: 2.26.0_eslint@8.15.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-airbnb-typescript/17.0.0_r46exuh3jlhq2wmrnqx2ufqspa:
|
||||
|
@ -3998,6 +3992,14 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils/2.7.3:
|
||||
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
find-up: 2.1.0
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils/2.7.3_sysdrzuw2ki4kxpuwc4tznw2ha:
|
||||
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -4082,24 +4084,19 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import/2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy:
|
||||
/eslint-plugin-import/2.26.0_eslint@8.15.0:
|
||||
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
|
||||
array-includes: 3.1.5
|
||||
array.prototype.flat: 1.3.0
|
||||
debug: 2.6.9
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.15.0
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-module-utils: 2.7.3_wex3ustmkv4ospy3s77r6ihlwq
|
||||
eslint-module-utils: 2.7.3
|
||||
has: 1.0.3
|
||||
is-core-module: 2.9.0
|
||||
is-glob: 4.0.3
|
||||
|
@ -4107,10 +4104,6 @@ packages:
|
|||
object.values: 1.1.5
|
||||
resolve: 1.22.0
|
||||
tsconfig-paths: 3.14.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import/2.26.0_hhyjdrupy4c2vgtpytri6cjwoy:
|
||||
|
@ -4462,8 +4455,6 @@ packages:
|
|||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/fast-deep-equal/3.1.3:
|
||||
|
@ -4536,8 +4527,6 @@ packages:
|
|||
parseurl: 1.3.3
|
||||
statuses: 2.0.1
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/find-root/1.1.0:
|
||||
|
@ -6162,6 +6151,10 @@ packages:
|
|||
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
|
||||
dev: false
|
||||
|
||||
/node-addon-api/4.3.0:
|
||||
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
||||
dev: false
|
||||
|
||||
/node-fetch/2.6.7:
|
||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
|
@ -6193,7 +6186,7 @@ packages:
|
|||
requiresBuild: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
debug: 3.2.7_supports-color@5.5.0
|
||||
debug: 3.2.7
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 3.1.2
|
||||
pstree.remy: 1.1.8
|
||||
|
@ -7056,8 +7049,6 @@ packages:
|
|||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/serve-static/1.15.0:
|
||||
|
@ -7068,8 +7059,6 @@ packages:
|
|||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.18.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/set-blocking/2.0.0:
|
||||
|
|
Loading…
Reference in a new issue