@nauth-toolkit/core 0.1.0 → 0.1.5
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/LICENSE +90 -0
- package/README.md +9 -0
- package/package.json +8 -3
- package/jest.config.js +0 -15
- package/jest.setup.ts +0 -6
- package/src/adapters/database-columns.ts +0 -165
- package/src/adapters/express.adapter.ts +0 -385
- package/src/adapters/fastify.adapter.ts +0 -416
- package/src/adapters/index.ts +0 -16
- package/src/adapters/storage.factory.ts +0 -143
- package/src/bootstrap.ts +0 -374
- package/src/dto/auth-challenge.dto.ts +0 -231
- package/src/dto/auth-response.dto.ts +0 -253
- package/src/dto/challenge-response.dto.ts +0 -234
- package/src/dto/change-password-request.dto.ts +0 -50
- package/src/dto/change-password-response.dto.ts +0 -29
- package/src/dto/change-password.dto.ts +0 -57
- package/src/dto/error-response.dto.ts +0 -136
- package/src/dto/get-available-methods.dto.ts +0 -55
- package/src/dto/get-challenge-data-response.dto.ts +0 -28
- package/src/dto/get-challenge-data.dto.ts +0 -69
- package/src/dto/get-client-info.dto.ts +0 -104
- package/src/dto/get-device-token-response.dto.ts +0 -25
- package/src/dto/get-events-by-type.dto.ts +0 -76
- package/src/dto/get-ip-address-response.dto.ts +0 -24
- package/src/dto/get-mfa-status.dto.ts +0 -94
- package/src/dto/get-risk-assessment-history.dto.ts +0 -39
- package/src/dto/get-session-id-response.dto.ts +0 -25
- package/src/dto/get-setup-data-response.dto.ts +0 -31
- package/src/dto/get-setup-data.dto.ts +0 -75
- package/src/dto/get-suspicious-activity.dto.ts +0 -42
- package/src/dto/get-user-agent-response.dto.ts +0 -23
- package/src/dto/get-user-auth-history.dto.ts +0 -95
- package/src/dto/get-user-by-email.dto.ts +0 -61
- package/src/dto/get-user-by-id.dto.ts +0 -46
- package/src/dto/get-user-devices.dto.ts +0 -53
- package/src/dto/get-user-response.dto.ts +0 -17
- package/src/dto/has-provider.dto.ts +0 -56
- package/src/dto/index.ts +0 -57
- package/src/dto/is-trusted-device-response.dto.ts +0 -34
- package/src/dto/list-providers-response.dto.ts +0 -23
- package/src/dto/login.dto.ts +0 -95
- package/src/dto/logout-all-response.dto.ts +0 -24
- package/src/dto/logout-all.dto.ts +0 -65
- package/src/dto/logout-response.dto.ts +0 -25
- package/src/dto/logout.dto.ts +0 -64
- package/src/dto/refresh-token.dto.ts +0 -36
- package/src/dto/remove-devices.dto.ts +0 -85
- package/src/dto/resend-code-response.dto.ts +0 -32
- package/src/dto/resend-code.dto.ts +0 -51
- package/src/dto/reset-password.dto.ts +0 -115
- package/src/dto/respond-challenge.dto.ts +0 -272
- package/src/dto/set-mfa-exemption.dto.ts +0 -112
- package/src/dto/set-must-change-password-response.dto.ts +0 -27
- package/src/dto/set-must-change-password.dto.ts +0 -46
- package/src/dto/set-preferred-method.dto.ts +0 -80
- package/src/dto/setup-mfa.dto.ts +0 -98
- package/src/dto/signup.dto.ts +0 -174
- package/src/dto/social-auth.dto.ts +0 -422
- package/src/dto/trust-device-response.dto.ts +0 -30
- package/src/dto/trust-device.dto.ts +0 -9
- package/src/dto/update-user-attributes-request.dto.ts +0 -51
- package/src/dto/user-response.dto.ts +0 -138
- package/src/dto/user-update.dto.ts +0 -222
- package/src/dto/verify-email.dto.ts +0 -313
- package/src/dto/verify-mfa-code.dto.ts +0 -103
- package/src/dto/verify-phone-by-sub.dto.ts +0 -78
- package/src/dto/verify-phone.dto.ts +0 -245
- package/src/entities/auth-audit.entity.ts +0 -232
- package/src/entities/challenge-session.entity.ts +0 -116
- package/src/entities/index.ts +0 -29
- package/src/entities/login-attempt.entity.ts +0 -64
- package/src/entities/mfa-device.entity.ts +0 -151
- package/src/entities/rate-limit.entity.ts +0 -44
- package/src/entities/session.entity.ts +0 -180
- package/src/entities/social-account.entity.ts +0 -96
- package/src/entities/storage-lock.entity.ts +0 -39
- package/src/entities/trusted-device.entity.ts +0 -112
- package/src/entities/user.entity.ts +0 -243
- package/src/entities/verification-token.entity.ts +0 -141
- package/src/enums/auth-audit-event-type.enum.ts +0 -360
- package/src/enums/error-codes.enum.ts +0 -420
- package/src/enums/mfa-method.enum.ts +0 -97
- package/src/enums/risk-factor.enum.ts +0 -111
- package/src/exceptions/nauth.exception.ts +0 -231
- package/src/handlers/auth.handler.ts +0 -260
- package/src/handlers/client-info.handler.ts +0 -101
- package/src/handlers/csrf.handler.ts +0 -156
- package/src/handlers/token-delivery.handler.ts +0 -118
- package/src/index.ts +0 -118
- package/src/interfaces/client-info.interface.ts +0 -85
- package/src/interfaces/config.interface.ts +0 -2135
- package/src/interfaces/entities.interface.ts +0 -226
- package/src/interfaces/index.ts +0 -15
- package/src/interfaces/logger.interface.ts +0 -283
- package/src/interfaces/mfa-provider.interface.ts +0 -154
- package/src/interfaces/oauth.interface.ts +0 -148
- package/src/interfaces/provider.interface.ts +0 -47
- package/src/interfaces/social-auth-provider.interface.ts +0 -131
- package/src/interfaces/storage-adapter.interface.ts +0 -82
- package/src/interfaces/template.interface.ts +0 -510
- package/src/interfaces/token-verifier.interface.ts +0 -110
- package/src/internal.ts +0 -178
- package/src/platform/interfaces.ts +0 -299
- package/src/schemas/auth-config.schema.ts +0 -646
- package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
- package/src/services/adaptive-mfa-decision.service.ts +0 -457
- package/src/services/auth-audit.service.spec.ts +0 -675
- package/src/services/auth-audit.service.ts +0 -558
- package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
- package/src/services/auth-challenge-helper.service.ts +0 -825
- package/src/services/auth-flow-context-builder.service.ts +0 -520
- package/src/services/auth-flow-rules.ts +0 -202
- package/src/services/auth-flow-state-definitions.ts +0 -190
- package/src/services/auth-flow-state-machine.service.ts +0 -207
- package/src/services/auth-flow-state-machine.types.ts +0 -316
- package/src/services/auth.service.spec.ts +0 -4195
- package/src/services/auth.service.ts +0 -3727
- package/src/services/challenge.service.spec.ts +0 -1363
- package/src/services/challenge.service.ts +0 -696
- package/src/services/client-info.service.spec.ts +0 -572
- package/src/services/client-info.service.ts +0 -374
- package/src/services/csrf.service.ts +0 -54
- package/src/services/email-verification.service.spec.ts +0 -1229
- package/src/services/email-verification.service.ts +0 -578
- package/src/services/geo-location.service.spec.ts +0 -603
- package/src/services/geo-location.service.ts +0 -599
- package/src/services/index.ts +0 -13
- package/src/services/jwt.service.spec.ts +0 -882
- package/src/services/jwt.service.ts +0 -621
- package/src/services/mfa-base.service.spec.ts +0 -246
- package/src/services/mfa-base.service.ts +0 -611
- package/src/services/mfa.service.spec.ts +0 -693
- package/src/services/mfa.service.ts +0 -960
- package/src/services/password.service.spec.ts +0 -166
- package/src/services/password.service.ts +0 -309
- package/src/services/phone-verification.service.spec.ts +0 -1120
- package/src/services/phone-verification.service.ts +0 -751
- package/src/services/risk-detection.service.spec.ts +0 -1292
- package/src/services/risk-detection.service.ts +0 -1012
- package/src/services/risk-scoring.service.spec.ts +0 -204
- package/src/services/risk-scoring.service.ts +0 -131
- package/src/services/session.service.spec.ts +0 -1293
- package/src/services/session.service.ts +0 -803
- package/src/services/social-account.service.spec.ts +0 -725
- package/src/services/social-auth-base.service.spec.ts +0 -418
- package/src/services/social-auth-base.service.ts +0 -581
- package/src/services/social-auth.service.spec.ts +0 -238
- package/src/services/social-auth.service.ts +0 -436
- package/src/services/social-provider-registry.service.spec.ts +0 -238
- package/src/services/social-provider-registry.service.ts +0 -122
- package/src/services/trusted-device.service.spec.ts +0 -505
- package/src/services/trusted-device.service.ts +0 -339
- package/src/storage/account-lockout-storage.service.spec.ts +0 -310
- package/src/storage/account-lockout-storage.service.ts +0 -89
- package/src/storage/index.ts +0 -3
- package/src/storage/memory-storage.adapter.ts +0 -443
- package/src/storage/rate-limit-storage.service.spec.ts +0 -247
- package/src/storage/rate-limit-storage.service.ts +0 -38
- package/src/templates/html-template.engine.spec.ts +0 -161
- package/src/templates/html-template.engine.ts +0 -688
- package/src/templates/index.ts +0 -7
- package/src/utils/common-passwords.spec.ts +0 -230
- package/src/utils/common-passwords.ts +0 -170
- package/src/utils/context-storage.ts +0 -188
- package/src/utils/cookie-names.util.ts +0 -67
- package/src/utils/cookies.util.ts +0 -94
- package/src/utils/index.ts +0 -12
- package/src/utils/ip-extractor.spec.ts +0 -330
- package/src/utils/ip-extractor.ts +0 -220
- package/src/utils/nauth-logger.spec.ts +0 -388
- package/src/utils/nauth-logger.ts +0 -215
- package/src/utils/pii-redactor.spec.ts +0 -130
- package/src/utils/pii-redactor.ts +0 -288
- package/src/utils/setup/get-repositories.ts +0 -140
- package/src/utils/setup/init-services.ts +0 -422
- package/src/utils/setup/init-social.ts +0 -189
- package/src/utils/setup/init-storage.ts +0 -94
- package/src/utils/setup/register-mfa.ts +0 -165
- package/src/utils/setup/run-nauth-migrations.ts +0 -61
- package/src/utils/token-delivery-policy.ts +0 -38
- package/src/validators/template.validator.ts +0 -219
- package/tsconfig.json +0 -37
- package/tsconfig.lint.json +0 -6
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fastify Framework Adapter
|
|
3
|
-
*
|
|
4
|
-
* Adapts NAuth to work with Fastify with proper AsyncLocalStorage support.
|
|
5
|
-
*
|
|
6
|
-
* **Context Management:**
|
|
7
|
-
* - First hook (clientInfo) initializes AsyncLocalStorage context
|
|
8
|
-
* - Context is stored on request object for subsequent hooks
|
|
9
|
-
* - Each hook restores context using ContextStorage.enterStore()
|
|
10
|
-
* - Route handlers MUST use wrapRouteHandler() for context access
|
|
11
|
-
*
|
|
12
|
-
* **Why Context Restoration is Needed:**
|
|
13
|
-
* Unlike Express where middleware runs in a continuous call stack,
|
|
14
|
-
* Fastify hooks run independently. Each hook invocation loses the
|
|
15
|
-
* AsyncLocalStorage context, so we store it on the request and restore it.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
NAuthAdapter,
|
|
20
|
-
NAuthRequest,
|
|
21
|
-
NAuthResponse,
|
|
22
|
-
NAuthCookieOptions,
|
|
23
|
-
NAuthRequestAttributes,
|
|
24
|
-
NAuthMiddlewareHandler,
|
|
25
|
-
NAuthResponseInterceptorHandler,
|
|
26
|
-
NAuthRouteHandler,
|
|
27
|
-
MiddlewareOptions,
|
|
28
|
-
} from '../platform/interfaces';
|
|
29
|
-
import { ContextStorage } from '../utils/context-storage';
|
|
30
|
-
|
|
31
|
-
// Symbol for storing context on request (avoids property name collisions)
|
|
32
|
-
const NAUTH_CONTEXT_STORE = Symbol.for('nauth.contextStore');
|
|
33
|
-
const NAUTH_ATTRIBUTES = Symbol.for('nauth.attributes');
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Fastify Adapter
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Fastify Adapter Implementation
|
|
41
|
-
*
|
|
42
|
-
* Provides NAuth integration for Fastify applications.
|
|
43
|
-
*/
|
|
44
|
-
export class FastifyAdapter implements NAuthAdapter {
|
|
45
|
-
public readonly name = 'FastifyAdapter';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Register a middleware handler as Fastify hook
|
|
49
|
-
*
|
|
50
|
-
* Handles context initialization for first hook and restoration for subsequent hooks.
|
|
51
|
-
*/
|
|
52
|
-
public registerMiddleware(name: string, handler: NAuthMiddlewareHandler, options?: MiddlewareOptions): FastifyHook {
|
|
53
|
-
const initializesContext = options?.initializesContext || name === 'clientInfo';
|
|
54
|
-
|
|
55
|
-
return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
|
|
56
|
-
// Ensure we have attribute storage
|
|
57
|
-
this.ensureAttributes(request);
|
|
58
|
-
|
|
59
|
-
const nauthReq = new FastifyRequestWrapper(request);
|
|
60
|
-
const nauthRes = new FastifyResponseWrapper(reply);
|
|
61
|
-
|
|
62
|
-
if (initializesContext) {
|
|
63
|
-
// First hook - initialize context
|
|
64
|
-
await ContextStorage.run(async () => {
|
|
65
|
-
// Store context on request for subsequent hooks
|
|
66
|
-
(request as FastifyRequestWithContext)[NAUTH_CONTEXT_STORE] = ContextStorage.getStore();
|
|
67
|
-
await this.executeHandler(handler, nauthReq, nauthRes);
|
|
68
|
-
});
|
|
69
|
-
} else {
|
|
70
|
-
// Subsequent hook - restore context
|
|
71
|
-
const store = (request as FastifyRequestWithContext)[NAUTH_CONTEXT_STORE];
|
|
72
|
-
|
|
73
|
-
if (store) {
|
|
74
|
-
await ContextStorage.enterStore(store, async () => {
|
|
75
|
-
await this.executeHandler(handler, nauthReq, nauthRes);
|
|
76
|
-
});
|
|
77
|
-
} else {
|
|
78
|
-
// No context available - execute without context
|
|
79
|
-
// This shouldn't happen if clientInfo runs first
|
|
80
|
-
await this.executeHandler(handler, nauthReq, nauthRes);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Execute handler with proper async flow control
|
|
88
|
-
*/
|
|
89
|
-
private async executeHandler(handler: NAuthMiddlewareHandler, req: NAuthRequest, res: NAuthResponse): Promise<void> {
|
|
90
|
-
await new Promise<void>((resolve, reject) => {
|
|
91
|
-
const result = handler(req, res, () => {
|
|
92
|
-
resolve();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (result instanceof Promise) {
|
|
96
|
-
result.then(() => resolve()).catch(reject);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Register a response interceptor using Fastify onSend hook
|
|
103
|
-
*
|
|
104
|
-
* The onSend hook receives the serialized payload before sending.
|
|
105
|
-
*/
|
|
106
|
-
public registerResponseInterceptor(handler: NAuthResponseInterceptorHandler): FastifyOnSendHook {
|
|
107
|
-
return async (request: FastifyRequest, reply: FastifyReply, payload: unknown): Promise<unknown> => {
|
|
108
|
-
this.ensureAttributes(request);
|
|
109
|
-
|
|
110
|
-
const nauthReq = new FastifyRequestWrapper(request);
|
|
111
|
-
const nauthRes = new FastifyResponseWrapper(reply);
|
|
112
|
-
|
|
113
|
-
// Restore context for interceptor
|
|
114
|
-
const store = (request as FastifyRequestWithContext)[NAUTH_CONTEXT_STORE];
|
|
115
|
-
|
|
116
|
-
if (!store) {
|
|
117
|
-
// No context - pass through unchanged
|
|
118
|
-
return payload;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return ContextStorage.enterStore(store, async () => {
|
|
122
|
-
try {
|
|
123
|
-
let parsedPayload = payload;
|
|
124
|
-
|
|
125
|
-
// Parse JSON payload if needed (Fastify serializes before onSend)
|
|
126
|
-
const contentType = reply.getHeader('content-type') as string | undefined;
|
|
127
|
-
const isJson = contentType?.includes('application/json');
|
|
128
|
-
|
|
129
|
-
if (isJson && typeof payload === 'string') {
|
|
130
|
-
try {
|
|
131
|
-
parsedPayload = JSON.parse(payload);
|
|
132
|
-
} catch {
|
|
133
|
-
// Not valid JSON, use as-is
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const modifiedBody = await handler(nauthReq, nauthRes, parsedPayload);
|
|
138
|
-
|
|
139
|
-
// Re-serialize if we parsed it
|
|
140
|
-
if (modifiedBody !== parsedPayload && isJson && typeof modifiedBody === 'object') {
|
|
141
|
-
return JSON.stringify(modifiedBody);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return modifiedBody !== parsedPayload ? modifiedBody : payload;
|
|
145
|
-
} catch {
|
|
146
|
-
// On error, return original payload
|
|
147
|
-
return payload;
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Wrap a route handler to ensure context is available
|
|
155
|
-
*
|
|
156
|
-
* For Fastify, this is REQUIRED for route handlers to access ContextStorage.
|
|
157
|
-
*/
|
|
158
|
-
public wrapRouteHandler<T>(handler: NAuthRouteHandler<T>): FastifyRouteHandler {
|
|
159
|
-
return async (request: FastifyRequest, reply: FastifyReply): Promise<T | void> => {
|
|
160
|
-
this.ensureAttributes(request);
|
|
161
|
-
|
|
162
|
-
const nauthReq = new FastifyRequestWrapper(request);
|
|
163
|
-
const nauthRes = new FastifyResponseWrapper(reply);
|
|
164
|
-
|
|
165
|
-
// Restore context
|
|
166
|
-
const store = (request as FastifyRequestWithContext)[NAUTH_CONTEXT_STORE];
|
|
167
|
-
|
|
168
|
-
if (store) {
|
|
169
|
-
return ContextStorage.enterStore(store, async () => {
|
|
170
|
-
return handler(nauthReq, nauthRes);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// No context - execute without (shouldn't happen with proper setup)
|
|
175
|
-
return handler(nauthReq, nauthRes);
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Ensure attribute storage exists on request
|
|
181
|
-
*/
|
|
182
|
-
private ensureAttributes(request: FastifyRequest): void {
|
|
183
|
-
if (!(request as FastifyRequestWithContext)[NAUTH_ATTRIBUTES]) {
|
|
184
|
-
(request as FastifyRequestWithContext)[NAUTH_ATTRIBUTES] = {};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ============================================================================
|
|
190
|
-
// Fastify Request Wrapper
|
|
191
|
-
// ============================================================================
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Wraps Fastify request into NAuthRequest interface
|
|
195
|
-
*/
|
|
196
|
-
class FastifyRequestWrapper implements NAuthRequest {
|
|
197
|
-
constructor(private readonly request: FastifyRequest) {}
|
|
198
|
-
|
|
199
|
-
get raw(): unknown {
|
|
200
|
-
return this.request;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
get method(): string {
|
|
204
|
-
return this.request.method.toUpperCase();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
get path(): string {
|
|
208
|
-
// Fastify provides routeOptions.url for path, or extract from url
|
|
209
|
-
return this.request.url.split('?')[0];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
get url(): string {
|
|
213
|
-
return this.request.url;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
get body(): Record<string, unknown> {
|
|
217
|
-
return (this.request.body as Record<string, unknown>) || {};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
get query(): Record<string, unknown> {
|
|
221
|
-
return (this.request.query as Record<string, unknown>) || {};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
get params(): Record<string, string> {
|
|
225
|
-
return (this.request.params as Record<string, string>) || {};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
get headers(): Record<string, string | string[] | undefined> {
|
|
229
|
-
return this.request.headers as Record<string, string | string[] | undefined>;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
get cookies(): Record<string, string | undefined> {
|
|
233
|
-
// Fastify with @fastify/cookie
|
|
234
|
-
return ((this.request as FastifyRequestWithCookies).cookies as Record<string, string | undefined>) || {};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
get ip(): string {
|
|
238
|
-
return this.request.ip || '0.0.0.0';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
get attributes(): NAuthRequestAttributes {
|
|
242
|
-
// Return isolated storage
|
|
243
|
-
return (this.request as FastifyRequestWithContext)[NAUTH_ATTRIBUTES] || {};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
public getHeader(name: string): string | undefined {
|
|
247
|
-
const val = this.request.headers[name.toLowerCase()];
|
|
248
|
-
if (Array.isArray(val)) return val[0];
|
|
249
|
-
return val;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ============================================================================
|
|
254
|
-
// Fastify Response Wrapper
|
|
255
|
-
// ============================================================================
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Wraps Fastify reply into NAuthResponse interface
|
|
259
|
-
*/
|
|
260
|
-
class FastifyResponseWrapper implements NAuthResponse {
|
|
261
|
-
constructor(private readonly reply: FastifyReply) {}
|
|
262
|
-
|
|
263
|
-
get raw(): unknown {
|
|
264
|
-
return this.reply;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
public status(code: number): this {
|
|
268
|
-
this.reply.code(code);
|
|
269
|
-
return this;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
public header(name: string, value: string | string[]): this {
|
|
273
|
-
this.reply.header(name, value);
|
|
274
|
-
return this;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
public setCookie(name: string, value: string, options?: NAuthCookieOptions): this {
|
|
278
|
-
// Fastify with @fastify/cookie
|
|
279
|
-
const replyWithCookies = this.reply as FastifyReplyWithCookies;
|
|
280
|
-
if (typeof replyWithCookies.setCookie === 'function') {
|
|
281
|
-
replyWithCookies.setCookie(name, value, this.convertCookieOptions(options));
|
|
282
|
-
}
|
|
283
|
-
return this;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
public clearCookie(name: string, options?: NAuthCookieOptions): this {
|
|
287
|
-
const replyWithCookies = this.reply as FastifyReplyWithCookies;
|
|
288
|
-
if (typeof replyWithCookies.clearCookie === 'function') {
|
|
289
|
-
replyWithCookies.clearCookie(name, this.convertCookieOptions(options));
|
|
290
|
-
} else if (typeof replyWithCookies.setCookie === 'function') {
|
|
291
|
-
// Fallback: set empty cookie with immediate expiry
|
|
292
|
-
replyWithCookies.setCookie(name, '', {
|
|
293
|
-
...this.convertCookieOptions(options),
|
|
294
|
-
maxAge: 0,
|
|
295
|
-
expires: new Date(0),
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return this;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
public send(body: unknown): void {
|
|
302
|
-
this.reply.send(body);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
public json(body: unknown): void {
|
|
306
|
-
// Fastify auto-serializes objects
|
|
307
|
-
this.reply.send(body);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
public redirect(url: string, status?: number): void {
|
|
311
|
-
if (status) {
|
|
312
|
-
this.reply.redirect(status, url);
|
|
313
|
-
} else {
|
|
314
|
-
this.reply.redirect(url);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
public isSent(): boolean {
|
|
319
|
-
return this.reply.sent;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Convert NAuth cookie options to Fastify cookie options
|
|
324
|
-
*/
|
|
325
|
-
private convertCookieOptions(options?: NAuthCookieOptions): Record<string, unknown> {
|
|
326
|
-
if (!options) return {};
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
httpOnly: options.httpOnly,
|
|
330
|
-
secure: options.secure,
|
|
331
|
-
sameSite: options.sameSite,
|
|
332
|
-
domain: options.domain,
|
|
333
|
-
path: options.path,
|
|
334
|
-
maxAge: options.maxAge,
|
|
335
|
-
expires: options.expires,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ============================================================================
|
|
341
|
-
// Fastify Type Definitions (minimal, for internal use)
|
|
342
|
-
// ============================================================================
|
|
343
|
-
|
|
344
|
-
interface FastifyRequest {
|
|
345
|
-
method: string;
|
|
346
|
-
url: string;
|
|
347
|
-
body: unknown;
|
|
348
|
-
query: unknown;
|
|
349
|
-
params: unknown;
|
|
350
|
-
headers: Record<string, string | string[] | undefined>;
|
|
351
|
-
ip: string;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
interface FastifyRequestWithCookies extends FastifyRequest {
|
|
355
|
-
cookies?: Record<string, string>;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
interface FastifyRequestWithContext extends FastifyRequest {
|
|
359
|
-
[NAUTH_CONTEXT_STORE]?: Map<string, unknown>;
|
|
360
|
-
[NAUTH_ATTRIBUTES]?: NAuthRequestAttributes;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
interface FastifyReply {
|
|
364
|
-
code(statusCode: number): this;
|
|
365
|
-
header(name: string, value: string | string[]): this;
|
|
366
|
-
send(payload?: unknown): this;
|
|
367
|
-
redirect(url: string): this;
|
|
368
|
-
redirect(statusCode: number, url: string): this;
|
|
369
|
-
getHeader(name: string): string | undefined;
|
|
370
|
-
sent: boolean;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
interface FastifyReplyWithCookies extends FastifyReply {
|
|
374
|
-
setCookie?(name: string, value: string, options?: Record<string, unknown>): this;
|
|
375
|
-
clearCookie?(name: string, options?: Record<string, unknown>): this;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
type FastifyHook = (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
379
|
-
type FastifyOnSendHook = (request: FastifyRequest, reply: FastifyReply, payload: unknown) => Promise<unknown>;
|
|
380
|
-
type FastifyRouteHandler = (request: FastifyRequest, reply: FastifyReply) => Promise<unknown>;
|
|
381
|
-
|
|
382
|
-
// ============================================================================
|
|
383
|
-
// Convenience Export
|
|
384
|
-
// ============================================================================
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Convenience function for wrapping Fastify route handlers
|
|
388
|
-
*
|
|
389
|
-
* @deprecated Use adapter.wrapRouteHandler() instead. This is kept for backward compatibility.
|
|
390
|
-
*
|
|
391
|
-
* @example
|
|
392
|
-
* ```typescript
|
|
393
|
-
* // Old way (deprecated):
|
|
394
|
-
* fastify.get('/me', withNAuthContext(async (request, reply) => {...}));
|
|
395
|
-
*
|
|
396
|
-
* // New way (recommended):
|
|
397
|
-
* const handler = nauth.adapter.wrapRouteHandler(async (req, res) => {...});
|
|
398
|
-
* fastify.get('/me', handler);
|
|
399
|
-
* ```
|
|
400
|
-
*/
|
|
401
|
-
export function withNAuthContext<T>(
|
|
402
|
-
handler: (request: FastifyRequest, reply: FastifyReply) => Promise<T>,
|
|
403
|
-
): (request: FastifyRequest, reply: FastifyReply) => Promise<T | void> {
|
|
404
|
-
return async (request: FastifyRequest, reply: FastifyReply): Promise<T | void> => {
|
|
405
|
-
const store = (request as FastifyRequestWithContext)[NAUTH_CONTEXT_STORE];
|
|
406
|
-
|
|
407
|
-
if (store) {
|
|
408
|
-
return ContextStorage.enterStore(store, async () => {
|
|
409
|
-
return handler(request, reply);
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// No context - execute without
|
|
414
|
-
return handler(request, reply);
|
|
415
|
-
};
|
|
416
|
-
}
|
package/src/adapters/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Platform Adapters
|
|
3
|
-
*
|
|
4
|
-
* Framework-specific adapters that implement NAuthAdapter interface.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Platform interfaces (for custom adapter development)
|
|
8
|
-
export * from '../platform/interfaces';
|
|
9
|
-
|
|
10
|
-
// Built-in adapters
|
|
11
|
-
export { ExpressAdapter, ExpressMiddlewareType } from './express.adapter';
|
|
12
|
-
export { FastifyAdapter } from './fastify.adapter';
|
|
13
|
-
|
|
14
|
-
// Legacy export for backward compatibility (deprecated)
|
|
15
|
-
// TODO: Remove in next major version
|
|
16
|
-
export { withNAuthContext } from './fastify.adapter';
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage Adapter Factory Functions
|
|
3
|
-
*
|
|
4
|
-
* Provides clean factory functions for creating storage adapters.
|
|
5
|
-
* These factories handle proper initialization and simplify configuration.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { createDatabaseStorageAdapter, createRedisStorageAdapter } from '@nauth-toolkit/express';
|
|
10
|
-
*
|
|
11
|
-
* export const authConfig = {
|
|
12
|
-
* // Database adapter (uses existing TypeORM connection)
|
|
13
|
-
* storageAdapter: createDatabaseStorageAdapter(),
|
|
14
|
-
*
|
|
15
|
-
* // Or Redis adapter
|
|
16
|
-
* storageAdapter: createRedisStorageAdapter(process.env.REDIS_URL),
|
|
17
|
-
* };
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { StorageAdapter } from '../interfaces/storage-adapter.interface';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a database storage adapter
|
|
25
|
-
*
|
|
26
|
-
* Uses the existing TypeORM connection. Make sure storage entities are included
|
|
27
|
-
* in your DataSource configuration:
|
|
28
|
-
*
|
|
29
|
-
* ```typescript
|
|
30
|
-
* import { getNAuthStorageEntities } from '@nauth-toolkit/database-typeorm-postgres';
|
|
31
|
-
* const dataSource = new DataSource({
|
|
32
|
-
* entities: [...getNAuthEntities(), ...getNAuthStorageEntities()],
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @returns DatabaseStorageAdapter instance
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* import { createDatabaseStorageAdapter } from '@nauth-toolkit/express';
|
|
41
|
-
*
|
|
42
|
-
* export const authConfig = {
|
|
43
|
-
* storageAdapter: createDatabaseStorageAdapter(),
|
|
44
|
-
* // ... other config
|
|
45
|
-
* };
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export function createDatabaseStorageAdapter(): StorageAdapter {
|
|
49
|
-
// Lazy import to avoid bundling if not used
|
|
50
|
-
const { DatabaseStorageAdapter } = require('@nauth-toolkit/storage-database');
|
|
51
|
-
return new DatabaseStorageAdapter(null, null, null);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Create a Redis storage adapter
|
|
56
|
-
*
|
|
57
|
-
* Creates and connects a Redis client automatically using the `redis` package (node-redis).
|
|
58
|
-
* The client connection is managed internally by the adapter.
|
|
59
|
-
*
|
|
60
|
-
* Supports both single-instance Redis and Redis Cluster configurations.
|
|
61
|
-
*
|
|
62
|
-
* @param url - Redis connection URL (default: 'redis://localhost:6379')
|
|
63
|
-
* Supports authentication in URL format:
|
|
64
|
-
* - redis://localhost:6379 (no auth)
|
|
65
|
-
* - redis://:password@localhost:6379 (password only)
|
|
66
|
-
* - redis://username:password@localhost:6379 (username + password)
|
|
67
|
-
* - rediss://localhost:6379 (TLS/SSL, with optional auth)
|
|
68
|
-
* @returns RedisStorageAdapter instance
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* import { createRedisStorageAdapter, createRedisClusterAdapter } from '@nauth-toolkit/express';
|
|
73
|
-
*
|
|
74
|
-
* export const authConfig = {
|
|
75
|
-
* // Single-instance Redis
|
|
76
|
-
* storageAdapter: createRedisStorageAdapter(process.env.REDIS_URL),
|
|
77
|
-
*
|
|
78
|
-
* // Or Redis Cluster (for production high-availability)
|
|
79
|
-
* storageAdapter: createRedisClusterAdapter([
|
|
80
|
-
* { url: 'redis://redis-node-1:6379' },
|
|
81
|
-
* { url: 'redis://redis-node-2:6379' },
|
|
82
|
-
* { url: 'redis://redis-node-3:6379' },
|
|
83
|
-
* ]),
|
|
84
|
-
* };
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export function createRedisStorageAdapter(url: string = 'redis://localhost:6379'): StorageAdapter {
|
|
88
|
-
// Lazy import to avoid bundling if not used
|
|
89
|
-
const { RedisStorageAdapter } = require('@nauth-toolkit/storage-redis');
|
|
90
|
-
const { createClient } = require('redis');
|
|
91
|
-
|
|
92
|
-
const redisClient = createClient({ url });
|
|
93
|
-
|
|
94
|
-
// Don't connect here - let adapter.initialize() handle connection
|
|
95
|
-
// This ensures proper error handling and allows initialize() to wait for connection
|
|
96
|
-
|
|
97
|
-
return new RedisStorageAdapter(redisClient);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a Redis Cluster storage adapter
|
|
102
|
-
*
|
|
103
|
-
* Creates and connects a Redis Cluster client automatically using the `redis` package (node-redis).
|
|
104
|
-
* The cluster client handles automatic topology discovery, command routing, and failover.
|
|
105
|
-
*
|
|
106
|
-
* **Production Use:**
|
|
107
|
-
* Use Redis Cluster for high-availability production deployments. The cluster automatically:
|
|
108
|
-
* - Discovers cluster topology
|
|
109
|
-
* - Routes commands to correct nodes based on key hash slots
|
|
110
|
-
* - Handles node failures and redirects (MOVED/ASK errors)
|
|
111
|
-
* - Provides high availability and horizontal scaling
|
|
112
|
-
*
|
|
113
|
-
* @param nodes - Array of cluster node URLs
|
|
114
|
-
* @returns RedisStorageAdapter instance
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```typescript
|
|
118
|
-
* import { createRedisClusterAdapter } from '@nauth-toolkit/express';
|
|
119
|
-
*
|
|
120
|
-
* export const authConfig = {
|
|
121
|
-
* // Redis Cluster with 3 nodes
|
|
122
|
-
* storageAdapter: createRedisClusterAdapter([
|
|
123
|
-
* { url: 'redis://redis-node-1:6379' },
|
|
124
|
-
* { url: 'redis://redis-node-2:6379' },
|
|
125
|
-
* { url: 'redis://redis-node-3:6379' },
|
|
126
|
-
* ]),
|
|
127
|
-
* };
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
export function createRedisClusterAdapter(nodes: Array<{ url: string }>): StorageAdapter {
|
|
131
|
-
// Lazy import to avoid bundling if not used
|
|
132
|
-
const { RedisStorageAdapter } = require('@nauth-toolkit/storage-redis');
|
|
133
|
-
const { createCluster } = require('redis');
|
|
134
|
-
|
|
135
|
-
const clusterClient = createCluster({
|
|
136
|
-
rootNodes: nodes,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Don't connect here - let adapter.initialize() handle connection
|
|
140
|
-
// This ensures proper error handling and allows initialize() to wait for connection
|
|
141
|
-
|
|
142
|
-
return new RedisStorageAdapter(clusterClient);
|
|
143
|
-
}
|