servcraft 0.1.0 → 0.1.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.
Files changed (216) hide show
  1. package/.claude/settings.local.json +29 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/README.md +1070 -1
  9. package/dist/cli/index.cjs +2026 -2168
  10. package/dist/cli/index.cjs.map +1 -1
  11. package/dist/cli/index.js +2026 -2168
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/index.cjs +595 -616
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +114 -52
  16. package/dist/index.d.ts +114 -52
  17. package/dist/index.js +595 -616
  18. package/dist/index.js.map +1 -1
  19. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  20. package/docs/DATABASE_MULTI_ORM.md +399 -0
  21. package/docs/PHASE1_BREAKDOWN.md +346 -0
  22. package/docs/PROGRESS.md +550 -0
  23. package/docs/modules/ANALYTICS.md +226 -0
  24. package/docs/modules/API-VERSIONING.md +252 -0
  25. package/docs/modules/AUDIT.md +192 -0
  26. package/docs/modules/AUTH.md +431 -0
  27. package/docs/modules/CACHE.md +346 -0
  28. package/docs/modules/EMAIL.md +254 -0
  29. package/docs/modules/FEATURE-FLAG.md +291 -0
  30. package/docs/modules/I18N.md +294 -0
  31. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  32. package/docs/modules/MFA.md +266 -0
  33. package/docs/modules/NOTIFICATION.md +311 -0
  34. package/docs/modules/OAUTH.md +237 -0
  35. package/docs/modules/PAYMENT.md +804 -0
  36. package/docs/modules/QUEUE.md +540 -0
  37. package/docs/modules/RATE-LIMIT.md +339 -0
  38. package/docs/modules/SEARCH.md +288 -0
  39. package/docs/modules/SECURITY.md +327 -0
  40. package/docs/modules/SESSION.md +382 -0
  41. package/docs/modules/SWAGGER.md +305 -0
  42. package/docs/modules/UPLOAD.md +296 -0
  43. package/docs/modules/USER.md +505 -0
  44. package/docs/modules/VALIDATION.md +294 -0
  45. package/docs/modules/WEBHOOK.md +270 -0
  46. package/docs/modules/WEBSOCKET.md +691 -0
  47. package/package.json +53 -38
  48. package/prisma/schema.prisma +395 -1
  49. package/src/cli/commands/add-module.ts +520 -87
  50. package/src/cli/commands/db.ts +3 -4
  51. package/src/cli/commands/docs.ts +256 -6
  52. package/src/cli/commands/generate.ts +12 -19
  53. package/src/cli/commands/init.ts +384 -214
  54. package/src/cli/index.ts +0 -4
  55. package/src/cli/templates/repository.ts +6 -1
  56. package/src/cli/templates/routes.ts +6 -21
  57. package/src/cli/utils/docs-generator.ts +6 -7
  58. package/src/cli/utils/env-manager.ts +717 -0
  59. package/src/cli/utils/field-parser.ts +16 -7
  60. package/src/cli/utils/interactive-prompt.ts +223 -0
  61. package/src/cli/utils/template-manager.ts +346 -0
  62. package/src/config/database.config.ts +183 -0
  63. package/src/config/env.ts +0 -10
  64. package/src/config/index.ts +0 -14
  65. package/src/core/server.ts +1 -1
  66. package/src/database/adapters/mongoose.adapter.ts +132 -0
  67. package/src/database/adapters/prisma.adapter.ts +118 -0
  68. package/src/database/connection.ts +190 -0
  69. package/src/database/interfaces/database.interface.ts +85 -0
  70. package/src/database/interfaces/index.ts +7 -0
  71. package/src/database/interfaces/repository.interface.ts +129 -0
  72. package/src/database/models/mongoose/index.ts +7 -0
  73. package/src/database/models/mongoose/payment.schema.ts +347 -0
  74. package/src/database/models/mongoose/user.schema.ts +154 -0
  75. package/src/database/prisma.ts +1 -4
  76. package/src/database/redis.ts +101 -0
  77. package/src/database/repositories/mongoose/index.ts +7 -0
  78. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  79. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  80. package/src/database/seed.ts +6 -1
  81. package/src/index.ts +9 -20
  82. package/src/middleware/security.ts +2 -6
  83. package/src/modules/analytics/analytics.routes.ts +80 -0
  84. package/src/modules/analytics/analytics.service.ts +364 -0
  85. package/src/modules/analytics/index.ts +18 -0
  86. package/src/modules/analytics/types.ts +180 -0
  87. package/src/modules/api-versioning/index.ts +15 -0
  88. package/src/modules/api-versioning/types.ts +86 -0
  89. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  90. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  91. package/src/modules/api-versioning/versioning.service.ts +189 -0
  92. package/src/modules/audit/audit.repository.ts +206 -0
  93. package/src/modules/audit/audit.service.ts +27 -59
  94. package/src/modules/auth/auth.controller.ts +2 -2
  95. package/src/modules/auth/auth.middleware.ts +3 -9
  96. package/src/modules/auth/auth.routes.ts +10 -107
  97. package/src/modules/auth/auth.service.ts +126 -23
  98. package/src/modules/auth/index.ts +3 -4
  99. package/src/modules/cache/cache.service.ts +367 -0
  100. package/src/modules/cache/index.ts +10 -0
  101. package/src/modules/cache/types.ts +44 -0
  102. package/src/modules/email/email.service.ts +3 -10
  103. package/src/modules/email/templates.ts +2 -8
  104. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  105. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  106. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  107. package/src/modules/feature-flag/index.ts +20 -0
  108. package/src/modules/feature-flag/types.ts +192 -0
  109. package/src/modules/i18n/i18n.middleware.ts +186 -0
  110. package/src/modules/i18n/i18n.routes.ts +191 -0
  111. package/src/modules/i18n/i18n.service.ts +456 -0
  112. package/src/modules/i18n/index.ts +18 -0
  113. package/src/modules/i18n/types.ts +118 -0
  114. package/src/modules/media-processing/index.ts +17 -0
  115. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  116. package/src/modules/media-processing/media-processing.service.ts +245 -0
  117. package/src/modules/media-processing/types.ts +156 -0
  118. package/src/modules/mfa/index.ts +20 -0
  119. package/src/modules/mfa/mfa.repository.ts +206 -0
  120. package/src/modules/mfa/mfa.routes.ts +595 -0
  121. package/src/modules/mfa/mfa.service.ts +572 -0
  122. package/src/modules/mfa/totp.ts +150 -0
  123. package/src/modules/mfa/types.ts +57 -0
  124. package/src/modules/notification/index.ts +20 -0
  125. package/src/modules/notification/notification.repository.ts +356 -0
  126. package/src/modules/notification/notification.service.ts +483 -0
  127. package/src/modules/notification/types.ts +119 -0
  128. package/src/modules/oauth/index.ts +20 -0
  129. package/src/modules/oauth/oauth.repository.ts +219 -0
  130. package/src/modules/oauth/oauth.routes.ts +446 -0
  131. package/src/modules/oauth/oauth.service.ts +293 -0
  132. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  133. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  134. package/src/modules/oauth/providers/github.provider.ts +248 -0
  135. package/src/modules/oauth/providers/google.provider.ts +189 -0
  136. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  137. package/src/modules/oauth/types.ts +94 -0
  138. package/src/modules/payment/index.ts +19 -0
  139. package/src/modules/payment/payment.repository.ts +733 -0
  140. package/src/modules/payment/payment.routes.ts +390 -0
  141. package/src/modules/payment/payment.service.ts +354 -0
  142. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  143. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  144. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  145. package/src/modules/payment/types.ts +140 -0
  146. package/src/modules/queue/cron.ts +438 -0
  147. package/src/modules/queue/index.ts +87 -0
  148. package/src/modules/queue/queue.routes.ts +600 -0
  149. package/src/modules/queue/queue.service.ts +842 -0
  150. package/src/modules/queue/types.ts +222 -0
  151. package/src/modules/queue/workers.ts +366 -0
  152. package/src/modules/rate-limit/index.ts +59 -0
  153. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  154. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  155. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  156. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  157. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  158. package/src/modules/rate-limit/types.ts +153 -0
  159. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  160. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  161. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  162. package/src/modules/search/index.ts +21 -0
  163. package/src/modules/search/search.service.ts +234 -0
  164. package/src/modules/search/types.ts +214 -0
  165. package/src/modules/security/index.ts +40 -0
  166. package/src/modules/security/sanitize.ts +223 -0
  167. package/src/modules/security/security-audit.service.ts +388 -0
  168. package/src/modules/security/security.middleware.ts +398 -0
  169. package/src/modules/session/index.ts +3 -0
  170. package/src/modules/session/session.repository.ts +159 -0
  171. package/src/modules/session/session.service.ts +340 -0
  172. package/src/modules/session/types.ts +38 -0
  173. package/src/modules/swagger/index.ts +7 -1
  174. package/src/modules/swagger/schema-builder.ts +16 -4
  175. package/src/modules/swagger/swagger.service.ts +9 -10
  176. package/src/modules/swagger/types.ts +0 -2
  177. package/src/modules/upload/index.ts +14 -0
  178. package/src/modules/upload/types.ts +83 -0
  179. package/src/modules/upload/upload.repository.ts +199 -0
  180. package/src/modules/upload/upload.routes.ts +311 -0
  181. package/src/modules/upload/upload.service.ts +448 -0
  182. package/src/modules/user/index.ts +3 -3
  183. package/src/modules/user/user.controller.ts +15 -9
  184. package/src/modules/user/user.repository.ts +237 -113
  185. package/src/modules/user/user.routes.ts +39 -164
  186. package/src/modules/user/user.service.ts +4 -3
  187. package/src/modules/validation/validator.ts +12 -17
  188. package/src/modules/webhook/index.ts +91 -0
  189. package/src/modules/webhook/retry.ts +196 -0
  190. package/src/modules/webhook/signature.ts +135 -0
  191. package/src/modules/webhook/types.ts +181 -0
  192. package/src/modules/webhook/webhook.repository.ts +358 -0
  193. package/src/modules/webhook/webhook.routes.ts +442 -0
  194. package/src/modules/webhook/webhook.service.ts +457 -0
  195. package/src/modules/websocket/features.ts +504 -0
  196. package/src/modules/websocket/index.ts +106 -0
  197. package/src/modules/websocket/middlewares.ts +298 -0
  198. package/src/modules/websocket/types.ts +181 -0
  199. package/src/modules/websocket/websocket.service.ts +692 -0
  200. package/src/utils/errors.ts +7 -0
  201. package/src/utils/pagination.ts +4 -1
  202. package/tests/helpers/db-check.ts +79 -0
  203. package/tests/integration/auth-redis.test.ts +94 -0
  204. package/tests/integration/cache-redis.test.ts +387 -0
  205. package/tests/integration/mongoose-repositories.test.ts +410 -0
  206. package/tests/integration/payment-prisma.test.ts +637 -0
  207. package/tests/integration/queue-bullmq.test.ts +417 -0
  208. package/tests/integration/user-prisma.test.ts +441 -0
  209. package/tests/integration/websocket-socketio.test.ts +552 -0
  210. package/tests/setup.ts +11 -9
  211. package/vitest.config.ts +3 -8
  212. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  213. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  216. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -107,7 +107,7 @@ export class AuthController {
107
107
  }
108
108
 
109
109
  // Blacklist old refresh token (token rotation)
110
- this.authService.blacklistToken(data.refreshToken);
110
+ await this.authService.blacklistToken(data.refreshToken);
111
111
 
112
112
  // Generate new tokens
113
113
  const tokens = this.authService.generateTokenPair({
@@ -123,7 +123,7 @@ export class AuthController {
123
123
  const authHeader = request.headers.authorization;
124
124
  if (authHeader?.startsWith('Bearer ')) {
125
125
  const token = authHeader.substring(7);
126
- this.authService.blacklistToken(token);
126
+ await this.authService.blacklistToken(token);
127
127
  }
128
128
 
129
129
  success(reply, { message: 'Logged out successfully' });
@@ -4,10 +4,7 @@ import type { AuthService } from './auth.service.js';
4
4
  import type { AuthUser } from './types.js';
5
5
 
6
6
  export function createAuthMiddleware(authService: AuthService) {
7
- return async function authenticate(
8
- request: FastifyRequest,
9
- reply: FastifyReply
10
- ): Promise<void> {
7
+ return async function authenticate(request: FastifyRequest, _reply: FastifyReply): Promise<void> {
11
8
  const authHeader = request.headers.authorization;
12
9
 
13
10
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
@@ -26,10 +23,7 @@ export function createAuthMiddleware(authService: AuthService) {
26
23
  }
27
24
 
28
25
  export function createRoleMiddleware(allowedRoles: string[]) {
29
- return async function authorize(
30
- request: FastifyRequest,
31
- _reply: FastifyReply
32
- ): Promise<void> {
26
+ return async function authorize(request: FastifyRequest, _reply: FastifyReply): Promise<void> {
33
27
  const user = request.user as AuthUser | undefined;
34
28
 
35
29
  if (!user) {
@@ -42,7 +36,7 @@ export function createRoleMiddleware(allowedRoles: string[]) {
42
36
  };
43
37
  }
44
38
 
45
- export function createPermissionMiddleware(requiredPermissions: string[]) {
39
+ export function createPermissionMiddleware(_requiredPermissions: string[]) {
46
40
  return async function checkPermissions(
47
41
  request: FastifyRequest,
48
42
  _reply: FastifyReply
@@ -2,25 +2,6 @@ import type { FastifyInstance } from 'fastify';
2
2
  import type { AuthController } from './auth.controller.js';
3
3
  import type { AuthService } from './auth.service.js';
4
4
  import { createAuthMiddleware } from './auth.middleware.js';
5
- import { commonResponses } from '../swagger/index.js';
6
-
7
- const credentialsBody = {
8
- type: 'object',
9
- required: ['email', 'password'],
10
- properties: {
11
- email: { type: 'string', format: 'email' },
12
- password: { type: 'string', minLength: 8 },
13
- },
14
- };
15
-
16
- const changePasswordBody = {
17
- type: 'object',
18
- required: ['currentPassword', 'newPassword'],
19
- properties: {
20
- currentPassword: { type: 'string', minLength: 8 },
21
- newPassword: { type: 'string', minLength: 8 },
22
- },
23
- };
24
5
 
25
6
  export function registerAuthRoutes(
26
7
  app: FastifyInstance,
@@ -30,94 +11,16 @@ export function registerAuthRoutes(
30
11
  const authenticate = createAuthMiddleware(authService);
31
12
 
32
13
  // Public routes
33
- app.post('/auth/register', {
34
- schema: {
35
- tags: ['Auth'],
36
- summary: 'Register a new user',
37
- body: credentialsBody,
38
- response: {
39
- 201: commonResponses.success,
40
- 400: commonResponses.error,
41
- 409: commonResponses.error,
42
- },
43
- },
44
- handler: controller.register.bind(controller),
45
- });
46
-
47
- app.post('/auth/login', {
48
- schema: {
49
- tags: ['Auth'],
50
- summary: 'Login and obtain tokens',
51
- body: credentialsBody,
52
- response: {
53
- 200: commonResponses.success,
54
- 400: commonResponses.error,
55
- 401: commonResponses.unauthorized,
56
- },
57
- },
58
- handler: controller.login.bind(controller),
59
- });
60
-
61
- app.post('/auth/refresh', {
62
- schema: {
63
- tags: ['Auth'],
64
- summary: 'Refresh access token',
65
- body: {
66
- type: 'object',
67
- required: ['refreshToken'],
68
- properties: {
69
- refreshToken: { type: 'string' },
70
- },
71
- },
72
- response: {
73
- 200: commonResponses.success,
74
- 401: commonResponses.unauthorized,
75
- },
76
- },
77
- handler: controller.refresh.bind(controller),
78
- });
14
+ app.post('/auth/register', controller.register.bind(controller));
15
+ app.post('/auth/login', controller.login.bind(controller));
16
+ app.post('/auth/refresh', controller.refresh.bind(controller));
79
17
 
80
18
  // Protected routes
81
- app.post('/auth/logout', {
82
- preHandler: [authenticate],
83
- schema: {
84
- tags: ['Auth'],
85
- summary: 'Logout current user',
86
- security: [{ bearerAuth: [] }],
87
- response: {
88
- 200: commonResponses.success,
89
- 401: commonResponses.unauthorized,
90
- },
91
- },
92
- handler: controller.logout.bind(controller),
93
- });
94
- app.get('/auth/me', {
95
- preHandler: [authenticate],
96
- schema: {
97
- tags: ['Auth'],
98
- summary: 'Get current user profile',
99
- security: [{ bearerAuth: [] }],
100
- response: {
101
- 200: commonResponses.success,
102
- 401: commonResponses.unauthorized,
103
- },
104
- },
105
- handler: controller.me.bind(controller),
106
- });
107
-
108
- app.post('/auth/change-password', {
109
- preHandler: [authenticate],
110
- schema: {
111
- tags: ['Auth'],
112
- summary: 'Change current user password',
113
- security: [{ bearerAuth: [] }],
114
- body: changePasswordBody,
115
- response: {
116
- 200: commonResponses.success,
117
- 400: commonResponses.error,
118
- 401: commonResponses.unauthorized,
119
- },
120
- },
121
- handler: controller.changePassword.bind(controller),
122
- });
19
+ app.post('/auth/logout', { preHandler: [authenticate] }, controller.logout.bind(controller));
20
+ app.get('/auth/me', { preHandler: [authenticate] }, controller.me.bind(controller));
21
+ app.post(
22
+ '/auth/change-password',
23
+ { preHandler: [authenticate] },
24
+ controller.changePassword.bind(controller)
25
+ );
123
26
  }
@@ -1,25 +1,40 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
2
  import bcrypt from 'bcryptjs';
3
+ import { Redis } from 'ioredis';
3
4
  import { config } from '../../config/index.js';
4
5
  import { logger } from '../../core/logger.js';
5
- import {
6
- UnauthorizedError,
7
- BadRequestError,
8
- ConflictError,
9
- NotFoundError,
10
- } from '../../utils/errors.js';
6
+ import { UnauthorizedError } from '../../utils/errors.js';
11
7
  import type { TokenPair, JwtPayload, AuthUser } from './types.js';
12
- import type { LoginInput, RegisterInput, ChangePasswordInput } from './schemas.js';
13
-
14
- // Token blacklist (in production, use Redis)
15
- const tokenBlacklist = new Set<string>();
16
8
 
17
9
  export class AuthService {
18
10
  private app: FastifyInstance;
19
11
  private readonly SALT_ROUNDS = 12;
12
+ private redis: Redis | null = null;
13
+ private readonly BLACKLIST_PREFIX = 'auth:blacklist:';
14
+ private readonly BLACKLIST_TTL = 7 * 24 * 60 * 60; // 7 days in seconds
20
15
 
21
- constructor(app: FastifyInstance) {
16
+ constructor(app: FastifyInstance, redisUrl?: string) {
22
17
  this.app = app;
18
+
19
+ // Initialize Redis connection for token blacklist
20
+ if (redisUrl || process.env.REDIS_URL) {
21
+ try {
22
+ this.redis = new Redis(redisUrl || process.env.REDIS_URL || 'redis://localhost:6379');
23
+ this.redis.on('connect', () => {
24
+ logger.info('Auth service connected to Redis for token blacklist');
25
+ });
26
+ this.redis.on('error', (error: Error) => {
27
+ logger.error({ err: error }, 'Redis connection error in Auth service');
28
+ });
29
+ } catch (error) {
30
+ logger.warn({ err: error }, 'Failed to connect to Redis, using in-memory blacklist');
31
+ this.redis = null;
32
+ }
33
+ } else {
34
+ logger.warn(
35
+ 'No REDIS_URL provided, using in-memory token blacklist (not recommended for production)'
36
+ );
37
+ }
23
38
  }
24
39
 
25
40
  async hashPassword(password: string): Promise<string> {
@@ -82,7 +97,7 @@ export class AuthService {
82
97
 
83
98
  async verifyAccessToken(token: string): Promise<JwtPayload> {
84
99
  try {
85
- if (this.isTokenBlacklisted(token)) {
100
+ if (await this.isTokenBlacklisted(token)) {
86
101
  throw new UnauthorizedError('Token has been revoked');
87
102
  }
88
103
 
@@ -102,7 +117,7 @@ export class AuthService {
102
117
 
103
118
  async verifyRefreshToken(token: string): Promise<JwtPayload> {
104
119
  try {
105
- if (this.isTokenBlacklisted(token)) {
120
+ if (await this.isTokenBlacklisted(token)) {
106
121
  throw new UnauthorizedError('Token has been revoked');
107
122
  }
108
123
 
@@ -120,20 +135,108 @@ export class AuthService {
120
135
  }
121
136
  }
122
137
 
123
- blacklistToken(token: string): void {
124
- tokenBlacklist.add(token);
125
- logger.debug('Token blacklisted');
138
+ /**
139
+ * Blacklist a token (JWT revocation)
140
+ * Uses Redis if available, falls back to in-memory Set
141
+ */
142
+ async blacklistToken(token: string): Promise<void> {
143
+ if (this.redis) {
144
+ try {
145
+ const key = `${this.BLACKLIST_PREFIX}${token}`;
146
+ await this.redis.setex(key, this.BLACKLIST_TTL, '1');
147
+ logger.debug('Token blacklisted in Redis');
148
+ } catch (error) {
149
+ logger.error({ err: error }, 'Failed to blacklist token in Redis');
150
+ throw new Error('Failed to revoke token');
151
+ }
152
+ } else {
153
+ // Fallback to in-memory (not recommended for production)
154
+ logger.warn('Using in-memory blacklist - not suitable for multi-instance deployments');
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Check if a token is blacklisted
160
+ * Uses Redis if available, falls back to always returning false
161
+ */
162
+ async isTokenBlacklisted(token: string): Promise<boolean> {
163
+ if (this.redis) {
164
+ try {
165
+ const key = `${this.BLACKLIST_PREFIX}${token}`;
166
+ const result = await this.redis.exists(key);
167
+ return result === 1;
168
+ } catch (error) {
169
+ logger.error({ err: error }, 'Failed to check token blacklist in Redis');
170
+ // Fail open: if Redis is down, don't block all requests
171
+ return false;
172
+ }
173
+ }
174
+ // If no Redis, can't check blacklist across instances
175
+ return false;
126
176
  }
127
177
 
128
- isTokenBlacklisted(token: string): boolean {
129
- return tokenBlacklist.has(token);
178
+ /**
179
+ * Get count of blacklisted tokens (Redis only)
180
+ */
181
+ async getBlacklistCount(): Promise<number> {
182
+ if (this.redis) {
183
+ try {
184
+ const keys = await this.redis.keys(`${this.BLACKLIST_PREFIX}*`);
185
+ return keys.length;
186
+ } catch (error) {
187
+ logger.error({ err: error }, 'Failed to get blacklist count from Redis');
188
+ return 0;
189
+ }
190
+ }
191
+ return 0;
192
+ }
193
+
194
+ /**
195
+ * Close Redis connection
196
+ */
197
+ async close(): Promise<void> {
198
+ if (this.redis) {
199
+ await this.redis.quit();
200
+ logger.info('Auth service Redis connection closed');
201
+ }
202
+ }
203
+
204
+ // OAuth support methods - to be implemented with user repository
205
+ async findUserByEmail(_email: string): Promise<AuthUser | null> {
206
+ // In production, query the user repository
207
+ return null;
208
+ }
209
+
210
+ async createUserFromOAuth(data: {
211
+ email: string;
212
+ name?: string;
213
+ picture?: string;
214
+ emailVerified?: boolean;
215
+ }): Promise<AuthUser> {
216
+ // In production, create user in database
217
+ const user: AuthUser = {
218
+ id: `oauth_${Date.now()}`,
219
+ email: data.email,
220
+ role: 'user',
221
+ };
222
+ logger.info({ email: data.email }, 'Created user from OAuth');
223
+ return user;
224
+ }
225
+
226
+ async generateTokensForUser(userId: string): Promise<TokenPair> {
227
+ // Generate tokens for a user by ID
228
+ const user: AuthUser = {
229
+ id: userId,
230
+ email: '', // Would be fetched from database in production
231
+ role: 'user',
232
+ };
233
+ return this.generateTokenPair(user);
130
234
  }
131
235
 
132
- // Clear expired tokens from blacklist periodically
133
- cleanupBlacklist(): void {
134
- // In production, this should be handled by Redis TTL
135
- tokenBlacklist.clear();
136
- logger.debug('Token blacklist cleared');
236
+ async verifyPasswordById(userId: string, _password: string): Promise<boolean> {
237
+ // In production, verify password against stored hash
238
+ logger.debug({ userId }, 'Password verification requested');
239
+ return false;
137
240
  }
138
241
  }
139
242
 
@@ -3,12 +3,12 @@ import jwt from '@fastify/jwt';
3
3
  import cookie from '@fastify/cookie';
4
4
  import { config } from '../../config/index.js';
5
5
  import { logger } from '../../core/logger.js';
6
- import { AuthService, createAuthService } from './auth.service.js';
7
- import { AuthController, createAuthController } from './auth.controller.js';
6
+ import { createAuthService } from './auth.service.js';
7
+ import { createAuthController } from './auth.controller.js';
8
8
  import { registerAuthRoutes } from './auth.routes.js';
9
9
  import { createUserService } from '../user/user.service.js';
10
10
 
11
- export async function registerAuthModule(app: FastifyInstance): Promise<AuthService> {
11
+ export async function registerAuthModule(app: FastifyInstance): Promise<void> {
12
12
  // Register JWT plugin
13
13
  await app.register(jwt, {
14
14
  secret: config.jwt.secret,
@@ -34,7 +34,6 @@ export async function registerAuthModule(app: FastifyInstance): Promise<AuthServ
34
34
  registerAuthRoutes(app, authController, authService);
35
35
 
36
36
  logger.info('Auth module registered');
37
- return authService;
38
37
  }
39
38
 
40
39
  export { AuthService, createAuthService } from './auth.service.js';