@plyaz/auth 1.0.0

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.
Files changed (113) hide show
  1. package/.github/pull_request_template.md +71 -0
  2. package/.github/workflows/deploy.yml +9 -0
  3. package/.github/workflows/publish.yml +14 -0
  4. package/.github/workflows/security.yml +20 -0
  5. package/README.md +89 -0
  6. package/commits.txt +5 -0
  7. package/dist/common/index.cjs +48 -0
  8. package/dist/common/index.cjs.map +1 -0
  9. package/dist/common/index.mjs +43 -0
  10. package/dist/common/index.mjs.map +1 -0
  11. package/dist/index.cjs +20411 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +5139 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/eslint.config.mjs +13 -0
  16. package/index.html +13 -0
  17. package/package.json +141 -0
  18. package/src/adapters/auth-adapter-factory.ts +26 -0
  19. package/src/adapters/auth-adapter.mapper.ts +53 -0
  20. package/src/adapters/base-auth.adapter.ts +119 -0
  21. package/src/adapters/clerk/clerk.adapter.ts +204 -0
  22. package/src/adapters/custom/custom.adapter.ts +119 -0
  23. package/src/adapters/index.ts +4 -0
  24. package/src/adapters/next-auth/authOptions.ts +81 -0
  25. package/src/adapters/next-auth/next-auth.adapter.ts +211 -0
  26. package/src/api/client.ts +37 -0
  27. package/src/audit/audit.logger.ts +52 -0
  28. package/src/client/components/ProtectedRoute.tsx +37 -0
  29. package/src/client/hooks/useAuth.ts +128 -0
  30. package/src/client/hooks/useConnectedAccounts.ts +108 -0
  31. package/src/client/hooks/usePermissions.ts +36 -0
  32. package/src/client/hooks/useRBAC.ts +36 -0
  33. package/src/client/hooks/useSession.ts +18 -0
  34. package/src/client/providers/AuthProvider.tsx +104 -0
  35. package/src/client/store/auth.store.ts +306 -0
  36. package/src/client/utils/storage.ts +70 -0
  37. package/src/common/constants/oauth-providers.ts +49 -0
  38. package/src/common/errors/auth.errors.ts +64 -0
  39. package/src/common/errors/specific-auth-errors.ts +201 -0
  40. package/src/common/index.ts +19 -0
  41. package/src/common/regex/index.ts +27 -0
  42. package/src/common/types/auth.types.ts +641 -0
  43. package/src/common/types/index.ts +297 -0
  44. package/src/common/utils/index.ts +84 -0
  45. package/src/core/blacklist/token.blacklist.ts +60 -0
  46. package/src/core/index.ts +2 -0
  47. package/src/core/jwt/jwt.manager.ts +131 -0
  48. package/src/core/session/session.manager.ts +56 -0
  49. package/src/db/repositories/connected-account.repository.ts +415 -0
  50. package/src/db/repositories/role.repository.ts +519 -0
  51. package/src/db/repositories/session.repository.ts +308 -0
  52. package/src/db/repositories/user.repository.ts +320 -0
  53. package/src/flows/index.ts +2 -0
  54. package/src/flows/sign-in.flow.ts +106 -0
  55. package/src/flows/sign-up.flow.ts +121 -0
  56. package/src/index.ts +54 -0
  57. package/src/libs/clerk.helper.ts +36 -0
  58. package/src/libs/supabase.helper.ts +255 -0
  59. package/src/libs/supabaseClient.ts +6 -0
  60. package/src/providers/base/auth-provider.interface.ts +42 -0
  61. package/src/providers/base/index.ts +1 -0
  62. package/src/providers/index.ts +2 -0
  63. package/src/providers/oauth/facebook.provider.ts +97 -0
  64. package/src/providers/oauth/github.provider.ts +148 -0
  65. package/src/providers/oauth/google.provider.ts +126 -0
  66. package/src/providers/oauth/index.ts +3 -0
  67. package/src/rbac/dynamic-roles.ts +552 -0
  68. package/src/rbac/index.ts +4 -0
  69. package/src/rbac/permission-checker.ts +464 -0
  70. package/src/rbac/role-hierarchy.ts +545 -0
  71. package/src/rbac/role.manager.ts +75 -0
  72. package/src/security/csrf/csrf.protection.ts +37 -0
  73. package/src/security/index.ts +3 -0
  74. package/src/security/rate-limiting/auth/auth.controller.ts +12 -0
  75. package/src/security/rate-limiting/auth/rate-limiting.interface.ts +67 -0
  76. package/src/security/rate-limiting/auth.module.ts +32 -0
  77. package/src/server/auth.module.ts +158 -0
  78. package/src/server/decorators/auth.decorator.ts +43 -0
  79. package/src/server/decorators/auth.decorators.ts +31 -0
  80. package/src/server/decorators/current-user.decorator.ts +49 -0
  81. package/src/server/decorators/permission.decorator.ts +49 -0
  82. package/src/server/guards/auth.guard.ts +56 -0
  83. package/src/server/guards/custom-throttler.guard.ts +46 -0
  84. package/src/server/guards/permissions.guard.ts +115 -0
  85. package/src/server/guards/roles.guard.ts +31 -0
  86. package/src/server/middleware/auth.middleware.ts +46 -0
  87. package/src/server/middleware/index.ts +2 -0
  88. package/src/server/middleware/middleware.ts +11 -0
  89. package/src/server/middleware/session.middleware.ts +255 -0
  90. package/src/server/services/account.service.ts +269 -0
  91. package/src/server/services/auth.service.ts +79 -0
  92. package/src/server/services/brute-force.service.ts +98 -0
  93. package/src/server/services/index.ts +15 -0
  94. package/src/server/services/rate-limiter.service.ts +60 -0
  95. package/src/server/services/session.service.ts +287 -0
  96. package/src/server/services/token.service.ts +262 -0
  97. package/src/session/cookie-store.ts +255 -0
  98. package/src/session/enhanced-session-manager.ts +406 -0
  99. package/src/session/index.ts +14 -0
  100. package/src/session/memory-store.ts +320 -0
  101. package/src/session/redis-store.ts +443 -0
  102. package/src/strategies/oauth.strategy.ts +128 -0
  103. package/src/strategies/traditional-auth.strategy.ts +116 -0
  104. package/src/tokens/index.ts +4 -0
  105. package/src/tokens/refresh-token-manager.ts +448 -0
  106. package/src/tokens/token-validator.ts +311 -0
  107. package/tsconfig.build.json +28 -0
  108. package/tsconfig.json +38 -0
  109. package/tsup.config.mjs +28 -0
  110. package/vitest.config.mjs +16 -0
  111. package/vitest.setup.d.ts +2 -0
  112. package/vitest.setup.d.ts.map +1 -0
  113. package/vitest.setup.ts +1 -0
@@ -0,0 +1,67 @@
1
+ import { NUMERIX } from "@plyaz/config";
2
+
3
+ export interface RouteRateLimitConfig {
4
+ limit: number; // max requests
5
+ windowMs: number; // time window
6
+ blockMs?: number; // block duration after limit exceeded
7
+ }
8
+ const thirty = 30;
9
+ const fifteen = 15;
10
+ export const TIME = {
11
+ SECOND: 1000,
12
+ MINUTE: 60_000,
13
+ FIVE_MIN: NUMERIX.FIVE * NUMERIX.SIXTY_THOUSAND,
14
+ FIFTEEN_MIN: fifteen * NUMERIX.SIXTY_THOUSAND,
15
+ THIRTY_MIN: thirty * NUMERIX.SIXTY_THOUSAND,
16
+ } as const;
17
+
18
+ export const authRateLimits = {
19
+ signin: {
20
+ limit: 10,
21
+ windowMs: TIME.MINUTE,
22
+ blockMs: TIME.FIFTEEN_MIN,
23
+ },
24
+
25
+ signup: {
26
+ limit: 5,
27
+ windowMs: TIME.MINUTE,
28
+ blockMs: TIME.THIRTY_MIN,
29
+ },
30
+
31
+ refresh: {
32
+ limit: 30,
33
+ windowMs: TIME.MINUTE,
34
+ blockMs: TIME.FIVE_MIN,
35
+ },
36
+
37
+ mfaVerify: {
38
+ limit: 5,
39
+ windowMs: TIME.MINUTE,
40
+ blockMs: TIME.THIRTY_MIN,
41
+ },
42
+
43
+ web3Nonce: {
44
+ limit: 20,
45
+ windowMs: TIME.MINUTE,
46
+ blockMs: TIME.FIVE_MIN,
47
+ },
48
+
49
+ web3Verify: {
50
+ limit: 10,
51
+ windowMs: TIME.MINUTE,
52
+ blockMs: TIME.FIFTEEN_MIN,
53
+ },
54
+ } as const satisfies Record<string, RouteRateLimitConfig>;
55
+
56
+ export const rateLimitConfig: Record<string, RouteRateLimitConfig> = {
57
+ "POST /auth/signin": { limit: 10, windowMs: 60_000, blockMs: fifteen * NUMERIX.SIXTY_THOUSAND },
58
+ "POST /auth/signup": { limit: 5, windowMs: 60_000, blockMs: thirty * NUMERIX.SIXTY_THOUSAND },
59
+ "POST /auth/refresh": { limit: 30, windowMs: 60_000, blockMs: NUMERIX.FIVE * NUMERIX.SIXTY_THOUSAND },
60
+ "POST /auth/mfa/verify": { limit: 5, windowMs: 60_000, blockMs: thirty * NUMERIX.SIXTY_THOUSAND },
61
+ "GET /auth/web3/nonce": { limit: 20, windowMs: 60_000, blockMs: NUMERIX.FIVE * NUMERIX.SIXTY_THOUSAND },
62
+ "POST /auth/web3/verify": {
63
+ limit: 10,
64
+ windowMs: 60_000,
65
+ blockMs: fifteen * NUMERIX.SIXTY_THOUSAND,
66
+ },
67
+ };
@@ -0,0 +1,32 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { ThrottlerModule } from "@nestjs/throttler";
3
+ import { Redis } from "ioredis";
4
+ import { ThrottlerStorageRedisService } from "@nest-lab/throttler-storage-redis";
5
+ import { AuthController } from "./auth/auth.controller";
6
+ import { RateLimiterGuard } from "../../server/guards/custom-throttler.guard";
7
+ import { BruteForceService, RateLimiterService } from "../../server/services";
8
+
9
+
10
+ @Module({
11
+ imports: [
12
+ ThrottlerModule.forRootAsync({
13
+ useFactory: () => ({
14
+ storage: new ThrottlerStorageRedisService(
15
+ new Redis({
16
+ host: "localhost",
17
+ port: 6379,
18
+ })
19
+ ),
20
+ throttlers: [
21
+ {
22
+ ttl: 60000,
23
+ limit: 10,
24
+ },
25
+ ],
26
+ }),
27
+ }),
28
+ ],
29
+ controllers: [AuthController],
30
+ providers: [RateLimiterGuard, RateLimiterService, BruteForceService],
31
+ })
32
+ export class AuthModule {}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview NestJS authentication module for @plyaz/auth
3
+ * @module @plyaz/auth/server/auth-module
4
+ *
5
+ * @description
6
+ * Main NestJS module that provides all authentication and authorization services,
7
+ * guards, decorators, and middleware. Configures dependency injection for the
8
+ * entire auth system including session management, token validation, RBAC,
9
+ * and security features.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { AuthModule } from '@plyaz/auth';
14
+ *
15
+ * @Module({
16
+ * imports: [AuthModule],
17
+ * })
18
+ * export class AppModule {}
19
+ * ```
20
+ */
21
+
22
+ import { Module } from '@nestjs/common';
23
+ import { ConfigModule } from '@nestjs/config';
24
+ import { JwtModule } from '@nestjs/jwt';
25
+
26
+ // Services
27
+ import { AuthService } from './services/auth.service';
28
+ import { SessionService } from './services/session.service';
29
+ import { TokenService } from './services/token.service';
30
+ import { AccountService } from './services/account.service';
31
+
32
+ // Guards
33
+ import { AuthGuard } from './guards/auth.guard';
34
+ import { RolesGuard } from './guards/roles.guard';
35
+ import { PermissionsGuard } from './guards/permissions.guard';
36
+
37
+ // Core Components
38
+ import { SessionManager } from '../core/session/session.manager';
39
+ import { EnhancedSessionManager } from '../session/enhanced-session-manager';
40
+ import { RoleManager } from '../rbac/role.manager';
41
+ import { PermissionChecker } from '../rbac/permission-checker';
42
+
43
+ // Strategies
44
+ import { TraditionalAuthStrategy } from '../strategies/traditional-auth.strategy';
45
+ import { OAuthStrategy } from '../strategies/oauth.strategy';
46
+
47
+ // Token Management
48
+ import { TokenValidator } from '../tokens/token-validator';
49
+ import { RefreshTokenManager } from '../tokens/refresh-token-manager';
50
+ import { TokenBlacklist } from '../core/blacklist/token.blacklist';
51
+
52
+ // Security
53
+
54
+ import { CSRFProtection } from '../security/csrf/csrf.protection';
55
+
56
+ // Repositories
57
+ import { UserRepository } from '../db/repositories/user.repository';
58
+ import { SessionRepository } from '../db/repositories/session.repository';
59
+ import { ConnectedAccountRepository } from '../db/repositories/connected-account.repository';
60
+
61
+ // Session Stores
62
+ import { MemoryStore } from '../session/memory-store';
63
+ import { RateLimiterService } from './services';
64
+
65
+ @Module({
66
+ imports: [
67
+ ConfigModule,
68
+ JwtModule.register({
69
+ secret: globalThis.process.env.JWT_SECRET ?? 'default-secret',
70
+ signOptions: { expiresIn: '15m' },
71
+ }),
72
+ ],
73
+ providers: [
74
+ // Services
75
+ AuthService,
76
+ SessionService,
77
+ TokenService,
78
+ AccountService,
79
+
80
+ // Guards
81
+ AuthGuard,
82
+ RolesGuard,
83
+ PermissionsGuard,
84
+
85
+ // Core Components
86
+ SessionManager,
87
+ EnhancedSessionManager,
88
+ RoleManager,
89
+ PermissionChecker,
90
+
91
+ // Strategies
92
+ TraditionalAuthStrategy,
93
+ OAuthStrategy,
94
+
95
+ // Token Management
96
+ TokenValidator,
97
+ RefreshTokenManager,
98
+ TokenBlacklist,
99
+
100
+ // Security
101
+ RateLimiterService,
102
+ CSRFProtection,
103
+
104
+ // Repositories
105
+ UserRepository,
106
+ SessionRepository,
107
+ ConnectedAccountRepository,
108
+
109
+
110
+ // Session Store
111
+ {
112
+ provide: 'SessionStore',
113
+ useClass: MemoryStore,
114
+ },
115
+
116
+ // Configuration Providers
117
+ {
118
+ provide: 'JWT_CONFIG',
119
+ useValue: {
120
+ privateKey: globalThis.process.env.JWT_PRIVATE_KEY ?? 'default-private-key',
121
+ publicKey: globalThis.process.env.JWT_PUBLIC_KEY ?? 'default-public-key',
122
+ issuer: globalThis.process.env.JWT_ISSUER ?? 'plyaz.com',
123
+ audience: globalThis.process.env.JWT_AUDIENCE ?? 'plyaz-api',
124
+ accessTokenTTL: globalThis.parseInt(globalThis.process.env.ACCESS_TOKEN_TTL ?? '900'), // 15 minutes
125
+ refreshTokenTTL:globalThis.parseInt(globalThis.process.env.REFRESH_TOKEN_TTL ?? '604800'), // 7 days
126
+ },
127
+ },
128
+ ],
129
+ exports: [
130
+ // Services
131
+ AuthService,
132
+ SessionService,
133
+ TokenService,
134
+ AccountService,
135
+
136
+ // Guards
137
+ AuthGuard,
138
+ RolesGuard,
139
+ PermissionsGuard,
140
+
141
+
142
+ SessionManager,
143
+ EnhancedSessionManager,
144
+ RoleManager,
145
+ PermissionChecker,
146
+
147
+ // Token Management
148
+ TokenValidator,
149
+ RefreshTokenManager,
150
+
151
+ // Repositories
152
+ UserRepository,
153
+ SessionRepository,
154
+ ConnectedAccountRepository,
155
+
156
+ ],
157
+ })
158
+ export class AuthModule {}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview @Auth() and @Public() decorators
3
+ * @module @plyaz/auth/server/decorators
4
+ */
5
+
6
+ import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';
7
+ import { AuthGuard } from '../guards/auth.guard';
8
+
9
+ export const IS_PUBLIC_KEY = 'isPublic';
10
+
11
+ /**
12
+ * Decorator to require authentication for endpoint
13
+ * Combines @UseGuards(AuthGuard) for convenience
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * @Get('protected')
18
+ * @Auth()
19
+ * getProtectedData(@CurrentUser() user: User) {
20
+ * return { message: 'This is protected data' };
21
+ * }
22
+ * ```
23
+ */
24
+ export const Auth = (): MethodDecorator & ClassDecorator =>
25
+ applyDecorators(
26
+ UseGuards(AuthGuard),
27
+ );
28
+
29
+ /**
30
+ * Decorator to mark endpoint as public (skip authentication)
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * @Get('health')
35
+ * @Public()
36
+ * getHealth() {
37
+ * return { status: 'ok' };
38
+ * }
39
+ * ```
40
+ */
41
+
42
+ export const Public = (): MethodDecorator & ClassDecorator =>
43
+ SetMetadata(IS_PUBLIC_KEY, true);
@@ -0,0 +1,31 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import { SetMetadata, createParamDecorator } from '@nestjs/common';
3
+
4
+ // Public decorator
5
+ export const Public = (): MethodDecorator & ClassDecorator =>
6
+ SetMetadata('isPublic', true);
7
+
8
+ // Roles decorator
9
+ export const Roles = (...roles: string[]): MethodDecorator & ClassDecorator =>
10
+ SetMetadata('roles', roles);
11
+
12
+ // Permissions decorator
13
+ export const Permissions = (...permissions: string[]): MethodDecorator & ClassDecorator =>
14
+ SetMetadata('permissions', permissions);
15
+ export const CurrentUser = createParamDecorator(
16
+ (data: unknown, ctx: ExecutionContext) => {
17
+ const request = ctx.switchToHttp().getRequest();
18
+ return request.user;
19
+ },
20
+ );
21
+
22
+ export const CurrentSession = createParamDecorator(
23
+ (data: unknown, ctx: ExecutionContext) => {
24
+ const request = ctx.switchToHttp().getRequest();
25
+ return request.session;
26
+ },
27
+ );
28
+
29
+ export const Auth = ():void => {
30
+
31
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview @CurrentUser() parameter decorator
3
+ * @module @plyaz/auth/server/decorators
4
+ */
5
+
6
+ import type { ExecutionContext } from '@nestjs/common';
7
+ import { createParamDecorator } from '@nestjs/common';
8
+ import type { User } from '@/common/types/auth.types';
9
+
10
+ /**
11
+ * Parameter decorator to inject authenticated user into controller methods
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * @Get('profile')
16
+ * @Auth()
17
+ * getProfile(@CurrentUser() user: User) {
18
+ * return { id: user.id, email: user.email };
19
+ * }
20
+ * ```
21
+ */
22
+ export const CurrentUser = createParamDecorator(
23
+ (data: keyof User | undefined, ctx: ExecutionContext): User => {
24
+ const request = ctx.switchToHttp().getRequest();
25
+ const user = request.user;
26
+
27
+ // Return specific property if requested
28
+ return data ? user?.[data] : user;
29
+ },
30
+ );
31
+
32
+ /**
33
+ * Get access token from request
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * @Post('logout')
38
+ * @Auth()
39
+ * async logout(@CurrentToken() token: string) {
40
+ * await this.authService.signOut(token);
41
+ * }
42
+ * ```
43
+ */
44
+ export const CurrentToken = createParamDecorator(
45
+ (data: unknown, ctx: ExecutionContext): string => {
46
+ const request = ctx.switchToHttp().getRequest();
47
+ return request.accessToken;
48
+ },
49
+ );
@@ -0,0 +1,49 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ /**
4
+ * Permission decorator metadata key
5
+ */
6
+ export const PERMISSION_KEY = 'permission';
7
+
8
+ /**
9
+ * Permission decorator for setting required permissions
10
+ * @param resource - Resource name (e.g., 'campaigns', 'users', 'organizations')
11
+ * @param action - Action name (e.g., 'create', 'read', 'update', 'delete')
12
+ * @param conditions - Optional conditions for the permission
13
+ * @returns Method & Class decorator
14
+ */
15
+ export const Permission = (
16
+ resource: string,
17
+ action: string,
18
+ conditions?: Record<string, string>
19
+ ): MethodDecorator & ClassDecorator => {
20
+ const permissionData = conditions
21
+ ? { resource, action, conditions }
22
+ : [resource, action];
23
+
24
+ return SetMetadata(PERMISSION_KEY, permissionData);
25
+ };
26
+
27
+ /**
28
+ * Permissions decorator for setting multiple required permissions
29
+ * User must have ALL specified permissions
30
+ * @param permissions - Array of permission objects
31
+ * @returns Method & Class decorator
32
+ */
33
+ export const Permissions = (
34
+ permissions: Array<{ resource: string; action: string; conditions?: Record<string, string> }>
35
+ ): MethodDecorator & ClassDecorator => {
36
+ return SetMetadata('permissions', permissions);
37
+ };
38
+
39
+ /**
40
+ * AnyPermission decorator for setting alternative permissions
41
+ * User must have ANY of the specified permissions
42
+ * @param permissions - Array of permission objects
43
+ * @returns Method & Class decorator
44
+ */
45
+ export const AnyPermission = (
46
+ permissions: Array<{ resource: string; action: string; conditions?: Record<string, string> }>
47
+ ): MethodDecorator & ClassDecorator => {
48
+ return SetMetadata('anyPermission', permissions);
49
+ };
@@ -0,0 +1,56 @@
1
+ import type { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Injectable } from '@nestjs/common';
3
+ import type { Reflector } from '@nestjs/core';
4
+
5
+ import type { SessionManager } from '../../core/session/session.manager';
6
+ import { AuthenticationError } from '@plyaz/errors';
7
+ import type { JwtManager } from '@/core';
8
+
9
+ @Injectable()
10
+ export class AuthGuard implements CanActivate {
11
+ constructor(
12
+ private jwtManager: JwtManager,
13
+ private sessionManager: SessionManager,
14
+ private reflector: Reflector
15
+ ) {}
16
+
17
+ // Explicit return type for canActivate
18
+ async canActivate(context: ExecutionContext): Promise<boolean> {
19
+ const isPublic: boolean | undefined = this.reflector.get<boolean>('isPublic', context.getHandler());
20
+ if (isPublic) return true;
21
+
22
+ const request = context.switchToHttp().getRequest();
23
+ const token: string | null = this.extractToken(request);
24
+ if (!token) throw new AuthenticationError('AUTH_TOKEN_INVALID');
25
+
26
+ try {
27
+ const payload = this.jwtManager.verifyAccessToken(token);
28
+ const session = await this.sessionManager.getSession(payload.sessionId);
29
+
30
+ if (!session) throw new AuthenticationError('AUTH_SESSION_EXPIRED');
31
+
32
+ await this.sessionManager.updateLastActive(session.id);
33
+
34
+ // Attach user and session to request
35
+ request.user = payload;
36
+ request.session = session;
37
+
38
+ return true;
39
+ } catch (error: unknown) {
40
+ if (error instanceof Error) {
41
+ if (error.name === 'TokenExpiredError') {
42
+ throw new AuthenticationError('AUTH_TOKEN_EXPIRED');
43
+ }
44
+ }
45
+ throw new AuthenticationError('AUTH_TOKEN_INVALID');
46
+ }
47
+ }
48
+
49
+ // Explicit return type for private helper
50
+ private extractToken(request: { headers: { authorization?: string } }): string | null {
51
+ const authHeader = request.headers.authorization;
52
+ if (!authHeader?.startsWith('Bearer ')) return null;
53
+ const seven = 7;
54
+ return authHeader.substring(seven);
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ import type {
2
+ CanActivate,
3
+ ExecutionContext} from "@nestjs/common";
4
+ import {
5
+ Injectable,
6
+ BadRequestException,
7
+ } from "@nestjs/common";
8
+ import type { RateLimiterService } from "../services/rate-limiter.service";
9
+
10
+ @Injectable()
11
+ export class RateLimiterGuard implements CanActivate {
12
+ constructor(private readonly rateLimiterService: RateLimiterService) {}
13
+
14
+ async canActivate(context: ExecutionContext): Promise<boolean> {
15
+ const req = context.switchToHttp().getRequest();
16
+
17
+ // Get IP address from headers or socket
18
+ const ip =
19
+ req.headers["x-forwarded-for"]?.split(",")[0] ??
20
+ req.ip ??
21
+ req.socket?.remoteAddress;
22
+ const identifier = `${ip}`;
23
+
24
+ // Construct route key, e.g., "POST /auth/signin"
25
+ const routeKey = `${req.method} ${req.route.path}`;
26
+
27
+ // Get rate limit options for this route
28
+ const options = this.rateLimiterService.getOption(routeKey);
29
+
30
+ // If no rate limit configured, allow request
31
+ if (options === undefined) return true;
32
+
33
+ // Check if the request is rate limited
34
+ const isLimited = await this.rateLimiterService.isRateLimited(
35
+ identifier,
36
+ options
37
+ );
38
+
39
+ if (isLimited)
40
+ throw new BadRequestException(
41
+ "Too many requests, please try again later"
42
+ );
43
+
44
+ return true;
45
+ }
46
+ }
@@ -0,0 +1,115 @@
1
+ import { Injectable, type CanActivate, type ExecutionContext } from '@nestjs/common';
2
+
3
+ import type { Reflector } from '@nestjs/core';
4
+ import type { PermissionChecker } from '../../rbac/permission-checker';
5
+ import { AuthenticationError } from '@plyaz/errors';
6
+
7
+ /**
8
+ * Typed request interface including user and request data
9
+ */
10
+ interface TypedRequest {
11
+ user?: {
12
+ sub?: string;
13
+ userId?: string;
14
+ };
15
+ params?: Record<string, string>;
16
+ query?: Record<string, string>;
17
+ body?: Record<string, string>;
18
+ permissionResult?: unknown;
19
+ }
20
+
21
+ /**
22
+ * Permission metadata interface
23
+ */
24
+ interface PermissionMetadata {
25
+ resource: string;
26
+ action: string;
27
+ conditions?: Record<string, string>;
28
+ }
29
+
30
+ @Injectable()
31
+ export class PermissionsGuard implements CanActivate {
32
+ constructor(
33
+ private readonly reflector: Reflector,
34
+ private readonly permissionChecker: PermissionChecker
35
+ ) {}
36
+
37
+ async canActivate(context: ExecutionContext): Promise<boolean> {
38
+ const request: TypedRequest = context.switchToHttp().getRequest();
39
+ const permissionMetadata = this.getPermissionMetadata(context);
40
+
41
+ if (!permissionMetadata) return true; // no permission required
42
+
43
+ const user = request.user;
44
+ if (!user) {
45
+ throw new AuthenticationError(
46
+ 'AUTH_INVALID_CREDENTIALS'
47
+ );
48
+ }
49
+
50
+ const permissionContext = this.buildPermissionContext(request, permissionMetadata);
51
+
52
+ const result = await this.permissionChecker.checkPermission(
53
+ user.sub ?? user.userId ?? '',
54
+ permissionMetadata.resource,
55
+ permissionMetadata.action,
56
+ permissionContext
57
+ );
58
+
59
+ if (!result.granted) {
60
+ throw new AuthenticationError(
61
+ 'AUTH_INSUFFICIENT_PERMISSIONS'
62
+ );
63
+ }
64
+
65
+ request.permissionResult = result; // attach for handler usage
66
+
67
+ return true;
68
+ }
69
+
70
+ private getPermissionMetadata(context: ExecutionContext): PermissionMetadata | null {
71
+ const permissionData = this.reflector.get<[string, string] | PermissionMetadata>(
72
+ 'permission',
73
+ context.getHandler()
74
+ );
75
+
76
+ if (!permissionData) return null;
77
+
78
+ if (Array.isArray(permissionData)) {
79
+ return { resource: permissionData[0], action: permissionData[1] };
80
+ }
81
+
82
+ return permissionData;
83
+ }
84
+
85
+ private buildPermissionContext(
86
+ request: TypedRequest,
87
+ metadata: PermissionMetadata
88
+ ): Record<string, string> {
89
+ const context: Record<string, string> = {};
90
+
91
+ if (request.user) {
92
+ context.ownerId = request.user.sub ?? request.user.userId ?? '';
93
+ }
94
+
95
+ if (request.params) Object.assign(context, request.params);
96
+
97
+ if (request.query) {
98
+ const relevantQueryParams = ['organizationId', 'teamId', 'projectId'];
99
+ for (const param of relevantQueryParams) {
100
+ if (request.query[param]) context[param] = request.query[param];
101
+ }
102
+ }
103
+
104
+ if (request.body) {
105
+ const relevantBodyFields = ['ownerId', 'organizationId', 'teamId', 'projectId'];
106
+ for (const field of relevantBodyFields) {
107
+ if (request.body[field]) context[field] = request.body[field];
108
+ }
109
+ }
110
+
111
+ if (metadata.conditions) Object.assign(context, metadata.conditions);
112
+
113
+ return context;
114
+ }
115
+ }
@@ -0,0 +1,31 @@
1
+ import type { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Injectable } from '@nestjs/common';
3
+ import type { Reflector } from '@nestjs/core';
4
+ import type { RoleManager } from '../../rbac/role.manager';
5
+ import { AuthenticationError } from '@plyaz/errors';
6
+
7
+
8
+ @Injectable()
9
+ export class RolesGuard implements CanActivate {
10
+ constructor(
11
+ private roleManager: RoleManager,
12
+ private reflector: Reflector
13
+ ) {}
14
+
15
+ async canActivate(context: ExecutionContext): Promise<boolean> {
16
+ const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
17
+ if (!requiredRoles) return true;
18
+
19
+ const request = context.switchToHttp().getRequest();
20
+ const user = request.user;
21
+
22
+ if (!user) throw new AuthenticationError('AUTH_INVALID_CREDENTIALS');
23
+
24
+ const hasRole = await this.roleManager.hasAnyRole(user.sub, requiredRoles);
25
+ if (!hasRole) {
26
+ throw new AuthenticationError('AUTH_ROLE_REQUIRED');
27
+ }
28
+
29
+ return true;
30
+ }
31
+ }