@zhmdff/auth-react 1.0.3 → 1.0.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/README.md CHANGED
@@ -32,8 +32,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
32
32
  <html lang="en">
33
33
  <body>
34
34
  <AuthProvider
35
- authUrl="http://localhost:5129/auth"
36
- apiUrl="http://localhost:5129/api"
35
+ authUrl={process.env.NEXT_PUBLIC_AUTH_URL}
36
+ apiUrl={process.env.NEXT_PUBLIC_API_URL}
37
+ loginPath="/login"
37
38
  >
38
39
  {children}
39
40
  </AuthProvider>
@@ -54,8 +55,9 @@ import App from './App';
54
55
  ReactDOM.createRoot(document.getElementById('root')!).render(
55
56
  <React.StrictMode>
56
57
  <AuthProvider
57
- authUrl="http://localhost:5129/auth"
58
- apiUrl="http://localhost:5129/api"
58
+ authUrl={import.meta.env.VITE_AUTH_URL}
59
+ apiUrl={import.meta.env.VITE_API_URL}
60
+ loginPath="/login"
59
61
  >
60
62
  <App />
61
63
  </AuthProvider>
@@ -85,6 +87,26 @@ export default function UserProfile() {
85
87
  </div>
86
88
  );
87
89
  }
90
+
91
+ ### 3. Protect routes with `AuthGuard`
92
+
93
+ ```tsx
94
+ import { AuthGuard } from "@zhmdff/auth-react";
95
+
96
+ export default function DashboardLayout({ children }) {
97
+ return (
98
+ <AuthGuard
99
+ loadingComponent={<Spinner />}
100
+ // Required roles (optional)
101
+ allowedRoles={["Admin", "SuperAdmin"]}
102
+ // Redirects to loginPath if not authenticated
103
+ >
104
+ {children}
105
+ </AuthGuard>
106
+ );
107
+ }
108
+ ```
109
+
88
110
  ```
89
111
 
90
112
  ## API Reference
@@ -93,8 +115,9 @@ export default function UserProfile() {
93
115
 
94
116
  | Prop | Type | Required | Description |
95
117
  | :--- | :--- | :--- | :--- |
96
- | `authUrl` | `string` | Yes | The base URL for authentication endpoints (e.g., `http://localhost:5129/auth`). |
97
- | `apiUrl` | `string` | No | The base URL for your API. Used by the `fetch` utility to resolve relative paths. |
118
+ | `authUrl` | `string` | Yes | The base URL for authentication endpoints. Recommended to use environment variables. |
119
+ | `apiUrl` | `string` | No | The base URL for your API. Recommended to use environment variables. |
120
+ | `loginPath` | `string` | No | Path to redirect unauthenticated users to (default: `/login`). |
98
121
  | `children` | `ReactNode` | Yes | The components to be wrapped. |
99
122
 
100
123
  ### `useAuth()` Hook
package/dist/index.d.mts CHANGED
@@ -34,6 +34,7 @@ type AuthContextType = {
34
34
  checkAuth: () => Promise<boolean>;
35
35
  logout: () => Promise<void>;
36
36
  fetch: (endpoint: string, options?: any) => Promise<any>;
37
+ loginPath?: string;
37
38
  };
38
39
  interface LoginRequest {
39
40
  identifier: string;
@@ -46,14 +47,18 @@ interface RegisterRequest {
46
47
  role?: string;
47
48
  adminLevel?: number;
48
49
  }
49
-
50
- declare const AuthContext: React.Context<AuthContextType>;
51
- interface AuthProviderProps extends PropsWithChildren {
50
+ interface AuthConfig {
52
51
  authUrl: string;
53
52
  apiUrl?: string;
53
+ loginPath?: string;
54
54
  storageKey?: string;
55
55
  }
56
- declare const AuthProvider: ({ children, authUrl, apiUrl }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
56
+
57
+ declare const AuthContext: React.Context<AuthContextType>;
58
+
59
+ interface AuthProviderProps extends PropsWithChildren, AuthConfig {
60
+ }
61
+ declare const AuthProvider: ({ children, authUrl, apiUrl, loginPath }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
57
62
  declare const useAuth: () => AuthContextType;
58
63
 
59
64
  type FetchOptions = {
@@ -81,4 +86,37 @@ type FetchOptions = {
81
86
  };
82
87
  declare function apiFetch(endpoint: string, options?: FetchOptions): Promise<any>;
83
88
 
84
- export { AuthContext, type AuthContextType, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
89
+ interface AuthGuardProps {
90
+ children: React.ReactNode;
91
+ /**
92
+ * Component to show while checking authentication status.
93
+ */
94
+ loadingComponent?: React.ReactNode;
95
+ /**
96
+ * Called when the user is confirmed to be unauthenticated.
97
+ * Use this to handle redirection (e.g., router.push('/login')).
98
+ */
99
+ onUnauthenticated?: () => void;
100
+ /**
101
+ * If true, the guard will allow access even if unauthenticated.
102
+ * Useful for layout-level guards where some sub-paths are public.
103
+ */
104
+ isPublic?: boolean;
105
+ /**
106
+ * Optional array of roles allowed to access this component/route.
107
+ * If the user is logged in but doesn't have one of these roles,
108
+ * the guard will not render children.
109
+ */
110
+ allowedRoles?: string[];
111
+ /**
112
+ * Fallback to show when a user is logged in but lacks required roles (forbidden).
113
+ */
114
+ forbiddenComponent?: React.ReactNode;
115
+ }
116
+ /**
117
+ * A flexible component to protect routes or sections of an application.
118
+ * It handles the loading state and unauthenticated callback automatically.
119
+ */
120
+ declare const AuthGuard: ({ children, loadingComponent, onUnauthenticated, isPublic, allowedRoles, forbiddenComponent }: AuthGuardProps) => react_jsx_runtime.JSX.Element;
121
+
122
+ export { type AuthConfig, AuthContext, type AuthContextType, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
package/dist/index.d.ts CHANGED
@@ -34,6 +34,7 @@ type AuthContextType = {
34
34
  checkAuth: () => Promise<boolean>;
35
35
  logout: () => Promise<void>;
36
36
  fetch: (endpoint: string, options?: any) => Promise<any>;
37
+ loginPath?: string;
37
38
  };
38
39
  interface LoginRequest {
39
40
  identifier: string;
@@ -46,14 +47,18 @@ interface RegisterRequest {
46
47
  role?: string;
47
48
  adminLevel?: number;
48
49
  }
49
-
50
- declare const AuthContext: React.Context<AuthContextType>;
51
- interface AuthProviderProps extends PropsWithChildren {
50
+ interface AuthConfig {
52
51
  authUrl: string;
53
52
  apiUrl?: string;
53
+ loginPath?: string;
54
54
  storageKey?: string;
55
55
  }
56
- declare const AuthProvider: ({ children, authUrl, apiUrl }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
56
+
57
+ declare const AuthContext: React.Context<AuthContextType>;
58
+
59
+ interface AuthProviderProps extends PropsWithChildren, AuthConfig {
60
+ }
61
+ declare const AuthProvider: ({ children, authUrl, apiUrl, loginPath }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
57
62
  declare const useAuth: () => AuthContextType;
58
63
 
59
64
  type FetchOptions = {
@@ -81,4 +86,37 @@ type FetchOptions = {
81
86
  };
82
87
  declare function apiFetch(endpoint: string, options?: FetchOptions): Promise<any>;
83
88
 
84
- export { AuthContext, type AuthContextType, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
89
+ interface AuthGuardProps {
90
+ children: React.ReactNode;
91
+ /**
92
+ * Component to show while checking authentication status.
93
+ */
94
+ loadingComponent?: React.ReactNode;
95
+ /**
96
+ * Called when the user is confirmed to be unauthenticated.
97
+ * Use this to handle redirection (e.g., router.push('/login')).
98
+ */
99
+ onUnauthenticated?: () => void;
100
+ /**
101
+ * If true, the guard will allow access even if unauthenticated.
102
+ * Useful for layout-level guards where some sub-paths are public.
103
+ */
104
+ isPublic?: boolean;
105
+ /**
106
+ * Optional array of roles allowed to access this component/route.
107
+ * If the user is logged in but doesn't have one of these roles,
108
+ * the guard will not render children.
109
+ */
110
+ allowedRoles?: string[];
111
+ /**
112
+ * Fallback to show when a user is logged in but lacks required roles (forbidden).
113
+ */
114
+ forbiddenComponent?: React.ReactNode;
115
+ }
116
+ /**
117
+ * A flexible component to protect routes or sections of an application.
118
+ * It handles the loading state and unauthenticated callback automatically.
119
+ */
120
+ declare const AuthGuard: ({ children, loadingComponent, onUnauthenticated, isPublic, allowedRoles, forbiddenComponent }: AuthGuardProps) => react_jsx_runtime.JSX.Element;
121
+
122
+ export { type AuthConfig, AuthContext, type AuthContextType, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var k=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var E=(n,r)=>{for(var c in r)k(n,c,{get:r[c],enumerable:!0})},U=(n,r,c,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of R(r))!C.call(n,o)&&o!==c&&k(n,o,{get:()=>r[o],enumerable:!(s=P(r,o))||s.enumerable});return n};var $=n=>U(k({},"__esModule",{value:!0}),n);var F={};E(F,{AuthContext:()=>w,AuthProvider:()=>S,apiFetch:()=>x,useAuth:()=>v});module.exports=$(F);var i=require("react");async function x(n,r={}){let{method:c="GET",body:s,token:o,onTokenRefresh:h,onAuthFail:d,apiUrl:g="/api",authUrl:T="/auth",headers:a,disableAutoRefresh:A=!1}=r,u={"Content-Type":"application/json",...a||{}};o&&(u.Authorization=`Bearer ${o}`);let f={method:c,headers:u,credentials:"include"};s&&c!=="GET"&&(f.body=JSON.stringify(s));let t=await fetch(`${g}${n}`,f);if(!A&&t.status===401&&o&&h)try{let e=await fetch(`${T}/refresh`,{method:"POST",credentials:"include"});if(e.ok){let p=(await e.json()).accessToken;h(p),u.Authorization=`Bearer ${p}`;let y=await fetch(`${g}${n}`,{...f,headers:u});if(!y.ok)throw new Error(await y.text());return y.json()}else throw d?.(),new Error("Session expired")}catch(e){throw d?.(),e}if(!t.ok){let e=await t.text(),l="An error occurred";try{let p=JSON.parse(e);l=p.message||p||l}catch{l=e||l}throw new Error(l)}return t.json()}var m=require("react/jsx-runtime"),w=(0,i.createContext)({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")}}),S=({children:n,authUrl:r,apiUrl:c})=>{let[s,o]=(0,i.useState)(null),[h,d]=(0,i.useState)(null),[g,T]=(0,i.useState)(!0);(0,i.useEffect)(()=>{(async()=>(await u(),T(!1)))()},[]);let a=(t,e)=>{o(t),d(e)},A=async(t,e={})=>x(t,{...e,token:s,apiUrl:c||"",authUrl:r,onTokenRefresh:l=>{a(l,h)},onAuthFail:()=>{a(null,null)}}),u=async()=>{try{let t=await fetch(`${r}/refresh`,{method:"POST",credentials:"include"});if(!t.ok)return a(null,null),!1;let e=await t.json();return e.success&&e.accessToken&&e.user?(a(e.accessToken,e.user),!0):(a(null,null),!1)}catch{return a(null,null),!1}},f=async()=>{try{await fetch(`${r}/logout`,{method:"POST",credentials:"include",headers:s?{Authorization:`Bearer ${s}`}:{}})}catch(t){console.error("Logout failed:",t)}finally{a(null,null)}};return(0,m.jsx)(w.Provider,{value:{accessToken:s,setAccessToken:t=>a(t,h),user:h,setUser:d,isLoading:g,checkAuth:u,logout:f,fetch:A},children:n})},v=()=>(0,i.useContext)(w);0&&(module.exports={AuthContext,AuthProvider,apiFetch,useAuth});
1
+ "use strict";var y=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var S=(n,t)=>{for(var s in t)y(n,s,{get:t[s],enumerable:!0})},b=(n,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of $(t))!v.call(n,r)&&r!==s&&y(n,r,{get:()=>t[r],enumerable:!(a=E(t,r))||a.enumerable});return n};var F=n=>b(y({},"__esModule",{value:!0}),n);var G={};S(G,{AuthContext:()=>R,AuthGuard:()=>U,AuthProvider:()=>O,apiFetch:()=>k,useAuth:()=>w});module.exports=F(G);var l=require("react");async function k(n,t={}){let{method:s="GET",body:a,token:r,onTokenRefresh:p,onAuthFail:i,apiUrl:h="/api",authUrl:f="/auth",headers:m,disableAutoRefresh:c=!1}=t,g={"Content-Type":"application/json",...m||{}};r&&(g.Authorization=`Bearer ${r}`);let A={method:s,headers:g,credentials:"include"};a&&s!=="GET"&&(A.body=JSON.stringify(a));let T=await fetch(`${h}${n}`,A);if(!c&&T.status===401&&r&&p)try{let e=await fetch(`${f}/refresh`,{method:"POST",credentials:"include"});if(e.ok){let d=(await e.json()).accessToken;p(d),g.Authorization=`Bearer ${d}`;let x=await fetch(`${h}${n}`,{...A,headers:g});if(!x.ok)throw new Error(await x.text());return x.json()}else throw i?.(),new Error("Session expired")}catch(e){throw i?.(),e}if(!T.ok){let e=await T.text(),o="An error occurred";try{let d=JSON.parse(e);o=d.message||d||o}catch{o=e||o}throw new Error(o)}return T.json()}var P=require("react/jsx-runtime"),R=(0,l.createContext)({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")},loginPath:"/login"}),O=({children:n,authUrl:t,apiUrl:s,loginPath:a="/login"})=>{let[r,p]=(0,l.useState)(null),[i,h]=(0,l.useState)(null),[f,m]=(0,l.useState)(!0);(0,l.useEffect)(()=>{(async()=>(await A(),m(!1)))()},[]);let c=(e,o)=>{p(e),h(o)},g=async(e,o={})=>k(e,{...o,token:r,apiUrl:s||"",authUrl:t,onTokenRefresh:d=>{c(d,i)},onAuthFail:()=>{c(null,null)}}),A=async()=>{try{let e=await fetch(`${t}/refresh`,{method:"POST",credentials:"include"});if(!e.ok)return c(null,null),!1;let o=await e.json();return o.success&&o.accessToken&&o.user?(c(o.accessToken,o.user),!0):(c(null,null),!1)}catch{return c(null,null),!1}},T=async()=>{try{await fetch(`${t}/logout`,{method:"POST",credentials:"include",headers:r?{Authorization:`Bearer ${r}`}:{}})}catch(e){console.error("Logout failed:",e)}finally{c(null,null)}};return(0,P.jsx)(R.Provider,{value:{accessToken:r,setAccessToken:e=>c(e,i),user:i,setUser:h,isLoading:f,checkAuth:A,logout:T,fetch:g,loginPath:a},children:n})},w=()=>(0,l.useContext)(R);var C=require("react");var u=require("react/jsx-runtime"),U=({children:n,loadingComponent:t,onUnauthenticated:s,isPublic:a=!1,allowedRoles:r,forbiddenComponent:p})=>{let{user:i,isLoading:h,loginPath:f}=w();return(0,C.useEffect)(()=>{!h&&!i&&!a&&(s?s():f&&(window.location.href=f))},[i,h,a,s,f]),h?(0,u.jsx)(u.Fragment,{children:t||null}):!i&&!a?(0,u.jsx)(u.Fragment,{children:t||null}):i&&r&&r.length>0&&!r.includes(i.role)?(0,u.jsx)(u.Fragment,{children:p||null}):(0,u.jsx)(u.Fragment,{children:n})};0&&(module.exports={AuthContext,AuthGuard,AuthProvider,apiFetch,useAuth});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createContext as w,useContext as m,useState as y,useEffect as P}from"react";async function k(d,i={}){let{method:f="GET",body:o,token:c,onTokenRefresh:a,onAuthFail:l,apiUrl:p="/api",authUrl:g="/auth",headers:r,disableAutoRefresh:T=!1}=i,s={"Content-Type":"application/json",...r||{}};c&&(s.Authorization=`Bearer ${c}`);let u={method:f,headers:s,credentials:"include"};o&&f!=="GET"&&(u.body=JSON.stringify(o));let t=await fetch(`${p}${d}`,u);if(!T&&t.status===401&&c&&a)try{let e=await fetch(`${g}/refresh`,{method:"POST",credentials:"include"});if(e.ok){let h=(await e.json()).accessToken;a(h),s.Authorization=`Bearer ${h}`;let A=await fetch(`${p}${d}`,{...u,headers:s});if(!A.ok)throw new Error(await A.text());return A.json()}else throw l?.(),new Error("Session expired")}catch(e){throw l?.(),e}if(!t.ok){let e=await t.text(),n="An error occurred";try{let h=JSON.parse(e);n=h.message||h||n}catch{n=e||n}throw new Error(n)}return t.json()}import{jsx as R}from"react/jsx-runtime";var x=w({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")}}),v=({children:d,authUrl:i,apiUrl:f})=>{let[o,c]=y(null),[a,l]=y(null),[p,g]=y(!0);P(()=>{(async()=>(await s(),g(!1)))()},[]);let r=(t,e)=>{c(t),l(e)},T=async(t,e={})=>k(t,{...e,token:o,apiUrl:f||"",authUrl:i,onTokenRefresh:n=>{r(n,a)},onAuthFail:()=>{r(null,null)}}),s=async()=>{try{let t=await fetch(`${i}/refresh`,{method:"POST",credentials:"include"});if(!t.ok)return r(null,null),!1;let e=await t.json();return e.success&&e.accessToken&&e.user?(r(e.accessToken,e.user),!0):(r(null,null),!1)}catch{return r(null,null),!1}},u=async()=>{try{await fetch(`${i}/logout`,{method:"POST",credentials:"include",headers:o?{Authorization:`Bearer ${o}`}:{}})}catch(t){console.error("Logout failed:",t)}finally{r(null,null)}};return R(x.Provider,{value:{accessToken:o,setAccessToken:t=>r(t,a),user:a,setUser:l,isLoading:p,checkAuth:s,logout:u,fetch:T},children:d})},F=()=>m(x);export{x as AuthContext,v as AuthProvider,k as apiFetch,F as useAuth};
1
+ import{createContext as P,useContext as C,useState as y,useEffect as E}from"react";async function k(h,i={}){let{method:a="GET",body:c,token:r,onTokenRefresh:f,onAuthFail:o,apiUrl:s="/api",authUrl:l="/auth",headers:A,disableAutoRefresh:n=!1}=i,d={"Content-Type":"application/json",...A||{}};r&&(d.Authorization=`Bearer ${r}`);let p={method:a,headers:d,credentials:"include"};c&&a!=="GET"&&(p.body=JSON.stringify(c));let g=await fetch(`${s}${h}`,p);if(!n&&g.status===401&&r&&f)try{let e=await fetch(`${l}/refresh`,{method:"POST",credentials:"include"});if(e.ok){let u=(await e.json()).accessToken;f(u),d.Authorization=`Bearer ${u}`;let x=await fetch(`${s}${h}`,{...p,headers:d});if(!x.ok)throw new Error(await x.text());return x.json()}else throw o?.(),new Error("Session expired")}catch(e){throw o?.(),e}if(!g.ok){let e=await g.text(),t="An error occurred";try{let u=JSON.parse(e);t=u.message||u||t}catch{t=e||t}throw new Error(t)}return g.json()}import{jsx as $}from"react/jsx-runtime";var R=P({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")},loginPath:"/login"}),G=({children:h,authUrl:i,apiUrl:a,loginPath:c="/login"})=>{let[r,f]=y(null),[o,s]=y(null),[l,A]=y(!0);E(()=>{(async()=>(await p(),A(!1)))()},[]);let n=(e,t)=>{f(e),s(t)},d=async(e,t={})=>k(e,{...t,token:r,apiUrl:a||"",authUrl:i,onTokenRefresh:u=>{n(u,o)},onAuthFail:()=>{n(null,null)}}),p=async()=>{try{let e=await fetch(`${i}/refresh`,{method:"POST",credentials:"include"});if(!e.ok)return n(null,null),!1;let t=await e.json();return t.success&&t.accessToken&&t.user?(n(t.accessToken,t.user),!0):(n(null,null),!1)}catch{return n(null,null),!1}},g=async()=>{try{await fetch(`${i}/logout`,{method:"POST",credentials:"include",headers:r?{Authorization:`Bearer ${r}`}:{}})}catch(e){console.error("Logout failed:",e)}finally{n(null,null)}};return $(R.Provider,{value:{accessToken:r,setAccessToken:e=>n(e,o),user:o,setUser:s,isLoading:l,checkAuth:p,logout:g,fetch:d,loginPath:c},children:h})},w=()=>C(R);import{useEffect as v}from"react";import{Fragment as T,jsx as m}from"react/jsx-runtime";var J=({children:h,loadingComponent:i,onUnauthenticated:a,isPublic:c=!1,allowedRoles:r,forbiddenComponent:f})=>{let{user:o,isLoading:s,loginPath:l}=w();return v(()=>{!s&&!o&&!c&&(a?a():l&&(window.location.href=l))},[o,s,c,a,l]),s?m(T,{children:i||null}):!o&&!c?m(T,{children:i||null}):o&&r&&r.length>0&&!r.includes(o.role)?m(T,{children:f||null}):m(T,{children:h})};export{R as AuthContext,J as AuthGuard,G as AuthProvider,k as apiFetch,w as useAuth};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhmdff/auth-react",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Plug and play authentication library for React/Next.js",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,