commit
c4eb712a34
11 changed files with 737 additions and 328 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -3,12 +3,12 @@ on:
|
|||
push:
|
||||
|
||||
env:
|
||||
ROOT_FOLDER: /test
|
||||
ROOT_FOLDER: /test
|
||||
JWT_SECRET: "secret"
|
||||
|
||||
jobs:
|
||||
cache-and-install:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
|
5
.husky/pre-commit
Executable file
5
.husky/pre-commit
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm -r test
|
||||
pnpm -r lint
|
41
package.json
Normal file
41
package.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "runtipi",
|
||||
"version": "0.0.1",
|
||||
"description": "A homeserver for everyone",
|
||||
"scripts": {
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-next": "^12.1.4",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-node": "^0.3.4",
|
||||
"eslint-import-resolver-typescript": "^2.4.0",
|
||||
"eslint-module-utils": "^2.7.3",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.29.1",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-scope": "^7.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"eslint-visitor-keys": "^3.3.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-linter-helpers": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^8.0.1"
|
||||
},
|
||||
"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;
|
565
pnpm-lock.yaml
generated
565
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue