@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,311 @@
1
+ /* eslint-disable no-magic-numbers */
2
+ /**
3
+ * @fileoverview Token validator for @plyaz/auth
4
+ * @module @plyaz/auth/tokens/token-validator
5
+ *
6
+ * @description
7
+ * Provides comprehensive JWT token validation including signature verification,
8
+ * expiration checks, blacklist validation, and issuer/audience verification.
9
+ * Used by guards and middleware to validate incoming authentication tokens.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { TokenValidator } from '@plyaz/auth';
14
+ *
15
+ * const validator = new TokenValidator({
16
+ * publicKey: 'your-public-key',
17
+ * issuer: 'plyaz.com',
18
+ * audience: 'plyaz-api'
19
+ * });
20
+ *
21
+ * const payload = await validator.validateToken(token);
22
+ * ```
23
+ */
24
+
25
+ import type { JwtPayload, Algorithm } from 'jsonwebtoken';
26
+ import { verify } from 'jsonwebtoken';
27
+ import { TokenBlacklist } from '../core/blacklist/token.blacklist';
28
+ import { Buffer } from 'buffer';
29
+
30
+ /**
31
+ * Token validation configuration
32
+ */
33
+ export interface TokenValidatorConfig {
34
+ /** Public key for signature verification */
35
+ publicKey: string;
36
+ /** Expected token issuer */
37
+ issuer: string;
38
+ /** Expected token audience */
39
+ audience: string;
40
+ /** Algorithm for verification */
41
+ algorithm?: string;
42
+ /** Clock tolerance in seconds */
43
+ clockTolerance?: number;
44
+ /** Enable blacklist checking */
45
+ enableBlacklist?: boolean;
46
+ }
47
+
48
+ /**
49
+ * Validated token payload
50
+ */
51
+ export interface ValidatedTokenPayload extends JwtPayload {
52
+ /** User ID (subject) */
53
+ sub: string;
54
+ /** Session ID */
55
+ sessionId?: string;
56
+ /** User roles */
57
+ roles?: string[];
58
+ /** User permissions */
59
+ permissions?: string[];
60
+ /** Token type */
61
+ type?: 'access' | 'refresh';
62
+ }
63
+
64
+ /**
65
+ * Token validation result
66
+ */
67
+ export interface TokenValidationResult {
68
+ /** Validation success */
69
+ valid: boolean;
70
+ /** Decoded payload (if valid) */
71
+ payload?: ValidatedTokenPayload;
72
+ /** Error details (if invalid) */
73
+ error?: {
74
+ code: string;
75
+ message: string;
76
+ details?: unknown;
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Token validator implementation
82
+ * Validates JWT tokens with comprehensive security checks
83
+ */
84
+ export class TokenValidator {
85
+ private readonly config: Required<TokenValidatorConfig>;
86
+ private readonly blacklist?: TokenBlacklist;
87
+
88
+ constructor(config: TokenValidatorConfig) {
89
+ this.config = {
90
+ algorithm: 'RS256',
91
+ clockTolerance: 30,
92
+ enableBlacklist: true,
93
+ ...config
94
+ };
95
+
96
+ if (this.config.enableBlacklist) {
97
+ this.blacklist = new TokenBlacklist({ keyPrefix: 'token:', defaultTTL: 3600 });
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Validate JWT token with comprehensive checks
103
+ * @param token - JWT token to validate
104
+ * @returns Validation result with payload or error
105
+ */
106
+ async validateToken(token: string): Promise<TokenValidationResult> {
107
+ try {
108
+ // Basic format check
109
+ if (!token || typeof token !== 'string') {
110
+ return this.createErrorResult('INVALID_FORMAT', 'Token format is invalid');
111
+ }
112
+
113
+ // Check if token is blacklisted
114
+ if (this.blacklist && await this.blacklist.isBlacklisted(token)) {
115
+ return this.createErrorResult('TOKEN_REVOKED', 'Token has been revoked');
116
+ }
117
+
118
+ // Verify JWT signature and claims
119
+ const payload = verify(token, this.config.publicKey, {
120
+ issuer: this.config.issuer,
121
+ audience: this.config.audience,
122
+ algorithms: [this.config.algorithm as Algorithm],
123
+ clockTolerance: this.config.clockTolerance
124
+ }) as ValidatedTokenPayload;
125
+
126
+ // Additional payload validation
127
+ const validationError = this.validatePayload(payload);
128
+ if (validationError) {
129
+ return validationError;
130
+ }
131
+
132
+ return {
133
+ valid: true,
134
+ payload
135
+ };
136
+
137
+ } catch (error) {
138
+ return this.handleVerificationError(error as Error);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Validate access token specifically
144
+ * @param token - Access token to validate
145
+ * @returns Validation result
146
+ */
147
+ async validateAccessToken(token: string): Promise<TokenValidationResult> {
148
+ const result = await this.validateToken(token);
149
+
150
+ if (result.valid && result.payload?.type && result.payload.type !== 'access') {
151
+ return this.createErrorResult('INVALID_TOKEN_TYPE', 'Expected access token');
152
+ }
153
+
154
+ return result;
155
+ }
156
+
157
+ /**
158
+ * Validate refresh token specifically
159
+ * @param token - Refresh token to validate
160
+ * @returns Validation result
161
+ */
162
+ async validateRefreshToken(token: string): Promise<TokenValidationResult> {
163
+ const result = await this.validateToken(token);
164
+
165
+ if (result.valid && result.payload?.type && result.payload.type !== 'refresh') {
166
+ return this.createErrorResult('INVALID_TOKEN_TYPE', 'Expected refresh token');
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ /**
173
+ * Extract token payload without validation (for debugging)
174
+ * @param token - JWT token
175
+ * @returns Decoded payload or null
176
+ */
177
+ extractPayload(token: string): ValidatedTokenPayload | null {
178
+ try {
179
+ const parts = token.split('.');
180
+ if (parts.length !== 3) {
181
+ return null;
182
+ }
183
+
184
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
185
+ return payload;
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Check if token is expired (without full validation)
193
+ * @param token - JWT token
194
+ * @returns True if token is expired
195
+ */
196
+ isTokenExpired(token: string): boolean {
197
+ const payload = this.extractPayload(token);
198
+
199
+ if (!payload?.exp) {
200
+ return true;
201
+ }
202
+
203
+ const now = Math.floor(Date.now() / 1000);
204
+ return payload.exp < now;
205
+ }
206
+
207
+ /**
208
+ * Get token expiration time
209
+ * @param token - JWT token
210
+ * @returns Expiration date or null
211
+ */
212
+ getTokenExpiration(token: string): Date | null {
213
+ const payload = this.extractPayload(token);
214
+
215
+ if (!payload?.exp) {
216
+ return null;
217
+ }
218
+
219
+ return new Date(payload.exp * 1000);
220
+ }
221
+
222
+ /**
223
+ * Get time until token expires
224
+ * @param token - JWT token
225
+ * @returns Seconds until expiration or null
226
+ */
227
+ getTimeUntilExpiration(token: string): number | null {
228
+ const expiration = this.getTokenExpiration(token);
229
+
230
+ if (!expiration) {
231
+ return null;
232
+ }
233
+
234
+ const now = Date.now();
235
+ const timeUntilExpiry = expiration.getTime() - now;
236
+
237
+ return Math.max(0, Math.floor(timeUntilExpiry / 1000));
238
+ }
239
+
240
+ /**
241
+ * Validate token payload structure and claims
242
+ * @param payload - Decoded JWT payload
243
+ * @returns Error result if invalid, null if valid
244
+ * @private
245
+ */
246
+ private validatePayload(payload: ValidatedTokenPayload): TokenValidationResult | null {
247
+ // Check required claims
248
+ if (!payload.sub) {
249
+ return this.createErrorResult('MISSING_SUBJECT', 'Token missing subject claim');
250
+ }
251
+
252
+ if (!payload.iat) {
253
+ return this.createErrorResult('MISSING_ISSUED_AT', 'Token missing issued at claim');
254
+ }
255
+
256
+ if (!payload.exp) {
257
+ return this.createErrorResult('MISSING_EXPIRATION', 'Token missing expiration claim');
258
+ }
259
+
260
+ // Validate issued at time (not in future)
261
+ const now = Math.floor(Date.now() / 1000);
262
+ if (payload.iat > now + this.config.clockTolerance) {
263
+ return this.createErrorResult('TOKEN_NOT_YET_VALID', 'Token issued in the future');
264
+ }
265
+
266
+ // Additional custom validations can be added here
267
+
268
+ return null;
269
+ }
270
+
271
+ /**
272
+ * Handle JWT verification errors
273
+ * @param error - Verification error
274
+ * @returns Error result
275
+ * @private
276
+ */
277
+ private handleVerificationError(error : Error): TokenValidationResult {
278
+ if (error.name === 'TokenExpiredError') {
279
+ return this.createErrorResult('TOKEN_EXPIRED', 'Token has expired', error);
280
+ }
281
+
282
+ if (error.name === 'JsonWebTokenError') {
283
+ return this.createErrorResult('INVALID_SIGNATURE', 'Token signature is invalid', error);
284
+ }
285
+
286
+ if (error.name === 'NotBeforeError') {
287
+ return this.createErrorResult('TOKEN_NOT_YET_VALID', 'Token not yet valid', error);
288
+ }
289
+
290
+ return this.createErrorResult('VALIDATION_FAILED', error.message ?? 'Token validation failed', error);
291
+ }
292
+
293
+ /**
294
+ * Create error result
295
+ * @param code - Error code
296
+ * @param message - Error message
297
+ * @param details - Additional error details
298
+ * @returns Error result
299
+ * @private
300
+ */
301
+ private createErrorResult(code: string, message: string, details?: unknown): TokenValidationResult {
302
+ return {
303
+ valid: false,
304
+ error: {
305
+ code,
306
+ message,
307
+ details
308
+ }
309
+ };
310
+ }
311
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "extends": "@plyaz/devtools/configs/tsconfig.build.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "composite": true,
7
+ "strict": true,
8
+ "strictNullChecks": true,
9
+ "moduleResolution": "bundler",
10
+ "module": "preserve",
11
+ "lib": ["ES2021", "DOM"],
12
+ "jsx": "react-jsx",
13
+ "types": ["node", "react"],
14
+ "target": "ES2017",
15
+ "resolveJsonModule": true,
16
+ "baseUrl": "./src",
17
+ "paths": {
18
+ "@/*": ["*"],
19
+ "@common/*": ["common/utils/*"],
20
+ "@providers/*": ["providers/clerk-supabase/*"],
21
+ "@server/*": ["server/*"],
22
+ "@strategies/*": ["server/strategies/*"],
23
+ "@libs/*" : ["src/libs/*"]
24
+ }
25
+ },
26
+ "include": ["src/**/*"],
27
+ "exclude": ["node_modules", "dist", "build", "coverage", "tests"]
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "dist",
4
+ "composite": true,
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "strictNullChecks": true,
8
+ "module": "preserve",
9
+ "jsx": "react-jsx",
10
+ "types": ["node", "react", "react-dom"],
11
+ "lib": ["ES2021", "DOM"],
12
+ "resolveJsonModule": true,
13
+ "target": "ES2017",
14
+ "baseUrl": "./src",
15
+ "paths": {
16
+ "@/*": ["*"],
17
+ "@/enums": ["common/enums"],
18
+ "@/interfaces": ["common/interfaces"],
19
+ "@/regex": ["common/regex"],
20
+ "@/constants": ["common/constants"],
21
+ "@/errors": ["common/errors"],
22
+ "@/types": ["types"],
23
+ "@/core": ["core"],
24
+ "@/rbac": ["rbac"],
25
+ "@/security": ["security"],
26
+ "@/session": ["session"],
27
+ "@/tokens": ["tokens"],
28
+ "@/db": ["db"],
29
+ "@/server": ["server"],
30
+ "@/client": ["client"],
31
+ "@/flows": ["flows"],
32
+ "@/providers": ["providers"],
33
+ "@libs/*" : ["src/libs/*"]
34
+ }
35
+ },
36
+ "include": ["**/*"],
37
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
38
+ }
@@ -0,0 +1,28 @@
1
+ import { createTsupConfig } from "@plyaz/devtools/configs/tsup.base.config.mjs";
2
+
3
+ export default createTsupConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ "common/index": "src/common/utils/index.ts",
7
+ },
8
+ dts: false,
9
+ splitting: false,
10
+ sourcemap: true,
11
+ clean: true,
12
+ external: [
13
+ '@nestjs/common',
14
+ '@nestjs/core',
15
+ '@nestjs/jwt',
16
+ '@nestjs/config',
17
+ 'react',
18
+ 'zustand',
19
+ 'jsonwebtoken',
20
+ '@clerk/clerk-sdk-node',
21
+ '@supabase/supabase-js'
22
+ ],
23
+ outExtension({ format }) {
24
+ return {
25
+ js: format === 'cjs' ? '.cjs' : '.mjs',
26
+ };
27
+ },
28
+ });
@@ -0,0 +1,16 @@
1
+ import { createVitestConfig } from '@plyaz/devtools/configs/vitest.config';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname } from 'node:path';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const config = createVitestConfig(__dirname);
9
+
10
+ // Configure for type-only package - run minimal test to satisfy CI
11
+ config.test = {
12
+ ...config.test,
13
+ passWithNoTests: true
14
+ };
15
+
16
+ export default config;
@@ -0,0 +1,2 @@
1
+ import '@plyaz/devtools/configs/vitest.setup';
2
+ //# sourceMappingURL=vitest.setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.setup.d.ts","sourceRoot":"","sources":["vitest.setup.ts"],"names":[],"mappings":"AAAA,OAAO,sCAAsC,CAAC"}
@@ -0,0 +1 @@
1
+ import '@plyaz/devtools/configs/vitest.setup';