@biggora/claude-plugins 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/registry/registry.json +15 -0
- package/specs/coding.md +11 -0
- package/src/commands/skills/add.js +115 -31
- package/src/commands/skills/list.js +25 -52
- package/src/commands/skills/remove.js +45 -27
- package/src/commands/skills/resolve.js +104 -0
- package/src/commands/skills/update.js +58 -74
- package/src/config.js +5 -0
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: terminus
|
|
3
|
+
description: Health checks and readiness/liveness probes with @nestjs/terminus
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Health Checks (Terminus)
|
|
7
|
+
|
|
8
|
+
Readiness/liveness probes for Kubernetes and orchestration.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @nestjs/terminus
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Basic Setup
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
@Module({
|
|
20
|
+
imports: [TerminusModule],
|
|
21
|
+
})
|
|
22
|
+
export class HealthModule {}
|
|
23
|
+
|
|
24
|
+
@Controller('health')
|
|
25
|
+
export class HealthController {
|
|
26
|
+
constructor(
|
|
27
|
+
private health: HealthCheckService,
|
|
28
|
+
private http: HttpHealthIndicator,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
@Get()
|
|
32
|
+
@HealthCheck()
|
|
33
|
+
check() {
|
|
34
|
+
return this.health.check([
|
|
35
|
+
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Health Indicators
|
|
42
|
+
|
|
43
|
+
| Indicator | Purpose |
|
|
44
|
+
|-----------|---------|
|
|
45
|
+
| `HttpHealthIndicator` | HTTP ping |
|
|
46
|
+
| `TypeOrmHealthIndicator` | Database (SELECT 1) |
|
|
47
|
+
| `MongooseHealthIndicator` | MongoDB |
|
|
48
|
+
| `PrismaHealthIndicator` | Prisma |
|
|
49
|
+
| `MemoryHealthIndicator` | Heap/RSS limits |
|
|
50
|
+
| `DiskHealthIndicator` | Disk space |
|
|
51
|
+
| `MicroserviceHealthIndicator` | Microservice |
|
|
52
|
+
| `GRPCHealthIndicator` | gRPC |
|
|
53
|
+
|
|
54
|
+
## TypeORM
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
constructor(
|
|
58
|
+
private health: HealthCheckService,
|
|
59
|
+
private db: TypeOrmHealthIndicator,
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
@Get()
|
|
63
|
+
@HealthCheck()
|
|
64
|
+
check() {
|
|
65
|
+
return this.health.check([
|
|
66
|
+
() => this.db.pingCheck('database'),
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Multiple databases: inject connections and pass to `pingCheck('name', { connection })`.
|
|
72
|
+
|
|
73
|
+
## Memory
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
return this.health.check([
|
|
77
|
+
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), // 150MB
|
|
78
|
+
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
|
|
79
|
+
]);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Disk
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
return this.health.check([
|
|
86
|
+
() => this.disk.checkStorage('storage', { path: '/', thresholdPercent: 0.5 }),
|
|
87
|
+
() => this.disk.checkStorage('storage', { path: '/', threshold: 250 * 1024 * 1024 * 1024 }),
|
|
88
|
+
]);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## HTTP Response Check
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
() => this.http.responseCheck(
|
|
95
|
+
'my-service',
|
|
96
|
+
'https://my-service.com',
|
|
97
|
+
(res) => res.status === 204,
|
|
98
|
+
),
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Custom Indicator
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
@Injectable()
|
|
105
|
+
export class DogHealthIndicator {
|
|
106
|
+
constructor(private health: HealthIndicatorService) {}
|
|
107
|
+
|
|
108
|
+
async isHealthy(key: string) {
|
|
109
|
+
const indicator = this.health.check(key);
|
|
110
|
+
const isHealthy = /* custom logic */;
|
|
111
|
+
if (!isHealthy) return indicator.down({ reason: '...' });
|
|
112
|
+
return indicator.up();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Response Format
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"status": "ok",
|
|
122
|
+
"info": { "database": { "status": "up" } },
|
|
123
|
+
"error": {},
|
|
124
|
+
"details": { "database": { "status": "up" } }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Status: `'ok' | 'error' | 'shutting_down'`
|
|
129
|
+
|
|
130
|
+
## Graceful Shutdown
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
TerminusModule.forRoot({
|
|
134
|
+
gracefulShutdownTimeoutMs: 1000,
|
|
135
|
+
}),
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Key Points
|
|
139
|
+
|
|
140
|
+
- Enable shutdown hooks for Terminus lifecycle
|
|
141
|
+
- Use `@HealthCheck()` decorator on endpoints
|
|
142
|
+
- `HttpHealthIndicator` requires `@nestjs/axios`
|
|
143
|
+
- Custom indicators extend `HealthIndicatorService`
|
|
144
|
+
|
|
145
|
+
<!--
|
|
146
|
+
Source references:
|
|
147
|
+
- https://docs.nestjs.com/recipes/terminus
|
|
148
|
+
-->
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recipes-typeorm
|
|
3
|
+
description: TypeORM integration and usage in NestJS
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeORM Integration
|
|
7
|
+
|
|
8
|
+
TypeORM is a popular ORM for TypeScript and JavaScript that works with NestJS.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install --save @nestjs/typeorm typeorm mysql2
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
20
|
+
|
|
21
|
+
@Module({
|
|
22
|
+
imports: [
|
|
23
|
+
TypeOrmModule.forRoot({
|
|
24
|
+
type: 'mysql',
|
|
25
|
+
host: 'localhost',
|
|
26
|
+
port: 3306,
|
|
27
|
+
username: 'root',
|
|
28
|
+
password: 'root',
|
|
29
|
+
database: 'test',
|
|
30
|
+
entities: [User],
|
|
31
|
+
synchronize: true,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
export class AppModule {}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Entity Definition
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
|
42
|
+
|
|
43
|
+
@Entity()
|
|
44
|
+
export class User {
|
|
45
|
+
@PrimaryGeneratedColumn()
|
|
46
|
+
id: number;
|
|
47
|
+
|
|
48
|
+
@Column()
|
|
49
|
+
firstName: string;
|
|
50
|
+
|
|
51
|
+
@Column()
|
|
52
|
+
lastName: string;
|
|
53
|
+
|
|
54
|
+
@Column({ default: true })
|
|
55
|
+
isActive: boolean;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Feature Module
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Module } from '@nestjs/common';
|
|
63
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
64
|
+
import { User } from './user.entity';
|
|
65
|
+
import { UsersService } from './users.service';
|
|
66
|
+
import { UsersController } from './users.controller';
|
|
67
|
+
|
|
68
|
+
@Module({
|
|
69
|
+
imports: [TypeOrmModule.forFeature([User])],
|
|
70
|
+
providers: [UsersService],
|
|
71
|
+
controllers: [UsersController],
|
|
72
|
+
})
|
|
73
|
+
export class UsersModule {}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Repository Injection
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Injectable } from '@nestjs/common';
|
|
80
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
81
|
+
import { Repository } from 'typeorm';
|
|
82
|
+
import { User } from './user.entity';
|
|
83
|
+
|
|
84
|
+
@Injectable()
|
|
85
|
+
export class UsersService {
|
|
86
|
+
constructor(
|
|
87
|
+
@InjectRepository(User)
|
|
88
|
+
private usersRepository: Repository<User>,
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
findAll(): Promise<User[]> {
|
|
92
|
+
return this.usersRepository.find();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
findOne(id: number): Promise<User> {
|
|
96
|
+
return this.usersRepository.findOneBy({ id });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async create(user: User): Promise<User> {
|
|
100
|
+
return this.usersRepository.save(user);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async remove(id: number): Promise<void> {
|
|
104
|
+
await this.usersRepository.delete(id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Key Points
|
|
110
|
+
|
|
111
|
+
- Use `TypeOrmModule.forRoot()` for configuration
|
|
112
|
+
- Use `TypeOrmModule.forFeature()` in feature modules
|
|
113
|
+
- Inject repositories with `@InjectRepository()`
|
|
114
|
+
- Define entities with decorators
|
|
115
|
+
- Use repository methods for database operations
|
|
116
|
+
- Configure connection in root module
|
|
117
|
+
|
|
118
|
+
<!--
|
|
119
|
+
Source references:
|
|
120
|
+
- https://docs.nestjs.com/techniques/database
|
|
121
|
+
- https://docs.nestjs.com/recipes/sql-typeorm
|
|
122
|
+
-->
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authorization
|
|
3
|
+
description: Role-based access control (RBAC) and CASL integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authorization
|
|
7
|
+
|
|
8
|
+
Authorization determines what authenticated users can do. NestJS supports RBAC, claims-based, and policy-based authorization.
|
|
9
|
+
|
|
10
|
+
## Basic RBAC Implementation
|
|
11
|
+
|
|
12
|
+
### 1. Define Roles
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// role.enum.ts
|
|
16
|
+
export enum Role {
|
|
17
|
+
User = 'user',
|
|
18
|
+
Admin = 'admin',
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Create Roles Decorator
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// roles.decorator.ts
|
|
26
|
+
import { SetMetadata } from '@nestjs/common';
|
|
27
|
+
import { Role } from './role.enum';
|
|
28
|
+
|
|
29
|
+
export const ROLES_KEY = 'roles';
|
|
30
|
+
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Create Roles Guard
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// roles.guard.ts
|
|
37
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
38
|
+
import { Reflector } from '@nestjs/core';
|
|
39
|
+
import { Role } from './role.enum';
|
|
40
|
+
import { ROLES_KEY } from './roles.decorator';
|
|
41
|
+
|
|
42
|
+
@Injectable()
|
|
43
|
+
export class RolesGuard implements CanActivate {
|
|
44
|
+
constructor(private reflector: Reflector) {}
|
|
45
|
+
|
|
46
|
+
canActivate(context: ExecutionContext): boolean {
|
|
47
|
+
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
|
48
|
+
context.getHandler(),
|
|
49
|
+
context.getClass(),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
if (!requiredRoles) return true;
|
|
53
|
+
|
|
54
|
+
const { user } = context.switchToHttp().getRequest();
|
|
55
|
+
return requiredRoles.some((role) => user.roles?.includes(role));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 4. Apply to Routes
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
@Post()
|
|
64
|
+
@Roles(Role.Admin)
|
|
65
|
+
@UseGuards(AuthGuard, RolesGuard)
|
|
66
|
+
create(@Body() dto: CreateDto) {
|
|
67
|
+
return this.service.create(dto);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 5. Register Globally
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
@Module({
|
|
75
|
+
providers: [
|
|
76
|
+
{ provide: APP_GUARD, useClass: RolesGuard },
|
|
77
|
+
],
|
|
78
|
+
})
|
|
79
|
+
export class AppModule {}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Claims-Based Authorization
|
|
83
|
+
|
|
84
|
+
Check specific permissions instead of roles:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// permissions.decorator.ts
|
|
88
|
+
export const PERMISSIONS_KEY = 'permissions';
|
|
89
|
+
export const RequirePermissions = (...permissions: Permission[]) =>
|
|
90
|
+
SetMetadata(PERMISSIONS_KEY, permissions);
|
|
91
|
+
|
|
92
|
+
// Usage
|
|
93
|
+
@Post()
|
|
94
|
+
@RequirePermissions(Permission.CREATE_POST)
|
|
95
|
+
create() {}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## CASL Integration
|
|
99
|
+
|
|
100
|
+
For complex authorization rules:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install @casl/ability
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Define Abilities
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// casl-ability.factory.ts
|
|
110
|
+
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
|
|
111
|
+
|
|
112
|
+
export type AppAbility = MongoAbility<[Action, Subjects]>;
|
|
113
|
+
|
|
114
|
+
@Injectable()
|
|
115
|
+
export class CaslAbilityFactory {
|
|
116
|
+
createForUser(user: User) {
|
|
117
|
+
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
|
|
118
|
+
|
|
119
|
+
if (user.isAdmin) {
|
|
120
|
+
can(Action.Manage, 'all');
|
|
121
|
+
} else {
|
|
122
|
+
can(Action.Read, 'all');
|
|
123
|
+
can(Action.Update, Article, { authorId: user.id });
|
|
124
|
+
cannot(Action.Delete, Article, { isPublished: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return build();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Use in Services
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
@Injectable()
|
|
136
|
+
export class ArticlesService {
|
|
137
|
+
constructor(private caslAbilityFactory: CaslAbilityFactory) {}
|
|
138
|
+
|
|
139
|
+
async update(user: User, articleId: string, dto: UpdateDto) {
|
|
140
|
+
const article = await this.findOne(articleId);
|
|
141
|
+
const ability = this.caslAbilityFactory.createForUser(user);
|
|
142
|
+
|
|
143
|
+
if (!ability.can(Action.Update, article)) {
|
|
144
|
+
throw new ForbiddenException('Cannot update this article');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return this.articlesRepository.update(articleId, dto);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Policies Guard
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
@Injectable()
|
|
156
|
+
export class PoliciesGuard implements CanActivate {
|
|
157
|
+
constructor(
|
|
158
|
+
private reflector: Reflector,
|
|
159
|
+
private caslAbilityFactory: CaslAbilityFactory,
|
|
160
|
+
) {}
|
|
161
|
+
|
|
162
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
163
|
+
const policyHandlers = this.reflector.get<PolicyHandler[]>(
|
|
164
|
+
CHECK_POLICIES_KEY,
|
|
165
|
+
context.getHandler(),
|
|
166
|
+
) || [];
|
|
167
|
+
|
|
168
|
+
const { user } = context.switchToHttp().getRequest();
|
|
169
|
+
const ability = this.caslAbilityFactory.createForUser(user);
|
|
170
|
+
|
|
171
|
+
return policyHandlers.every((handler) =>
|
|
172
|
+
typeof handler === 'function'
|
|
173
|
+
? handler(ability)
|
|
174
|
+
: handler.handle(ability),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Usage
|
|
180
|
+
@Get()
|
|
181
|
+
@UseGuards(PoliciesGuard)
|
|
182
|
+
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Article))
|
|
183
|
+
findAll() {}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Key Points
|
|
187
|
+
|
|
188
|
+
- Authorization is independent from authentication
|
|
189
|
+
- Use `Reflector` to access route metadata
|
|
190
|
+
- CASL provides fine-grained, attribute-based control
|
|
191
|
+
- `user.roles` should be attached by authentication guard
|
|
192
|
+
|
|
193
|
+
<!--
|
|
194
|
+
Source references:
|
|
195
|
+
- https://docs.nestjs.com/security/authorization
|
|
196
|
+
-->
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cors-helmet-rate-limiting
|
|
3
|
+
description: CORS, security headers, and rate limiting protection
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Security Middleware
|
|
7
|
+
|
|
8
|
+
Essential security measures for NestJS applications.
|
|
9
|
+
|
|
10
|
+
## CORS (Cross-Origin Resource Sharing)
|
|
11
|
+
|
|
12
|
+
Enable CORS to allow cross-origin requests:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// Simple enable
|
|
16
|
+
const app = await NestFactory.create(AppModule, { cors: true });
|
|
17
|
+
|
|
18
|
+
// Or with configuration
|
|
19
|
+
const app = await NestFactory.create(AppModule);
|
|
20
|
+
app.enableCors({
|
|
21
|
+
origin: ['https://example.com', 'https://app.example.com'],
|
|
22
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
23
|
+
credentials: true,
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Dynamic origin:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
app.enableCors({
|
|
31
|
+
origin: (origin, callback) => {
|
|
32
|
+
const whitelist = ['https://example.com'];
|
|
33
|
+
if (!origin || whitelist.includes(origin)) {
|
|
34
|
+
callback(null, true);
|
|
35
|
+
} else {
|
|
36
|
+
callback(new Error('Not allowed by CORS'));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Helmet (Security Headers)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install helmet
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import helmet from 'helmet';
|
|
50
|
+
|
|
51
|
+
async function bootstrap() {
|
|
52
|
+
const app = await NestFactory.create(AppModule);
|
|
53
|
+
app.use(helmet());
|
|
54
|
+
await app.listen(3000);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Rate Limiting
|
|
59
|
+
|
|
60
|
+
Protect against brute-force attacks:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install @nestjs/throttler
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Basic Setup
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Module } from '@nestjs/common';
|
|
70
|
+
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
|
71
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
72
|
+
|
|
73
|
+
@Module({
|
|
74
|
+
imports: [
|
|
75
|
+
ThrottlerModule.forRoot({
|
|
76
|
+
throttlers: [
|
|
77
|
+
{ ttl: 60000, limit: 10 }, // 10 requests per minute
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
providers: [
|
|
82
|
+
{ provide: APP_GUARD, useClass: ThrottlerGuard },
|
|
83
|
+
],
|
|
84
|
+
})
|
|
85
|
+
export class AppModule {}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Multiple Limits
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
ThrottlerModule.forRoot({
|
|
92
|
+
throttlers: [
|
|
93
|
+
{ name: 'short', ttl: 1000, limit: 3 }, // 3 per second
|
|
94
|
+
{ name: 'medium', ttl: 10000, limit: 20 }, // 20 per 10 seconds
|
|
95
|
+
{ name: 'long', ttl: 60000, limit: 100 }, // 100 per minute
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Skip Routes
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { SkipThrottle } from '@nestjs/throttler';
|
|
104
|
+
|
|
105
|
+
@SkipThrottle()
|
|
106
|
+
@Controller('health')
|
|
107
|
+
export class HealthController {}
|
|
108
|
+
|
|
109
|
+
// Or skip specific throttlers
|
|
110
|
+
@SkipThrottle({ short: true })
|
|
111
|
+
@Get()
|
|
112
|
+
findAll() {}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Override Limits
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { Throttle } from '@nestjs/throttler';
|
|
119
|
+
|
|
120
|
+
@Throttle({ default: { limit: 3, ttl: 60000 } })
|
|
121
|
+
@Get()
|
|
122
|
+
findAll() {}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Behind Proxy
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// main.ts
|
|
129
|
+
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
130
|
+
app.set('trust proxy', 'loopback');
|
|
131
|
+
|
|
132
|
+
// Custom tracker for proxy
|
|
133
|
+
@Injectable()
|
|
134
|
+
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
|
135
|
+
protected async getTracker(req: Record<string, any>): Promise<string> {
|
|
136
|
+
return req.ips.length ? req.ips[0] : req.ip;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Redis Storage
|
|
142
|
+
|
|
143
|
+
For distributed systems:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install @nest-lab/throttler-storage-redis
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';
|
|
151
|
+
|
|
152
|
+
ThrottlerModule.forRoot({
|
|
153
|
+
throttlers: [{ ttl: 60000, limit: 10 }],
|
|
154
|
+
storage: new ThrottlerStorageRedisService('redis://localhost:6379'),
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### WebSocket Rate Limiting
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
@Injectable()
|
|
162
|
+
export class WsThrottlerGuard extends ThrottlerGuard {
|
|
163
|
+
async handleRequest(requestProps: ThrottlerRequest): Promise<boolean> {
|
|
164
|
+
const { context, limit, ttl } = requestProps;
|
|
165
|
+
const client = context.switchToWs().getClient();
|
|
166
|
+
const tracker = client._socket.remoteAddress;
|
|
167
|
+
// Custom logic
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### GraphQL Rate Limiting
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
@Injectable()
|
|
177
|
+
export class GqlThrottlerGuard extends ThrottlerGuard {
|
|
178
|
+
getRequestResponse(context: ExecutionContext) {
|
|
179
|
+
const gqlCtx = GqlExecutionContext.create(context);
|
|
180
|
+
const ctx = gqlCtx.getContext();
|
|
181
|
+
return { req: ctx.req, res: ctx.res };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Time Helpers
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { seconds, minutes, hours } from '@nestjs/throttler';
|
|
190
|
+
|
|
191
|
+
ThrottlerModule.forRoot({
|
|
192
|
+
throttlers: [
|
|
193
|
+
{ ttl: seconds(30), limit: 10 },
|
|
194
|
+
{ ttl: minutes(5), limit: 100 },
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
<!--
|
|
200
|
+
Source references:
|
|
201
|
+
- https://docs.nestjs.com/security/cors
|
|
202
|
+
- https://docs.nestjs.com/security/helmet
|
|
203
|
+
- https://docs.nestjs.com/security/rate-limiting
|
|
204
|
+
-->
|