Compare commits
5 Commits
7b7f2ac8e7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 13744eaaaa | |||
| 494d05c755 | |||
| 14823ed769 | |||
| d3e9dc61a5 | |||
| 2e9e438baa |
12
backend/package-lock.json
generated
12
backend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"@nestjs/config": "^4.0.3",
|
"@nestjs/config": "^4.0.3",
|
||||||
"@nestjs/core": "^11.1.17",
|
"@nestjs/core": "^11.1.17",
|
||||||
"@nestjs/platform-express": "^11.1.17",
|
"@nestjs/platform-express": "^11.1.17",
|
||||||
|
"@nestjs/throttler": "^6.5.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.15.1",
|
"class-validator": "^0.15.1",
|
||||||
@@ -2141,6 +2142,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/throttler": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||||
|
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||||
|
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/typeorm": {
|
"node_modules/@nestjs/typeorm": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@nestjs/config": "^4.0.3",
|
"@nestjs/config": "^4.0.3",
|
||||||
"@nestjs/core": "^11.1.17",
|
"@nestjs/core": "^11.1.17",
|
||||||
"@nestjs/platform-express": "^11.1.17",
|
"@nestjs/platform-express": "^11.1.17",
|
||||||
|
"@nestjs/throttler": "^6.5.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.15.1",
|
"class-validator": "^0.15.1",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||||
import { getDatabaseConfig } from './config/database.config';
|
import { getDatabaseConfig } from './config/database.config';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
@@ -11,6 +13,7 @@ import { ListModule } from './list/list.module';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
ThrottlerModule.forRoot([{ ttl: 60000, limit: 60 }]),
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: getDatabaseConfig,
|
useFactory: getDatabaseConfig,
|
||||||
@@ -21,5 +24,8 @@ import { ListModule } from './list/list.module';
|
|||||||
WorkModule,
|
WorkModule,
|
||||||
ListModule,
|
ListModule,
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_GUARD, useClass: ThrottlerGuard },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { IS_PUBLIC_KEY } from './public.decorator';
|
||||||
|
|
||||||
interface CacheEntry {
|
interface CacheEntry {
|
||||||
user: any;
|
user: any;
|
||||||
@@ -12,14 +14,24 @@ interface CacheEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TOKEN_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
const TOKEN_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||||
|
const MAX_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
private readonly cache = new Map<string, CacheEntry>();
|
private readonly cache = new Map<string, CacheEntry>();
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService) {}
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass(),
|
||||||
|
]);
|
||||||
|
if (isPublic) return true;
|
||||||
|
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const token = this.extractToken(request);
|
const token = this.extractToken(request);
|
||||||
|
|
||||||
@@ -49,6 +61,10 @@ export class AuthGuard implements CanActivate {
|
|||||||
|
|
||||||
const user = await this.introspect(token);
|
const user = await this.introspect(token);
|
||||||
if (user) {
|
if (user) {
|
||||||
|
if (this.cache.size >= MAX_CACHE_SIZE) {
|
||||||
|
const oldest = this.cache.keys().next().value;
|
||||||
|
if (oldest) this.cache.delete(oldest);
|
||||||
|
}
|
||||||
this.cache.set(token, {
|
this.cache.set(token, {
|
||||||
user,
|
user,
|
||||||
expiresAt: Date.now() + TOKEN_CACHE_TTL_MS,
|
expiresAt: Date.now() + TOKEN_CACHE_TTL_MS,
|
||||||
|
|||||||
4
backend/src/auth/public.decorator.ts
Normal file
4
backend/src/auth/public.decorator.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const IS_PUBLIC_KEY = 'isPublic';
|
||||||
|
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||||
@@ -2,12 +2,12 @@ import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
|||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { AuthGuard } from '../auth/auth.guard';
|
import { AuthGuard } from '../auth/auth.guard';
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
async me(@Req() req: any) {
|
async me(@Req() req: any) {
|
||||||
const user = await this.userService.findOrCreate({
|
const user = await this.userService.findOrCreate({
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { Controller, Get, Query } from '@nestjs/common';
|
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import { Throttle } from '@nestjs/throttler';
|
||||||
|
import { AuthGuard } from '../auth/auth.guard';
|
||||||
|
import { Public } from '../auth/public.decorator';
|
||||||
import { WorkService } from './work.service';
|
import { WorkService } from './work.service';
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
@Controller('api/works')
|
@Controller('api/works')
|
||||||
export class WorkController {
|
export class WorkController {
|
||||||
constructor(private readonly workService: WorkService) {}
|
constructor(private readonly workService: WorkService) {}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Throttle([{ ttl: 60000, limit: 20 }])
|
||||||
@Get('search')
|
@Get('search')
|
||||||
async search(
|
async search(
|
||||||
@Query('q') query: string,
|
@Query('q') query: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user