servcraft 0.1.0 → 0.1.3
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/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- 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(
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 {
|
|
7
|
-
import {
|
|
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<
|
|
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';
|