@darkauth/client 1.22.2 → 1.23.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.
package/README.md CHANGED
@@ -72,7 +72,8 @@ setConfig({
72
72
  tokenStorage: 'memory', // Optional. Default 'memory'. Use 'localStorage' only for legacy flows.
73
73
  drkStorage: 'memory', // Optional. Default 'memory'. Use 'localStorage' only for explicit convenience mode.
74
74
  refreshMode: 'cookie', // Optional. Default 'cookie'. Use 'token' only for legacy refresh-token clients.
75
- credentials: 'include' // Optional. Default 'include' for cookie refresh.
75
+ credentials: 'include', // Optional. Default 'include' for cookie refresh.
76
+ endSessionEndpoint: 'https://auth.example.com/api/logout' // Optional. Override the RP-initiated logout endpoint (otherwise read from discovery).
76
77
  });
77
78
  ```
78
79
 
@@ -106,7 +107,28 @@ Behavior:
106
107
 
107
108
  #### `logout(): void`
108
109
 
109
- Clears the in-memory session, callback state, PKCE verifier, ephemeral ZK key, and any explicitly configured legacy storage.
110
+ Clears the in-memory session, callback state, PKCE verifier, ephemeral ZK key, and any explicitly configured legacy storage. This is a **local-only** logout — it does not end the DarkAuth SSO session, so a subsequent `initiateLogin()` may silently re-authenticate. Use `endSession()` when you need to end the IdP session too.
111
+
112
+ #### `endSession(options?: EndSessionOptions): Promise<void>`
113
+
114
+ Performs OIDC [RP-initiated logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html). Clears the local session (same as `logout()`) and then redirects the browser to DarkAuth's `end_session_endpoint`, which ends the SSO session and redirects back to your app.
115
+
116
+ ```typescript
117
+ import { endSession } from '@DarkAuth/client';
118
+
119
+ await endSession({
120
+ postLogoutRedirectUri: `${window.location.origin}/login`,
121
+ state: 'optional-csrf-value',
122
+ });
123
+ ```
124
+
125
+ Options (`EndSessionOptions`):
126
+ - `postLogoutRedirectUri?`: Where DarkAuth redirects after ending the session. **Must be registered in the client's `post_logout_redirect_uris` allowlist** on the DarkAuth server (exact match), otherwise the request is rejected. When provided, the SDK also sends `client_id` (the configured `clientId` unless overridden).
127
+ - `state?`: Opaque value echoed back on the post-logout redirect (use it to protect against CSRF / restore app state).
128
+ - `idTokenHint?`: The ID token to send as `id_token_hint`. Defaults to the current session's ID token. With a valid hint DarkAuth ends the session without a confirmation prompt.
129
+ - `clientId?`: Override the `client_id` sent alongside `post_logout_redirect_uri` (defaults to the configured `clientId`).
130
+
131
+ The `end_session_endpoint` is resolved from the server's `/.well-known/openid-configuration` discovery document, falling back to `<issuer>/api/logout`, or to the `endSessionEndpoint` config value when set.
110
132
 
111
133
  #### `getStoredSession(): AuthSession | null`
112
134
 
@@ -300,6 +322,17 @@ type Config = {
300
322
  drkStorage?: 'memory' | 'localStorage';
301
323
  refreshMode?: 'cookie' | 'token';
302
324
  credentials?: RequestCredentials;
325
+ endSessionEndpoint?: string;
326
+ }
327
+ ```
328
+
329
+ ### `EndSessionOptions`
330
+ ```typescript
331
+ type EndSessionOptions = {
332
+ postLogoutRedirectUri?: string;
333
+ state?: string;
334
+ idTokenHint?: string;
335
+ clientId?: string;
303
336
  }
304
337
  ```
305
338
 
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ type Config = {
9
9
  zk?: boolean;
10
10
  authorizationEndpoint?: string;
11
11
  tokenEndpoint?: string;
12
+ endSessionEndpoint?: string;
12
13
  discovery?: boolean;
13
14
  firstParty?: boolean;
14
15
  tokenStorage?: "memory" | "localStorage";
@@ -60,6 +61,12 @@ export type SwitchOrganizationOptions = {
60
61
  export type RefreshSessionOptions = {
61
62
  force?: boolean;
62
63
  };
64
+ export type EndSessionOptions = {
65
+ postLogoutRedirectUri?: string;
66
+ state?: string;
67
+ idTokenHint?: string;
68
+ clientId?: string;
69
+ };
63
70
  export type DarkAuthSessionInfo = {
64
71
  authenticated: boolean;
65
72
  sub?: string;
@@ -94,4 +101,5 @@ export declare function listOrganizations(): Promise<DarkAuthOrganization[]>;
94
101
  export declare function getSessionInfo(): Promise<DarkAuthSessionInfo>;
95
102
  export declare function switchOrganization(organizationId: string, options?: SwitchOrganizationOptions): Promise<AuthSession | null>;
96
103
  export declare function logout(): void;
104
+ export declare function endSession(options?: EndSessionOptions): Promise<void>;
97
105
  export declare function getCurrentUser(): JwtClaims | null;
package/dist/index.js CHANGED
@@ -155,6 +155,7 @@ async function resolveEndpoints() {
155
155
  cfg.scope || "",
156
156
  cfg.authorizationEndpoint || "",
157
157
  cfg.tokenEndpoint || "",
158
+ cfg.endSessionEndpoint || "",
158
159
  cfg.discovery === false ? "0" : "1",
159
160
  ].join("|");
160
161
  if (endpointsInFlight && endpointsCacheKey === cacheKey)
@@ -164,8 +165,9 @@ async function resolveEndpoints() {
164
165
  const fallback = {
165
166
  authorizationEndpoint: cfg.authorizationEndpoint || rootEndpoint("/authorize"),
166
167
  tokenEndpoint: cfg.tokenEndpoint || rootEndpoint("/token"),
168
+ endSessionEndpoint: cfg.endSessionEndpoint || rootEndpoint("/api/logout"),
167
169
  };
168
- if (cfg.authorizationEndpoint && cfg.tokenEndpoint)
170
+ if (cfg.authorizationEndpoint && cfg.tokenEndpoint && cfg.endSessionEndpoint)
169
171
  return fallback;
170
172
  if (cfg.discovery === false || typeof fetch !== "function")
171
173
  return fallback;
@@ -184,6 +186,10 @@ async function resolveEndpoints() {
184
186
  (typeof metadata.token_endpoint === "string"
185
187
  ? metadata.token_endpoint
186
188
  : fallback.tokenEndpoint),
189
+ endSessionEndpoint: cfg.endSessionEndpoint ||
190
+ (typeof metadata.end_session_endpoint === "string"
191
+ ? metadata.end_session_endpoint
192
+ : fallback.endSessionEndpoint),
187
193
  };
188
194
  }
189
195
  catch {
@@ -744,7 +750,7 @@ export async function switchOrganization(organizationId, options = {}) {
744
750
  location.assign(switchUrl.toString());
745
751
  return null;
746
752
  }
747
- export function logout() {
753
+ function clearLocalSession() {
748
754
  memorySession = null;
749
755
  memoryRefreshToken = null;
750
756
  clearStoredIdToken();
@@ -755,6 +761,27 @@ export function logout() {
755
761
  sessionStorage.removeItem(OAUTH_STATE_KEY);
756
762
  localStorage.removeItem(REFRESH_TOKEN_KEY);
757
763
  }
764
+ export function logout() {
765
+ clearLocalSession();
766
+ }
767
+ export async function endSession(options = {}) {
768
+ const idTokenHint = options.idTokenHint ||
769
+ memorySession?.idToken ||
770
+ (tokenStorageMode() === "localStorage" ? getStoredIdToken() : null) ||
771
+ undefined;
772
+ const endpoints = await resolveEndpoints();
773
+ const url = new URL(endpoints.endSessionEndpoint);
774
+ if (idTokenHint)
775
+ url.searchParams.set("id_token_hint", idTokenHint);
776
+ if (options.postLogoutRedirectUri) {
777
+ url.searchParams.set("post_logout_redirect_uri", options.postLogoutRedirectUri);
778
+ url.searchParams.set("client_id", options.clientId || cfg.clientId);
779
+ }
780
+ if (options.state)
781
+ url.searchParams.set("state", options.state);
782
+ clearLocalSession();
783
+ location.assign(url.toString());
784
+ }
758
785
  export function getCurrentUser() {
759
786
  const idToken = memorySession?.idToken || (tokenStorageMode() === "localStorage" ? getStoredIdToken() : null);
760
787
  if (!idToken)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkauth/client",
3
- "version": "1.22.2",
3
+ "version": "1.23.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "directory": "packages/darkauth-client",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "scripts": {
27
27
  "build": "tsc",
28
- "test": "pnpm run build && node --test --test-reporter=dot --experimental-specifier-resolution=node",
28
+ "test": "tsc -p tsconfig.test.json && node --test --test-reporter=dot \".test-build/**/*.test.js\"",
29
29
  "typecheck": "tsc --noEmit",
30
30
  "format": "biome format --write .",
31
31
  "lint": "biome lint .",