This commit is contained in:
parent
b4eb949a3e
commit
418a9e2c7a
17 changed files with 105 additions and 78 deletions
2
build.ts
2
build.ts
|
@ -1,4 +1,4 @@
|
|||
// @ts-ignore
|
||||
// @ts-expect-error bun build process
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/app.ts'],
|
||||
format: 'esm',
|
||||
|
|
|
@ -6,7 +6,7 @@ import tseslint from 'typescript-eslint';
|
|||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
ignores: ['eslint.config.mjs', 'dist/', 'node_modules/'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
|
@ -29,7 +29,7 @@ export default tseslint.config(
|
|||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ApiTags } from '@nestjs/swagger';
|
|||
@UseInterceptors(CacheInterceptor)
|
||||
export class AppController {
|
||||
@Get('version')
|
||||
async getVersion(): Promise<VersionEntity> {
|
||||
getVersion(): VersionEntity {
|
||||
return {
|
||||
version: process.env.npm_package_version,
|
||||
};
|
||||
|
|
|
@ -24,10 +24,14 @@ import KeyvRedis from '@keyv/redis';
|
|||
ScheduleModule.forRoot(),
|
||||
CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
useFactory: async (): Promise<any> => {
|
||||
const redisUrl: string | undefined = process.env.REDIS_URL;
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
useFactory: async () => {
|
||||
return {
|
||||
stores: [redisUrl ? new KeyvRedis(redisUrl) : new CacheableMemory()],
|
||||
stores: [
|
||||
process.env.REDIS_URL
|
||||
? new KeyvRedis(process.env.REDIS_URL)
|
||||
: new CacheableMemory(),
|
||||
],
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
|
31
src/app.ts
31
src/app.ts
|
@ -13,6 +13,8 @@ import { NestFactory } from '@nestjs/core';
|
|||
import { AppModule } from './app.module';
|
||||
import { ConsoleLogger, Logger } from '@nestjs/common';
|
||||
import * as process from 'process';
|
||||
import { FastifyReply } from 'fastify/types/reply';
|
||||
import { FastifyRequest } from 'fastify/types/request';
|
||||
|
||||
const logger: Logger = new Logger('App');
|
||||
|
||||
|
@ -50,17 +52,20 @@ async function loadServer(server: NestFastifyApplication) {
|
|||
});
|
||||
|
||||
// Middlewares
|
||||
server.use(new LoggerMiddleware().use);
|
||||
await server.register(fastifyMultipart as any);
|
||||
await server.register(
|
||||
fastifyHelmet as any,
|
||||
{
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false,
|
||||
crossOriginOpenerPolicy: false,
|
||||
crossOriginResourcePolicy: false,
|
||||
} as any,
|
||||
);
|
||||
const loggerMiddleware = new LoggerMiddleware();
|
||||
const loggerMiddlewareUse = (
|
||||
req: FastifyRequest['raw'],
|
||||
res: FastifyReply['raw'],
|
||||
next: () => void,
|
||||
) => loggerMiddleware.use(req, res, next);
|
||||
server.use(loggerMiddlewareUse);
|
||||
await server.register(fastifyMultipart);
|
||||
await server.register(fastifyHelmet, {
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false,
|
||||
crossOriginOpenerPolicy: false,
|
||||
crossOriginResourcePolicy: false,
|
||||
});
|
||||
|
||||
// Swagger
|
||||
const config = new DocumentBuilder()
|
||||
|
@ -88,4 +93,6 @@ async function loadServer(server: NestFastifyApplication) {
|
|||
server.useGlobalPipes(new CustomValidationPipe());
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
bootstrap().catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ export class LoggerMiddleware implements NestMiddleware {
|
|||
|
||||
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
|
||||
const startTime = Date.now();
|
||||
(res as any).on('finish', () => {
|
||||
res.on('finish', () => {
|
||||
const path = req.url;
|
||||
try {
|
||||
const protocol = LoggerMiddleware.getProtocol(req);
|
||||
|
@ -15,7 +15,7 @@ export class LoggerMiddleware implements NestMiddleware {
|
|||
if (method === 'OPTIONS') return;
|
||||
const statusCode = res.statusCode;
|
||||
const duration = Date.now() - startTime;
|
||||
const resSize: any = res.getHeader('Content-Length') || '0';
|
||||
const resSize = res.getHeader('Content-Length').toString() || '0';
|
||||
const intResSize = parseInt(resSize);
|
||||
LoggerMiddleware.logger.log(
|
||||
`${protocol} ${method} ${path} ${statusCode} ${duration}ms ${intResSize}`,
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import { Controller, Delete, NotImplementedException } from '@nestjs/common';
|
||||
import { Controller, Delete, UseGuards } from '@nestjs/common';
|
||||
import { UserEntity } from './models/entities/user.entity';
|
||||
import { User } from './decorators/user.decorator';
|
||||
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||
import { AuthService } from './auth.service';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor() {}
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Delete('logout/all')
|
||||
logoutAll() {
|
||||
throw new NotImplementedException();
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async logoutAll(@User() user: UserEntity) {
|
||||
await this.authService.invalidateTokens(user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,17 @@ import { RegisterService } from './register.service';
|
|||
import { UsersModule } from '../../../modules/users/users.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AuthJwtStrategy } from './strategies/auth-jwt.strategy';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Module({
|
||||
controllers: [AuthController, LoginController, RegisterController],
|
||||
providers: [JwtStrategy, AuthJwtStrategy, LoginService, RegisterService],
|
||||
providers: [
|
||||
JwtStrategy,
|
||||
AuthJwtStrategy,
|
||||
LoginService,
|
||||
RegisterService,
|
||||
AuthService,
|
||||
],
|
||||
imports: [
|
||||
JwtModule.registerAsync({
|
||||
inject: [ConfigService],
|
||||
|
|
23
src/common/modules/auth/auth.service.ts
Normal file
23
src/common/modules/auth/auth.service.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { CipherService } from '../helper/cipher.service';
|
||||
import { PrismaService } from '../helper/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UserEntity } from './models/entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly cipherService: CipherService,
|
||||
) {}
|
||||
|
||||
async invalidateTokens(user: UserEntity): Promise<void> {
|
||||
await this.prismaService.users.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
token_id: this.cipherService.generateRandomBytes(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
12
src/common/modules/auth/decorators/user.decorator.ts
Normal file
12
src/common/modules/auth/decorators/user.decorator.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { UserEntity } from '../models/entities/user.entity';
|
||||
|
||||
export const User = createParamDecorator(
|
||||
(_: unknown, ctx: ExecutionContext): UserEntity => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const request: any = ctx.switchToHttp().getRequest();
|
||||
if (request.user.user) return request.user.user as UserEntity;
|
||||
return request.user as UserEntity;
|
||||
},
|
||||
);
|
|
@ -1,10 +1,7 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpCode,
|
||||
NotImplementedException,
|
||||
Post,
|
||||
Req,
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
@ -16,6 +13,7 @@ import { LoginService } from './login.service';
|
|||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { UsersService } from '../../../modules/users/users.service';
|
||||
import { User } from './decorators/user.decorator';
|
||||
|
||||
@Controller('auth/login')
|
||||
@ApiTags('Auth')
|
||||
|
@ -60,35 +58,15 @@ export class LoginController {
|
|||
@Post('callback')
|
||||
@UseGuards(AuthGuard('auth-jwt'))
|
||||
@ApiBearerAuth()
|
||||
loginCallback(@Req() req: any): Promise<LoginPayload> {
|
||||
// TODO: Fix this line
|
||||
req = req.user;
|
||||
if (req.scope === JwtScope.MAGIC) {
|
||||
// Check for 2FA/Passkey
|
||||
const token: string = this.loginService.generateToken(
|
||||
req.user.id,
|
||||
req.user.tokenId,
|
||||
JwtScope.USAGE,
|
||||
);
|
||||
return new LoginPayload({
|
||||
user: req.user,
|
||||
token,
|
||||
});
|
||||
}
|
||||
loginCallback(@User() user: UserEntity): LoginPayload {
|
||||
const authToken: string = this.loginService.generateToken(
|
||||
req.user.id,
|
||||
req.user.tokenId,
|
||||
user.id,
|
||||
user.tokenId,
|
||||
JwtScope.USAGE,
|
||||
);
|
||||
return new LoginPayload({
|
||||
user: req.user,
|
||||
user,
|
||||
token: authToken,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('passkey')
|
||||
loginPasskey() {
|
||||
// Generate JWT token
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { UserEntity } from './models/entities/user.entity';
|
||||
import { CipherService } from '../helper/cipher.service';
|
||||
import { PrismaService } from '../helper/prisma.service';
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConflictException, Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../helper/prisma.service';
|
||||
import { CipherService } from '../helper/cipher.service';
|
||||
import {
|
||||
EmailVerifications,
|
||||
Passkeys,
|
||||
TwoFactorAuth,
|
||||
Users,
|
||||
} from '@prisma/client';
|
||||
import { UserEntity } from './models/entities/user.entity';
|
||||
import { Users } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class RegisterService {
|
||||
|
|
|
@ -26,7 +26,7 @@ export class AuthJwtStrategy extends PassportStrategy(Strategy, 'auth-jwt') {
|
|||
throw new UnauthorizedException('Invalid token');
|
||||
return {
|
||||
user,
|
||||
scope: payload.scope,
|
||||
scope: JwtScope.USAGE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ export class CipherService {
|
|||
decipher.setAuthTag(tag);
|
||||
try {
|
||||
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
||||
} catch (_) {
|
||||
} catch {
|
||||
throw new Error('Decryption failed');
|
||||
}
|
||||
}
|
||||
|
@ -142,13 +142,13 @@ export class CipherService {
|
|||
// Asymmetric functions
|
||||
generateKeyPair(
|
||||
modulusLength = 4096,
|
||||
privateEncryptionKey = null,
|
||||
privateEncryptionKey: string | null = null,
|
||||
): crypto.KeyPairSyncResult<string, string> {
|
||||
if (!privateEncryptionKey)
|
||||
console.warn(
|
||||
'No private encryption key provided, the private key will not be encrypted',
|
||||
);
|
||||
let options = undefined;
|
||||
let options: { cipher: string; passphrase: string } | undefined = undefined;
|
||||
if (privateEncryptionKey) {
|
||||
options = {
|
||||
cipher: 'aes-256-cbc',
|
||||
|
@ -185,7 +185,7 @@ export class CipherService {
|
|||
decryptAsymmetric(
|
||||
encryptedContent: string,
|
||||
privateKey: string | Buffer,
|
||||
privateEncryptionKey = undefined,
|
||||
privateEncryptionKey: string | undefined = undefined,
|
||||
): string {
|
||||
const buffer = Buffer.from(encryptedContent, 'base64');
|
||||
if (!privateEncryptionKey)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../common/modules/auth/guards/jwt-auth.guard';
|
||||
import { UserEntity } from '../../common/modules/auth/models/entities/user.entity';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { User } from 'src/common/modules/auth/decorators/user.decorator';
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
|
@ -10,7 +11,7 @@ export class UsersController {
|
|||
@Get('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
getMyself(@Req() req: any): UserEntity {
|
||||
return req.user;
|
||||
getMyself(@User() user: UserEntity): UserEntity {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue