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/api.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkOXXKU4OM_cjs=require('./chunk-OXXKU4OM.cjs');require('./chunk-6ENVQMWQ.cjs');Object.defineProperty(exports,"createApiClient",{enumerable:true,get:function(){return chunkOXXKU4OM_cjs.d}});Object.defineProperty(exports,"createSanitizer",{enumerable:true,get:function(){return chunkOXXKU4OM_cjs.a}});Object.defineProperty(exports,"defaultSanitizer",{enumerable:true,get:function(){return chunkOXXKU4OM_cjs.b}});Object.defineProperty(exports,"sanitize",{enumerable:true,get:function(){return chunkOXXKU4OM_cjs.c}});//# sourceMappingURL=api.cjs.map
|
|
2
|
+
//# sourceMappingURL=api.cjs.map
|
package/dist/api.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"api.cjs"}
|
package/dist/api.d.cts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { S as SanitizationConfig } from './createApiClient-CIDYcpNI.cjs';
|
|
2
|
+
export { f as ApiClient, g as ApiClientConfig, s as RequestOptions, r as createApiClient } from './createApiClient-CIDYcpNI.cjs';
|
|
3
|
+
import 'next/server';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* XSS Sanitization utilities
|
|
7
|
+
* Zero-dependency, lightweight sanitizer for API responses
|
|
8
|
+
*
|
|
9
|
+
* Modes:
|
|
10
|
+
* - 'escape' (default): Escapes HTML entities (<script>)
|
|
11
|
+
* - 'strip': Removes all HTML tags completely
|
|
12
|
+
* - 'allowList': Only allows specified tags (advanced)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface SanitizeOptions {
|
|
16
|
+
config: SanitizationConfig;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a sanitization function based on config
|
|
20
|
+
*/
|
|
21
|
+
declare function createSanitizer(config?: SanitizationConfig): {
|
|
22
|
+
sanitize: <T>(data: T) => T;
|
|
23
|
+
sanitizeString: (value: string) => string;
|
|
24
|
+
sanitizeValue: (value: unknown, path?: string) => unknown;
|
|
25
|
+
sanitizeFormData: (formData: FormData) => FormData;
|
|
26
|
+
};
|
|
27
|
+
type Sanitizer = ReturnType<typeof createSanitizer>;
|
|
28
|
+
/**
|
|
29
|
+
* Default sanitizer with escape mode (enabled by default)
|
|
30
|
+
*/
|
|
31
|
+
declare const defaultSanitizer: {
|
|
32
|
+
sanitize: <T>(data: T) => T;
|
|
33
|
+
sanitizeString: (value: string) => string;
|
|
34
|
+
sanitizeValue: (value: unknown, path?: string) => unknown;
|
|
35
|
+
sanitizeFormData: (formData: FormData) => FormData;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Quick sanitize function with default config
|
|
39
|
+
*/
|
|
40
|
+
declare function sanitize<T>(data: T): T;
|
|
41
|
+
|
|
42
|
+
export { SanitizationConfig, type SanitizeOptions, type Sanitizer, createSanitizer, defaultSanitizer, sanitize };
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { S as SanitizationConfig } from './createApiClient-CIDYcpNI.js';
|
|
2
|
+
export { f as ApiClient, g as ApiClientConfig, s as RequestOptions, r as createApiClient } from './createApiClient-CIDYcpNI.js';
|
|
3
|
+
import 'next/server';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* XSS Sanitization utilities
|
|
7
|
+
* Zero-dependency, lightweight sanitizer for API responses
|
|
8
|
+
*
|
|
9
|
+
* Modes:
|
|
10
|
+
* - 'escape' (default): Escapes HTML entities (<script>)
|
|
11
|
+
* - 'strip': Removes all HTML tags completely
|
|
12
|
+
* - 'allowList': Only allows specified tags (advanced)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface SanitizeOptions {
|
|
16
|
+
config: SanitizationConfig;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a sanitization function based on config
|
|
20
|
+
*/
|
|
21
|
+
declare function createSanitizer(config?: SanitizationConfig): {
|
|
22
|
+
sanitize: <T>(data: T) => T;
|
|
23
|
+
sanitizeString: (value: string) => string;
|
|
24
|
+
sanitizeValue: (value: unknown, path?: string) => unknown;
|
|
25
|
+
sanitizeFormData: (formData: FormData) => FormData;
|
|
26
|
+
};
|
|
27
|
+
type Sanitizer = ReturnType<typeof createSanitizer>;
|
|
28
|
+
/**
|
|
29
|
+
* Default sanitizer with escape mode (enabled by default)
|
|
30
|
+
*/
|
|
31
|
+
declare const defaultSanitizer: {
|
|
32
|
+
sanitize: <T>(data: T) => T;
|
|
33
|
+
sanitizeString: (value: string) => string;
|
|
34
|
+
sanitizeValue: (value: unknown, path?: string) => unknown;
|
|
35
|
+
sanitizeFormData: (formData: FormData) => FormData;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Quick sanitize function with default config
|
|
39
|
+
*/
|
|
40
|
+
declare function sanitize<T>(data: T): T;
|
|
41
|
+
|
|
42
|
+
export { SanitizationConfig, type SanitizeOptions, type Sanitizer, createSanitizer, defaultSanitizer, sanitize };
|
package/dist/api.js
ADDED
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"api.js"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var e={httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:"/",maxAge:604800},s={validate:"auth/me",refresh:"auth/refresh",guest:"auth/guest"},o={GUEST:"guest"},a={NO_TOKEN:"Token bulunamad\u0131.",INVALID_TOKEN:"Token ge\xE7ersiz veya s\xFCresi dolmu\u015F.",CONNECTION_ERROR:"Ba\u011Flant\u0131 hatas\u0131 olu\u015Ftu.",UNAUTHORIZED:"Bu i\u015Flem i\xE7in yetkiniz yok."},r={AUTH_USER:"x-auth-user",REFRESHED_TOKEN:"x-refreshed-token",AUTHORIZATION:"Authorization",CONTENT_TYPE:"Content-Type",SKIP_AUTH:"x-skip-auth",LOCALE:"x-locale"};var t=["GET","HEAD","OPTIONS"],E={strategy:"both",cookieName:"__csrf",headerName:"x-csrf-token",ignoreMethods:[...t],trustSameSite:false},c={windowMs:60*1e3,maxRequests:100,skipRoutes:[]},i={events:["auth:success","auth:fail","auth:refresh","access:denied","csrf:fail","rateLimit:exceeded","error"]};exports.a=e;exports.b=s;exports.c=o;exports.d=a;exports.e=r;exports.f=t;exports.g=E;exports.h=c;exports.i=i;//# sourceMappingURL=chunk-6ENVQMWQ.cjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-6ENVQMWQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/constants.ts"],"names":["DEFAULT_COOKIE_OPTIONS","DEFAULT_ENDPOINTS","TOKEN_TYPES","ERROR_MESSAGES","HEADERS","CSRF_SAFE_METHODS","DEFAULT_CSRF_CONFIG","DEFAULT_RATE_LIMIT_CONFIG","DEFAULT_AUDIT_CONFIG"],"mappings":"aASO,IAAMA,CAAAA,CAAkD,CAC7D,QAAA,CAAU,IAAA,CACV,MAAA,CAAQ,QAAQ,GAAA,CAAI,QAAA,GAAa,YAAA,CACjC,QAAA,CAAU,KAAA,CACV,IAAA,CAAM,IACN,MAAA,CAAQ,MACV,CAAA,CAEaC,CAAAA,CAA8C,CACzD,QAAA,CAAU,UACV,OAAA,CAAS,cAAA,CACT,KAAA,CAAO,YACT,CAAA,CAIaC,CAAAA,CAAc,CACzB,KAAA,CAAO,OACT,CAAA,CAYaC,EAAiB,CAC5B,QAAA,CAAU,wBAAA,CACV,aAAA,CAAe,+CAAA,CACf,gBAAA,CAAkB,6CAAA,CAClB,YAAA,CAAc,qCAChB,CAAA,CAIaC,CAAAA,CAAU,CACrB,SAAA,CAAW,aAAA,CACX,gBAAiB,mBAAA,CACjB,aAAA,CAAe,eAAA,CACf,YAAA,CAAc,cAAA,CACd,SAAA,CAAW,cACX,MAAA,CAAQ,UACV,EAmBO,IAAMC,CAAAA,CAAoB,CAAC,MAAO,MAAA,CAAQ,SAAS,CAAA,CAE7CC,CAAAA,CAAsB,CACjC,QAAA,CAAU,MAAA,CACV,UAAA,CAAY,QAAA,CACZ,UAAA,CAAY,cAAA,CACZ,aAAA,CAAe,CAAC,GAAGD,CAAiB,CAAA,CACpC,aAAA,CAAe,KACjB,CAAA,CAEaE,CAAAA,CAA4B,CACvC,SAAU,EAAA,CAAK,GAAA,CACf,WAAA,CAAa,GAAA,CACb,UAAA,CAAY,EACd,CAAA,CAEaC,CAAAA,CAAuB,CAClC,MAAA,CAAQ,CACN,cAAA,CACA,WAAA,CACA,cAAA,CACA,eAAA,CACA,WAAA,CACA,oBAAA,CACA,OACF,CACF","file":"chunk-6ENVQMWQ.cjs","sourcesContent":["/**\r\n * Shared Constants\r\n * Default values and constant configurations\r\n */\r\n\r\nimport type { CookieOptions, EndpointConfig } from './types';\r\n\r\n// ==================== Default Values ====================\r\n\r\nexport const DEFAULT_COOKIE_OPTIONS: Required<CookieOptions> = {\r\n httpOnly: true,\r\n secure: process.env.NODE_ENV === 'production',\r\n sameSite: 'lax',\r\n path: '/',\r\n maxAge: 7 * 24 * 60 * 60, // 7 days\r\n} as const;\r\n\r\nexport const DEFAULT_ENDPOINTS: Required<EndpointConfig> = {\r\n validate: 'auth/me',\r\n refresh: 'auth/refresh',\r\n guest: 'auth/guest',\r\n} as const;\r\n\r\n// ==================== Token Types ====================\r\n\r\nexport const TOKEN_TYPES = {\r\n GUEST: 'guest',\r\n} as const;\r\n\r\n// ==================== Time Constants ====================\r\n\r\nexport const TIME = {\r\n SEVEN_DAYS: 7 * 24 * 60 * 60,\r\n ONE_HOUR: 60 * 60,\r\n TWO_HOURS: 2 * 60 * 60,\r\n} as const;\r\n\r\n// ==================== Error Messages ====================\r\n\r\nexport const ERROR_MESSAGES = {\r\n NO_TOKEN: 'Token bulunamadı.',\r\n INVALID_TOKEN: 'Token geçersiz veya süresi dolmuş.',\r\n CONNECTION_ERROR: 'Bağlantı hatası oluştu.',\r\n UNAUTHORIZED: 'Bu işlem için yetkiniz yok.',\r\n} as const;\r\n\r\n// ==================== Headers ====================\r\n\r\nexport const HEADERS = {\r\n AUTH_USER: 'x-auth-user',\r\n REFRESHED_TOKEN: 'x-refreshed-token',\r\n AUTHORIZATION: 'Authorization',\r\n CONTENT_TYPE: 'Content-Type',\r\n SKIP_AUTH: 'x-skip-auth',\r\n LOCALE: 'x-locale',\r\n} as const;\r\n\r\n// ==================== Sanitization Defaults ====================\r\n\r\n/**\r\n * Common safe HTML tags for allowList mode\r\n * Use with: sanitization: { mode: 'allowList', allowedTags: SAFE_HTML_TAGS }\r\n */\r\nexport const SAFE_HTML_TAGS = [\r\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'p', 'a', 'ul', 'ol', 'li',\r\n 'strong', 'em', 'b', 'i', 'br',\r\n 'blockquote', 'div', 'span',\r\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\r\n] as const;\r\n\r\n// ==================== Security Defaults ====================\r\n\r\n/** CSRF safe methods that don't need validation */\r\nexport const CSRF_SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] as const;\r\n\r\nexport const DEFAULT_CSRF_CONFIG = {\r\n strategy: 'both' as const,\r\n cookieName: '__csrf',\r\n headerName: 'x-csrf-token',\r\n ignoreMethods: [...CSRF_SAFE_METHODS],\r\n trustSameSite: false,\r\n} as const;\r\n\r\nexport const DEFAULT_RATE_LIMIT_CONFIG = {\r\n windowMs: 60 * 1000, // 1 minute\r\n maxRequests: 100,\r\n skipRoutes: [] as string[],\r\n} as const;\r\n\r\nexport const DEFAULT_AUDIT_CONFIG = {\r\n events: [\r\n 'auth:success',\r\n 'auth:fail',\r\n 'auth:refresh',\r\n 'access:denied',\r\n 'csrf:fail',\r\n 'rateLimit:exceeded',\r\n 'error',\r\n ] as const,\r\n} as const;\r\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {e}from'./chunk-XBAO7FJN.js';import {headers,cookies}from'next/headers';var M={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="},$=/[&<>"'`=/]/g,x=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,F=/\s*on\w+\s*=\s*["'][^"']*["']/gi,D=/javascript\s*:/gi,b=/data\s*:[^;]*;base64/gi;function L(n){return n.replace($,a=>M[a]||a)}function j(n){return n.replace(x,"").replace(/<[^>]*>/g,"").replace(F,"").replace(D,"").replace(b,"")}function q(n,a){let c=n.replace(x,"").replace(F,"").replace(D,"").replace(b,"");if(a.length===0)return L(c);let f=a.join("|"),l=new RegExp(`<(?!/?(?:${f})\\b)[^>]*>`,"gi");return c=c.replace(l,""),c}function z(n){let a=n?.mode??"escape",c=n?.allowedTags??[],f=n?.enabled!==false,l=n?.skipFields??[];function g(e){if(!f)return e;switch(a){case "strip":return j(e);case "allowList":return q(e,c);default:return L(e)}}function d(e,p=""){if(l.some(i=>p.endsWith(i))||e==null)return e;if(typeof e=="string")return g(e);if(typeof e=="number"||typeof e=="boolean")return e;if(Array.isArray(e))return e.map((i,r)=>d(i,`${p}[${r}]`));if(typeof e=="object"){let i={};for(let[r,u]of Object.entries(e)){let t=p?`${p}.${r}`:r;i[r]=d(u,t);}return i}return e}function R(e){return f?d(e):e}function k(e){if(!f)return e;let p=new FormData;for(let[i,r]of e.entries())r instanceof File?p.append(i,r):typeof r=="string"?p.append(i,g(r)):p.append(i,r);return p}return {sanitize:R,sanitizeString:g,sanitizeValue:d,sanitizeFormData:k}}var O=z({enabled:true,mode:"escape",skipFields:[]});function I(n){return O.sanitize(n)}function B(n){let{apiBaseUrl:a,cookies:c,methodSpoofing:f=false,errorMessages:l={},i18n:g}=n,d=a.endsWith("/")?a:`${a}/`,R=z(n.sanitization),k=l.noToken??"Token bulunamad\u0131. L\xFCtfen giri\u015F yap\u0131n.",e$1=l.connectionError??"Ba\u011Flant\u0131 hatas\u0131 olu\u015Ftu.";async function p(){let o=(await headers()).get(e.REFRESHED_TOKEN);if(o)return o;let s=await cookies();return s.get(c.user)?.value||s.get(c.guest)?.value||null}function i(t,o=500){return Response.json({success:false,message:t},{status:o})}function r(){return i(k,401)}async function u(t,o,s,T={}){let h=T.skipAuth?null:await p();if(!T.skipAuth&&!h)return r();try{let m={};h&&(m.Authorization=`Bearer ${h}`);let S,y=t;s&&(T.isFormData&&s instanceof FormData?(S=R.sanitizeFormData(s),(T.methodSpoofing??f)&&(t==="PUT"||t==="PATCH")&&(S.append("_method",t),y="POST")):s instanceof FormData||(m["Content-Type"]="application/json",S=JSON.stringify(R.sanitize(s))));let A=`${d}${o}`;if(g?.enabled){let E=(await headers()).get(e.LOCALE);if(E&&(!g.locales||g.locales.includes(E))){let P=new URL(A);P.searchParams.set(g.paramName||"lang",E),A=P.toString();}}let C=await fetch(A,{method:y,headers:m,body:S,cache:"no-store"}),_=await C.json();return Response.json(_,{status:C.status})}catch(m){return console.error(`API ${t} Error:`,m),i(e$1)}}return {get:t=>u("GET",t),post:(t,o,s)=>u("POST",t,o,s),put:(t,o,s)=>u("PUT",t,o,s),patch:(t,o)=>u("PATCH",t,o),delete:t=>u("DELETE",t)}}export{z as a,O as b,I as c,B as d};//# sourceMappingURL=chunk-NBYI46RO.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-NBYI46RO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/sanitize.ts","../src/api/createApiClient.ts"],"names":["HTML_ENTITIES","HTML_ENTITY_REGEX","SCRIPT_PATTERN","EVENT_HANDLER_PATTERN","JAVASCRIPT_URL_PATTERN","DATA_URL_PATTERN","escapeHtml","str","char","stripHtml","sanitizeWithAllowList","allowedTags","result","allowedPattern","allowedRegex","createSanitizer","config","mode","enabled","skipFields","sanitizeString","value","sanitizeValue","path","field","item","index","sanitized","key","val","newPath","sanitize","data","sanitizeFormData","formData","defaultSanitizer","createApiClient","apiBaseUrl","cookieNames","globalMethodSpoofing","errorMessages","i18n","baseUrl","sanitizer","noTokenMessage","connectionErrorMessage","getToken","refreshedToken","headers","HEADERS","cookieStore","cookies","errorResponse","message","status","noTokenResponse","makeRequest","method","endpoint","body","options","token","fetchHeaders","fetchBody","actualMethod","url","locale","urlObj","res","error"],"mappings":"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","file":"chunk-NBYI46RO.js","sourcesContent":["/**\r\n * XSS Sanitization utilities\r\n * Zero-dependency, lightweight sanitizer for API responses\r\n * \r\n * Modes:\r\n * - 'escape' (default): Escapes HTML entities (<script>)\r\n * - 'strip': Removes all HTML tags completely\r\n * - 'allowList': Only allows specified tags (advanced)\r\n */\r\n\r\nimport type { SanitizationConfig } from '../shared/types';\r\n\r\nexport interface SanitizeOptions {\r\n config: SanitizationConfig;\r\n}\r\n\r\n// HTML entities to escape\r\nconst HTML_ENTITIES: Record<string, string> = {\r\n '&': '&',\r\n '<': '<',\r\n '>': '>',\r\n '\"': '"',\r\n \"'\": ''',\r\n '/': '/',\r\n '`': '`',\r\n '=': '=',\r\n};\r\n\r\n// Regex patterns\r\nconst HTML_ENTITY_REGEX = /[&<>\"'`=/]/g;\r\nconst SCRIPT_PATTERN = /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi;\r\nconst EVENT_HANDLER_PATTERN = /\\s*on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi;\r\nconst JAVASCRIPT_URL_PATTERN = /javascript\\s*:/gi;\r\nconst DATA_URL_PATTERN = /data\\s*:[^;]*;base64/gi;\r\n\r\n/**\r\n * Escapes HTML entities in a string\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str.replace(HTML_ENTITY_REGEX, (char) => HTML_ENTITIES[char] || char);\r\n}\r\n\r\n/**\r\n * Strips all HTML tags from a string\r\n */\r\nfunction stripHtml(str: string): string {\r\n return str\r\n .replace(SCRIPT_PATTERN, '') // Remove script tags first\r\n .replace(/<[^>]*>/g, '') // Remove all HTML tags\r\n .replace(EVENT_HANDLER_PATTERN, '') // Remove any remaining event handlers\r\n .replace(JAVASCRIPT_URL_PATTERN, '') // Remove javascript: URLs\r\n .replace(DATA_URL_PATTERN, ''); // Remove suspicious data URLs\r\n}\r\n\r\n/**\r\n * Sanitizes HTML while allowing specific tags\r\n */\r\nfunction sanitizeWithAllowList(str: string, allowedTags: string[]): string {\r\n // First, remove dangerous content\r\n let result = str\r\n .replace(SCRIPT_PATTERN, '')\r\n .replace(EVENT_HANDLER_PATTERN, '')\r\n .replace(JAVASCRIPT_URL_PATTERN, '')\r\n .replace(DATA_URL_PATTERN, '');\r\n \r\n // Build regex for allowed tags\r\n if (allowedTags.length === 0) {\r\n return escapeHtml(result);\r\n }\r\n \r\n const allowedPattern = allowedTags.join('|');\r\n const allowedRegex = new RegExp(`<(?!\\/?(?:${allowedPattern})\\\\b)[^>]*>`, 'gi');\r\n \r\n // Remove non-allowed tags\r\n result = result.replace(allowedRegex, '');\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * Creates a sanitization function based on config\r\n */\r\nexport function createSanitizer(config?: SanitizationConfig) {\r\n const mode = config?.mode ?? 'escape';\r\n const allowedTags = config?.allowedTags ?? [];\r\n const enabled = config?.enabled !== false; // default: true\r\n const skipFields = config?.skipFields ?? [];\r\n\r\n /**\r\n * Sanitizes a single string value\r\n */\r\n function sanitizeString(value: string): string {\r\n if (!enabled) return value;\r\n \r\n switch (mode) {\r\n case 'strip':\r\n return stripHtml(value);\r\n case 'allowList':\r\n return sanitizeWithAllowList(value, allowedTags);\r\n case 'escape':\r\n default:\r\n return escapeHtml(value);\r\n }\r\n }\r\n\r\n /**\r\n * Recursively sanitizes an object, array, or primitive value\r\n */\r\n function sanitizeValue(value: unknown, path: string = ''): unknown {\r\n // Skip fields in skipFields list\r\n if (skipFields.some(field => path.endsWith(field))) {\r\n return value;\r\n }\r\n\r\n // Null/undefined pass through\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n // Strings get sanitized\r\n if (typeof value === 'string') {\r\n return sanitizeString(value);\r\n }\r\n\r\n // Numbers, booleans pass through\r\n if (typeof value === 'number' || typeof value === 'boolean') {\r\n return value;\r\n }\r\n\r\n // Arrays - sanitize each element\r\n if (Array.isArray(value)) {\r\n return value.map((item, index) => sanitizeValue(item, `${path}[${index}]`));\r\n }\r\n\r\n // Objects - sanitize each property\r\n if (typeof value === 'object') {\r\n const sanitized: Record<string, unknown> = {};\r\n for (const [key, val] of Object.entries(value)) {\r\n const newPath = path ? `${path}.${key}` : key;\r\n sanitized[key] = sanitizeValue(val, newPath);\r\n }\r\n return sanitized;\r\n }\r\n\r\n // Everything else passes through\r\n return value;\r\n }\r\n\r\n /**\r\n * Main sanitize function\r\n */\r\n function sanitize<T>(data: T): T {\r\n if (!enabled) {\r\n return data;\r\n }\r\n return sanitizeValue(data) as T;\r\n }\r\n\r\n /**\r\n * Sanitize FormData values (returns new FormData with sanitized values)\r\n */\r\n function sanitizeFormData(formData: FormData): FormData {\r\n if (!enabled) {\r\n return formData;\r\n }\r\n \r\n const sanitized = new FormData();\r\n \r\n for (const [key, value] of formData.entries()) {\r\n if (value instanceof File) {\r\n // Files pass through unchanged\r\n sanitized.append(key, value);\r\n } else if (typeof value === 'string') {\r\n // Strings get sanitized\r\n sanitized.append(key, sanitizeString(value));\r\n } else {\r\n // Everything else passes through\r\n sanitized.append(key, value);\r\n }\r\n }\r\n \r\n return sanitized;\r\n }\r\n\r\n return {\r\n sanitize,\r\n sanitizeString,\r\n sanitizeValue,\r\n sanitizeFormData,\r\n };\r\n}\r\n\r\nexport type Sanitizer = ReturnType<typeof createSanitizer>;\r\n\r\n/**\r\n * Default sanitizer with escape mode (enabled by default)\r\n */\r\nexport const defaultSanitizer = createSanitizer({\r\n enabled: true,\r\n mode: 'escape',\r\n skipFields: [],\r\n});\r\n\r\n/**\r\n * Quick sanitize function with default config\r\n */\r\nexport function sanitize<T>(data: T): T {\r\n return defaultSanitizer.sanitize(data);\r\n}\r\n\r\n/**\r\n * Quick escape function for single strings\r\n */\r\nexport function escapeString(str: string): string {\r\n return escapeHtml(str);\r\n}\r\n\r\n/**\r\n * Quick strip function for single strings\r\n */\r\nexport function stripString(str: string): string {\r\n return stripHtml(str);\r\n}\r\n","/**\r\n * createApiClient\r\n * Server-side API client for Route Handlers\r\n * \r\n * Makes requests directly to backend with auth token from cookies/headers.\r\n * Returns Response objects that can be directly returned from route handlers.\r\n */\r\n\r\nimport { cookies, headers } from 'next/headers';\r\nimport { createSanitizer } from './sanitize';\r\nimport type { SanitizationConfig, I18nConfig } from '../shared/types';\r\nimport { HEADERS } from '../shared/constants';\r\n\r\n// ==================== Types ====================\r\n\r\nexport interface ApiClientConfig {\r\n /** Backend API base URL */\r\n apiBaseUrl: string;\r\n \r\n /** Cookie names for token retrieval */\r\n cookies: {\r\n user: string;\r\n guest: string;\r\n };\r\n \r\n /** Sanitization options */\r\n sanitization?: SanitizationConfig;\r\n \r\n /** Enable Laravel method spoofing for PUT/PATCH */\r\n methodSpoofing?: boolean;\r\n \r\n /** Custom error messages */\r\n errorMessages?: {\r\n noToken?: string;\r\n connectionError?: string;\r\n };\r\n \r\n /** i18n configuration - auto-append locale to API requests */\r\n i18n?: I18nConfig & {\r\n /** Query parameter name (default: 'lang') */\r\n paramName?: string;\r\n };\r\n}\r\n\r\nexport interface RequestOptions {\r\n /** Form data mode (use FormData, skip Content-Type header) */\r\n isFormData?: boolean;\r\n /** Use method spoofing for this request */\r\n methodSpoofing?: boolean;\r\n /** Skip authentication for this request */\r\n skipAuth?: boolean;\r\n}\r\n\r\nexport interface ApiClient {\r\n get: (endpoint: string) => Promise<Response>;\r\n post: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;\r\n put: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;\r\n patch: (endpoint: string, body?: Record<string, unknown>) => Promise<Response>;\r\n delete: (endpoint: string) => Promise<Response>;\r\n}\r\n\r\n// ==================== Factory ====================\r\n\r\n/**\r\n * Creates a server-side API client for Next.js Route Handlers\r\n * \r\n * @example\r\n * ```ts\r\n * // lib/api.ts\r\n * import { createApiClient } from 'next-api-layer';\r\n * \r\n * export const api = createApiClient({\r\n * apiBaseUrl: process.env.API_BASE_URL!,\r\n * cookies: {\r\n * user: process.env.COOKIE_USER_AUTH_TOKEN_NAME!,\r\n * guest: process.env.COOKIE_PUBLIC_AUTH_TOKEN_NAME!,\r\n * },\r\n * });\r\n * \r\n * // Usage in route handler - direct return!\r\n * export async function GET() {\r\n * return api.get('superadmin/home/list');\r\n * }\r\n * \r\n * export async function POST(request: Request) {\r\n * const body = await request.json();\r\n * return api.post('donations/create', body);\r\n * }\r\n * ```\r\n */\r\nexport function createApiClient(config: ApiClientConfig): ApiClient {\r\n const {\r\n apiBaseUrl,\r\n cookies: cookieNames,\r\n methodSpoofing: globalMethodSpoofing = false,\r\n errorMessages = {},\r\n i18n,\r\n } = config;\r\n \r\n // Ensure baseUrl ends with /\r\n const baseUrl = apiBaseUrl.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;\r\n \r\n // Create sanitizer\r\n const sanitizer = createSanitizer(config.sanitization);\r\n \r\n // Error messages\r\n const noTokenMessage = errorMessages.noToken ?? 'Token bulunamadı. Lütfen giriş yapın.';\r\n const connectionErrorMessage = errorMessages.connectionError ?? 'Bağlantı hatası oluştu.';\r\n\r\n /**\r\n * Get auth token from headers (refreshed) or cookies\r\n */\r\n async function getToken(): Promise<string | null> {\r\n // Check for refreshed token from proxy\r\n const headersList = await headers();\r\n const refreshedToken = headersList.get(HEADERS.REFRESHED_TOKEN);\r\n if (refreshedToken) {\r\n return refreshedToken;\r\n }\r\n \r\n // Get from cookies\r\n const cookieStore = await cookies();\r\n return (\r\n cookieStore.get(cookieNames.user)?.value ||\r\n cookieStore.get(cookieNames.guest)?.value ||\r\n null\r\n );\r\n }\r\n\r\n /**\r\n * Create error response\r\n */\r\n function errorResponse(message: string, status = 500): Response {\r\n return Response.json(\r\n { success: false, message },\r\n { status }\r\n );\r\n }\r\n\r\n /**\r\n * Create no-token response\r\n */\r\n function noTokenResponse(): Response {\r\n return errorResponse(noTokenMessage, 401);\r\n }\r\n\r\n /**\r\n * Make a fetch request to backend\r\n */\r\n async function makeRequest(\r\n method: string,\r\n endpoint: string,\r\n body?: Record<string, unknown> | FormData | null,\r\n options: RequestOptions = {}\r\n ): Promise<Response> {\r\n // Skip auth check if requested\r\n const token = options.skipAuth ? null : await getToken();\r\n \r\n if (!options.skipAuth && !token) {\r\n return noTokenResponse();\r\n }\r\n\r\n try {\r\n const fetchHeaders: HeadersInit = {};\r\n \r\n // Add auth header if we have a token\r\n if (token) {\r\n fetchHeaders['Authorization'] = `Bearer ${token}`;\r\n }\r\n\r\n let fetchBody: string | FormData | undefined;\r\n let actualMethod = method;\r\n\r\n // Handle body\r\n if (body) {\r\n if (options.isFormData && body instanceof FormData) {\r\n // FormData - sanitize and optionally add method spoofing\r\n fetchBody = sanitizer.sanitizeFormData(body);\r\n \r\n const useMethodSpoofing = options.methodSpoofing ?? globalMethodSpoofing;\r\n if (useMethodSpoofing && (method === 'PUT' || method === 'PATCH')) {\r\n (fetchBody as FormData).append('_method', method);\r\n actualMethod = 'POST';\r\n }\r\n } else if (!(body instanceof FormData)) {\r\n // JSON body\r\n fetchHeaders['Content-Type'] = 'application/json';\r\n fetchBody = JSON.stringify(sanitizer.sanitize(body));\r\n }\r\n }\r\n\r\n // Build URL with optional locale parameter\r\n let url = `${baseUrl}${endpoint}`;\r\n if (i18n?.enabled) {\r\n const headersList = await headers();\r\n const locale = headersList.get(HEADERS.LOCALE);\r\n if (locale && (!i18n.locales || i18n.locales.includes(locale))) {\r\n const urlObj = new URL(url);\r\n urlObj.searchParams.set(i18n.paramName || 'lang', locale);\r\n url = urlObj.toString();\r\n }\r\n }\r\n\r\n const res = await fetch(url, {\r\n method: actualMethod,\r\n headers: fetchHeaders,\r\n body: fetchBody,\r\n cache: 'no-store',\r\n });\r\n\r\n // Clone response data (stream can only be read once)\r\n const data = await res.json();\r\n return Response.json(data, { status: res.status });\r\n } catch (error) {\r\n console.error(`API ${method} Error:`, error);\r\n return errorResponse(connectionErrorMessage);\r\n }\r\n }\r\n\r\n // Return API client interface\r\n return {\r\n get: (endpoint: string) => makeRequest('GET', endpoint),\r\n \r\n post: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) =>\r\n makeRequest('POST', endpoint, body, options),\r\n \r\n put: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) =>\r\n makeRequest('PUT', endpoint, body, options),\r\n \r\n patch: (endpoint: string, body?: Record<string, unknown>) =>\r\n makeRequest('PATCH', endpoint, body),\r\n \r\n delete: (endpoint: string) => makeRequest('DELETE', endpoint),\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunk6ENVQMWQ_cjs=require('./chunk-6ENVQMWQ.cjs'),headers=require('next/headers');var M={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="},$=/[&<>"'`=/]/g,x=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,F=/\s*on\w+\s*=\s*["'][^"']*["']/gi,D=/javascript\s*:/gi,b=/data\s*:[^;]*;base64/gi;function L(n){return n.replace($,a=>M[a]||a)}function j(n){return n.replace(x,"").replace(/<[^>]*>/g,"").replace(F,"").replace(D,"").replace(b,"")}function q(n,a){let c=n.replace(x,"").replace(F,"").replace(D,"").replace(b,"");if(a.length===0)return L(c);let f=a.join("|"),l=new RegExp(`<(?!/?(?:${f})\\b)[^>]*>`,"gi");return c=c.replace(l,""),c}function z(n){let a=n?.mode??"escape",c=n?.allowedTags??[],f=n?.enabled!==false,l=n?.skipFields??[];function g(e){if(!f)return e;switch(a){case "strip":return j(e);case "allowList":return q(e,c);default:return L(e)}}function d(e,p=""){if(l.some(i=>p.endsWith(i))||e==null)return e;if(typeof e=="string")return g(e);if(typeof e=="number"||typeof e=="boolean")return e;if(Array.isArray(e))return e.map((i,r)=>d(i,`${p}[${r}]`));if(typeof e=="object"){let i={};for(let[r,u]of Object.entries(e)){let t=p?`${p}.${r}`:r;i[r]=d(u,t);}return i}return e}function R(e){return f?d(e):e}function k(e){if(!f)return e;let p=new FormData;for(let[i,r]of e.entries())r instanceof File?p.append(i,r):typeof r=="string"?p.append(i,g(r)):p.append(i,r);return p}return {sanitize:R,sanitizeString:g,sanitizeValue:d,sanitizeFormData:k}}var O=z({enabled:true,mode:"escape",skipFields:[]});function I(n){return O.sanitize(n)}function B(n){let{apiBaseUrl:a,cookies:c,methodSpoofing:f=false,errorMessages:l={},i18n:g}=n,d=a.endsWith("/")?a:`${a}/`,R=z(n.sanitization),k=l.noToken??"Token bulunamad\u0131. L\xFCtfen giri\u015F yap\u0131n.",e=l.connectionError??"Ba\u011Flant\u0131 hatas\u0131 olu\u015Ftu.";async function p(){let o=(await headers.headers()).get(chunk6ENVQMWQ_cjs.e.REFRESHED_TOKEN);if(o)return o;let s=await headers.cookies();return s.get(c.user)?.value||s.get(c.guest)?.value||null}function i(t,o=500){return Response.json({success:false,message:t},{status:o})}function r(){return i(k,401)}async function u(t,o,s,T={}){let h=T.skipAuth?null:await p();if(!T.skipAuth&&!h)return r();try{let m={};h&&(m.Authorization=`Bearer ${h}`);let S,y=t;s&&(T.isFormData&&s instanceof FormData?(S=R.sanitizeFormData(s),(T.methodSpoofing??f)&&(t==="PUT"||t==="PATCH")&&(S.append("_method",t),y="POST")):s instanceof FormData||(m["Content-Type"]="application/json",S=JSON.stringify(R.sanitize(s))));let A=`${d}${o}`;if(g?.enabled){let E=(await headers.headers()).get(chunk6ENVQMWQ_cjs.e.LOCALE);if(E&&(!g.locales||g.locales.includes(E))){let P=new URL(A);P.searchParams.set(g.paramName||"lang",E),A=P.toString();}}let C=await fetch(A,{method:y,headers:m,body:S,cache:"no-store"}),_=await C.json();return Response.json(_,{status:C.status})}catch(m){return console.error(`API ${t} Error:`,m),i(e)}}return {get:t=>u("GET",t),post:(t,o,s)=>u("POST",t,o,s),put:(t,o,s)=>u("PUT",t,o,s),patch:(t,o)=>u("PATCH",t,o),delete:t=>u("DELETE",t)}}exports.a=z;exports.b=O;exports.c=I;exports.d=B;//# sourceMappingURL=chunk-OXXKU4OM.cjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-OXXKU4OM.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/sanitize.ts","../src/api/createApiClient.ts"],"names":["HTML_ENTITIES","HTML_ENTITY_REGEX","SCRIPT_PATTERN","EVENT_HANDLER_PATTERN","JAVASCRIPT_URL_PATTERN","DATA_URL_PATTERN","escapeHtml","str","char","stripHtml","sanitizeWithAllowList","allowedTags","result","allowedPattern","allowedRegex","createSanitizer","config","mode","enabled","skipFields","sanitizeString","value","sanitizeValue","path","field","item","index","sanitized","key","val","newPath","sanitize","data","sanitizeFormData","formData","defaultSanitizer","createApiClient","apiBaseUrl","cookieNames","globalMethodSpoofing","errorMessages","i18n","baseUrl","sanitizer","noTokenMessage","connectionErrorMessage","getToken","refreshedToken","headers","HEADERS","cookieStore","cookies","errorResponse","message","status","noTokenResponse","makeRequest","method","endpoint","body","options","token","fetchHeaders","fetchBody","actualMethod","url","locale","urlObj","res","error"],"mappings":"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","file":"chunk-OXXKU4OM.cjs","sourcesContent":["/**\r\n * XSS Sanitization utilities\r\n * Zero-dependency, lightweight sanitizer for API responses\r\n * \r\n * Modes:\r\n * - 'escape' (default): Escapes HTML entities (<script>)\r\n * - 'strip': Removes all HTML tags completely\r\n * - 'allowList': Only allows specified tags (advanced)\r\n */\r\n\r\nimport type { SanitizationConfig } from '../shared/types';\r\n\r\nexport interface SanitizeOptions {\r\n config: SanitizationConfig;\r\n}\r\n\r\n// HTML entities to escape\r\nconst HTML_ENTITIES: Record<string, string> = {\r\n '&': '&',\r\n '<': '<',\r\n '>': '>',\r\n '\"': '"',\r\n \"'\": ''',\r\n '/': '/',\r\n '`': '`',\r\n '=': '=',\r\n};\r\n\r\n// Regex patterns\r\nconst HTML_ENTITY_REGEX = /[&<>\"'`=/]/g;\r\nconst SCRIPT_PATTERN = /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi;\r\nconst EVENT_HANDLER_PATTERN = /\\s*on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi;\r\nconst JAVASCRIPT_URL_PATTERN = /javascript\\s*:/gi;\r\nconst DATA_URL_PATTERN = /data\\s*:[^;]*;base64/gi;\r\n\r\n/**\r\n * Escapes HTML entities in a string\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str.replace(HTML_ENTITY_REGEX, (char) => HTML_ENTITIES[char] || char);\r\n}\r\n\r\n/**\r\n * Strips all HTML tags from a string\r\n */\r\nfunction stripHtml(str: string): string {\r\n return str\r\n .replace(SCRIPT_PATTERN, '') // Remove script tags first\r\n .replace(/<[^>]*>/g, '') // Remove all HTML tags\r\n .replace(EVENT_HANDLER_PATTERN, '') // Remove any remaining event handlers\r\n .replace(JAVASCRIPT_URL_PATTERN, '') // Remove javascript: URLs\r\n .replace(DATA_URL_PATTERN, ''); // Remove suspicious data URLs\r\n}\r\n\r\n/**\r\n * Sanitizes HTML while allowing specific tags\r\n */\r\nfunction sanitizeWithAllowList(str: string, allowedTags: string[]): string {\r\n // First, remove dangerous content\r\n let result = str\r\n .replace(SCRIPT_PATTERN, '')\r\n .replace(EVENT_HANDLER_PATTERN, '')\r\n .replace(JAVASCRIPT_URL_PATTERN, '')\r\n .replace(DATA_URL_PATTERN, '');\r\n \r\n // Build regex for allowed tags\r\n if (allowedTags.length === 0) {\r\n return escapeHtml(result);\r\n }\r\n \r\n const allowedPattern = allowedTags.join('|');\r\n const allowedRegex = new RegExp(`<(?!\\/?(?:${allowedPattern})\\\\b)[^>]*>`, 'gi');\r\n \r\n // Remove non-allowed tags\r\n result = result.replace(allowedRegex, '');\r\n \r\n return result;\r\n}\r\n\r\n/**\r\n * Creates a sanitization function based on config\r\n */\r\nexport function createSanitizer(config?: SanitizationConfig) {\r\n const mode = config?.mode ?? 'escape';\r\n const allowedTags = config?.allowedTags ?? [];\r\n const enabled = config?.enabled !== false; // default: true\r\n const skipFields = config?.skipFields ?? [];\r\n\r\n /**\r\n * Sanitizes a single string value\r\n */\r\n function sanitizeString(value: string): string {\r\n if (!enabled) return value;\r\n \r\n switch (mode) {\r\n case 'strip':\r\n return stripHtml(value);\r\n case 'allowList':\r\n return sanitizeWithAllowList(value, allowedTags);\r\n case 'escape':\r\n default:\r\n return escapeHtml(value);\r\n }\r\n }\r\n\r\n /**\r\n * Recursively sanitizes an object, array, or primitive value\r\n */\r\n function sanitizeValue(value: unknown, path: string = ''): unknown {\r\n // Skip fields in skipFields list\r\n if (skipFields.some(field => path.endsWith(field))) {\r\n return value;\r\n }\r\n\r\n // Null/undefined pass through\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n // Strings get sanitized\r\n if (typeof value === 'string') {\r\n return sanitizeString(value);\r\n }\r\n\r\n // Numbers, booleans pass through\r\n if (typeof value === 'number' || typeof value === 'boolean') {\r\n return value;\r\n }\r\n\r\n // Arrays - sanitize each element\r\n if (Array.isArray(value)) {\r\n return value.map((item, index) => sanitizeValue(item, `${path}[${index}]`));\r\n }\r\n\r\n // Objects - sanitize each property\r\n if (typeof value === 'object') {\r\n const sanitized: Record<string, unknown> = {};\r\n for (const [key, val] of Object.entries(value)) {\r\n const newPath = path ? `${path}.${key}` : key;\r\n sanitized[key] = sanitizeValue(val, newPath);\r\n }\r\n return sanitized;\r\n }\r\n\r\n // Everything else passes through\r\n return value;\r\n }\r\n\r\n /**\r\n * Main sanitize function\r\n */\r\n function sanitize<T>(data: T): T {\r\n if (!enabled) {\r\n return data;\r\n }\r\n return sanitizeValue(data) as T;\r\n }\r\n\r\n /**\r\n * Sanitize FormData values (returns new FormData with sanitized values)\r\n */\r\n function sanitizeFormData(formData: FormData): FormData {\r\n if (!enabled) {\r\n return formData;\r\n }\r\n \r\n const sanitized = new FormData();\r\n \r\n for (const [key, value] of formData.entries()) {\r\n if (value instanceof File) {\r\n // Files pass through unchanged\r\n sanitized.append(key, value);\r\n } else if (typeof value === 'string') {\r\n // Strings get sanitized\r\n sanitized.append(key, sanitizeString(value));\r\n } else {\r\n // Everything else passes through\r\n sanitized.append(key, value);\r\n }\r\n }\r\n \r\n return sanitized;\r\n }\r\n\r\n return {\r\n sanitize,\r\n sanitizeString,\r\n sanitizeValue,\r\n sanitizeFormData,\r\n };\r\n}\r\n\r\nexport type Sanitizer = ReturnType<typeof createSanitizer>;\r\n\r\n/**\r\n * Default sanitizer with escape mode (enabled by default)\r\n */\r\nexport const defaultSanitizer = createSanitizer({\r\n enabled: true,\r\n mode: 'escape',\r\n skipFields: [],\r\n});\r\n\r\n/**\r\n * Quick sanitize function with default config\r\n */\r\nexport function sanitize<T>(data: T): T {\r\n return defaultSanitizer.sanitize(data);\r\n}\r\n\r\n/**\r\n * Quick escape function for single strings\r\n */\r\nexport function escapeString(str: string): string {\r\n return escapeHtml(str);\r\n}\r\n\r\n/**\r\n * Quick strip function for single strings\r\n */\r\nexport function stripString(str: string): string {\r\n return stripHtml(str);\r\n}\r\n","/**\r\n * createApiClient\r\n * Server-side API client for Route Handlers\r\n * \r\n * Makes requests directly to backend with auth token from cookies/headers.\r\n * Returns Response objects that can be directly returned from route handlers.\r\n */\r\n\r\nimport { cookies, headers } from 'next/headers';\r\nimport { createSanitizer } from './sanitize';\r\nimport type { SanitizationConfig, I18nConfig } from '../shared/types';\r\nimport { HEADERS } from '../shared/constants';\r\n\r\n// ==================== Types ====================\r\n\r\nexport interface ApiClientConfig {\r\n /** Backend API base URL */\r\n apiBaseUrl: string;\r\n \r\n /** Cookie names for token retrieval */\r\n cookies: {\r\n user: string;\r\n guest: string;\r\n };\r\n \r\n /** Sanitization options */\r\n sanitization?: SanitizationConfig;\r\n \r\n /** Enable Laravel method spoofing for PUT/PATCH */\r\n methodSpoofing?: boolean;\r\n \r\n /** Custom error messages */\r\n errorMessages?: {\r\n noToken?: string;\r\n connectionError?: string;\r\n };\r\n \r\n /** i18n configuration - auto-append locale to API requests */\r\n i18n?: I18nConfig & {\r\n /** Query parameter name (default: 'lang') */\r\n paramName?: string;\r\n };\r\n}\r\n\r\nexport interface RequestOptions {\r\n /** Form data mode (use FormData, skip Content-Type header) */\r\n isFormData?: boolean;\r\n /** Use method spoofing for this request */\r\n methodSpoofing?: boolean;\r\n /** Skip authentication for this request */\r\n skipAuth?: boolean;\r\n}\r\n\r\nexport interface ApiClient {\r\n get: (endpoint: string) => Promise<Response>;\r\n post: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;\r\n put: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;\r\n patch: (endpoint: string, body?: Record<string, unknown>) => Promise<Response>;\r\n delete: (endpoint: string) => Promise<Response>;\r\n}\r\n\r\n// ==================== Factory ====================\r\n\r\n/**\r\n * Creates a server-side API client for Next.js Route Handlers\r\n * \r\n * @example\r\n * ```ts\r\n * // lib/api.ts\r\n * import { createApiClient } from 'next-api-layer';\r\n * \r\n * export const api = createApiClient({\r\n * apiBaseUrl: process.env.API_BASE_URL!,\r\n * cookies: {\r\n * user: process.env.COOKIE_USER_AUTH_TOKEN_NAME!,\r\n * guest: process.env.COOKIE_PUBLIC_AUTH_TOKEN_NAME!,\r\n * },\r\n * });\r\n * \r\n * // Usage in route handler - direct return!\r\n * export async function GET() {\r\n * return api.get('superadmin/home/list');\r\n * }\r\n * \r\n * export async function POST(request: Request) {\r\n * const body = await request.json();\r\n * return api.post('donations/create', body);\r\n * }\r\n * ```\r\n */\r\nexport function createApiClient(config: ApiClientConfig): ApiClient {\r\n const {\r\n apiBaseUrl,\r\n cookies: cookieNames,\r\n methodSpoofing: globalMethodSpoofing = false,\r\n errorMessages = {},\r\n i18n,\r\n } = config;\r\n \r\n // Ensure baseUrl ends with /\r\n const baseUrl = apiBaseUrl.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;\r\n \r\n // Create sanitizer\r\n const sanitizer = createSanitizer(config.sanitization);\r\n \r\n // Error messages\r\n const noTokenMessage = errorMessages.noToken ?? 'Token bulunamadı. Lütfen giriş yapın.';\r\n const connectionErrorMessage = errorMessages.connectionError ?? 'Bağlantı hatası oluştu.';\r\n\r\n /**\r\n * Get auth token from headers (refreshed) or cookies\r\n */\r\n async function getToken(): Promise<string | null> {\r\n // Check for refreshed token from proxy\r\n const headersList = await headers();\r\n const refreshedToken = headersList.get(HEADERS.REFRESHED_TOKEN);\r\n if (refreshedToken) {\r\n return refreshedToken;\r\n }\r\n \r\n // Get from cookies\r\n const cookieStore = await cookies();\r\n return (\r\n cookieStore.get(cookieNames.user)?.value ||\r\n cookieStore.get(cookieNames.guest)?.value ||\r\n null\r\n );\r\n }\r\n\r\n /**\r\n * Create error response\r\n */\r\n function errorResponse(message: string, status = 500): Response {\r\n return Response.json(\r\n { success: false, message },\r\n { status }\r\n );\r\n }\r\n\r\n /**\r\n * Create no-token response\r\n */\r\n function noTokenResponse(): Response {\r\n return errorResponse(noTokenMessage, 401);\r\n }\r\n\r\n /**\r\n * Make a fetch request to backend\r\n */\r\n async function makeRequest(\r\n method: string,\r\n endpoint: string,\r\n body?: Record<string, unknown> | FormData | null,\r\n options: RequestOptions = {}\r\n ): Promise<Response> {\r\n // Skip auth check if requested\r\n const token = options.skipAuth ? null : await getToken();\r\n \r\n if (!options.skipAuth && !token) {\r\n return noTokenResponse();\r\n }\r\n\r\n try {\r\n const fetchHeaders: HeadersInit = {};\r\n \r\n // Add auth header if we have a token\r\n if (token) {\r\n fetchHeaders['Authorization'] = `Bearer ${token}`;\r\n }\r\n\r\n let fetchBody: string | FormData | undefined;\r\n let actualMethod = method;\r\n\r\n // Handle body\r\n if (body) {\r\n if (options.isFormData && body instanceof FormData) {\r\n // FormData - sanitize and optionally add method spoofing\r\n fetchBody = sanitizer.sanitizeFormData(body);\r\n \r\n const useMethodSpoofing = options.methodSpoofing ?? globalMethodSpoofing;\r\n if (useMethodSpoofing && (method === 'PUT' || method === 'PATCH')) {\r\n (fetchBody as FormData).append('_method', method);\r\n actualMethod = 'POST';\r\n }\r\n } else if (!(body instanceof FormData)) {\r\n // JSON body\r\n fetchHeaders['Content-Type'] = 'application/json';\r\n fetchBody = JSON.stringify(sanitizer.sanitize(body));\r\n }\r\n }\r\n\r\n // Build URL with optional locale parameter\r\n let url = `${baseUrl}${endpoint}`;\r\n if (i18n?.enabled) {\r\n const headersList = await headers();\r\n const locale = headersList.get(HEADERS.LOCALE);\r\n if (locale && (!i18n.locales || i18n.locales.includes(locale))) {\r\n const urlObj = new URL(url);\r\n urlObj.searchParams.set(i18n.paramName || 'lang', locale);\r\n url = urlObj.toString();\r\n }\r\n }\r\n\r\n const res = await fetch(url, {\r\n method: actualMethod,\r\n headers: fetchHeaders,\r\n body: fetchBody,\r\n cache: 'no-store',\r\n });\r\n\r\n // Clone response data (stream can only be read once)\r\n const data = await res.json();\r\n return Response.json(data, { status: res.status });\r\n } catch (error) {\r\n console.error(`API ${method} Error:`, error);\r\n return errorResponse(connectionErrorMessage);\r\n }\r\n }\r\n\r\n // Return API client interface\r\n return {\r\n get: (endpoint: string) => makeRequest('GET', endpoint),\r\n \r\n post: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) =>\r\n makeRequest('POST', endpoint, body, options),\r\n \r\n put: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) =>\r\n makeRequest('PUT', endpoint, body, options),\r\n \r\n patch: (endpoint: string, body?: Record<string, unknown>) =>\r\n makeRequest('PATCH', endpoint, body),\r\n \r\n delete: (endpoint: string) => makeRequest('DELETE', endpoint),\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e={httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:"/",maxAge:604800},s={validate:"auth/me",refresh:"auth/refresh",guest:"auth/guest"},o={GUEST:"guest"},a={NO_TOKEN:"Token bulunamad\u0131.",INVALID_TOKEN:"Token ge\xE7ersiz veya s\xFCresi dolmu\u015F.",CONNECTION_ERROR:"Ba\u011Flant\u0131 hatas\u0131 olu\u015Ftu.",UNAUTHORIZED:"Bu i\u015Flem i\xE7in yetkiniz yok."},r={AUTH_USER:"x-auth-user",REFRESHED_TOKEN:"x-refreshed-token",AUTHORIZATION:"Authorization",CONTENT_TYPE:"Content-Type",SKIP_AUTH:"x-skip-auth",LOCALE:"x-locale"};var t=["GET","HEAD","OPTIONS"],E={strategy:"both",cookieName:"__csrf",headerName:"x-csrf-token",ignoreMethods:[...t],trustSameSite:false},c={windowMs:60*1e3,maxRequests:100,skipRoutes:[]},i={events:["auth:success","auth:fail","auth:refresh","access:denied","csrf:fail","rateLimit:exceeded","error"]};export{e as a,s as b,o as c,a as d,r as e,t as f,E as g,c as h,i};//# sourceMappingURL=chunk-XBAO7FJN.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-XBAO7FJN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/constants.ts"],"names":["DEFAULT_COOKIE_OPTIONS","DEFAULT_ENDPOINTS","TOKEN_TYPES","ERROR_MESSAGES","HEADERS","CSRF_SAFE_METHODS","DEFAULT_CSRF_CONFIG","DEFAULT_RATE_LIMIT_CONFIG","DEFAULT_AUDIT_CONFIG"],"mappings":"AASO,IAAMA,CAAAA,CAAkD,CAC7D,QAAA,CAAU,IAAA,CACV,MAAA,CAAQ,QAAQ,GAAA,CAAI,QAAA,GAAa,YAAA,CACjC,QAAA,CAAU,KAAA,CACV,IAAA,CAAM,IACN,MAAA,CAAQ,MACV,CAAA,CAEaC,CAAAA,CAA8C,CACzD,QAAA,CAAU,UACV,OAAA,CAAS,cAAA,CACT,KAAA,CAAO,YACT,CAAA,CAIaC,CAAAA,CAAc,CACzB,KAAA,CAAO,OACT,CAAA,CAYaC,EAAiB,CAC5B,QAAA,CAAU,wBAAA,CACV,aAAA,CAAe,+CAAA,CACf,gBAAA,CAAkB,6CAAA,CAClB,YAAA,CAAc,qCAChB,CAAA,CAIaC,CAAAA,CAAU,CACrB,SAAA,CAAW,aAAA,CACX,gBAAiB,mBAAA,CACjB,aAAA,CAAe,eAAA,CACf,YAAA,CAAc,cAAA,CACd,SAAA,CAAW,cACX,MAAA,CAAQ,UACV,EAmBO,IAAMC,CAAAA,CAAoB,CAAC,MAAO,MAAA,CAAQ,SAAS,CAAA,CAE7CC,CAAAA,CAAsB,CACjC,QAAA,CAAU,MAAA,CACV,UAAA,CAAY,QAAA,CACZ,UAAA,CAAY,cAAA,CACZ,aAAA,CAAe,CAAC,GAAGD,CAAiB,CAAA,CACpC,aAAA,CAAe,KACjB,CAAA,CAEaE,CAAAA,CAA4B,CACvC,SAAU,EAAA,CAAK,GAAA,CACf,WAAA,CAAa,GAAA,CACb,UAAA,CAAY,EACd,CAAA,CAEaC,CAAAA,CAAuB,CAClC,MAAA,CAAQ,CACN,cAAA,CACA,WAAA,CACA,cAAA,CACA,eAAA,CACA,WAAA,CACA,oBAAA,CACA,OACF,CACF","file":"chunk-XBAO7FJN.js","sourcesContent":["/**\r\n * Shared Constants\r\n * Default values and constant configurations\r\n */\r\n\r\nimport type { CookieOptions, EndpointConfig } from './types';\r\n\r\n// ==================== Default Values ====================\r\n\r\nexport const DEFAULT_COOKIE_OPTIONS: Required<CookieOptions> = {\r\n httpOnly: true,\r\n secure: process.env.NODE_ENV === 'production',\r\n sameSite: 'lax',\r\n path: '/',\r\n maxAge: 7 * 24 * 60 * 60, // 7 days\r\n} as const;\r\n\r\nexport const DEFAULT_ENDPOINTS: Required<EndpointConfig> = {\r\n validate: 'auth/me',\r\n refresh: 'auth/refresh',\r\n guest: 'auth/guest',\r\n} as const;\r\n\r\n// ==================== Token Types ====================\r\n\r\nexport const TOKEN_TYPES = {\r\n GUEST: 'guest',\r\n} as const;\r\n\r\n// ==================== Time Constants ====================\r\n\r\nexport const TIME = {\r\n SEVEN_DAYS: 7 * 24 * 60 * 60,\r\n ONE_HOUR: 60 * 60,\r\n TWO_HOURS: 2 * 60 * 60,\r\n} as const;\r\n\r\n// ==================== Error Messages ====================\r\n\r\nexport const ERROR_MESSAGES = {\r\n NO_TOKEN: 'Token bulunamadı.',\r\n INVALID_TOKEN: 'Token geçersiz veya süresi dolmuş.',\r\n CONNECTION_ERROR: 'Bağlantı hatası oluştu.',\r\n UNAUTHORIZED: 'Bu işlem için yetkiniz yok.',\r\n} as const;\r\n\r\n// ==================== Headers ====================\r\n\r\nexport const HEADERS = {\r\n AUTH_USER: 'x-auth-user',\r\n REFRESHED_TOKEN: 'x-refreshed-token',\r\n AUTHORIZATION: 'Authorization',\r\n CONTENT_TYPE: 'Content-Type',\r\n SKIP_AUTH: 'x-skip-auth',\r\n LOCALE: 'x-locale',\r\n} as const;\r\n\r\n// ==================== Sanitization Defaults ====================\r\n\r\n/**\r\n * Common safe HTML tags for allowList mode\r\n * Use with: sanitization: { mode: 'allowList', allowedTags: SAFE_HTML_TAGS }\r\n */\r\nexport const SAFE_HTML_TAGS = [\r\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'p', 'a', 'ul', 'ol', 'li',\r\n 'strong', 'em', 'b', 'i', 'br',\r\n 'blockquote', 'div', 'span',\r\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\r\n] as const;\r\n\r\n// ==================== Security Defaults ====================\r\n\r\n/** CSRF safe methods that don't need validation */\r\nexport const CSRF_SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] as const;\r\n\r\nexport const DEFAULT_CSRF_CONFIG = {\r\n strategy: 'both' as const,\r\n cookieName: '__csrf',\r\n headerName: 'x-csrf-token',\r\n ignoreMethods: [...CSRF_SAFE_METHODS],\r\n trustSameSite: false,\r\n} as const;\r\n\r\nexport const DEFAULT_RATE_LIMIT_CONFIG = {\r\n windowMs: 60 * 1000, // 1 minute\r\n maxRequests: 100,\r\n skipRoutes: [] as string[],\r\n} as const;\r\n\r\nexport const DEFAULT_AUDIT_CONFIG = {\r\n events: [\r\n 'auth:success',\r\n 'auth:fail',\r\n 'auth:refresh',\r\n 'access:denied',\r\n 'csrf:fail',\r\n 'rateLimit:exceeded',\r\n 'error',\r\n ] as const,\r\n} as const;\r\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import r from'prompts';import s from'fs';import c from'path';var l={laravel:{methodSpoofing:true,description:"Laravel (PHP)"},rails:{methodSpoofing:true,description:"Ruby on Rails"},django:{methodSpoofing:false,description:"Django (Python)"},dotnet:{methodSpoofing:false,description:".NET Core / ASP.NET"},express:{methodSpoofing:false,description:"Express.js (Node)"},go:{methodSpoofing:false,description:"Go (Gin, Echo, etc.)"},other:{methodSpoofing:false,description:"Other"}};async function u(){console.log(`
|
|
3
|
+
\u{1F510} next-api-layer setup
|
|
4
|
+
`);let e=await r([{type:"select",name:"backend",message:"Backend framework?",choices:Object.entries(l).map(([n,{description:p}])=>({title:p,value:n}))},{type:"text",name:"apiBaseUrl",message:"API base URL? (Enter to skip)",initial:"",hint:"e.g. http://localhost:8000/api"},{type:"confirm",name:"useI18n",message:"Use i18n (next-intl)?",initial:false},{type:n=>n?"text":null,name:"defaultLocale",message:"Default locale?",initial:"en"},{type:"confirm",name:"useGuestToken",message:"Use guest token for unauthenticated users?",initial:false}]);e.backend||(console.log(`
|
|
5
|
+
\u274C Setup cancelled.
|
|
6
|
+
`),process.exit(1));let o=l[e.backend],a=f(e,o),t=d(),i=c.join(t,"auth.config.ts");if(s.existsSync(i)){let{overwrite:n}=await r({type:"confirm",name:"overwrite",message:`${i} already exists. Overwrite?`,initial:false});n||(console.log(`
|
|
7
|
+
\u274C Setup cancelled.
|
|
8
|
+
`),process.exit(1));}s.mkdirSync(t,{recursive:true}),s.writeFileSync(i,a,"utf-8"),console.log(`
|
|
9
|
+
\u2705 Created ${i}
|
|
10
|
+
`),console.log("Next steps:"),console.log(" 1. Review and update the config file"),console.log(" 2. Create your proxy route: app/api/[...path]/route.ts"),console.log(` 3. Import { authProxy, api } from your config
|
|
11
|
+
`),o.methodSpoofing&&console.log(`\u{1F4A1} Method spoofing enabled for ${e.backend} (PUT/PATCH \u2192 POST + _method)
|
|
12
|
+
`);}function f(e,o){let t=["/**"," * next-api-layer configuration"," * Generated by: npx next-api-layer init"," */","","import { createAuthProxy, createApiClient } from 'next-api-layer';","","// ==================== Auth Proxy ====================","","export const authProxy = createAuthProxy({",` apiBaseUrl: ${e.apiBaseUrl?`'${e.apiBaseUrl}'`:"process.env.API_BASE_URL || 'http://localhost:8000/api'"},`,""," cookies: {"," user: 'auth_token',"," guest: 'guest_token',"," },",""," endpoints: {"," validate: 'auth/me',"," refresh: 'auth/refresh',"," guest: 'auth/guest',"," },"];return e.useGuestToken?t.push(""," guestToken: {"," enabled: true,"," // credentials: { username: 'guest', password: 'guest' },"," },"):t.push(""," guestToken: {"," enabled: false,"," },"),e.useI18n&&t.push(""," i18n: {"," enabled: true,",` defaultLocale: '${e.defaultLocale||"en"}',`," },"),t.push("});",""),t.push("// ==================== API Client ====================","","export const api = createApiClient({"),o.methodSpoofing&&t.push(` methodSpoofing: true, // ${e.backend==="laravel"?"Laravel":"Rails"} PUT/PATCH support`),t.push(""," sanitization: {"," enabled: true,"," mode: 'escape', // 'escape' | 'strip' | 'allowList'"," },"),e.useI18n&&t.push(""," i18n: {"," enabled: true,"," paramName: 'lang',"," },"),t.push("});",""),t.push("// Re-export for convenience","export type { ApiResponse } from 'next-api-layer/api';",""),t.join(`
|
|
13
|
+
`)}function d(){let e=["lib/auth","src/lib/auth","lib","src/lib","config","src/config"];for(let o of e)if(s.existsSync(o))return o;return "lib/auth"}u().catch(e=>{console.error("Error:",e.message),process.exit(1);});//# sourceMappingURL=init.js.map
|
|
14
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/init.ts"],"names":["BACKEND_PRESETS","main","answers","prompts","value","description","prev","preset","config","generateConfig","outputDir","findConfigDir","outputPath","path","fs","overwrite","lines","candidates","dir","err"],"mappings":"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","file":"init.js","sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * CLI init wizard for next-api-layer\r\n * Usage: npx next-api-layer init\r\n */\r\n\r\nimport prompts from 'prompts';\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\ninterface InitAnswers {\r\n backend: 'laravel' | 'rails' | 'django' | 'dotnet' | 'express' | 'go' | 'other';\r\n apiBaseUrl: string;\r\n useI18n: boolean;\r\n defaultLocale?: string;\r\n useGuestToken: boolean;\r\n}\r\n\r\nconst BACKEND_PRESETS: Record<string, { methodSpoofing: boolean; description: string }> = {\r\n laravel: { methodSpoofing: true, description: 'Laravel (PHP)' },\r\n rails: { methodSpoofing: true, description: 'Ruby on Rails' },\r\n django: { methodSpoofing: false, description: 'Django (Python)' },\r\n dotnet: { methodSpoofing: false, description: '.NET Core / ASP.NET' },\r\n express: { methodSpoofing: false, description: 'Express.js (Node)' },\r\n go: { methodSpoofing: false, description: 'Go (Gin, Echo, etc.)' },\r\n other: { methodSpoofing: false, description: 'Other' },\r\n};\r\n\r\nasync function main() {\r\n console.log('\\n🔐 next-api-layer setup\\n');\r\n\r\n const answers = await prompts([\r\n {\r\n type: 'select',\r\n name: 'backend',\r\n message: 'Backend framework?',\r\n choices: Object.entries(BACKEND_PRESETS).map(([value, { description }]) => ({\r\n title: description,\r\n value,\r\n })),\r\n },\r\n {\r\n type: 'text',\r\n name: 'apiBaseUrl',\r\n message: 'API base URL? (Enter to skip)',\r\n initial: '',\r\n hint: 'e.g. http://localhost:8000/api',\r\n },\r\n {\r\n type: 'confirm',\r\n name: 'useI18n',\r\n message: 'Use i18n (next-intl)?',\r\n initial: false,\r\n },\r\n {\r\n type: (prev) => prev ? 'text' : null,\r\n name: 'defaultLocale',\r\n message: 'Default locale?',\r\n initial: 'en',\r\n },\r\n {\r\n type: 'confirm',\r\n name: 'useGuestToken',\r\n message: 'Use guest token for unauthenticated users?',\r\n initial: false,\r\n },\r\n ]);\r\n\r\n // User cancelled\r\n if (!answers.backend) {\r\n console.log('\\n❌ Setup cancelled.\\n');\r\n process.exit(1);\r\n }\r\n\r\n const preset = BACKEND_PRESETS[answers.backend];\r\n const config = generateConfig(answers, preset);\r\n\r\n // Determine output path\r\n const outputDir = findConfigDir();\r\n const outputPath = path.join(outputDir, 'auth.config.ts');\r\n\r\n // Check if file exists\r\n if (fs.existsSync(outputPath)) {\r\n const { overwrite } = await prompts({\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: `${outputPath} already exists. Overwrite?`,\r\n initial: false,\r\n });\r\n\r\n if (!overwrite) {\r\n console.log('\\n❌ Setup cancelled.\\n');\r\n process.exit(1);\r\n }\r\n }\r\n\r\n // Ensure directory exists\r\n fs.mkdirSync(outputDir, { recursive: true });\r\n\r\n // Write config file\r\n fs.writeFileSync(outputPath, config, 'utf-8');\r\n\r\n console.log(`\\n✅ Created ${outputPath}\\n`);\r\n console.log('Next steps:');\r\n console.log(' 1. Review and update the config file');\r\n console.log(' 2. Create your proxy route: app/api/[...path]/route.ts');\r\n console.log(' 3. Import { authProxy, api } from your config\\n');\r\n\r\n if (preset.methodSpoofing) {\r\n console.log(`💡 Method spoofing enabled for ${answers.backend} (PUT/PATCH → POST + _method)\\n`);\r\n }\r\n}\r\n\r\nfunction generateConfig(answers: InitAnswers, preset: { methodSpoofing: boolean }): string {\r\n const apiUrlValue = answers.apiBaseUrl \r\n ? `'${answers.apiBaseUrl}'` \r\n : `process.env.API_BASE_URL || 'http://localhost:8000/api'`;\r\n\r\n const lines: string[] = [\r\n `/**`,\r\n ` * next-api-layer configuration`,\r\n ` * Generated by: npx next-api-layer init`,\r\n ` */`,\r\n ``,\r\n `import { createAuthProxy, createApiClient } from 'next-api-layer';`,\r\n ``,\r\n `// ==================== Auth Proxy ====================`,\r\n ``,\r\n `export const authProxy = createAuthProxy({`,\r\n ` apiBaseUrl: ${apiUrlValue},`,\r\n ``,\r\n ` cookies: {`,\r\n ` user: 'auth_token',`,\r\n ` guest: 'guest_token',`,\r\n ` },`,\r\n ``,\r\n ` endpoints: {`,\r\n ` validate: 'auth/me',`,\r\n ` refresh: 'auth/refresh',`,\r\n ` guest: 'auth/guest',`,\r\n ` },`,\r\n ];\r\n\r\n // Guest token\r\n if (answers.useGuestToken) {\r\n lines.push(``,\r\n ` guestToken: {`,\r\n ` enabled: true,`,\r\n ` // credentials: { username: 'guest', password: 'guest' },`,\r\n ` },`\r\n );\r\n } else {\r\n lines.push(``,\r\n ` guestToken: {`,\r\n ` enabled: false,`,\r\n ` },`\r\n );\r\n }\r\n\r\n // i18n\r\n if (answers.useI18n) {\r\n lines.push(``,\r\n ` i18n: {`,\r\n ` enabled: true,`,\r\n ` defaultLocale: '${answers.defaultLocale || 'en'}',`,\r\n ` },`\r\n );\r\n }\r\n\r\n lines.push(`});`, ``);\r\n\r\n // API Client\r\n lines.push(\r\n `// ==================== API Client ====================`,\r\n ``,\r\n `export const api = createApiClient({`\r\n );\r\n\r\n // Method spoofing\r\n if (preset.methodSpoofing) {\r\n lines.push(` methodSpoofing: true, // ${answers.backend === 'laravel' ? 'Laravel' : 'Rails'} PUT/PATCH support`);\r\n }\r\n\r\n // Sanitization (always on by default)\r\n lines.push(\r\n ``,\r\n ` sanitization: {`,\r\n ` enabled: true,`,\r\n ` mode: 'escape', // 'escape' | 'strip' | 'allowList'`,\r\n ` },`\r\n );\r\n\r\n // i18n for API client\r\n if (answers.useI18n) {\r\n lines.push(\r\n ``,\r\n ` i18n: {`,\r\n ` enabled: true,`,\r\n ` paramName: 'lang',`,\r\n ` },`\r\n );\r\n }\r\n\r\n lines.push(`});`, ``);\r\n\r\n // Export types\r\n lines.push(\r\n `// Re-export for convenience`,\r\n `export type { ApiResponse } from 'next-api-layer/api';`,\r\n ``\r\n );\r\n\r\n return lines.join('\\n');\r\n}\r\n\r\nfunction findConfigDir(): string {\r\n // Look for common directories\r\n const candidates = [\r\n 'lib/auth',\r\n 'src/lib/auth',\r\n 'lib',\r\n 'src/lib',\r\n 'config',\r\n 'src/config',\r\n ];\r\n\r\n for (const dir of candidates) {\r\n if (fs.existsSync(dir)) {\r\n return dir;\r\n }\r\n }\r\n\r\n // Default to lib/auth\r\n return 'lib/auth';\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Error:', err.message);\r\n process.exit(1);\r\n});\r\n"]}
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';var react=require('react'),S=require('swr'),jsxRuntime=require('react/jsx-runtime'),navigation=require('next/navigation');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var S__default=/*#__PURE__*/_interopDefault(S);var m=react.createContext(void 0);function E(t){if(!t||typeof t!="object")return null;let e=t;return e.success&&e.data?e.data:e.user?e.user:"id"in e||"email"in e||"name"in e||"type"in e?e:null}function F(t){if(!t||typeof t!="object")return false;let e=t;return e.token_type==="guest"||e.type==="guest"}function G({children:t,initialUser:e,userEndpoint:c="/api/auth/me",loginEndpoint:o="/api/auth/login",registerEndpoint:l="/api/auth/register",logoutEndpoint:g="/api/auth/logout",logoutRedirect:w,isGuestFn:j=F,parseResponse:f=E,swrConfig:b={},onLogin:p,onLogout:x,onError:i}){let V=react.useCallback(async n=>{let r=await fetch(n);if(!r.ok){if(r.status===401)return null;throw new Error("Failed to fetch user")}let s=await r.json();return f(s)},[f]),{data:A,error:D,isLoading:R,mutate:u}=S__default.default(c,V,{fallbackData:e??void 0,revalidateOnFocus:true,revalidateOnReconnect:true,revalidateOnMount:!e,refreshInterval:0,shouldRetryOnError:false,...b,onError:n=>{i?.(n);}}),P=react.useCallback(async n=>{try{let s=await(await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json(),a=f(s);if(a)return await u(a,!1),p?.(a),{success:!0,user:a};let d=s;return {success:!1,message:d.message||"Login failed",errors:d.errors}}catch(r){let s=r instanceof Error?r:new Error("Login failed");return i?.(s),{success:false,message:s.message}}},[o,u,p,i,f]),C=react.useCallback(async n=>{try{let s=await(await fetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json(),a=f(s);if(a)return await u(a,!1),p?.(a),{success:!0,user:a};let d=s;return {success:!1,message:d.message||"Registration failed",errors:d.errors}}catch(r){let s=r instanceof Error?r:new Error("Registration failed");return i?.(s),{success:false,message:s.message}}},[l,u,p,i,f]),v=react.useCallback(async()=>{try{await fetch(g,{method:"POST"}),await u(null,!1),x?.(),w&&typeof window<"u"&&(window.location.href=w);}catch(n){let r=n instanceof Error?n:new Error("Logout failed");throw i?.(r),r}},[g,w,u,x,i]),y=react.useCallback(async()=>{await u();},[u]),T=j(A??null),O=!!A&&!T,k=react.useMemo(()=>({user:A??null,isLoading:R,isAuthenticated:O,isGuest:T,error:D??null,login:P,register:C,logout:v,refresh:y,mutate:y}),[A,R,O,T,D,P,C,v,y]);return jsxRuntime.jsx(m.Provider,{value:k,children:t})}function U(t={}){let e=react.useContext(m),c=navigation.useRouter();if(!e)throw new Error("useAuth must be used within an AuthProvider. Wrap your app with <AuthProvider> from next-api-layer/client");let{redirectTo:o,redirectIfFound:l}=t;return e.isLoading||(o&&!e.isAuthenticated&&!e.isGuest&&typeof window<"u"&&c.replace(o),l&&e.isAuthenticated&&typeof window<"u"&&c.replace(l)),e}function N(){let{user:t,isLoading:e,isAuthenticated:c,isGuest:o}=U();return {user:t,isLoading:e,isAuthenticated:c,isGuest:o}}function W(t="/login"){let e=U({redirectTo:t});if(!e.isLoading&&!e.isAuthenticated)throw new Error("Authentication required");return e}function _(t="/"){return U({redirectIfFound:t})}exports.AuthContext=m;exports.AuthProvider=G;exports.useAuth=U;exports.useRedirectIfAuth=_;exports.useRequireAuth=W;exports.useUser=N;//# sourceMappingURL=client.cjs.map
|
|
3
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/AuthProvider.tsx","../src/client/useAuth.ts"],"names":["AuthContext","createContext","defaultParseResponse","response","res","defaultIsGuest","user","u","AuthProvider","children","initialUser","userEndpoint","loginEndpoint","registerEndpoint","logoutEndpoint","logoutRedirect","isGuestFn","parseResponse","swrConfig","onLogin","onLogout","onError","fetcher","useCallback","url","json","error","isLoading","mutate","useSWR","err","login","credentials","userData","errorResponse","register","data","logout","refresh","isGuest","isAuthenticated","contextValue","useMemo","jsx","useAuth","options","context","useContext","router","useRouter","redirectTo","redirectIfFound","useUser","useRequireAuth","auth","useRedirectIfAuth"],"mappings":"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","file":"client.cjs","sourcesContent":["'use client';\r\n\r\n/**\r\n * AuthProvider - Generic React context for any backend format\r\n * \r\n * @example Basic usage\r\n * ```tsx\r\n * <AuthProvider>\r\n * {children}\r\n * </AuthProvider>\r\n * ```\r\n * \r\n * @example With custom user type\r\n * ```tsx\r\n * interface MyUser {\r\n * type: 'guest' | 'superadmin';\r\n * user?: { name: string; email: string; };\r\n * }\r\n * \r\n * <AuthProvider<MyUser>\r\n * initialUser={serverUser}\r\n * isGuestFn={(u) => u?.type === 'guest'}\r\n * parseResponse={(res) => res.data}\r\n * >\r\n * {children}\r\n * </AuthProvider>\r\n * ```\r\n */\r\n\r\nimport React, { createContext, useCallback, useMemo } from 'react';\r\nimport useSWR from 'swr';\r\nimport type { \r\n AuthContextValue, \r\n AuthProviderProps, \r\n LoginCredentials,\r\n RegisterData,\r\n AuthResult,\r\n DefaultUserData,\r\n ApiResponse,\r\n} from './types';\r\n\r\n// ==================== Context ====================\r\n\r\n// We use 'any' here because the context needs to work with any user type\r\n// The actual type safety comes from useAuth<TUser>() hook\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport const AuthContext = createContext<AuthContextValue<any> | undefined>(undefined);\r\n\r\n// ==================== Default Functions ====================\r\n\r\n/** Default response parser - handles common API formats */\r\nfunction defaultParseResponse<TUser>(response: unknown): TUser | null {\r\n if (!response || typeof response !== 'object') return null;\r\n \r\n const res = response as Record<string, unknown>;\r\n \r\n // Format: { success: true, data: user }\r\n if (res.success && res.data) {\r\n return res.data as TUser;\r\n }\r\n \r\n // Format: { user: {...} }\r\n if (res.user) {\r\n return res.user as TUser;\r\n }\r\n \r\n // Format: user object directly\r\n if ('id' in res || 'email' in res || 'name' in res || 'type' in res) {\r\n return res as TUser;\r\n }\r\n \r\n return null;\r\n}\r\n\r\n/** Default guest check - handles common patterns */\r\nfunction defaultIsGuest<TUser>(user: TUser | null): boolean {\r\n if (!user || typeof user !== 'object') return false;\r\n \r\n const u = user as Record<string, unknown>;\r\n \r\n // Check token_type (flat structure)\r\n if (u.token_type === 'guest') return true;\r\n \r\n // Check type (nested structure)\r\n if (u.type === 'guest') return true;\r\n \r\n return false;\r\n}\r\n\r\n// ==================== Provider Component ====================\r\n\r\nexport function AuthProvider<TUser = DefaultUserData>({\r\n children,\r\n initialUser,\r\n userEndpoint = '/api/auth/me',\r\n loginEndpoint = '/api/auth/login',\r\n registerEndpoint = '/api/auth/register',\r\n logoutEndpoint = '/api/auth/logout',\r\n logoutRedirect,\r\n isGuestFn = defaultIsGuest,\r\n parseResponse = defaultParseResponse,\r\n swrConfig = {},\r\n onLogin,\r\n onLogout,\r\n onError,\r\n}: AuthProviderProps<TUser>): React.ReactElement {\r\n \r\n // Create fetcher with custom parser\r\n const fetcher = useCallback(async (url: string): Promise<TUser | null> => {\r\n const res = await fetch(url);\r\n \r\n if (!res.ok) {\r\n if (res.status === 401) {\r\n return null;\r\n }\r\n throw new Error('Failed to fetch user');\r\n }\r\n \r\n const json = await res.json();\r\n return parseResponse(json);\r\n }, [parseResponse]);\r\n\r\n // Fetch user data with SWR\r\n const {\r\n data: user,\r\n error,\r\n isLoading,\r\n mutate,\r\n } = useSWR<TUser | null>(\r\n userEndpoint,\r\n fetcher,\r\n {\r\n fallbackData: initialUser ?? undefined,\r\n revalidateOnFocus: true,\r\n revalidateOnReconnect: true,\r\n revalidateOnMount: !initialUser,\r\n refreshInterval: 0,\r\n shouldRetryOnError: false,\r\n ...swrConfig,\r\n onError: (err: Error) => {\r\n onError?.(err);\r\n },\r\n }\r\n );\r\n\r\n // Login function\r\n const login = useCallback(async (credentials: LoginCredentials): Promise<AuthResult<TUser>> => {\r\n try {\r\n const res = await fetch(loginEndpoint, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(credentials),\r\n });\r\n\r\n const json = await res.json();\r\n const userData = parseResponse(json);\r\n\r\n if (userData) {\r\n await mutate(userData, false);\r\n onLogin?.(userData);\r\n return { success: true, user: userData };\r\n }\r\n\r\n const errorResponse = json as ApiResponse;\r\n return {\r\n success: false,\r\n message: errorResponse.message || 'Login failed',\r\n errors: errorResponse.errors,\r\n };\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error('Login failed');\r\n onError?.(error);\r\n return { success: false, message: error.message };\r\n }\r\n }, [loginEndpoint, mutate, onLogin, onError, parseResponse]);\r\n\r\n // Register function\r\n const register = useCallback(async (data: RegisterData): Promise<AuthResult<TUser>> => {\r\n try {\r\n const res = await fetch(registerEndpoint, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data),\r\n });\r\n\r\n const json = await res.json();\r\n const userData = parseResponse(json);\r\n\r\n if (userData) {\r\n await mutate(userData, false);\r\n onLogin?.(userData);\r\n return { success: true, user: userData };\r\n }\r\n\r\n const errorResponse = json as ApiResponse;\r\n return {\r\n success: false,\r\n message: errorResponse.message || 'Registration failed',\r\n errors: errorResponse.errors,\r\n };\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error('Registration failed');\r\n onError?.(error);\r\n return { success: false, message: error.message };\r\n }\r\n }, [registerEndpoint, mutate, onLogin, onError, parseResponse]);\r\n\r\n // Logout function\r\n const logout = useCallback(async (): Promise<void> => {\r\n try {\r\n await fetch(logoutEndpoint, { method: 'POST' });\r\n await mutate(null, false);\r\n onLogout?.();\r\n \r\n // Redirect after logout if configured\r\n if (logoutRedirect && typeof window !== 'undefined') {\r\n window.location.href = logoutRedirect;\r\n }\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error('Logout failed');\r\n onError?.(error);\r\n throw error;\r\n }\r\n }, [logoutEndpoint, logoutRedirect, mutate, onLogout, onError]);\r\n\r\n // Refresh user data\r\n const refresh = useCallback(async (): Promise<void> => {\r\n await mutate();\r\n }, [mutate]);\r\n\r\n // Compute derived state using custom isGuestFn\r\n const isGuest = isGuestFn(user ?? null);\r\n const isAuthenticated = !!user && !isGuest;\r\n\r\n // Memoize context value\r\n const contextValue = useMemo<AuthContextValue<TUser>>(() => ({\r\n user: user ?? null,\r\n isLoading,\r\n isAuthenticated,\r\n isGuest,\r\n error: error ?? null,\r\n login,\r\n register,\r\n logout,\r\n refresh,\r\n mutate: refresh,\r\n }), [user, isLoading, isAuthenticated, isGuest, error, login, register, logout, refresh]);\r\n\r\n return (\r\n <AuthContext.Provider value={contextValue}>\r\n {children}\r\n </AuthContext.Provider>\r\n );\r\n}\r\n","'use client';\r\n\r\n/**\r\n * useAuth Hook - Generic for any user type\r\n * \r\n * @example Basic usage\r\n * ```tsx\r\n * const { user, isAuthenticated, logout } = useAuth();\r\n * ```\r\n * \r\n * @example With custom type\r\n * ```tsx\r\n * interface MyUser {\r\n * type: 'guest' | 'superadmin';\r\n * user?: { name: string; };\r\n * }\r\n * \r\n * const { user } = useAuth<MyUser>();\r\n * // user is MyUser | null\r\n * ```\r\n */\r\n\r\nimport { useContext } from 'react';\r\nimport { useRouter } from 'next/navigation';\r\nimport { AuthContext } from './AuthProvider';\r\nimport type { AuthContextValue, UseAuthOptions, DefaultUserData } from './types';\r\n\r\n/**\r\n * Hook to access authentication state and methods\r\n * \r\n * @typeParam TUser - User data type (defaults to DefaultUserData)\r\n */\r\nexport function useAuth<TUser = DefaultUserData>(\r\n options: UseAuthOptions = {}\r\n): AuthContextValue<TUser> {\r\n const context = useContext(AuthContext) as AuthContextValue<TUser> | undefined;\r\n const router = useRouter();\r\n\r\n if (!context) {\r\n throw new Error(\r\n 'useAuth must be used within an AuthProvider. ' +\r\n 'Wrap your app with <AuthProvider> from next-api-layer/client'\r\n );\r\n }\r\n\r\n const { redirectTo, redirectIfFound } = options;\r\n\r\n // Handle redirects based on auth state\r\n if (!context.isLoading) {\r\n if (redirectTo && !context.isAuthenticated && !context.isGuest) {\r\n if (typeof window !== 'undefined') {\r\n router.replace(redirectTo);\r\n }\r\n }\r\n\r\n if (redirectIfFound && context.isAuthenticated) {\r\n if (typeof window !== 'undefined') {\r\n router.replace(redirectIfFound);\r\n }\r\n }\r\n }\r\n\r\n return context;\r\n}\r\n\r\n/**\r\n * Hook to get only the user object\r\n * \r\n * @typeParam TUser - User data type\r\n */\r\nexport function useUser<TUser = DefaultUserData>() {\r\n const { user, isLoading, isAuthenticated, isGuest } = useAuth<TUser>();\r\n return { user, isLoading, isAuthenticated, isGuest };\r\n}\r\n\r\n/**\r\n * Hook for protected pages - redirects if not authenticated\r\n * \r\n * @typeParam TUser - User data type\r\n */\r\nexport function useRequireAuth<TUser = DefaultUserData>(redirectTo = '/login') {\r\n const auth = useAuth<TUser>({ redirectTo });\r\n \r\n if (!auth.isLoading && !auth.isAuthenticated) {\r\n throw new Error('Authentication required');\r\n }\r\n \r\n return auth;\r\n}\r\n\r\n/**\r\n * Hook for auth pages - redirects if already authenticated\r\n * \r\n * @typeParam TUser - User data type\r\n */\r\nexport function useRedirectIfAuth<TUser = DefaultUserData>(redirectTo = '/') {\r\n return useAuth<TUser>({ redirectIfFound: redirectTo });\r\n}\r\n"]}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client-side types - Generic for any backend format
|
|
5
|
+
*/
|
|
6
|
+
/** Default user data structure (can be overridden with generics) */
|
|
7
|
+
interface DefaultUserData {
|
|
8
|
+
id?: number | string;
|
|
9
|
+
name?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
token_type?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
interface AuthContextValue<TUser = DefaultUserData> {
|
|
16
|
+
/** Current user data, null if not authenticated */
|
|
17
|
+
user: TUser | null;
|
|
18
|
+
/** Loading state for initial auth check */
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
/** True if user is authenticated (not guest) */
|
|
21
|
+
isAuthenticated: boolean;
|
|
22
|
+
/** True if user is a guest */
|
|
23
|
+
isGuest: boolean;
|
|
24
|
+
/** Error from last auth operation */
|
|
25
|
+
error: Error | null;
|
|
26
|
+
/** Login function */
|
|
27
|
+
login: (credentials: LoginCredentials) => Promise<AuthResult<TUser>>;
|
|
28
|
+
/** Register function */
|
|
29
|
+
register: (data: RegisterData) => Promise<AuthResult<TUser>>;
|
|
30
|
+
/** Logout function */
|
|
31
|
+
logout: () => Promise<void>;
|
|
32
|
+
/** Refresh user data */
|
|
33
|
+
refresh: () => Promise<void>;
|
|
34
|
+
/** SWR mutate function for manual revalidation */
|
|
35
|
+
mutate: () => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
interface AuthResult<TUser = DefaultUserData> {
|
|
38
|
+
success: boolean;
|
|
39
|
+
message?: string;
|
|
40
|
+
user?: TUser;
|
|
41
|
+
errors?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
interface AuthProviderProps<TUser = DefaultUserData> {
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
/** Initial user data from server (SSR) */
|
|
46
|
+
initialUser?: TUser | null;
|
|
47
|
+
/** Endpoint to fetch user data */
|
|
48
|
+
userEndpoint?: string;
|
|
49
|
+
/** Endpoint for login */
|
|
50
|
+
loginEndpoint?: string;
|
|
51
|
+
/** Endpoint for register */
|
|
52
|
+
registerEndpoint?: string;
|
|
53
|
+
/** Endpoint for logout */
|
|
54
|
+
logoutEndpoint?: string;
|
|
55
|
+
/** Redirect to this path after logout (uses window.location.href) */
|
|
56
|
+
logoutRedirect?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Function to check if user is a guest
|
|
59
|
+
* @default (user) => user?.token_type === 'guest' || user?.type === 'guest'
|
|
60
|
+
*/
|
|
61
|
+
isGuestFn?: (user: TUser | null) => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Function to parse API response and extract user data
|
|
64
|
+
* @default (response) => response.data || response.user || response
|
|
65
|
+
*/
|
|
66
|
+
parseResponse?: (response: unknown) => TUser | null;
|
|
67
|
+
/** SWR config overrides */
|
|
68
|
+
swrConfig?: {
|
|
69
|
+
refreshInterval?: number;
|
|
70
|
+
revalidateOnFocus?: boolean;
|
|
71
|
+
revalidateOnReconnect?: boolean;
|
|
72
|
+
};
|
|
73
|
+
/** Called when user logs in */
|
|
74
|
+
onLogin?: (user: TUser) => void;
|
|
75
|
+
/** Called when user logs out */
|
|
76
|
+
onLogout?: () => void;
|
|
77
|
+
/** Called on auth error */
|
|
78
|
+
onError?: (error: Error) => void;
|
|
79
|
+
}
|
|
80
|
+
interface LoginCredentials {
|
|
81
|
+
email?: string;
|
|
82
|
+
username?: string;
|
|
83
|
+
password: string;
|
|
84
|
+
remember?: boolean;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
}
|
|
87
|
+
interface RegisterData {
|
|
88
|
+
name?: string;
|
|
89
|
+
email?: string;
|
|
90
|
+
password?: string;
|
|
91
|
+
password_confirmation?: string;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
}
|
|
94
|
+
interface UseAuthOptions {
|
|
95
|
+
/** Redirect to this path if not authenticated */
|
|
96
|
+
redirectTo?: string;
|
|
97
|
+
/** Redirect to this path if authenticated */
|
|
98
|
+
redirectIfFound?: string;
|
|
99
|
+
}
|
|
100
|
+
interface ApiResponse<T = unknown> {
|
|
101
|
+
success: boolean;
|
|
102
|
+
data?: T;
|
|
103
|
+
message?: string;
|
|
104
|
+
errors?: Record<string, unknown>;
|
|
105
|
+
}
|
|
106
|
+
/** @deprecated Use DefaultUserData or your own type */
|
|
107
|
+
type UserData = DefaultUserData;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* AuthProvider - Generic React context for any backend format
|
|
111
|
+
*
|
|
112
|
+
* @example Basic usage
|
|
113
|
+
* ```tsx
|
|
114
|
+
* <AuthProvider>
|
|
115
|
+
* {children}
|
|
116
|
+
* </AuthProvider>
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @example With custom user type
|
|
120
|
+
* ```tsx
|
|
121
|
+
* interface MyUser {
|
|
122
|
+
* type: 'guest' | 'superadmin';
|
|
123
|
+
* user?: { name: string; email: string; };
|
|
124
|
+
* }
|
|
125
|
+
*
|
|
126
|
+
* <AuthProvider<MyUser>
|
|
127
|
+
* initialUser={serverUser}
|
|
128
|
+
* isGuestFn={(u) => u?.type === 'guest'}
|
|
129
|
+
* parseResponse={(res) => res.data}
|
|
130
|
+
* >
|
|
131
|
+
* {children}
|
|
132
|
+
* </AuthProvider>
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
declare const AuthContext: React$1.Context<AuthContextValue<any> | undefined>;
|
|
137
|
+
declare function AuthProvider<TUser = DefaultUserData>({ children, initialUser, userEndpoint, loginEndpoint, registerEndpoint, logoutEndpoint, logoutRedirect, isGuestFn, parseResponse, swrConfig, onLogin, onLogout, onError, }: AuthProviderProps<TUser>): React$1.ReactElement;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Hook to access authentication state and methods
|
|
141
|
+
*
|
|
142
|
+
* @typeParam TUser - User data type (defaults to DefaultUserData)
|
|
143
|
+
*/
|
|
144
|
+
declare function useAuth<TUser = DefaultUserData>(options?: UseAuthOptions): AuthContextValue<TUser>;
|
|
145
|
+
/**
|
|
146
|
+
* Hook to get only the user object
|
|
147
|
+
*
|
|
148
|
+
* @typeParam TUser - User data type
|
|
149
|
+
*/
|
|
150
|
+
declare function useUser<TUser = DefaultUserData>(): {
|
|
151
|
+
user: TUser | null;
|
|
152
|
+
isLoading: boolean;
|
|
153
|
+
isAuthenticated: boolean;
|
|
154
|
+
isGuest: boolean;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Hook for protected pages - redirects if not authenticated
|
|
158
|
+
*
|
|
159
|
+
* @typeParam TUser - User data type
|
|
160
|
+
*/
|
|
161
|
+
declare function useRequireAuth<TUser = DefaultUserData>(redirectTo?: string): AuthContextValue<TUser>;
|
|
162
|
+
/**
|
|
163
|
+
* Hook for auth pages - redirects if already authenticated
|
|
164
|
+
*
|
|
165
|
+
* @typeParam TUser - User data type
|
|
166
|
+
*/
|
|
167
|
+
declare function useRedirectIfAuth<TUser = DefaultUserData>(redirectTo?: string): AuthContextValue<TUser>;
|
|
168
|
+
|
|
169
|
+
export { type ApiResponse, AuthContext, type AuthContextValue, AuthProvider, type AuthProviderProps, type AuthResult, type DefaultUserData, type LoginCredentials, type RegisterData, type UseAuthOptions, type UserData, useAuth, useRedirectIfAuth, useRequireAuth, useUser };
|