najm-auth 3.0.0 → 3.1.1

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.
@@ -356,7 +356,8 @@ __export(getSession_exports, {
356
356
  AuthConfigError: () => AuthConfigError,
357
357
  AuthTransportError: () => AuthTransportError,
358
358
  NoSessionError: () => NoSessionError,
359
- getSession: () => getSession
359
+ getSession: () => getSession,
360
+ resolveSessionOutcome: () => resolveSessionOutcome
360
361
  });
361
362
  function defaultBaseURL() {
362
363
  const explicit = typeof process !== "undefined" ? process.env.NAJM_AUTH_BASE_URL : void 0;
@@ -381,12 +382,11 @@ function requestOriginFromHeaders(headers) {
381
382
  return void 0;
382
383
  }
383
384
  }
384
- async function getSession(config = {}) {
385
+ async function resolveSessionOutcome(config = {}) {
385
386
  const cookieName2 = config.cookieName ?? "refreshToken";
386
387
  const sessionCookieName = config.sessionCookieName ?? "najm.session";
387
388
  const baseURL = config.baseURL ?? defaultBaseURL();
388
389
  const prefix = config.authPrefix ?? "/auth";
389
- const strict = config.mode === "strict";
390
390
  const internalRecoveryURL = resolveInternalRecoveryURL(config.internalRecoveryURL);
391
391
  let sessionCookieValue;
392
392
  let refreshCookieValue;
@@ -400,8 +400,7 @@ async function getSession(config = {}) {
400
400
  requestOrigin = requestOriginFromHeaders(await mod.headers());
401
401
  }
402
402
  } catch {
403
- if (strict) throw new AuthConfigError("Failed to read cookies from Next.js headers()");
404
- return null;
403
+ return failed(new AuthConfigError("Failed to read cookies from Next.js headers()"));
405
404
  }
406
405
  const secret = resolveSessionSecret(config.sessionSecret);
407
406
  if (sessionCookieValue && secret) {
@@ -410,24 +409,21 @@ async function getSession(config = {}) {
410
409
  maxAgeSeconds: config.sessionMaxAge
411
410
  });
412
411
  if (claims) {
413
- return {
412
+ return authenticated({
414
413
  user: claims.user,
415
414
  roles: claims.roles,
416
415
  permissions: claims.permissions
417
- };
416
+ });
418
417
  }
419
418
  }
420
419
  if (!secret) {
421
- if (strict) throw new AuthConfigError("Session cookie secret is not configured");
422
- return null;
420
+ return failed(new AuthConfigError("Session cookie secret is not configured"));
423
421
  }
424
422
  if (!refreshCookieValue || config.recoveryURL === false && !internalRecoveryURL) {
425
- if (strict) throw new NoSessionError("No recoverable refresh session");
426
- return null;
423
+ return unauthenticated("No recoverable refresh session");
427
424
  }
428
425
  if (!requestOrigin) {
429
- if (strict) throw new AuthConfigError("Incoming request origin is unavailable");
430
- return null;
426
+ return failed(new AuthConfigError("Incoming request origin is unavailable"));
431
427
  }
432
428
  const endpoint = internalRecoveryURL ?? (config.recoveryURL ? new URL(config.recoveryURL, requestOrigin).toString() : authEndpoint(baseURL, prefix, "/session/recover", requestOrigin));
433
429
  const recovery = await requestSessionRecovery({
@@ -442,24 +438,27 @@ async function getSession(config = {}) {
442
438
  onFailure: config.onRecoveryFailure
443
439
  });
444
440
  if (recovery.status === "recovered") {
445
- return {
441
+ return authenticated({
446
442
  user: recovery.claims.user,
447
443
  roles: recovery.claims.roles,
448
444
  permissions: recovery.claims.permissions
449
- };
445
+ });
450
446
  }
451
- if (strict) {
452
- if (recovery.status === "invalid") {
453
- throw new NoSessionError("Refresh session is invalid or revoked");
454
- }
455
- throw new AuthTransportError(
456
- "Session recovery endpoint was unavailable or returned an invalid session",
457
- recovery.httpStatus
458
- );
447
+ if (recovery.status === "invalid") {
448
+ return unauthenticated("Refresh session is invalid or revoked");
459
449
  }
450
+ return failed(new AuthTransportError(
451
+ "Session recovery endpoint was unavailable or returned an invalid session",
452
+ recovery.httpStatus
453
+ ));
454
+ }
455
+ async function getSession(config = {}) {
456
+ const outcome = await resolveSessionOutcome(config);
457
+ if (outcome.status === "authenticated") return outcome.session;
458
+ if (config.mode === "strict") throw outcome.error;
460
459
  return null;
461
460
  }
462
- var NoSessionError, AuthConfigError, AuthTransportError;
461
+ var NoSessionError, AuthConfigError, AuthTransportError, authenticated, unauthenticated, failed;
463
462
  var init_getSession = __esm({
464
463
  "src/client/server/getSession.ts"() {
465
464
  init_sessionCookie();
@@ -498,6 +497,19 @@ var init_getSession = __esm({
498
497
  __name(defaultBaseURL, "defaultBaseURL");
499
498
  __name(firstForwardedValue, "firstForwardedValue");
500
499
  __name(requestOriginFromHeaders, "requestOriginFromHeaders");
500
+ authenticated = /* @__PURE__ */ __name((session) => ({
501
+ status: "authenticated",
502
+ session
503
+ }), "authenticated");
504
+ unauthenticated = /* @__PURE__ */ __name((message) => ({
505
+ status: "unauthenticated",
506
+ error: new NoSessionError(message)
507
+ }), "unauthenticated");
508
+ failed = /* @__PURE__ */ __name((error) => ({
509
+ status: "failed",
510
+ error
511
+ }), "failed");
512
+ __name(resolveSessionOutcome, "resolveSessionOutcome");
501
513
  __name(getSession, "getSession");
502
514
  }
503
515
  });
@@ -949,10 +961,10 @@ var NajmAuthClient = class _NajmAuthClient {
949
961
  if (setup) {
950
962
  return { ...setup };
951
963
  }
952
- const authenticated = res.data;
953
- this.applyTokens(authenticated);
954
- if (authenticated.user) {
955
- this.state = { ...this.state, user: authenticated.user };
964
+ const authenticated2 = res.data;
965
+ this.applyTokens(authenticated2);
966
+ if (authenticated2.user) {
967
+ this.state = { ...this.state, user: authenticated2.user };
956
968
  this.notify();
957
969
  } else {
958
970
  await this.fetchUser();
@@ -1325,6 +1337,29 @@ function createAuthClient(config) {
1325
1337
  }
1326
1338
  __name(createAuthClient, "createAuthClient");
1327
1339
 
1340
+ // src/client/server/internals.ts
1341
+ var REACT_SERVER_INTERNALS = Symbol.for("najm-auth.reactServerInternals");
1342
+ function redirectsToLogin(outcome) {
1343
+ if (outcome.status === "unauthenticated") return true;
1344
+ const { code, status } = outcome.error;
1345
+ return code === "AUTH_TRANSPORT_ERROR" && (status === 401 || status === 403);
1346
+ }
1347
+ __name(redirectsToLogin, "redirectsToLogin");
1348
+ function heldRoles(session) {
1349
+ return session.roles ?? (session.user.role ? [session.user.role] : []);
1350
+ }
1351
+ __name(heldRoles, "heldRoles");
1352
+ function attachReactServerInternals(kit, internals) {
1353
+ Object.defineProperty(kit, REACT_SERVER_INTERNALS, {
1354
+ value: internals,
1355
+ enumerable: false,
1356
+ configurable: false,
1357
+ writable: false
1358
+ });
1359
+ return kit;
1360
+ }
1361
+ __name(attachReactServerInternals, "attachReactServerInternals");
1362
+
1328
1363
  // src/client/server/defineAuth.ts
1329
1364
  function defineAuth(authConfig = {}) {
1330
1365
  const {
@@ -1379,32 +1414,22 @@ function defineAuth(authConfig = {}) {
1379
1414
  const { getSession: resolveSession } = await Promise.resolve().then(() => (init_getSession(), getSession_exports));
1380
1415
  return resolveSession({ ...sessionConfig, ...opts });
1381
1416
  }, "getSession");
1417
+ const resolveSessionOutcome2 = /* @__PURE__ */ __name(async () => {
1418
+ const { resolveSessionOutcome: resolve } = await Promise.resolve().then(() => (init_getSession(), getSession_exports));
1419
+ return resolve(sessionConfig);
1420
+ }, "resolveSessionOutcome");
1382
1421
  const requireSession = /* @__PURE__ */ __name(async () => {
1383
- const sessionModule = await Promise.resolve().then(() => (init_getSession(), getSession_exports));
1384
- const {
1385
- getSession: resolveSession,
1386
- NoSessionError: NoSessionError2,
1387
- AuthTransportError: AuthTransportError2
1388
- } = sessionModule;
1389
- try {
1390
- const session = await resolveSession({ ...sessionConfig, mode: "strict" });
1391
- return session;
1392
- } catch (err) {
1393
- if (err instanceof NoSessionError2) {
1394
- const { redirect } = await import("next/navigation");
1395
- redirect(loginRoute);
1396
- }
1397
- if (err instanceof AuthTransportError2 && (err.status === 401 || err.status === 403)) {
1398
- const { redirect } = await import("next/navigation");
1399
- redirect(loginRoute);
1400
- }
1401
- throw err;
1422
+ const outcome = await resolveSessionOutcome2();
1423
+ if (outcome.status === "authenticated") return outcome.session;
1424
+ if (redirectsToLogin(outcome)) {
1425
+ const { redirect } = await import("next/navigation");
1426
+ redirect(loginRoute);
1402
1427
  }
1428
+ throw outcome.error;
1403
1429
  }, "requireSession");
1404
1430
  const requireRole = /* @__PURE__ */ __name(async (roles) => {
1405
1431
  const session = await requireSession();
1406
- const held = session.roles ?? (session.user.role ? [session.user.role] : []);
1407
- if (!held.some((role) => roles.includes(role))) {
1432
+ if (!heldRoles(session).some((role) => roles.includes(role))) {
1408
1433
  const { redirect } = await import("next/navigation");
1409
1434
  redirect(forbiddenRoute);
1410
1435
  }
@@ -1434,8 +1459,7 @@ function defineAuth(authConfig = {}) {
1434
1459
  redirect(loginRoute);
1435
1460
  }
1436
1461
  if (options?.role) {
1437
- const userRoles = session.roles ?? (session.user.role ? [session.user.role] : []);
1438
- if (!userRoles.includes(options.role)) {
1462
+ if (!heldRoles(session).includes(options.role)) {
1439
1463
  const { redirect } = await import("next/navigation");
1440
1464
  redirect(forbiddenRoute);
1441
1465
  }
@@ -1451,7 +1475,7 @@ function defineAuth(authConfig = {}) {
1451
1475
  return Page({ session, ...props });
1452
1476
  }, "ProtectedPage");
1453
1477
  }, "protect");
1454
- return {
1478
+ return attachReactServerInternals({
1455
1479
  get client() {
1456
1480
  return getClient();
1457
1481
  },
@@ -1464,7 +1488,7 @@ function defineAuth(authConfig = {}) {
1464
1488
  middleware,
1465
1489
  config: { matcher },
1466
1490
  protect
1467
- };
1491
+ }, { resolveSessionOutcome: resolveSessionOutcome2, loginRoute, forbiddenRoute });
1468
1492
  }
1469
1493
  __name(defineAuth, "defineAuth");
1470
1494
 
@@ -0,0 +1,48 @@
1
+ import { S as ServerSession } from '../../getSession-BthP85UA.js';
2
+ import '../../types-BaSfgxqE.js';
3
+ import '../../sessionRecovery-D5Fa0yZ1.js';
4
+
5
+ /**
6
+ * The `defineAuth()` result.
7
+ *
8
+ * Declared structurally rather than as `AuthKit` on purpose: each entry point
9
+ * bundles its own declarations, and `AuthKit` reaches classes whose private
10
+ * members would make the two copies nominally incompatible.
11
+ */
12
+ interface ReactServerAuthSource {
13
+ getSession(): Promise<ServerSession | null>;
14
+ requireSession(): Promise<ServerSession>;
15
+ requireRole(roles: string[]): Promise<ServerSession>;
16
+ }
17
+ interface ReactServerAuth {
18
+ /** The session for this request, or `null` when there is none. */
19
+ getSession(): Promise<ServerSession | null>;
20
+ /**
21
+ * The session for this request, redirecting to `loginRoute` when the visitor
22
+ * is unauthenticated.
23
+ *
24
+ * A recovery endpoint that is unreachable or a missing session secret is an
25
+ * operational fault, not an anonymous visitor: those stay visible errors
26
+ * rather than becoming a login redirect that hides the outage.
27
+ */
28
+ requireSession(): Promise<ServerSession>;
29
+ /**
30
+ * The session for this request, redirecting to `forbiddenRoute` when it holds
31
+ * none of `roles`. Not the login route — the visitor is already
32
+ * authenticated, so signing in again cannot change the answer.
33
+ */
34
+ requireRole(roles: string[]): Promise<ServerSession>;
35
+ }
36
+ /**
37
+ * Create the app's single request-scoped session accessor.
38
+ *
39
+ * Call this exactly once, at module scope, in a module the whole app imports.
40
+ * Calling it inside a layout, page, or component creates a fresh memoized
41
+ * resolver per call and shares nothing.
42
+ *
43
+ * Redirect routes and error classification come from the `defineAuth()` config
44
+ * that produced `auth`; they are not re-accepted here.
45
+ */
46
+ declare function createReactServerAuth(auth: ReactServerAuthSource): ReactServerAuth;
47
+
48
+ export { type ReactServerAuth, type ReactServerAuthSource, createReactServerAuth };
@@ -0,0 +1,71 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/client/server/react.ts
5
+ import * as React from "react";
6
+
7
+ // src/client/server/internals.ts
8
+ var REACT_SERVER_INTERNALS = Symbol.for("najm-auth.reactServerInternals");
9
+ function redirectsToLogin(outcome) {
10
+ if (outcome.status === "unauthenticated") return true;
11
+ const { code, status } = outcome.error;
12
+ return code === "AUTH_TRANSPORT_ERROR" && (status === 401 || status === 403);
13
+ }
14
+ __name(redirectsToLogin, "redirectsToLogin");
15
+ function heldRoles(session) {
16
+ return session.roles ?? (session.user.role ? [session.user.role] : []);
17
+ }
18
+ __name(heldRoles, "heldRoles");
19
+ function readReactServerInternals(auth) {
20
+ if (auth === null || typeof auth !== "object" && typeof auth !== "function") return void 0;
21
+ return auth[REACT_SERVER_INTERNALS];
22
+ }
23
+ __name(readReactServerInternals, "readReactServerInternals");
24
+
25
+ // src/client/server/react.ts
26
+ if (typeof window !== "undefined") {
27
+ throw new Error(
28
+ "najm-auth/client/server/react is a React Server Component module and cannot be imported from a Client Component. Import najm-auth/client/react instead."
29
+ );
30
+ }
31
+ function createReactServerAuth(auth) {
32
+ const internals = readReactServerInternals(auth);
33
+ if (!internals) {
34
+ throw new Error(
35
+ "createReactServerAuth() expects the object returned by defineAuth() from najm-auth/client/server. Check that both imports resolve to the same installed najm-auth version."
36
+ );
37
+ }
38
+ const cache2 = React.cache;
39
+ if (typeof cache2 !== "function") {
40
+ throw new Error(
41
+ "najm-auth/client/server/react requires a React version that exports cache() (React 18.3 or newer). Upgrade react, or use auth.getSession() directly."
42
+ );
43
+ }
44
+ const resolve = cache2(internals.resolveSessionOutcome);
45
+ const getSession = /* @__PURE__ */ __name(async () => {
46
+ const outcome = await resolve();
47
+ return outcome.status === "authenticated" ? outcome.session : null;
48
+ }, "getSession");
49
+ const requireSession = /* @__PURE__ */ __name(async () => {
50
+ const outcome = await resolve();
51
+ if (outcome.status === "authenticated") return outcome.session;
52
+ if (redirectsToLogin(outcome)) {
53
+ const { redirect } = await import("next/navigation");
54
+ redirect(internals.loginRoute);
55
+ }
56
+ throw outcome.error;
57
+ }, "requireSession");
58
+ const requireRole = /* @__PURE__ */ __name(async (roles) => {
59
+ const session = await requireSession();
60
+ if (!heldRoles(session).some((role) => roles.includes(role))) {
61
+ const { redirect } = await import("next/navigation");
62
+ redirect(internals.forbiddenRoute);
63
+ }
64
+ return session;
65
+ }, "requireRole");
66
+ return { getSession, requireSession, requireRole };
67
+ }
68
+ __name(createReactServerAuth, "createReactServerAuth");
69
+ export {
70
+ createReactServerAuth
71
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,4 @@
1
+ // src/client/server/reactClientGuard.ts
2
+ throw new Error(
3
+ "najm-auth/client/server/react is a React Server Component module. It cannot be imported from a Client Component, the Edge runtime, or any browser bundle. Use najm-auth/client/react for client components, and najm-auth/client/server for proxy and Edge code."
4
+ );
@@ -0,0 +1,68 @@
1
+ import { A as AuthUser } from './types-BaSfgxqE.js';
2
+ import { S as SessionRecoveryFailure } from './sessionRecovery-D5Fa0yZ1.js';
3
+
4
+ interface ServerSession {
5
+ user: AuthUser;
6
+ roles?: string[];
7
+ permissions?: string[];
8
+ }
9
+ interface GetSessionConfig {
10
+ /**
11
+ * Base URL for auth endpoints.
12
+ * Defaults to `NEXT_PUBLIC_API_URL` or the same-origin `/api` path.
13
+ */
14
+ baseURL?: string;
15
+ /** Auth route prefix appended to baseURL (default: '/auth'). */
16
+ authPrefix?: string;
17
+ /** Refresh token cookie name (default: 'refreshToken'). */
18
+ cookieName?: string;
19
+ /** Signed session cookie name (default: 'najm.session'). */
20
+ sessionCookieName?: string;
21
+ /**
22
+ * Secret used to verify the session cookie HMAC signature.
23
+ * Falls back to NAJM_SESSION_SECRET or JWT_ACCESS_SECRET env vars.
24
+ */
25
+ sessionSecret?: string;
26
+ /**
27
+ * Maximum accepted session-cookie age in seconds.
28
+ * Must match the auth plugin's `session.maxAge`. Default: 300.
29
+ */
30
+ sessionMaxAge?: number;
31
+ /**
32
+ * Session-recovery endpoint. Defaults to
33
+ * `${baseURL}${authPrefix}/session/recover`. Set to false to disable fallback.
34
+ */
35
+ recoveryURL?: string | false;
36
+ /** Loopback-only recovery endpoint for self-hosted reverse-proxy setups. */
37
+ internalRecoveryURL?: string;
38
+ /**
39
+ * Error handling mode:
40
+ * - 'nullable' (default): returns null on any failure
41
+ * - 'strict': throws typed errors for debugging
42
+ */
43
+ mode?: 'nullable' | 'strict';
44
+ /** Secret-free diagnostic hook for failed recovery attempts. */
45
+ onRecoveryFailure?: (failure: SessionRecoveryFailure) => void;
46
+ }
47
+ declare class NoSessionError extends Error {
48
+ readonly code = "NO_SESSION";
49
+ constructor(message?: string);
50
+ }
51
+ declare class AuthConfigError extends Error {
52
+ readonly code = "AUTH_CONFIG_ERROR";
53
+ constructor(message: string);
54
+ }
55
+ declare class AuthTransportError extends Error {
56
+ readonly status?: number;
57
+ readonly code = "AUTH_TRANSPORT_ERROR";
58
+ constructor(message: string, status?: number);
59
+ }
60
+ /**
61
+ * Resolve a session in a Next.js Server Component, Route Handler, or Server
62
+ * Action. Recovery returns claims for the current render but cannot persist
63
+ * response cookies during Server Component rendering; middleware performs that
64
+ * persistence for protected navigation.
65
+ */
66
+ declare function getSession(config?: GetSessionConfig): Promise<ServerSession | null>;
67
+
68
+ export { AuthConfigError as A, type GetSessionConfig as G, NoSessionError as N, type ServerSession as S, AuthTransportError as a, getSession as g };
@@ -0,0 +1,18 @@
1
+ type SessionRecoveryFailureReason = 'invalid-cookie-name' | 'invalid-refresh-cookie' | 'invalid-endpoint' | 'fetch-error' | 'http-status' | 'missing-set-cookie' | 'session-cookie-parse' | 'session-cookie-hmac' | 'session-cookie-payload';
2
+ interface SessionRecoveryErrorDetails {
3
+ name: string;
4
+ message: string;
5
+ code?: string;
6
+ cause?: {
7
+ name: string;
8
+ message: string;
9
+ code?: string;
10
+ };
11
+ }
12
+ interface SessionRecoveryFailure {
13
+ reason: SessionRecoveryFailureReason;
14
+ httpStatus?: number;
15
+ error?: SessionRecoveryErrorDetails;
16
+ }
17
+
18
+ export type { SessionRecoveryFailure as S, SessionRecoveryErrorDetails as a, SessionRecoveryFailureReason as b };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * User data from the auth server
3
+ */
4
+ interface AuthUser {
5
+ id: string;
6
+ email: string;
7
+ name?: string;
8
+ role?: string | null;
9
+ permissions?: string[];
10
+ [key: string]: unknown;
11
+ }
12
+ /**
13
+ * Full auth state snapshot
14
+ */
15
+ interface AuthState {
16
+ user: AuthUser | null;
17
+ accessToken: string | null;
18
+ isAuthenticated: boolean;
19
+ isLoading: boolean;
20
+ roles: string[];
21
+ permissions: string[];
22
+ }
23
+ /**
24
+ * Decoded JWT payload (client-side, no verification)
25
+ */
26
+ interface DecodedToken {
27
+ userId: string;
28
+ jti?: string;
29
+ sessionVersion?: number;
30
+ roles?: string[];
31
+ permissions?: string[];
32
+ exp?: number;
33
+ iat?: number;
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * Server response envelope
38
+ */
39
+ interface ServerResponse<T = unknown> {
40
+ data: T;
41
+ message?: string;
42
+ status?: string;
43
+ }
44
+ /**
45
+ * Token pair from login/refresh (internal — refresh token is httpOnly cookie)
46
+ */
47
+ interface TokenPair {
48
+ accessToken: string;
49
+ refreshToken?: string;
50
+ accessTokenExpiresAt?: number;
51
+ refreshTokenExpiresAt?: number;
52
+ }
53
+ /**
54
+ * Login credentials. `identifier` accepts an email address or a phone number;
55
+ * `email` stays supported for existing callers.
56
+ */
57
+ interface LoginCredentials {
58
+ identifier?: string;
59
+ email?: string;
60
+ password: string;
61
+ /** Persist the auth cookies past the browser closing. */
62
+ rememberMe?: boolean;
63
+ [key: string]: unknown;
64
+ }
65
+ /** The account must replace its credential before it gets a session. */
66
+ interface CredentialSetupPending {
67
+ nextStep: 'credential_setup';
68
+ setupRequired: true;
69
+ purpose: string;
70
+ expiresAt: string;
71
+ }
72
+ interface AuthenticatedLogin {
73
+ nextStep: 'authenticated';
74
+ user: AuthUser;
75
+ }
76
+ /**
77
+ * Login answer. Branch on `nextStep`: `credential_setup` carries no tokens and
78
+ * leaves the client unauthenticated.
79
+ */
80
+ type LoginResult = AuthenticatedLogin | CredentialSetupPending;
81
+ /**
82
+ * Retry configuration
83
+ */
84
+ interface RetryConfig {
85
+ maxRetries?: number;
86
+ backoff?: 'exponential' | 'linear';
87
+ baseDelay?: number;
88
+ }
89
+ /**
90
+ * Auth client configuration
91
+ */
92
+ interface AuthClientConfig {
93
+ /** API base URL (e.g., '/api' or 'https://api.example.com') */
94
+ baseURL: string;
95
+ /** Auth endpoints prefix (default: '/auth') */
96
+ authPrefix?: string;
97
+ /** Proactive refresh at this fraction of token lifetime (default: 0.8) */
98
+ refreshThreshold?: number;
99
+ /** Enable multi-tab sync via BroadcastChannel (default: true) */
100
+ tabSync?: boolean;
101
+ /** BroadcastChannel name (default: 'najm-auth') */
102
+ channelName?: string;
103
+ /** Network retry configuration */
104
+ retry?: RetryConfig;
105
+ /** Request timeout in milliseconds (default: 30000) */
106
+ timeout?: number;
107
+ }
108
+ type OAuthProvider = 'google';
109
+ interface OAuthLoginOptions {
110
+ /** Same-origin frontend path after OAuth completes. */
111
+ returnTo?: string;
112
+ }
113
+ /**
114
+ * Auth event types
115
+ */
116
+ interface AuthEventMap {
117
+ login: AuthUser;
118
+ logout: null;
119
+ /** Emitted when server-side logout invalidation fails (state was already cleared) */
120
+ logoutError: unknown;
121
+ tokenRefresh: null;
122
+ sessionExpired: null;
123
+ stateChange: AuthState;
124
+ userUpdated: AuthUser;
125
+ }
126
+ type AuthEvent = keyof AuthEventMap;
127
+ type AuthEventHandler<K extends AuthEvent = AuthEvent> = (data: AuthEventMap[K]) => void;
128
+ /**
129
+ * Tab sync message types
130
+ */
131
+ type TabSyncMessage = {
132
+ type: 'logout';
133
+ } | {
134
+ type: 'sync';
135
+ state: SyncPayload;
136
+ };
137
+ interface SyncPayload {
138
+ accessToken: string | null;
139
+ user: AuthUser | null;
140
+ roles: string[];
141
+ permissions: string[];
142
+ isAuthenticated: boolean;
143
+ }
144
+ /**
145
+ * FetchClient request options
146
+ */
147
+ interface RequestOptions {
148
+ body?: unknown;
149
+ headers?: Record<string, string>;
150
+ signal?: AbortSignal;
151
+ timeout?: number;
152
+ /** Skip auth header attachment (for public endpoints like /login, /register) */
153
+ skipAuth?: boolean;
154
+ /** @internal Prevents 401-refresh loop after a single retry */
155
+ _retried?: boolean;
156
+ }
157
+ /**
158
+ * Auth error thrown by the client
159
+ */
160
+ declare class AuthError extends Error {
161
+ status: number;
162
+ body?: unknown;
163
+ constructor(status: number, message: string, body?: unknown);
164
+ }
165
+
166
+ export { type AuthUser as A, type CredentialSetupPending as C, type DecodedToken as D, type LoginCredentials as L, type OAuthProvider as O, type RetryConfig as R, type SyncPayload as S, type TabSyncMessage as T, type RequestOptions as a, type AuthClientConfig as b, type LoginResult as c, type OAuthLoginOptions as d, type AuthState as e, type AuthEvent as f, type AuthEventHandler as g, AuthError as h, type AuthEventMap as i, type AuthenticatedLogin as j, type ServerResponse as k, type TokenPair as l };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -34,6 +34,13 @@
34
34
  "import": "./dist/client/server/index.js",
35
35
  "default": "./dist/client/server/index.js"
36
36
  },
37
+ "./client/server/react": {
38
+ "types": "./dist/client/server/react.d.ts",
39
+ "react-server": "./dist/client/server/react.js",
40
+ "browser": "./dist/client/server/reactClientGuard.js",
41
+ "import": "./dist/client/server/react.js",
42
+ "default": "./dist/client/server/react.js"
43
+ },
37
44
  "./identity/ma": {
38
45
  "types": "./dist/identity/ma.d.ts",
39
46
  "import": "./dist/identity/ma.js",
@@ -52,7 +59,8 @@
52
59
  },
53
60
  "scripts": {
54
61
  "build": "tsup",
55
- "test": "bun test",
62
+ "test": "bun test && bun run test:rsc",
63
+ "test:rsc": "bun test --conditions react-server test/react-server-auth.test.ts",
56
64
  "test:next16": "bun run build && bun integration/next16-proxy/run.ts",
57
65
  "test:watch": "bun test --watch",
58
66
  "start": "bun --watch src/main.ts",