apps/api: add users functional
This commit is contained in:
parent
9c76654406
commit
724d8ccf28
@ -36,8 +36,11 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"cache-manager": "^5.2.4",
|
||||
"cache-manager-ioredis": "^2.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"ldap-authentication": "2.3.1",
|
||||
"mongoose": "^7.6.3",
|
||||
"radash": "^11.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^5.0.5",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { DecodedToken } from './ldap/types/jwt';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { omit } from 'radash';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
@ -11,9 +12,8 @@ export class AppService {
|
||||
}
|
||||
|
||||
public refreshToken(token: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { exp, iat, ...payload } = this.jwtService.decode(token) as DecodedToken;
|
||||
const payload = this.jwtService.decode(token) as DecodedToken;
|
||||
|
||||
return this.jwtService.sign(payload);
|
||||
return this.jwtService.sign(omit(payload, ['iat', 'exp']));
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/api/src/dto/credentials.ts
Normal file
11
apps/api/src/dto/credentials.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { IsNotEmpty, IsString } from "class-validator";
|
||||
|
||||
export class Credentials {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
readonly login: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
readonly password: string;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export class Credentials {
|
||||
readonly login: string;
|
||||
readonly password: string;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { Credentials } from './dto/credentials';
|
||||
import { Credentials } from '../dto/credentials';
|
||||
import { LdapService } from './ldap.service';
|
||||
import type { CookieSerializeOptions } from '@fastify/cookie';
|
||||
import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common';
|
||||
@ -26,10 +26,8 @@ export class LdapController {
|
||||
|
||||
@Post('/signin')
|
||||
async login(@Body() credentials: Credentials, @Res() reply: FastifyReply) {
|
||||
const { login, password } = credentials;
|
||||
|
||||
try {
|
||||
const token = await this.ldapService.login(login, password);
|
||||
const token = await this.ldapService.login(credentials);
|
||||
|
||||
return reply.setCookie(env.COOKIE_TOKEN_NAME, token, cookieOptions).status(200).send();
|
||||
} catch {
|
||||
|
||||
@ -4,6 +4,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { env } from 'src/config/env';
|
||||
import type { Credentials } from 'src/dto/credentials';
|
||||
import * as ldap from 'src/utils/ldap';
|
||||
|
||||
@Injectable()
|
||||
@ -13,7 +14,7 @@ export class LdapService {
|
||||
private readonly jwtService: JwtService
|
||||
) {}
|
||||
|
||||
public async login(login: string, password: string) {
|
||||
public async login({ login, password }: Credentials) {
|
||||
const user = await ldap.authenticate(login, password);
|
||||
const { username } = user;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export type TokenPayload = {
|
||||
domain: string;
|
||||
[key: string]: unknown;
|
||||
username: string;
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
/* eslint-disable import/no-duplicates */
|
||||
/* eslint-disable unicorn/prefer-top-level-await */
|
||||
import { AppModule } from './app.module';
|
||||
import { env } from './config/env';
|
||||
import { fastifyCookie } from '@fastify/cookie';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
||||
@ -18,6 +20,8 @@ async function bootstrap() {
|
||||
secret: env.API_SECRET,
|
||||
});
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe({ stopAtFirstError: true }));
|
||||
|
||||
await app.listen(env.API_PORT, '0.0.0.0');
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
import { IsNotEmpty, IsOptional, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
readonly username: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(10)
|
||||
@IsOptional()
|
||||
readonly password: string;
|
||||
|
||||
readonly [key: string]: unknown;
|
||||
}
|
||||
|
||||
@ -3,30 +3,82 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UsersService } from './users.service';
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import { User } from 'src/schemas/user.schema';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { cookieOptions } from 'src/config/cookie';
|
||||
import { env } from 'src/config/env';
|
||||
import { Credentials } from 'src/dto/credentials';
|
||||
|
||||
@Controller()
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@Post('/users/create-user')
|
||||
async create(@Body() createUserDto: CreateUserDto) {
|
||||
return this.usersService.create(createUserDto);
|
||||
private clearCookies(req, reply) {
|
||||
if (req.cookies) {
|
||||
Object.keys(req.cookies).forEach((cookieName) => {
|
||||
reply.clearCookie(cookieName, {
|
||||
path: '/',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/users')
|
||||
@Post('/create')
|
||||
async create(@Body() createUserDto: CreateUserDto, @Res() reply: FastifyReply) {
|
||||
try {
|
||||
const createdUser = await this.usersService.create(createUserDto);
|
||||
|
||||
return reply.send(createdUser);
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
return this.usersService.findAll();
|
||||
}
|
||||
|
||||
@Delete('/users/delete/:username')
|
||||
async delete(@Param('username') username: string) {
|
||||
@Delete('/delete/:login')
|
||||
async delete(@Param('login') username: CreateUserDto['username']) {
|
||||
return this.usersService.delete(username);
|
||||
}
|
||||
|
||||
@Post('/users/check')
|
||||
async check(@Body() user: User) {
|
||||
return this.usersService.check(user);
|
||||
@Post('/signin')
|
||||
async login(@Body() credentials: Credentials, @Res() reply: FastifyReply) {
|
||||
try {
|
||||
const token = await this.usersService.login(credentials);
|
||||
|
||||
return reply.setCookie(env.COOKIE_TOKEN_NAME, token, cookieOptions).status(200).send();
|
||||
} catch {
|
||||
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/logout')
|
||||
async logout(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
|
||||
this.clearCookies(req, reply);
|
||||
|
||||
return reply.status(302).redirect('/login');
|
||||
}
|
||||
|
||||
@Get('/get-user')
|
||||
async getUser(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
|
||||
const token = req.cookies[env.COOKIE_TOKEN_NAME];
|
||||
|
||||
const user = await this.usersService.getUser(token);
|
||||
|
||||
return reply.send(user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import type { CreateUserDto } from './dto/create-user.dto';
|
||||
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { Model } from 'mongoose';
|
||||
import { omit } from 'radash';
|
||||
import type { Credentials } from 'src/dto/credentials';
|
||||
import type { TokenPayload } from 'src/ldap/types/jwt';
|
||||
import { User } from 'src/schemas/user.schema';
|
||||
import { generatePassword } from 'src/utils/password';
|
||||
|
||||
@ -32,20 +35,29 @@ export class UsersService {
|
||||
return this.userModel.findOneAndDelete({ username }).exec();
|
||||
}
|
||||
|
||||
public async check({ username, password }: User) {
|
||||
public async login({ login, password }: Credentials) {
|
||||
try {
|
||||
const user = await this.userModel.findOne({ username });
|
||||
const user = await this.userModel.findOne({ username: login });
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
if (!passwordMatch) {
|
||||
throw new UnauthorizedException('Invalid login credentials');
|
||||
}
|
||||
|
||||
return passwordMatch;
|
||||
const payload: TokenPayload = {
|
||||
username: login,
|
||||
...omit(user.toJSON(), ['password']),
|
||||
};
|
||||
|
||||
return this.jwtService.sign(payload);
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getUser(token: string) {
|
||||
return this.jwtService.decode(token);
|
||||
}
|
||||
}
|
||||
|
||||
34
yarn.lock
34
yarn.lock
@ -1578,6 +1578,11 @@
|
||||
dependencies:
|
||||
"@types/superagent" "*"
|
||||
|
||||
"@types/validator@^13.7.10":
|
||||
version "13.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.5.tgz#1911964fd5556b08d3479d1ded977c06f89a44a7"
|
||||
integrity sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==
|
||||
|
||||
"@types/webidl-conversions@*":
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.2.tgz#d703e2bf61d8b77a7669adcd8fdf98108155d594"
|
||||
@ -2668,6 +2673,20 @@ cjs-module-lexer@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
|
||||
integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
|
||||
|
||||
class-transformer@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
|
||||
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
|
||||
|
||||
class-validator@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.0.tgz#40ed0ecf3c83b2a8a6a320f4edb607be0f0df159"
|
||||
integrity sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==
|
||||
dependencies:
|
||||
"@types/validator" "^13.7.10"
|
||||
libphonenumber-js "^1.10.14"
|
||||
validator "^13.7.0"
|
||||
|
||||
clean-regexp@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
|
||||
@ -5774,6 +5793,11 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
libphonenumber-js@^1.10.14:
|
||||
version "1.10.49"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.49.tgz#c871661c62452348d228c96425f75ddf7e10f05a"
|
||||
integrity sha512-gvLtyC3tIuqfPzjvYLH9BmVdqzGDiSi4VjtWe2fAgSdBf0yt8yPmbNnRIHNbR5IdtVkm0ayGuzwQKTWmU0hdjQ==
|
||||
|
||||
light-my-request@5.11.0, light-my-request@^5.9.1:
|
||||
version "5.11.0"
|
||||
resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.11.0.tgz#90e446c303b3a47b59df38406d5f5c2cf224f2d1"
|
||||
@ -6829,6 +6853,11 @@ quick-format-unescaped@^4.0.3:
|
||||
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
|
||||
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
|
||||
|
||||
radash@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/radash/-/radash-11.0.0.tgz#925698e94b554336fc159c253ac33a9a49881835"
|
||||
integrity sha512-CRWxTFTDff0IELGJ/zz58yY4BDgyI14qSM5OLNKbCItJrff7m7dXbVF0kWYVCXQtPb3SXIVhXvAImH6eT7VLSg==
|
||||
|
||||
rambda@^7.4.0:
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe"
|
||||
@ -8231,6 +8260,11 @@ validate-npm-package-license@^3.0.1:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
validator@^13.7.0:
|
||||
version "13.11.0"
|
||||
resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b"
|
||||
integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==
|
||||
|
||||
value-or-promise@^1.0.11, value-or-promise@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user