auth in backend and frontend

This commit is contained in:
echo committed 2025-03-24 02:32:57 +01:00
1 parent 39d2698f28
commit 9857acd4c1
65 files changed
+1632 -3923

No files matched your search

+17 -23
View File
@@ -1,36 +1,30 @@
{
"name": "@mono/auth-api",
"version": "0.1.0",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"type": "commonjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --clean --tsconfig tsconfig.json",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --clean --tsconfig tsconfig.json",
"clean": "rm -rf dist",
"lint": "eslint src/",
"check-types": "tsc --noEmit"
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint .",
"clean": "rm -rf dist"
},
"dependencies": {
"@mono/auth-core": "0.1.0",
"@nestjs/common": "^10.3.3",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"bcryptjs": "^2.4.3",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0"
"@mono/auth-core": "file:../auth-core",
"@mono/database": "file:../database",
"bcryptjs": "^2.4.3"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"tsup": "^8.0.2",
"typescript": "^5.3.3"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0"
"@types/bcryptjs": "^2.4.6"
}
}
+67
View File
@@ -0,0 +1,67 @@
import {
Body,
Controller,
Post,
UseGuards,
Request,
UnauthorizedException,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard, JwtAuthGuard } from './guards';
import { LoginDto, RegisterDto, RefreshTokenDto } from './dto/auth.dto';
import { User } from '@mono/auth-core';
interface RequestWithUser {
user: User;
}
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('register')
async register(@Body() registerDto: RegisterDto) {
const user = await this.authService.createUser(
registerDto.email,
registerDto.password,
registerDto.name,
);
const tokens = await this.authService.generateTokens(user);
return {
user,
...tokens,
};
}
@UseGuards(LocalAuthGuard)
@Post('login')
@HttpCode(HttpStatus.OK)
async login(@Request() req: RequestWithUser, @Body() _loginDto: LoginDto) {
const tokens = await this.authService.generateTokens(req.user);
return {
user: req.user,
...tokens,
};
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
async refresh(@Body() { refreshToken }: RefreshTokenDto) {
try {
const tokens = await this.authService.refreshTokens(refreshToken);
return tokens;
} catch (error) {
throw new UnauthorizedException('Invalid refresh token');
}
}
@UseGuards(JwtAuthGuard)
@Post('logout')
@HttpCode(HttpStatus.OK)
async logout(@Body() { refreshToken }: RefreshTokenDto) {
await this.authService.logout(refreshToken);
return { message: 'Logged out successfully' };
}
}
+2
View File
@@ -4,6 +4,7 @@ import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';
import { LocalStrategy } from './strategies/local.strategy';
import { AuthController } from './auth.controller';
export interface AuthModuleOptions {
jwtSecret: string;
@@ -22,6 +23,7 @@ export class AuthModule {
signOptions: { expiresIn: options.jwtExpiresIn || '1h' },
}),
],
controllers: [AuthController],
providers: [
AuthService,
LocalStrategy,
+119 -3
View File
@@ -1,12 +1,54 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { User, AuthTokens } from '@mono/auth-core';
import { prisma } from '@mono/database';
import * as bcrypt from 'bcryptjs';
type UserWithPassword = User & { password: string };
interface SessionWithUser {
id: string;
userId: string;
token: string;
expiresAt: Date;
user: {
id: string;
email: string;
name: string | null;
password: string;
};
}
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async validateUser(email: string, password: string): Promise<User> {
const user = await prisma.user.findUnique({
where: { email },
select: {
id: true,
email: true,
password: true,
name: true,
}
});
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const isPasswordValid = await this.validatePassword(password, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials');
}
const { password: _, ...result } = user;
return {
...result,
name: result.name || undefined,
};
}
async validatePassword(password: string, hashedPassword: string): Promise<boolean> {
return bcrypt.compare(password, hashedPassword);
}
@@ -15,16 +57,90 @@ export class AuthService {
return bcrypt.hash(password, 10);
}
async createUser(email: string, password: string, name?: string): Promise<User> {
const existingUser = await prisma.user.findUnique({ where: { email } });
if (existingUser) {
throw new UnauthorizedException('Email already exists');
}
const hashedPassword = await this.hashPassword(password);
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
name: name || null,
},
select: {
id: true,
email: true,
name: true,
},
});
return {
...user,
name: user.name || undefined,
};
}
async generateTokens(user: User): Promise<AuthTokens> {
const payload = { sub: user.id, email: user.email };
const accessToken = this.jwtService.sign(payload);
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
// Store refresh token in sessions table
await prisma.session.create({
data: {
userId: user.id,
token: refreshToken,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
},
});
return {
accessToken: this.jwtService.sign(payload),
refreshToken: this.jwtService.sign(payload, { expiresIn: '7d' }),
accessToken,
refreshToken,
};
}
async verifyToken(token: string): Promise<any> {
return this.jwtService.verify(token);
}
async refreshTokens(refreshToken: string): Promise<AuthTokens> {
const session = await prisma.session.findUnique({
where: { token: refreshToken },
include: {
user: {
select: {
id: true,
email: true,
name: true,
password: true,
}
}
}
});
if (!session || session.expiresAt < new Date()) {
throw new UnauthorizedException('Invalid refresh token');
}
// Delete the old session
await prisma.session.delete({ where: { id: session.id } });
// Convert null to undefined for name and remove password
const { password: _, ...userWithoutPassword } = session.user;
const user: User = {
...userWithoutPassword,
name: userWithoutPassword.name || undefined,
};
// Generate new tokens
return this.generateTokens(user);
}
async logout(refreshToken: string): Promise<void> {
await prisma.session.delete({ where: { token: refreshToken } });
}
}
+28
View File
@@ -0,0 +1,28 @@
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator';
export class LoginDto {
@IsEmail()
email!: string;
@IsString()
@MinLength(6)
password!: string;
}
export class RegisterDto {
@IsEmail()
email!: string;
@IsString()
@MinLength(6)
password!: string;
@IsString()
@IsOptional()
name?: string;
}
export class RefreshTokenDto {
@IsString()
refreshToken!: string;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './jwt-auth.guard';
export * from './local-auth.guard';
@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
@@ -1,10 +1,11 @@
import { Injectable, Inject } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { prisma } from '@mono/database';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(@Inject('JWT_SECRET') jwtSecret: string) {
constructor(@Inject('JWT_SECRET') private jwtSecret: string) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
@@ -13,6 +14,14 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}
async validate(payload: any) {
return { id: payload.sub, email: payload.email };
const user = await prisma.user.findUnique({
where: { id: payload.sub },
select: {
id: true,
email: true,
name: true,
},
});
return user;
}
}
@@ -1,15 +1,21 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from '../auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor() {
super({ usernameField: 'email' });
constructor(private authService: AuthService) {
super({
usernameField: 'email',
});
}
async validate(email: string, password: string): Promise<any> {
// This method should be overridden in the implementing application
throw new UnauthorizedException('Strategy not implemented');
async validate(email: string, password: string) {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
+14 -5
View File
@@ -3,18 +3,27 @@
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": true,
"declaration": true,
"declarationMap": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"composite": true,
"incremental": true,
"module": "commonjs",
"target": "es2020",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": "../..",
"paths": {
"@mono/auth-core": ["packages/auth-core/src"],
"@mono/auth-core/*": ["packages/auth-core/src/*"]
"@mono/database": ["packages/database/src"]
}
},
"include": ["src"],
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"],
"references": [
{ "path": "../auth-core" }
{ "path": "../auth-core" },
{ "path": "../database" }
]
}
+1 -1
View File
@@ -1 +1 @@
{"fileNames":["../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es5.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2021.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2022.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2023.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2024.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.esnext.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.dom.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/libLine truncated
{"fileNames":["../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es5.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2021.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2022.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2023.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2024.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.esnext.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.dom.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../../.nvm/versions/node/v22.13.1/lib/node_modules/typescript/lib/libLine truncated