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/client.d.ts
ADDED
|
@@ -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 };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {createContext,useCallback,useMemo,useContext}from'react';import S from'swr';import {jsx}from'react/jsx-runtime';import {useRouter}from'next/navigation';var m=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=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(c,V,{fallbackData:e??void 0,revalidateOnFocus:true,revalidateOnReconnect:true,revalidateOnMount:!e,refreshInterval:0,shouldRetryOnError:false,...b,onError:n=>{i?.(n);}}),P=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=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=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=useCallback(async()=>{await u();},[u]),T=j(A??null),O=!!A&&!T,k=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 jsx(m.Provider,{value:k,children:t})}function U(t={}){let e=useContext(m),c=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})}export{m as AuthContext,G as AuthProvider,U as useAuth,_ as useRedirectIfAuth,W as useRequireAuth,N as useUser};//# sourceMappingURL=client.js.map
|
|
3
|
+
//# sourceMappingURL=client.js.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.js","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,383 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared Types
|
|
5
|
+
* Core type definitions used across the library
|
|
6
|
+
*/
|
|
7
|
+
interface CookieOptions {
|
|
8
|
+
httpOnly?: boolean;
|
|
9
|
+
secure?: boolean;
|
|
10
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
11
|
+
path?: string;
|
|
12
|
+
maxAge?: number;
|
|
13
|
+
}
|
|
14
|
+
interface CookieConfig {
|
|
15
|
+
user: string;
|
|
16
|
+
guest: string;
|
|
17
|
+
options?: CookieOptions;
|
|
18
|
+
}
|
|
19
|
+
interface TokenInfo {
|
|
20
|
+
isValid: boolean;
|
|
21
|
+
tokenType: string | null;
|
|
22
|
+
exp: number | null;
|
|
23
|
+
userData: Record<string, unknown> | null;
|
|
24
|
+
timestamp?: number;
|
|
25
|
+
}
|
|
26
|
+
interface RefreshResult {
|
|
27
|
+
success: boolean;
|
|
28
|
+
newToken: string | null;
|
|
29
|
+
}
|
|
30
|
+
interface ApiResponse<T = unknown> {
|
|
31
|
+
success: boolean;
|
|
32
|
+
message?: string;
|
|
33
|
+
data?: T;
|
|
34
|
+
errors?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
interface EndpointConfig {
|
|
37
|
+
validate?: string;
|
|
38
|
+
refresh?: string;
|
|
39
|
+
guest?: string;
|
|
40
|
+
}
|
|
41
|
+
interface GuestTokenConfig {
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
credentials?: {
|
|
44
|
+
username: string;
|
|
45
|
+
password: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
interface AccessConfig {
|
|
49
|
+
/** Token types allowed to access the app (e.g., ['superadmin', 'admin']) */
|
|
50
|
+
allowedTokenTypes?: string[];
|
|
51
|
+
/** Routes that require authentication */
|
|
52
|
+
protectedRoutes?: string[];
|
|
53
|
+
/** Auth pages (login, register) - authenticated users redirected away */
|
|
54
|
+
authRoutes?: string[];
|
|
55
|
+
/** Routes accessible without authentication */
|
|
56
|
+
publicRoutes?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* If true, all routes are protected by default (except publicRoutes and authRoutes)
|
|
59
|
+
* Useful for admin panels where everything requires auth
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
protectedByDefault?: boolean;
|
|
63
|
+
}
|
|
64
|
+
interface I18nConfig {
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
locales?: string[];
|
|
67
|
+
defaultLocale?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Result of auth validation passed to afterAuth hook */
|
|
71
|
+
interface AuthResult {
|
|
72
|
+
isAuthenticated: boolean;
|
|
73
|
+
isGuest: boolean;
|
|
74
|
+
tokenType: string | null;
|
|
75
|
+
user: Record<string, unknown> | null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Response mappers for different backend formats
|
|
79
|
+
* Allows adapting any backend response format to library's internal format
|
|
80
|
+
*/
|
|
81
|
+
interface ResponseMappers {
|
|
82
|
+
/**
|
|
83
|
+
* Parse auth/me response to extract token info
|
|
84
|
+
* @param response - Raw response from backend
|
|
85
|
+
* @returns TokenInfo or null if invalid
|
|
86
|
+
*
|
|
87
|
+
* @example Laravel format
|
|
88
|
+
* ```ts
|
|
89
|
+
* parseAuthMe: (res) => ({
|
|
90
|
+
* isValid: res.success,
|
|
91
|
+
* tokenType: res.data?.token_type || 'user',
|
|
92
|
+
* exp: res.data?.expires_at,
|
|
93
|
+
* userData: res.data?.user,
|
|
94
|
+
* })
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @example Django format
|
|
98
|
+
* ```ts
|
|
99
|
+
* parseAuthMe: (res) => ({
|
|
100
|
+
* isValid: !!res.user,
|
|
101
|
+
* tokenType: res.is_guest ? 'guest' : 'user',
|
|
102
|
+
* exp: res.exp,
|
|
103
|
+
* userData: res.user,
|
|
104
|
+
* })
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
parseAuthMe?: (response: unknown) => TokenInfo | null;
|
|
108
|
+
/**
|
|
109
|
+
* Parse refresh token response
|
|
110
|
+
* @returns The new access token or null
|
|
111
|
+
*/
|
|
112
|
+
parseRefreshToken?: (response: unknown) => string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Parse guest token response
|
|
115
|
+
* @returns The guest access token or null
|
|
116
|
+
*/
|
|
117
|
+
parseGuestToken?: (response: unknown) => string | null;
|
|
118
|
+
}
|
|
119
|
+
interface AuthProxyConfig {
|
|
120
|
+
apiBaseUrl: string;
|
|
121
|
+
cookies: CookieConfig;
|
|
122
|
+
endpoints?: EndpointConfig;
|
|
123
|
+
guestToken?: GuestTokenConfig;
|
|
124
|
+
access?: AccessConfig;
|
|
125
|
+
i18n?: I18nConfig;
|
|
126
|
+
excludedPaths?: string[];
|
|
127
|
+
onError?: (error: Error) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Block browser direct access to API routes (when Accept: text/html)
|
|
130
|
+
* Redirects to home page. Default: false
|
|
131
|
+
*/
|
|
132
|
+
blockBrowserApiAccess?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* CSRF Protection configuration
|
|
135
|
+
* Protects against Cross-Site Request Forgery attacks
|
|
136
|
+
*/
|
|
137
|
+
csrf?: CsrfConfig;
|
|
138
|
+
/**
|
|
139
|
+
* Rate Limiting configuration
|
|
140
|
+
* Prevents abuse and DoS attacks
|
|
141
|
+
*/
|
|
142
|
+
rateLimit?: RateLimitConfig;
|
|
143
|
+
/**
|
|
144
|
+
* Audit Logging configuration
|
|
145
|
+
* For security monitoring and compliance
|
|
146
|
+
*/
|
|
147
|
+
audit?: AuditConfig;
|
|
148
|
+
/**
|
|
149
|
+
* Custom response parsers for different backend formats.
|
|
150
|
+
* If not provided, expects standard format:
|
|
151
|
+
* - auth/me: { success: true, data: { type, exp, ...user } }
|
|
152
|
+
* - refresh: { success: true, data: { accessToken } }
|
|
153
|
+
* - guest: { success: true, data: { accessToken } }
|
|
154
|
+
*/
|
|
155
|
+
responseMappers?: ResponseMappers;
|
|
156
|
+
/**
|
|
157
|
+
* Hook that runs BEFORE auth validation.
|
|
158
|
+
* Return a NextResponse to bypass auth, or null/undefined to continue.
|
|
159
|
+
* Use this for custom route handling, logging, rate limiting, etc.
|
|
160
|
+
*/
|
|
161
|
+
beforeAuth?: (req: NextRequest) => NextResponse | null | undefined | Promise<NextResponse | null | undefined>;
|
|
162
|
+
/**
|
|
163
|
+
* Hook that runs AFTER auth validation.
|
|
164
|
+
* Allows modifying the response or adding custom headers.
|
|
165
|
+
* Receives the auth result for conditional logic.
|
|
166
|
+
*/
|
|
167
|
+
afterAuth?: (req: NextRequest, response: NextResponse, authResult: AuthResult) => NextResponse | Promise<NextResponse>;
|
|
168
|
+
}
|
|
169
|
+
interface SanitizationConfig {
|
|
170
|
+
/** Enable/disable sanitization. Default: true */
|
|
171
|
+
enabled?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Sanitization mode:
|
|
174
|
+
* - 'escape': Escapes HTML entities (default, safest)
|
|
175
|
+
* - 'strip': Removes all HTML tags
|
|
176
|
+
* - 'allowList': Only allows specified tags in allowedTags
|
|
177
|
+
*/
|
|
178
|
+
mode?: 'escape' | 'strip' | 'allowList';
|
|
179
|
+
/** Tags to allow when mode is 'allowList' */
|
|
180
|
+
allowedTags?: string[];
|
|
181
|
+
/** Fields to skip sanitization (e.g., ['html_content', 'markdown']) */
|
|
182
|
+
skipFields?: string[];
|
|
183
|
+
/**
|
|
184
|
+
* Endpoints to skip sanitization entirely (glob-like matching)
|
|
185
|
+
* e.g., ['cms/*', 'pages/raw', 'content/**']
|
|
186
|
+
*/
|
|
187
|
+
skipEndpoints?: string[];
|
|
188
|
+
}
|
|
189
|
+
interface UserProfile {
|
|
190
|
+
id: number;
|
|
191
|
+
name: string;
|
|
192
|
+
email: string;
|
|
193
|
+
[key: string]: unknown;
|
|
194
|
+
}
|
|
195
|
+
/** User data returned from auth endpoints */
|
|
196
|
+
interface UserData extends UserProfile {
|
|
197
|
+
token_type?: string;
|
|
198
|
+
}
|
|
199
|
+
interface AuthData {
|
|
200
|
+
type: string;
|
|
201
|
+
user?: UserProfile;
|
|
202
|
+
exp?: number;
|
|
203
|
+
[key: string]: unknown;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* CSRF Protection Configuration
|
|
207
|
+
* Uses Fetch Metadata (primary) + Signed Double-Submit Cookie (fallback)
|
|
208
|
+
*/
|
|
209
|
+
interface CsrfConfig {
|
|
210
|
+
/** Enable CSRF protection. Default: false */
|
|
211
|
+
enabled: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* CSRF strategy:
|
|
214
|
+
* - 'fetch-metadata': Modern browsers (Sec-Fetch-Site header check)
|
|
215
|
+
* - 'double-submit': Signed HMAC cookie pattern
|
|
216
|
+
* - 'both': Use both (recommended for max compatibility)
|
|
217
|
+
* @default 'both'
|
|
218
|
+
*/
|
|
219
|
+
strategy?: 'fetch-metadata' | 'double-submit' | 'both';
|
|
220
|
+
/** Secret for HMAC signing. Auto-generated if not provided. */
|
|
221
|
+
secret?: string;
|
|
222
|
+
/** Cookie name for CSRF token. @default '__csrf' */
|
|
223
|
+
cookieName?: string;
|
|
224
|
+
/** Header name for CSRF token. @default 'x-csrf-token' */
|
|
225
|
+
headerName?: string;
|
|
226
|
+
/** HTTP methods that don't need CSRF check. @default ['GET', 'HEAD', 'OPTIONS'] */
|
|
227
|
+
ignoreMethods?: string[];
|
|
228
|
+
/** Trust same-site requests (less strict). @default false */
|
|
229
|
+
trustSameSite?: boolean;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Rate Limiting Configuration
|
|
233
|
+
* Token bucket algorithm with configurable windows
|
|
234
|
+
*/
|
|
235
|
+
interface RateLimitConfig {
|
|
236
|
+
/** Enable rate limiting. Default: false */
|
|
237
|
+
enabled: boolean;
|
|
238
|
+
/** Time window in milliseconds. @default 60000 (1 minute) */
|
|
239
|
+
windowMs?: number;
|
|
240
|
+
/** Max requests per window. @default 100 */
|
|
241
|
+
maxRequests?: number;
|
|
242
|
+
/**
|
|
243
|
+
* Function to generate rate limit key (IP, token, user ID, etc.)
|
|
244
|
+
* @default IP-based
|
|
245
|
+
*/
|
|
246
|
+
keyFn?: (req: NextRequest) => string;
|
|
247
|
+
/** Routes to skip rate limiting (glob patterns) */
|
|
248
|
+
skipRoutes?: string[];
|
|
249
|
+
/** Custom response when rate limited */
|
|
250
|
+
onRateLimited?: (req: NextRequest) => NextResponse;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Audit Logging Configuration
|
|
254
|
+
* Event-based logging for security monitoring
|
|
255
|
+
*/
|
|
256
|
+
interface AuditConfig {
|
|
257
|
+
/** Enable audit logging. Default: false */
|
|
258
|
+
enabled: boolean;
|
|
259
|
+
/** Event types to log */
|
|
260
|
+
events?: AuditEventType[];
|
|
261
|
+
/** Logger function */
|
|
262
|
+
logger?: (event: AuditEvent) => void | Promise<void>;
|
|
263
|
+
}
|
|
264
|
+
type AuditEventType = 'auth:success' | 'auth:fail' | 'auth:refresh' | 'auth:guest' | 'access:denied' | 'csrf:fail' | 'rateLimit:exceeded' | 'error';
|
|
265
|
+
interface AuditEvent {
|
|
266
|
+
type: AuditEventType;
|
|
267
|
+
timestamp: Date;
|
|
268
|
+
ip: string | null;
|
|
269
|
+
userId?: string;
|
|
270
|
+
path: string;
|
|
271
|
+
method: string;
|
|
272
|
+
success: boolean;
|
|
273
|
+
metadata?: Record<string, unknown>;
|
|
274
|
+
}
|
|
275
|
+
type ResolvedCookieOptions = Required<CookieOptions>;
|
|
276
|
+
interface ResolvedCsrfConfig {
|
|
277
|
+
enabled: boolean;
|
|
278
|
+
strategy: 'fetch-metadata' | 'double-submit' | 'both';
|
|
279
|
+
secret: string;
|
|
280
|
+
cookieName: string;
|
|
281
|
+
headerName: string;
|
|
282
|
+
ignoreMethods: string[];
|
|
283
|
+
trustSameSite: boolean;
|
|
284
|
+
}
|
|
285
|
+
interface ResolvedRateLimitConfig {
|
|
286
|
+
enabled: boolean;
|
|
287
|
+
windowMs: number;
|
|
288
|
+
maxRequests: number;
|
|
289
|
+
keyFn: (req: NextRequest) => string;
|
|
290
|
+
skipRoutes: string[];
|
|
291
|
+
onRateLimited?: (req: NextRequest) => NextResponse;
|
|
292
|
+
}
|
|
293
|
+
interface ResolvedAuditConfig {
|
|
294
|
+
enabled: boolean;
|
|
295
|
+
events: AuditEventType[];
|
|
296
|
+
logger?: (event: AuditEvent) => void | Promise<void>;
|
|
297
|
+
}
|
|
298
|
+
interface InternalProxyConfig extends AuthProxyConfig {
|
|
299
|
+
_resolved: {
|
|
300
|
+
cookieOptions: ResolvedCookieOptions;
|
|
301
|
+
endpoints: Required<EndpointConfig>;
|
|
302
|
+
csrf: ResolvedCsrfConfig;
|
|
303
|
+
rateLimit: ResolvedRateLimitConfig;
|
|
304
|
+
audit: ResolvedAuditConfig;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* createApiClient
|
|
310
|
+
* Server-side API client for Route Handlers
|
|
311
|
+
*
|
|
312
|
+
* Makes requests directly to backend with auth token from cookies/headers.
|
|
313
|
+
* Returns Response objects that can be directly returned from route handlers.
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
interface ApiClientConfig {
|
|
317
|
+
/** Backend API base URL */
|
|
318
|
+
apiBaseUrl: string;
|
|
319
|
+
/** Cookie names for token retrieval */
|
|
320
|
+
cookies: {
|
|
321
|
+
user: string;
|
|
322
|
+
guest: string;
|
|
323
|
+
};
|
|
324
|
+
/** Sanitization options */
|
|
325
|
+
sanitization?: SanitizationConfig;
|
|
326
|
+
/** Enable Laravel method spoofing for PUT/PATCH */
|
|
327
|
+
methodSpoofing?: boolean;
|
|
328
|
+
/** Custom error messages */
|
|
329
|
+
errorMessages?: {
|
|
330
|
+
noToken?: string;
|
|
331
|
+
connectionError?: string;
|
|
332
|
+
};
|
|
333
|
+
/** i18n configuration - auto-append locale to API requests */
|
|
334
|
+
i18n?: I18nConfig & {
|
|
335
|
+
/** Query parameter name (default: 'lang') */
|
|
336
|
+
paramName?: string;
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
interface RequestOptions {
|
|
340
|
+
/** Form data mode (use FormData, skip Content-Type header) */
|
|
341
|
+
isFormData?: boolean;
|
|
342
|
+
/** Use method spoofing for this request */
|
|
343
|
+
methodSpoofing?: boolean;
|
|
344
|
+
/** Skip authentication for this request */
|
|
345
|
+
skipAuth?: boolean;
|
|
346
|
+
}
|
|
347
|
+
interface ApiClient {
|
|
348
|
+
get: (endpoint: string) => Promise<Response>;
|
|
349
|
+
post: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;
|
|
350
|
+
put: (endpoint: string, body?: Record<string, unknown> | FormData, options?: RequestOptions) => Promise<Response>;
|
|
351
|
+
patch: (endpoint: string, body?: Record<string, unknown>) => Promise<Response>;
|
|
352
|
+
delete: (endpoint: string) => Promise<Response>;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Creates a server-side API client for Next.js Route Handlers
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```ts
|
|
359
|
+
* // lib/api.ts
|
|
360
|
+
* import { createApiClient } from 'next-api-layer';
|
|
361
|
+
*
|
|
362
|
+
* export const api = createApiClient({
|
|
363
|
+
* apiBaseUrl: process.env.API_BASE_URL!,
|
|
364
|
+
* cookies: {
|
|
365
|
+
* user: process.env.COOKIE_USER_AUTH_TOKEN_NAME!,
|
|
366
|
+
* guest: process.env.COOKIE_PUBLIC_AUTH_TOKEN_NAME!,
|
|
367
|
+
* },
|
|
368
|
+
* });
|
|
369
|
+
*
|
|
370
|
+
* // Usage in route handler - direct return!
|
|
371
|
+
* export async function GET() {
|
|
372
|
+
* return api.get('superadmin/home/list');
|
|
373
|
+
* }
|
|
374
|
+
*
|
|
375
|
+
* export async function POST(request: Request) {
|
|
376
|
+
* const body = await request.json();
|
|
377
|
+
* return api.post('donations/create', body);
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
declare function createApiClient(config: ApiClientConfig): ApiClient;
|
|
382
|
+
|
|
383
|
+
export { type AuthProxyConfig as A, type CookieOptions as C, type EndpointConfig as E, type GuestTokenConfig as G, type InternalProxyConfig as I, type ResolvedRateLimitConfig as R, type SanitizationConfig as S, type TokenInfo as T, type UserData as U, type ResolvedCsrfConfig as a, type AuditEventType as b, type ResolvedAuditConfig as c, type RefreshResult as d, type AccessConfig as e, type ApiClient as f, type ApiClientConfig as g, type ApiResponse as h, type AuditConfig as i, type AuditEvent as j, type AuthData as k, type AuthResult as l, type CookieConfig as m, type CsrfConfig as n, type I18nConfig as o, type RateLimitConfig as p, type ResponseMappers as q, createApiClient as r, type RequestOptions as s };
|