next-api-layer 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 +21 -0
- package/README.md +743 -0
- package/dist/api.cjs +2 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +42 -0
- package/dist/api.d.ts +42 -0
- package/dist/api.js +2 -0
- package/dist/api.js.map +1 -0
- package/dist/chunk-6ENVQMWQ.cjs +2 -0
- package/dist/chunk-6ENVQMWQ.cjs.map +1 -0
- package/dist/chunk-NBYI46RO.js +2 -0
- package/dist/chunk-NBYI46RO.js.map +1 -0
- package/dist/chunk-OXXKU4OM.cjs +2 -0
- package/dist/chunk-OXXKU4OM.cjs.map +1 -0
- package/dist/chunk-XBAO7FJN.js +2 -0
- package/dist/chunk-XBAO7FJN.js.map +1 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +14 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/client.cjs +3 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +169 -0
- package/dist/client.d.ts +169 -0
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -0
- package/dist/createApiClient-CIDYcpNI.d.cts +383 -0
- package/dist/createApiClient-CIDYcpNI.d.ts +383 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +2 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +77 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/package.json +124 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { R as ResolvedRateLimitConfig, a as ResolvedCsrfConfig, A as AuthProxyConfig, I as InternalProxyConfig, b as AuditEventType, c as ResolvedAuditConfig, T as TokenInfo, d as RefreshResult, C as CookieOptions, E as EndpointConfig } from './createApiClient-CIDYcpNI.cjs';
|
|
3
|
+
export { e as AccessConfig, f as ApiClient, g as ApiClientConfig, h as ApiResponse, i as AuditConfig, j as AuditEvent, k as AuthData, l as AuthResult, m as CookieConfig, n as CsrfConfig, G as GuestTokenConfig, o as I18nConfig, p as RateLimitConfig, q as ResponseMappers, S as SanitizationConfig, U as UserData, r as createApiClient } from './createApiClient-CIDYcpNI.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Rate Limiting
|
|
7
|
+
*
|
|
8
|
+
* Implements token bucket algorithm for rate limiting.
|
|
9
|
+
* In-memory store by default, designed for single-instance deployments.
|
|
10
|
+
*
|
|
11
|
+
* For horizontal scaling (multiple instances), use a custom store
|
|
12
|
+
* with Redis or similar distributed cache.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface RateLimitResult {
|
|
16
|
+
allowed: boolean;
|
|
17
|
+
remaining: number;
|
|
18
|
+
resetAt: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates a rate limiter with in-memory store
|
|
23
|
+
*/
|
|
24
|
+
declare function createRateLimiter(config: ResolvedRateLimitConfig): {
|
|
25
|
+
check: (req: NextRequest) => RateLimitResult;
|
|
26
|
+
applyHeaders: (response: NextResponse, result: RateLimitResult) => NextResponse;
|
|
27
|
+
createLimitedResponse: (req: NextRequest, result: RateLimitResult) => NextResponse;
|
|
28
|
+
shouldSkip: (pathname: string) => boolean;
|
|
29
|
+
reset: (key: string) => void;
|
|
30
|
+
clear: () => void;
|
|
31
|
+
size: () => number;
|
|
32
|
+
};
|
|
33
|
+
type RateLimiter = ReturnType<typeof createRateLimiter>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* CSRF Protection
|
|
37
|
+
*
|
|
38
|
+
* Implements OWASP recommended CSRF protection:
|
|
39
|
+
* - Fetch Metadata (Sec-Fetch-Site header) for modern browsers (98%+ coverage)
|
|
40
|
+
* - Signed HMAC Double-Submit Cookie as fallback
|
|
41
|
+
*
|
|
42
|
+
* @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
interface CsrfValidationResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates CSRF validator functions
|
|
51
|
+
*/
|
|
52
|
+
declare function createCsrfValidator(config: ResolvedCsrfConfig): {
|
|
53
|
+
validateRequest: (req: NextRequest) => CsrfValidationResult;
|
|
54
|
+
generateToken: (sessionId: string) => Promise<string>;
|
|
55
|
+
attachCsrfCookie: (response: NextResponse, sessionId: string) => Promise<NextResponse>;
|
|
56
|
+
};
|
|
57
|
+
type CsrfValidator = ReturnType<typeof createCsrfValidator>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates an authentication proxy middleware for Next.js
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* // middleware.ts
|
|
65
|
+
* import { createAuthProxy } from 'next-api-layer';
|
|
66
|
+
*
|
|
67
|
+
* const authProxy = createAuthProxy({
|
|
68
|
+
* apiBaseUrl: process.env.API_BASE_URL!,
|
|
69
|
+
* cookies: {
|
|
70
|
+
* user: 'userAuthToken',
|
|
71
|
+
* guest: 'guestAuthToken',
|
|
72
|
+
* },
|
|
73
|
+
* guestToken: {
|
|
74
|
+
* enabled: true,
|
|
75
|
+
* credentials: {
|
|
76
|
+
* username: process.env.GUEST_USERNAME!,
|
|
77
|
+
* password: process.env.GUEST_PASSWORD!,
|
|
78
|
+
* },
|
|
79
|
+
* },
|
|
80
|
+
* access: {
|
|
81
|
+
* protectedRoutes: ['/dashboard', '/profile'],
|
|
82
|
+
* authRoutes: ['/login', '/register'],
|
|
83
|
+
* },
|
|
84
|
+
* // Security features
|
|
85
|
+
* csrf: { enabled: true },
|
|
86
|
+
* rateLimit: { enabled: true, maxRequests: 100 },
|
|
87
|
+
* audit: {
|
|
88
|
+
* enabled: true,
|
|
89
|
+
* logger: (event) => console.log('[AUDIT]', event)
|
|
90
|
+
* },
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* export default authProxy;
|
|
94
|
+
*
|
|
95
|
+
* export const config = {
|
|
96
|
+
* matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
97
|
+
* };
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function createAuthProxy(userConfig: AuthProxyConfig): {
|
|
101
|
+
(req: NextRequest): Promise<NextResponse>;
|
|
102
|
+
config: InternalProxyConfig;
|
|
103
|
+
csrf: {
|
|
104
|
+
validateRequest: (req: NextRequest) => CsrfValidationResult;
|
|
105
|
+
generateToken: (sessionId: string) => Promise<string>;
|
|
106
|
+
attachCsrfCookie: (response: NextResponse, sessionId: string) => Promise<NextResponse>;
|
|
107
|
+
};
|
|
108
|
+
rateLimiter: {
|
|
109
|
+
check: (req: NextRequest) => RateLimitResult;
|
|
110
|
+
applyHeaders: (response: NextResponse, result: RateLimitResult) => NextResponse;
|
|
111
|
+
createLimitedResponse: (req: NextRequest, result: RateLimitResult) => NextResponse;
|
|
112
|
+
shouldSkip: (pathname: string) => boolean;
|
|
113
|
+
reset: (key: string) => void;
|
|
114
|
+
clear: () => void;
|
|
115
|
+
size: () => number;
|
|
116
|
+
};
|
|
117
|
+
audit: {
|
|
118
|
+
emit: (type: AuditEventType, req: NextRequest, options: {
|
|
119
|
+
success: boolean;
|
|
120
|
+
userId?: string;
|
|
121
|
+
metadata?: Record<string, unknown>;
|
|
122
|
+
}) => Promise<void>;
|
|
123
|
+
isEnabled: (type: AuditEventType) => boolean;
|
|
124
|
+
authSuccess: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
125
|
+
authFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
126
|
+
authRefresh: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
127
|
+
authGuest: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
128
|
+
accessDenied: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
129
|
+
csrfFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
130
|
+
rateLimitExceeded: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
131
|
+
error: (req: NextRequest, err: Error, metadata?: Record<string, unknown>) => Promise<void>;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
type AuthProxy = ReturnType<typeof createAuthProxy>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* createProxyHandler
|
|
138
|
+
* Factory function to create Next.js API route handlers that proxy requests to backend
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
interface ProxyHandlerConfig {
|
|
142
|
+
/** Base URL of the backend API */
|
|
143
|
+
apiBaseUrl: string;
|
|
144
|
+
/** Cookie name for user auth token */
|
|
145
|
+
userCookieName?: string;
|
|
146
|
+
/** Cookie name for guest auth token */
|
|
147
|
+
guestCookieName?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Default behavior for auth - if true, all requests skip auth by default
|
|
150
|
+
* Individual requests can override with X-Skip-Auth header
|
|
151
|
+
*/
|
|
152
|
+
skipAuthByDefault?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Public endpoints that should never include auth token (glob patterns)
|
|
155
|
+
* e.g., ['news/*', 'public/**', 'categories']
|
|
156
|
+
*/
|
|
157
|
+
publicEndpoints?: string[];
|
|
158
|
+
/** Headers to forward from client request */
|
|
159
|
+
forwardHeaders?: string[];
|
|
160
|
+
/** Headers to exclude from forwarding */
|
|
161
|
+
excludeHeaders?: string[];
|
|
162
|
+
/** Custom request transformer */
|
|
163
|
+
transformRequest?: (req: NextRequest, headers: Headers) => Headers | Promise<Headers>;
|
|
164
|
+
/** Custom response transformer */
|
|
165
|
+
transformResponse?: (response: Response) => Response | Promise<Response>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Creates a proxy handler for Next.js API routes
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* // app/api/[...path]/route.ts
|
|
173
|
+
* import { createProxyHandler } from 'next-api-layer';
|
|
174
|
+
*
|
|
175
|
+
* const handler = createProxyHandler({
|
|
176
|
+
* apiBaseUrl: process.env.API_BASE_URL!,
|
|
177
|
+
* userCookieName: 'auth_token',
|
|
178
|
+
* guestCookieName: 'guest_token',
|
|
179
|
+
* publicEndpoints: ['news/*', 'categories', 'public/**'],
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* export const GET = handler;
|
|
183
|
+
* export const POST = handler;
|
|
184
|
+
* export const PUT = handler;
|
|
185
|
+
* export const PATCH = handler;
|
|
186
|
+
* export const DELETE = handler;
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function createProxyHandler(config: ProxyHandlerConfig): {
|
|
190
|
+
(req: NextRequest): Promise<NextResponse>;
|
|
191
|
+
config: ProxyHandlerConfig;
|
|
192
|
+
};
|
|
193
|
+
type ProxyHandler = ReturnType<typeof createProxyHandler>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Audit Logging
|
|
197
|
+
*
|
|
198
|
+
* Event-based security audit logging system.
|
|
199
|
+
* Emits events for authentication, access control, and security violations.
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates an audit logger instance
|
|
204
|
+
*/
|
|
205
|
+
declare function createAuditLogger(config: ResolvedAuditConfig): {
|
|
206
|
+
emit: (type: AuditEventType, req: NextRequest, options: {
|
|
207
|
+
success: boolean;
|
|
208
|
+
userId?: string;
|
|
209
|
+
metadata?: Record<string, unknown>;
|
|
210
|
+
}) => Promise<void>;
|
|
211
|
+
isEnabled: (type: AuditEventType) => boolean;
|
|
212
|
+
authSuccess: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
213
|
+
authFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
214
|
+
authRefresh: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
215
|
+
authGuest: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
216
|
+
accessDenied: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
217
|
+
csrfFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
218
|
+
rateLimitExceeded: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
219
|
+
error: (req: NextRequest, err: Error, metadata?: Record<string, unknown>) => Promise<void>;
|
|
220
|
+
};
|
|
221
|
+
type AuditLogger = ReturnType<typeof createAuditLogger>;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Token Validation
|
|
225
|
+
* Handles token validation and refresh with backend API
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates token validation functions
|
|
230
|
+
*/
|
|
231
|
+
declare function createTokenValidation(config: InternalProxyConfig): {
|
|
232
|
+
validateToken: (token: string) => Promise<TokenInfo>;
|
|
233
|
+
getTokenInfo: (token: string) => Promise<TokenInfo>;
|
|
234
|
+
refreshToken: (oldToken: string) => Promise<RefreshResult>;
|
|
235
|
+
createGuestToken: () => Promise<string | null>;
|
|
236
|
+
};
|
|
237
|
+
type TokenValidation = ReturnType<typeof createTokenValidation>;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Proxy Handlers
|
|
241
|
+
* Request handling logic for different scenarios
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Creates proxy handlers
|
|
246
|
+
*/
|
|
247
|
+
declare function createHandlers(config: InternalProxyConfig, validation: TokenValidation): {
|
|
248
|
+
deleteAllAuthCookies: (req: NextRequest, response: NextResponse) => NextResponse;
|
|
249
|
+
jsonError: (message: string, status?: number) => NextResponse;
|
|
250
|
+
isAuthPage: (pathname: string) => boolean;
|
|
251
|
+
isProtectedRoute: (pathname: string) => boolean;
|
|
252
|
+
isPublicRoute: (pathname: string) => boolean;
|
|
253
|
+
isTokenTypeAllowed: (tokenType: string | null) => boolean;
|
|
254
|
+
handleNoToken: (req: NextRequest, isApiRoute: boolean) => Promise<NextResponse>;
|
|
255
|
+
handleValidationResult: (req: NextRequest, tokenInfo: TokenInfo, isUserToken: boolean, currentToken: string, isApiRoute: boolean) => Promise<NextResponse>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Shared Constants
|
|
260
|
+
* Default values and constant configurations
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
declare const DEFAULT_COOKIE_OPTIONS: Required<CookieOptions>;
|
|
264
|
+
declare const DEFAULT_ENDPOINTS: Required<EndpointConfig>;
|
|
265
|
+
declare const TOKEN_TYPES: {
|
|
266
|
+
readonly GUEST: "guest";
|
|
267
|
+
};
|
|
268
|
+
declare const ERROR_MESSAGES: {
|
|
269
|
+
readonly NO_TOKEN: "Token bulunamadı.";
|
|
270
|
+
readonly INVALID_TOKEN: "Token geçersiz veya süresi dolmuş.";
|
|
271
|
+
readonly CONNECTION_ERROR: "Bağlantı hatası oluştu.";
|
|
272
|
+
readonly UNAUTHORIZED: "Bu işlem için yetkiniz yok.";
|
|
273
|
+
};
|
|
274
|
+
declare const HEADERS: {
|
|
275
|
+
readonly AUTH_USER: "x-auth-user";
|
|
276
|
+
readonly REFRESHED_TOKEN: "x-refreshed-token";
|
|
277
|
+
readonly AUTHORIZATION: "Authorization";
|
|
278
|
+
readonly CONTENT_TYPE: "Content-Type";
|
|
279
|
+
readonly SKIP_AUTH: "x-skip-auth";
|
|
280
|
+
readonly LOCALE: "x-locale";
|
|
281
|
+
};
|
|
282
|
+
/** CSRF safe methods that don't need validation */
|
|
283
|
+
declare const CSRF_SAFE_METHODS: readonly ["GET", "HEAD", "OPTIONS"];
|
|
284
|
+
declare const DEFAULT_CSRF_CONFIG: {
|
|
285
|
+
readonly strategy: "both";
|
|
286
|
+
readonly cookieName: "__csrf";
|
|
287
|
+
readonly headerName: "x-csrf-token";
|
|
288
|
+
readonly ignoreMethods: readonly ["GET", "HEAD", "OPTIONS"];
|
|
289
|
+
readonly trustSameSite: false;
|
|
290
|
+
};
|
|
291
|
+
declare const DEFAULT_RATE_LIMIT_CONFIG: {
|
|
292
|
+
readonly windowMs: number;
|
|
293
|
+
readonly maxRequests: 100;
|
|
294
|
+
readonly skipRoutes: string[];
|
|
295
|
+
};
|
|
296
|
+
declare const DEFAULT_AUDIT_CONFIG: {
|
|
297
|
+
readonly events: readonly ["auth:success", "auth:fail", "auth:refresh", "access:denied", "csrf:fail", "rateLimit:exceeded", "error"];
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export { AuditEventType, type AuditLogger, type AuthProxy, AuthProxyConfig, CSRF_SAFE_METHODS, CookieOptions, type CsrfValidator, DEFAULT_AUDIT_CONFIG, DEFAULT_COOKIE_OPTIONS, DEFAULT_CSRF_CONFIG, DEFAULT_ENDPOINTS, DEFAULT_RATE_LIMIT_CONFIG, ERROR_MESSAGES, EndpointConfig, HEADERS, type ProxyHandler, type ProxyHandlerConfig, type RateLimiter, TOKEN_TYPES, TokenInfo, createAuditLogger, createAuthProxy, createCsrfValidator, createHandlers, createProxyHandler, createRateLimiter, createTokenValidation };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { R as ResolvedRateLimitConfig, a as ResolvedCsrfConfig, A as AuthProxyConfig, I as InternalProxyConfig, b as AuditEventType, c as ResolvedAuditConfig, T as TokenInfo, d as RefreshResult, C as CookieOptions, E as EndpointConfig } from './createApiClient-CIDYcpNI.js';
|
|
3
|
+
export { e as AccessConfig, f as ApiClient, g as ApiClientConfig, h as ApiResponse, i as AuditConfig, j as AuditEvent, k as AuthData, l as AuthResult, m as CookieConfig, n as CsrfConfig, G as GuestTokenConfig, o as I18nConfig, p as RateLimitConfig, q as ResponseMappers, S as SanitizationConfig, U as UserData, r as createApiClient } from './createApiClient-CIDYcpNI.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Rate Limiting
|
|
7
|
+
*
|
|
8
|
+
* Implements token bucket algorithm for rate limiting.
|
|
9
|
+
* In-memory store by default, designed for single-instance deployments.
|
|
10
|
+
*
|
|
11
|
+
* For horizontal scaling (multiple instances), use a custom store
|
|
12
|
+
* with Redis or similar distributed cache.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface RateLimitResult {
|
|
16
|
+
allowed: boolean;
|
|
17
|
+
remaining: number;
|
|
18
|
+
resetAt: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates a rate limiter with in-memory store
|
|
23
|
+
*/
|
|
24
|
+
declare function createRateLimiter(config: ResolvedRateLimitConfig): {
|
|
25
|
+
check: (req: NextRequest) => RateLimitResult;
|
|
26
|
+
applyHeaders: (response: NextResponse, result: RateLimitResult) => NextResponse;
|
|
27
|
+
createLimitedResponse: (req: NextRequest, result: RateLimitResult) => NextResponse;
|
|
28
|
+
shouldSkip: (pathname: string) => boolean;
|
|
29
|
+
reset: (key: string) => void;
|
|
30
|
+
clear: () => void;
|
|
31
|
+
size: () => number;
|
|
32
|
+
};
|
|
33
|
+
type RateLimiter = ReturnType<typeof createRateLimiter>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* CSRF Protection
|
|
37
|
+
*
|
|
38
|
+
* Implements OWASP recommended CSRF protection:
|
|
39
|
+
* - Fetch Metadata (Sec-Fetch-Site header) for modern browsers (98%+ coverage)
|
|
40
|
+
* - Signed HMAC Double-Submit Cookie as fallback
|
|
41
|
+
*
|
|
42
|
+
* @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
interface CsrfValidationResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates CSRF validator functions
|
|
51
|
+
*/
|
|
52
|
+
declare function createCsrfValidator(config: ResolvedCsrfConfig): {
|
|
53
|
+
validateRequest: (req: NextRequest) => CsrfValidationResult;
|
|
54
|
+
generateToken: (sessionId: string) => Promise<string>;
|
|
55
|
+
attachCsrfCookie: (response: NextResponse, sessionId: string) => Promise<NextResponse>;
|
|
56
|
+
};
|
|
57
|
+
type CsrfValidator = ReturnType<typeof createCsrfValidator>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates an authentication proxy middleware for Next.js
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* // middleware.ts
|
|
65
|
+
* import { createAuthProxy } from 'next-api-layer';
|
|
66
|
+
*
|
|
67
|
+
* const authProxy = createAuthProxy({
|
|
68
|
+
* apiBaseUrl: process.env.API_BASE_URL!,
|
|
69
|
+
* cookies: {
|
|
70
|
+
* user: 'userAuthToken',
|
|
71
|
+
* guest: 'guestAuthToken',
|
|
72
|
+
* },
|
|
73
|
+
* guestToken: {
|
|
74
|
+
* enabled: true,
|
|
75
|
+
* credentials: {
|
|
76
|
+
* username: process.env.GUEST_USERNAME!,
|
|
77
|
+
* password: process.env.GUEST_PASSWORD!,
|
|
78
|
+
* },
|
|
79
|
+
* },
|
|
80
|
+
* access: {
|
|
81
|
+
* protectedRoutes: ['/dashboard', '/profile'],
|
|
82
|
+
* authRoutes: ['/login', '/register'],
|
|
83
|
+
* },
|
|
84
|
+
* // Security features
|
|
85
|
+
* csrf: { enabled: true },
|
|
86
|
+
* rateLimit: { enabled: true, maxRequests: 100 },
|
|
87
|
+
* audit: {
|
|
88
|
+
* enabled: true,
|
|
89
|
+
* logger: (event) => console.log('[AUDIT]', event)
|
|
90
|
+
* },
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* export default authProxy;
|
|
94
|
+
*
|
|
95
|
+
* export const config = {
|
|
96
|
+
* matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
97
|
+
* };
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function createAuthProxy(userConfig: AuthProxyConfig): {
|
|
101
|
+
(req: NextRequest): Promise<NextResponse>;
|
|
102
|
+
config: InternalProxyConfig;
|
|
103
|
+
csrf: {
|
|
104
|
+
validateRequest: (req: NextRequest) => CsrfValidationResult;
|
|
105
|
+
generateToken: (sessionId: string) => Promise<string>;
|
|
106
|
+
attachCsrfCookie: (response: NextResponse, sessionId: string) => Promise<NextResponse>;
|
|
107
|
+
};
|
|
108
|
+
rateLimiter: {
|
|
109
|
+
check: (req: NextRequest) => RateLimitResult;
|
|
110
|
+
applyHeaders: (response: NextResponse, result: RateLimitResult) => NextResponse;
|
|
111
|
+
createLimitedResponse: (req: NextRequest, result: RateLimitResult) => NextResponse;
|
|
112
|
+
shouldSkip: (pathname: string) => boolean;
|
|
113
|
+
reset: (key: string) => void;
|
|
114
|
+
clear: () => void;
|
|
115
|
+
size: () => number;
|
|
116
|
+
};
|
|
117
|
+
audit: {
|
|
118
|
+
emit: (type: AuditEventType, req: NextRequest, options: {
|
|
119
|
+
success: boolean;
|
|
120
|
+
userId?: string;
|
|
121
|
+
metadata?: Record<string, unknown>;
|
|
122
|
+
}) => Promise<void>;
|
|
123
|
+
isEnabled: (type: AuditEventType) => boolean;
|
|
124
|
+
authSuccess: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
125
|
+
authFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
126
|
+
authRefresh: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
127
|
+
authGuest: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
128
|
+
accessDenied: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
129
|
+
csrfFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
130
|
+
rateLimitExceeded: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
131
|
+
error: (req: NextRequest, err: Error, metadata?: Record<string, unknown>) => Promise<void>;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
type AuthProxy = ReturnType<typeof createAuthProxy>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* createProxyHandler
|
|
138
|
+
* Factory function to create Next.js API route handlers that proxy requests to backend
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
interface ProxyHandlerConfig {
|
|
142
|
+
/** Base URL of the backend API */
|
|
143
|
+
apiBaseUrl: string;
|
|
144
|
+
/** Cookie name for user auth token */
|
|
145
|
+
userCookieName?: string;
|
|
146
|
+
/** Cookie name for guest auth token */
|
|
147
|
+
guestCookieName?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Default behavior for auth - if true, all requests skip auth by default
|
|
150
|
+
* Individual requests can override with X-Skip-Auth header
|
|
151
|
+
*/
|
|
152
|
+
skipAuthByDefault?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Public endpoints that should never include auth token (glob patterns)
|
|
155
|
+
* e.g., ['news/*', 'public/**', 'categories']
|
|
156
|
+
*/
|
|
157
|
+
publicEndpoints?: string[];
|
|
158
|
+
/** Headers to forward from client request */
|
|
159
|
+
forwardHeaders?: string[];
|
|
160
|
+
/** Headers to exclude from forwarding */
|
|
161
|
+
excludeHeaders?: string[];
|
|
162
|
+
/** Custom request transformer */
|
|
163
|
+
transformRequest?: (req: NextRequest, headers: Headers) => Headers | Promise<Headers>;
|
|
164
|
+
/** Custom response transformer */
|
|
165
|
+
transformResponse?: (response: Response) => Response | Promise<Response>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Creates a proxy handler for Next.js API routes
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* // app/api/[...path]/route.ts
|
|
173
|
+
* import { createProxyHandler } from 'next-api-layer';
|
|
174
|
+
*
|
|
175
|
+
* const handler = createProxyHandler({
|
|
176
|
+
* apiBaseUrl: process.env.API_BASE_URL!,
|
|
177
|
+
* userCookieName: 'auth_token',
|
|
178
|
+
* guestCookieName: 'guest_token',
|
|
179
|
+
* publicEndpoints: ['news/*', 'categories', 'public/**'],
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* export const GET = handler;
|
|
183
|
+
* export const POST = handler;
|
|
184
|
+
* export const PUT = handler;
|
|
185
|
+
* export const PATCH = handler;
|
|
186
|
+
* export const DELETE = handler;
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function createProxyHandler(config: ProxyHandlerConfig): {
|
|
190
|
+
(req: NextRequest): Promise<NextResponse>;
|
|
191
|
+
config: ProxyHandlerConfig;
|
|
192
|
+
};
|
|
193
|
+
type ProxyHandler = ReturnType<typeof createProxyHandler>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Audit Logging
|
|
197
|
+
*
|
|
198
|
+
* Event-based security audit logging system.
|
|
199
|
+
* Emits events for authentication, access control, and security violations.
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates an audit logger instance
|
|
204
|
+
*/
|
|
205
|
+
declare function createAuditLogger(config: ResolvedAuditConfig): {
|
|
206
|
+
emit: (type: AuditEventType, req: NextRequest, options: {
|
|
207
|
+
success: boolean;
|
|
208
|
+
userId?: string;
|
|
209
|
+
metadata?: Record<string, unknown>;
|
|
210
|
+
}) => Promise<void>;
|
|
211
|
+
isEnabled: (type: AuditEventType) => boolean;
|
|
212
|
+
authSuccess: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
213
|
+
authFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
214
|
+
authRefresh: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
215
|
+
authGuest: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
216
|
+
accessDenied: (req: NextRequest, userId?: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
217
|
+
csrfFail: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
218
|
+
rateLimitExceeded: (req: NextRequest, metadata?: Record<string, unknown>) => Promise<void>;
|
|
219
|
+
error: (req: NextRequest, err: Error, metadata?: Record<string, unknown>) => Promise<void>;
|
|
220
|
+
};
|
|
221
|
+
type AuditLogger = ReturnType<typeof createAuditLogger>;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Token Validation
|
|
225
|
+
* Handles token validation and refresh with backend API
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates token validation functions
|
|
230
|
+
*/
|
|
231
|
+
declare function createTokenValidation(config: InternalProxyConfig): {
|
|
232
|
+
validateToken: (token: string) => Promise<TokenInfo>;
|
|
233
|
+
getTokenInfo: (token: string) => Promise<TokenInfo>;
|
|
234
|
+
refreshToken: (oldToken: string) => Promise<RefreshResult>;
|
|
235
|
+
createGuestToken: () => Promise<string | null>;
|
|
236
|
+
};
|
|
237
|
+
type TokenValidation = ReturnType<typeof createTokenValidation>;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Proxy Handlers
|
|
241
|
+
* Request handling logic for different scenarios
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Creates proxy handlers
|
|
246
|
+
*/
|
|
247
|
+
declare function createHandlers(config: InternalProxyConfig, validation: TokenValidation): {
|
|
248
|
+
deleteAllAuthCookies: (req: NextRequest, response: NextResponse) => NextResponse;
|
|
249
|
+
jsonError: (message: string, status?: number) => NextResponse;
|
|
250
|
+
isAuthPage: (pathname: string) => boolean;
|
|
251
|
+
isProtectedRoute: (pathname: string) => boolean;
|
|
252
|
+
isPublicRoute: (pathname: string) => boolean;
|
|
253
|
+
isTokenTypeAllowed: (tokenType: string | null) => boolean;
|
|
254
|
+
handleNoToken: (req: NextRequest, isApiRoute: boolean) => Promise<NextResponse>;
|
|
255
|
+
handleValidationResult: (req: NextRequest, tokenInfo: TokenInfo, isUserToken: boolean, currentToken: string, isApiRoute: boolean) => Promise<NextResponse>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Shared Constants
|
|
260
|
+
* Default values and constant configurations
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
declare const DEFAULT_COOKIE_OPTIONS: Required<CookieOptions>;
|
|
264
|
+
declare const DEFAULT_ENDPOINTS: Required<EndpointConfig>;
|
|
265
|
+
declare const TOKEN_TYPES: {
|
|
266
|
+
readonly GUEST: "guest";
|
|
267
|
+
};
|
|
268
|
+
declare const ERROR_MESSAGES: {
|
|
269
|
+
readonly NO_TOKEN: "Token bulunamadı.";
|
|
270
|
+
readonly INVALID_TOKEN: "Token geçersiz veya süresi dolmuş.";
|
|
271
|
+
readonly CONNECTION_ERROR: "Bağlantı hatası oluştu.";
|
|
272
|
+
readonly UNAUTHORIZED: "Bu işlem için yetkiniz yok.";
|
|
273
|
+
};
|
|
274
|
+
declare const HEADERS: {
|
|
275
|
+
readonly AUTH_USER: "x-auth-user";
|
|
276
|
+
readonly REFRESHED_TOKEN: "x-refreshed-token";
|
|
277
|
+
readonly AUTHORIZATION: "Authorization";
|
|
278
|
+
readonly CONTENT_TYPE: "Content-Type";
|
|
279
|
+
readonly SKIP_AUTH: "x-skip-auth";
|
|
280
|
+
readonly LOCALE: "x-locale";
|
|
281
|
+
};
|
|
282
|
+
/** CSRF safe methods that don't need validation */
|
|
283
|
+
declare const CSRF_SAFE_METHODS: readonly ["GET", "HEAD", "OPTIONS"];
|
|
284
|
+
declare const DEFAULT_CSRF_CONFIG: {
|
|
285
|
+
readonly strategy: "both";
|
|
286
|
+
readonly cookieName: "__csrf";
|
|
287
|
+
readonly headerName: "x-csrf-token";
|
|
288
|
+
readonly ignoreMethods: readonly ["GET", "HEAD", "OPTIONS"];
|
|
289
|
+
readonly trustSameSite: false;
|
|
290
|
+
};
|
|
291
|
+
declare const DEFAULT_RATE_LIMIT_CONFIG: {
|
|
292
|
+
readonly windowMs: number;
|
|
293
|
+
readonly maxRequests: 100;
|
|
294
|
+
readonly skipRoutes: string[];
|
|
295
|
+
};
|
|
296
|
+
declare const DEFAULT_AUDIT_CONFIG: {
|
|
297
|
+
readonly events: readonly ["auth:success", "auth:fail", "auth:refresh", "access:denied", "csrf:fail", "rateLimit:exceeded", "error"];
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export { AuditEventType, type AuditLogger, type AuthProxy, AuthProxyConfig, CSRF_SAFE_METHODS, CookieOptions, type CsrfValidator, DEFAULT_AUDIT_CONFIG, DEFAULT_COOKIE_OPTIONS, DEFAULT_CSRF_CONFIG, DEFAULT_ENDPOINTS, DEFAULT_RATE_LIMIT_CONFIG, ERROR_MESSAGES, EndpointConfig, HEADERS, type ProxyHandler, type ProxyHandlerConfig, type RateLimiter, TOKEN_TYPES, TokenInfo, createAuditLogger, createAuthProxy, createCsrfValidator, createHandlers, createProxyHandler, createRateLimiter, createTokenValidation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export{d as createApiClient}from'./chunk-NBYI46RO.js';import {c,e,a,b,g,h,i}from'./chunk-XBAO7FJN.js';export{f as CSRF_SAFE_METHODS,i as DEFAULT_AUDIT_CONFIG,a as DEFAULT_COOKIE_OPTIONS,g as DEFAULT_CSRF_CONFIG,b as DEFAULT_ENDPOINTS,h as DEFAULT_RATE_LIMIT_CONFIG,d as ERROR_MESSAGES,e as HEADERS,c as TOKEN_TYPES}from'./chunk-XBAO7FJN.js';import {NextResponse}from'next/server';function ie(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID()+crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2)}`}function ue(e){return `rl:${e.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||e.headers.get("x-real-ip")||"unknown"}`}function Q(e){if(!e.apiBaseUrl)throw new Error("next-api-layer: apiBaseUrl is required");if(!e.cookies?.user||!e.cookies?.guest)throw new Error("next-api-layer: cookies.user and cookies.guest are required");let t=e.apiBaseUrl.endsWith("/")?e.apiBaseUrl:`${e.apiBaseUrl}/`,f={...a,...e.cookies.options},l={...b,...e.endpoints},m={enabled:e.csrf?.enabled??false,strategy:e.csrf?.strategy??g.strategy,secret:e.csrf?.secret??ie(),cookieName:e.csrf?.cookieName??g.cookieName,headerName:e.csrf?.headerName??g.headerName,ignoreMethods:e.csrf?.ignoreMethods??g.ignoreMethods,trustSameSite:e.csrf?.trustSameSite??g.trustSameSite},R={enabled:e.rateLimit?.enabled??false,windowMs:e.rateLimit?.windowMs??h.windowMs,maxRequests:e.rateLimit?.maxRequests??h.maxRequests,keyFn:e.rateLimit?.keyFn??ue,skipRoutes:e.rateLimit?.skipRoutes??h.skipRoutes,onRateLimited:e.rateLimit?.onRateLimited},u={enabled:e.audit?.enabled??false,events:e.audit?.events??[...i.events],logger:e.audit?.logger};return {...e,apiBaseUrl:t,_resolved:{cookieOptions:f,endpoints:l,csrf:m,rateLimit:R,audit:u}}}var $={parseAuthMe:e=>{let t=e;return !t?.success||!t?.data?null:{isValid:true,tokenType:t.data.type||"user",exp:t.data.exp||null,userData:t.data}},parseRefreshToken:e=>{let t=e;return t?.success&&t?.data?.accessToken?t.data.accessToken:null},parseGuestToken:e=>e?.data?.accessToken||null};function j(e){let{apiBaseUrl:t,_resolved:f,responseMappers:l}=e,{endpoints:m}=f,R={parseAuthMe:l?.parseAuthMe||$.parseAuthMe,parseRefreshToken:l?.parseRefreshToken||$.parseRefreshToken,parseGuestToken:l?.parseGuestToken||$.parseGuestToken};async function u(a){let i={isValid:false,tokenType:null,exp:null,userData:null};try{let n=await fetch(`${t}${m.validate}`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},cache:"no-store"});if(!n.ok)return i;let s=await n.json().catch(()=>null),d=R.parseAuthMe(s);return !d||!d.isValid?i:d}catch{return i}}async function c(a){return u(a)}async function p(a){try{let i=await fetch(`${t}${m.refresh}`,{method:"POST",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},cache:"no-store"});if(!i.ok)return {success:!1,newToken:null};let n=await i.json().catch(()=>null),s=R.parseRefreshToken(n);return s?{success:!0,newToken:s}:{success:!1,newToken:null}}catch{return {success:false,newToken:null}}}async function o(){let a=e.guestToken;if(!a?.enabled||!a.credentials)return null;try{let i=await fetch(`${t}${m.guest}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:a.credentials.username,password:a.credentials.password}),cache:"no-store"});if(!i.ok)return null;let n=await i.json().catch(()=>null);return R.parseGuestToken(n)}catch{return null}}return {validateToken:u,getTokenInfo:c,refreshToken:p,createGuestToken:o}}function ee(e,t){if(!t?.enabled)return null;let f=t.locales??[],l=t.defaultLocale,R=e.split("/").filter(Boolean)[0];return R&&f.includes(R)?R:l??null}function z(e$1,t){let{cookies:f,guestToken:l,access:m,i18n:R,_resolved:u}=e$1,{cookieOptions:c$1}=u;function p(r,x,h){r.cookies.get(h)?.value&&x.cookies.delete(h);}function o(r,x){return p(r,x,f.guest),p(r,x,f.user),x}function a(r,x=500){return new NextResponse(JSON.stringify({success:false,message:r}),{status:x,headers:{"Content-Type":"application/json"}})}function i(r){return (m?.authRoutes??[]).some(h=>r===h||r.startsWith(`${h}/`))}function n(r){return m?.protectedByDefault?!s(r)&&!i(r):(m?.protectedRoutes??[]).some(h=>r===h||r.startsWith(`${h}/`))}function s(r){return (m?.publicRoutes??[]).some(h=>r===h||r.startsWith(`${h}/`))}function d(r){let x=m?.allowedTokenTypes;return !x||x.length===0?true:r?x.includes(r):false}async function C(r,x){let{origin:h}=r.nextUrl;if(l?.enabled){let y=await t.createGuestToken();if(y){let N;return x?N=NextResponse.next():n(r.nextUrl.pathname)?N=NextResponse.redirect(new URL("/login",h)):N=NextResponse.next(),N.cookies.set(f.guest,y,{...c$1,maxAge:3600}),N}}return x?a("Token bulunamad\u0131",401):n(r.nextUrl.pathname)?NextResponse.redirect(new URL("/login",h)):NextResponse.next()}async function v(r,x,h,y,N){let{pathname:k,origin:g}=r.nextUrl,{isValid:S,tokenType:T,userData:A}=x,E=T===c.GUEST;if(!S){if(h&&y){let D=await t.refreshToken(y);if(D.success&&D.newToken){let O=await t.getTokenInfo(D.newToken);if(O.isValid){let _=new Headers(r.headers);O.userData&&_.set(e.AUTH_USER,JSON.stringify(O.userData)),_.set(e.REFRESHED_TOKEN,D.newToken);let Z=ee(k,R);Z&&_.set(e.LOCALE,Z);let I;return i(k)?I=NextResponse.redirect(new URL("/",g)):I=NextResponse.next({request:{headers:_}}),I.cookies.set(f.user,D.newToken,{...c$1,maxAge:c$1.maxAge}),p(r,I,f.guest),I}}}let P=await C(r,N);return P.cookies.get(f.guest)?.value?p(r,P,f.user):o(r,P),P}let U=new Headers(r.headers);A&&U.set(e.AUTH_USER,JSON.stringify(A));let J=ee(k,R);if(J&&U.set(e.LOCALE,J),!E&&!d(T)){if(N){let Y=a("Bu i\u015Flem i\xE7in yetkiniz yok",403);return o(r,Y)}let P=NextResponse.redirect(new URL("/login",g));return o(r,P)}if(E)return N?NextResponse.next({request:{headers:U}}):n(k)?NextResponse.redirect(new URL("/login",g)):NextResponse.next({request:{headers:U}});if(i(k))return NextResponse.redirect(new URL("/",g));let X=NextResponse.next({request:{headers:U}});return p(r,X,f.guest),X}return {deleteAllAuthCookies:o,jsonError:a,isAuthPage:i,isProtectedRoute:n,isPublicRoute:s,isTokenTypeAllowed:d,handleNoToken:C,handleValidationResult:v}}function K(e){async function t(u){let c=le();return `${await ce(e.secret,u,c)}.${c}`}function f(u){let c=u.method.toUpperCase();if(e.ignoreMethods.includes(c))return {valid:true};let p=e.strategy;if(p==="fetch-metadata"||p==="both"){let o=l(u);if(p==="fetch-metadata"||o.valid||o.reason!=="missing-headers")return o}return p==="double-submit"||p==="both"?m(u):{valid:true}}function l(u){let c=u.headers.get("sec-fetch-site");if(!c)return {valid:false,reason:"missing-headers"};if(c==="same-origin")return {valid:true};if(c==="none"){let p=u.method.toUpperCase();return e.ignoreMethods.includes(p)?{valid:true}:{valid:false,reason:"direct-navigation-unsafe-method"}}return c==="same-site"?e.trustSameSite?{valid:true}:{valid:false,reason:"same-site-not-trusted"}:c==="cross-site"?{valid:false,reason:"cross-site-request"}:{valid:false,reason:"unknown-sec-fetch-site"}}function m(u){let c=u.cookies.get(e.cookieName)?.value;if(!c)return {valid:false,reason:"missing-cookie-token"};let p=u.headers.get(e.headerName);return p?de(c,p)?c.split(".").length!==2?{valid:false,reason:"invalid-token-format"}:{valid:true}:{valid:false,reason:"token-mismatch"}:{valid:false,reason:"missing-header-token"}}async function R(u,c){let p=await t(c);return u.cookies.set(e.cookieName,p,{httpOnly:false,secure:process.env.NODE_ENV==="production",sameSite:"strict",path:"/"}),u}return {validateRequest:f,generateToken:t,attachCsrfCookie:R}}function le(){if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}return Math.random().toString(36).substring(2)+Date.now().toString(36)}async function ce(e,t,f){let l=`${t.length}!${t}!${f.length}!${f}`;if(typeof crypto<"u"&&crypto.subtle){let u=new TextEncoder,c=u.encode(e),p=u.encode(l),o=await crypto.subtle.importKey("raw",c,{name:"HMAC",hash:"SHA-256"},false,["sign"]),a=await crypto.subtle.sign("HMAC",o,p);return Array.from(new Uint8Array(a),i=>i.toString(16).padStart(2,"0")).join("")}let m=0,R=e+l;for(let u=0;u<R.length;u++){let c=R.charCodeAt(u);m=(m<<5)-m+c,m=m&m;}return Math.abs(m).toString(16)}function de(e,t){if(e.length!==t.length)return false;let f=0;for(let l=0;l<e.length;l++)f|=e.charCodeAt(l)^t.charCodeAt(l);return f===0}function W(e){let t=new Map,f=setInterval(()=>{let a=Date.now();for(let[i,n]of t)n.resetAt<=a&&t.delete(i);},e.windowMs);typeof process<"u"&&process.on&&process.on("beforeExit",()=>clearInterval(f));function l(a){return e.skipRoutes.some(i=>i.endsWith("*")?a.startsWith(i.slice(0,-1)):i.endsWith("**")?a.startsWith(i.slice(0,-2)):a===i)}function m(a){let i=a.nextUrl.pathname;if(l(i))return {allowed:true,remaining:e.maxRequests,resetAt:0,limit:e.maxRequests};let n=e.keyFn(a),s=Date.now(),d=t.get(n);if(!d||d.resetAt<=s)return d={count:1,resetAt:s+e.windowMs},t.set(n,d),{allowed:true,remaining:e.maxRequests-1,resetAt:d.resetAt,limit:e.maxRequests};d.count++;let C=Math.max(0,e.maxRequests-d.count);return {allowed:d.count<=e.maxRequests,remaining:C,resetAt:d.resetAt,limit:e.maxRequests}}function R(a,i){return a.headers.set("X-RateLimit-Limit",i.limit.toString()),a.headers.set("X-RateLimit-Remaining",i.remaining.toString()),a.headers.set("X-RateLimit-Reset",Math.ceil(i.resetAt/1e3).toString()),a}function u(a,i){if(e.onRateLimited){let s=e.onRateLimited(a);return R(s,i)}let n=NextResponse.json({success:false,message:"Too many requests. Please try again later.",retryAfter:Math.ceil((i.resetAt-Date.now())/1e3)},{status:429});return n.headers.set("Retry-After",Math.ceil((i.resetAt-Date.now())/1e3).toString()),R(n,i)}function c(a){t.delete(a);}function p(){t.clear();}function o(){return t.size}return {check:m,applyHeaders:R,createLimitedResponse:u,shouldSkip:l,reset:c,clear:p,size:o}}function q(e){function t(n){return e.enabled&&e.events.includes(n)}function f(n){return n.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||n.headers.get("x-real-ip")||null}async function l(n,s,d){if(!t(n))return;let C={type:n,timestamp:new Date,ip:f(s),userId:d.userId,path:s.nextUrl.pathname,method:s.method,success:d.success,metadata:d.metadata};if(e.logger)try{await e.logger(C);}catch(v){console.error("[next-api-layer] Audit logger error:",v);}}function m(n,s,d){return l("auth:success",n,{success:true,userId:s,metadata:d})}function R(n,s){return l("auth:fail",n,{success:false,metadata:s})}function u(n,s,d){return l("auth:refresh",n,{success:true,userId:s,metadata:d})}function c(n,s){return l("auth:guest",n,{success:true,metadata:s})}function p(n,s,d){return l("access:denied",n,{success:false,userId:s,metadata:d})}function o(n,s){return l("csrf:fail",n,{success:false,metadata:s})}function a(n,s){return l("rateLimit:exceeded",n,{success:false,metadata:s})}function i(n,s,d){return l("error",n,{success:false,metadata:{...d,error:s.message,stack:s.stack}})}return {emit:l,isEnabled:t,authSuccess:m,authFail:R,authRefresh:u,authGuest:c,accessDenied:p,csrfFail:o,rateLimitExceeded:a,error:i}}function te(e){let t=Q(e),f=j(t),l=z(t,f),m=K(t._resolved.csrf),R=W(t._resolved.rateLimit),u=q(t._resolved.audit);async function c(o){let{pathname:a,origin:i}=o.nextUrl,n=a.startsWith("/api");if(t._resolved.rateLimit.enabled){let g=R.check(o);if(!g.allowed)return await u.rateLimitExceeded(o,{limit:g.limit,resetAt:g.resetAt}),R.createLimitedResponse(o,g)}if(t._resolved.csrf.enabled){let g=m.validateRequest(o);if(!g.valid)return await u.csrfFail(o,{reason:g.reason}),NextResponse.json({success:false,message:"CSRF validation failed"},{status:403})}if(t.blockBrowserApiAccess&&n&&(o.headers.get("accept")||"").includes("text/html"))return NextResponse.redirect(new URL("/",i));if(t.beforeAuth){let g=await t.beforeAuth(o);if(g)return g}if((t.excludedPaths??[]).some(g=>a.startsWith(g)))return p(o,NextResponse.next(),{isAuthenticated:false,isGuest:false,tokenType:null,user:null});if(["/api/auth/login","/api/auth/logout","/api/auth/me","/api/auth/refresh","/api/auth/register"].includes(a))return p(o,NextResponse.next(),{isAuthenticated:false,isGuest:false,tokenType:null,user:null});let C=o.cookies?.get(t.cookies.user)?.value,v=o.cookies?.get(t.cookies.guest)?.value,r=C||v,x=!!C;if(!r){await u.authFail(o,{reason:"no-token"});let g=await l.handleNoToken(o,n);return p(o,g,{isAuthenticated:false,isGuest:false,tokenType:null,user:null})}let h=await f.getTokenInfo(r),y={isAuthenticated:h.isValid&&h.tokenType!=="guest",isGuest:h.isValid&&h.tokenType==="guest",tokenType:h.tokenType,user:h.userData};if(h.isValid)if(h.tokenType==="guest")await u.authGuest(o);else {let g=h.userData?.id?.toString();await u.authSuccess(o,g,{tokenType:h.tokenType});}else await u.authFail(o,{reason:"invalid-token"});let N=await l.handleValidationResult(o,h,x,r,n),k=await p(o,N,y);if(t._resolved.csrf.enabled&&y.isAuthenticated){let g=h.userData?.id?.toString()||r.slice(0,32);k=await m.attachCsrfCookie(k,g);}if(t._resolved.rateLimit.enabled){let g=R.check(o);k=R.applyHeaders(k,g);}return k}async function p(o,a,i){return t.afterAuth?t.afterAuth(o,a,i):a}return c.config=t,c.csrf=m,c.rateLimiter=R,c.audit=u,c}function pe(e,t){if(!t||t.length===0)return false;let f=e.replace(/^\/api\//,"").replace(/^\//,"");return t.some(l=>{if(l===f)return true;if(l.includes("*")){let m=l.replace(/\*\*/g,"<<<DOUBLE>>>").replace(/\*/g,"[^/]+").replace(/<<<DOUBLE>>>/g,".+");return new RegExp(`^${m}$`).test(f)}return false})}function ne(e$1){let{apiBaseUrl:t,userCookieName:f="auth_token",guestCookieName:l="guest_token",skipAuthByDefault:m=false,publicEndpoints:R=[],forwardHeaders:u=["content-type","accept","accept-language","x-requested-with"],excludeHeaders:c=["host","connection","cookie"],transformRequest:p,transformResponse:o}=e$1,a=t.replace(/\/$/,"");function i(s,d){return s.headers.get(e.SKIP_AUTH)==="true"||pe(d,R)?true:m}async function n(s){try{let d=new URL(s.url),C=d.pathname.replace(/^\/api\/?/,""),v=new URL(`${a}/${C}`);v.search=d.search;let r=new Headers;if(u.forEach(T=>{let A=s.headers.get(T);A&&r.set(T,A);}),s.headers.forEach((T,A)=>{let E=A.toLowerCase();!c.includes(E)&&!r.has(A)&&r.set(A,T);}),!i(s,C)){let T=s.cookies.get(f)?.value,A=s.cookies.get(l)?.value,E=T||A;E&&r.set(e.AUTHORIZATION,`Bearer ${E}`);}r.delete(e.SKIP_AUTH);let x=p?await p(s,r):r,h=null;if(s.method!=="GET"&&s.method!=="HEAD"){let T=s.headers.get("content-type")||"";T.includes("application/json")?h=await s.text():T.includes("multipart/form-data")?h=await s.formData():h=await s.text();}let y=await fetch(v.toString(),{method:s.method,headers:x,body:h}),N=y.headers.get("content-type")||"",k;N.includes("application/json")?k=await y.text():k=await y.arrayBuffer();let g=new Headers;y.headers.forEach((T,A)=>{let E=A.toLowerCase();["transfer-encoding","connection","keep-alive"].includes(E)||g.set(A,T);});let S=new NextResponse(k,{status:y.status,statusText:y.statusText,headers:g});return o&&(S=await o(S)),S}catch(d){return console.error("[Proxy Error]",d),NextResponse.json({success:false,message:"Proxy error: Unable to connect to backend",error:d instanceof Error?d.message:"Unknown error"},{status:502})}}return n.config=e$1,n}
|
|
2
|
+
export{q as createAuditLogger,te as createAuthProxy,K as createCsrfValidator,z as createHandlers,ne as createProxyHandler,W as createRateLimiter,j as createTokenValidation};//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|