@dloizides/auth-client 2.1.0 → 3.0.0
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/CHANGELOG.md +27 -0
- package/README.md +37 -1
- package/dist/index.d.mts +106 -3
- package/dist/index.d.ts +106 -3
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0 (2026-05-19)
|
|
4
|
+
|
|
5
|
+
Major release for Phase 2 of the identity-hardening initiative. Adds the
|
|
6
|
+
shared `BffAuthClient` — the same-origin client for a per-app
|
|
7
|
+
**Backend-For-Frontend** (`bff-katalogos`, `bff-erevna`). The BFF terminates
|
|
8
|
+
authentication server-side: the browser holds only an opaque httpOnly session
|
|
9
|
+
cookie, never a token.
|
|
10
|
+
|
|
11
|
+
This is the **new recommended auth surface**. The major bump signals that
|
|
12
|
+
recommendation — it is **not** a breaking change: every v2.x export
|
|
13
|
+
(`AuthClient`, the direct-KC ROPC adapters, `useDirectKcAuth`, the OIDC
|
|
14
|
+
primitives, storage adapters, hooks) remains and is unchanged. BaseClient
|
|
15
|
+
still consumes the direct-KC path; it is removed in a later phase.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `BffAuthClient` — same-origin client for a per-app BFF. Methods:
|
|
20
|
+
`login({username,password})` → `POST /bff/login`; `logout()` →
|
|
21
|
+
`POST /bff/logout`; `getCurrentUser()` → `GET /bff/me`; `register(...)`,
|
|
22
|
+
`forgotPassword(...)`, `resetPassword(...)` → the matching `/bff/*`
|
|
23
|
+
endpoints. Every call is a same-origin `fetch` with
|
|
24
|
+
`credentials: 'include'`; state-changing calls carry the `X-BFF-Csrf: 1`
|
|
25
|
+
header the `Bff.AspNetCore` anti-forgery middleware requires. Does **no
|
|
26
|
+
token handling** — the BFF owns tokens, the browser owns only the cookie.
|
|
27
|
+
- Types: `BffAuthClientOptions`, `BffLoginRequest`, `BffRegisterRequest`,
|
|
28
|
+
`BffForgotPasswordRequest`, `BffResetPasswordRequest`, `BffUser`.
|
|
29
|
+
|
|
3
30
|
## 2.1.0 (2026-05-17)
|
|
4
31
|
|
|
5
32
|
Additive release. Lays the groundwork for the "shrink identity service"
|
package/README.md
CHANGED
|
@@ -119,6 +119,41 @@ const storage = new SecureStoreTokenStorage({
|
|
|
119
119
|
await biometricGate.hydrate();
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## BFF auth (v3 — recommended)
|
|
123
|
+
|
|
124
|
+
`BffAuthClient` is the same-origin client for a per-app **Backend-For-Frontend**
|
|
125
|
+
(`bff-katalogos`, `bff-erevna`). The BFF terminates authentication
|
|
126
|
+
server-side: it does ROPC against Keycloak with a confidential client, stores
|
|
127
|
+
the tokens in a Redis vault, and hands the browser only an opaque httpOnly
|
|
128
|
+
session cookie. The SPA never sees a token — an XSS cannot exfiltrate one.
|
|
129
|
+
|
|
130
|
+
`BffAuthClient` does **no token handling**: every call is a same-origin
|
|
131
|
+
`fetch` with `credentials: 'include'`, and state-changing calls carry the
|
|
132
|
+
`X-BFF-Csrf: 1` header the BFF anti-forgery middleware requires.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { BffAuthClient, createFetchHttpClient } from '@dloizides/auth-client';
|
|
136
|
+
|
|
137
|
+
const bff = new BffAuthClient({
|
|
138
|
+
http: createFetchHttpClient(window.fetch.bind(window)),
|
|
139
|
+
// baseUrl defaults to '' (same-origin) — the production wiring.
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Login — the BFF does ROPC server-side and sets the session cookie.
|
|
143
|
+
const user = await bff.login({ username, password });
|
|
144
|
+
|
|
145
|
+
// Bootstrap on app load — null when there is no live session.
|
|
146
|
+
const current = await bff.getCurrentUser();
|
|
147
|
+
|
|
148
|
+
await bff.register({ firstName, lastName, username, email, password, tenantName });
|
|
149
|
+
await bff.forgotPassword({ email, resetUrlTemplate });
|
|
150
|
+
await bff.resetPassword({ token, newPassword });
|
|
151
|
+
await bff.logout();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The direct-KC `AuthClient` / ROPC surface below is retained for consumers not
|
|
155
|
+
yet on a BFF; it is deprecated and removed once every app has migrated.
|
|
156
|
+
|
|
122
157
|
## React Query hooks
|
|
123
158
|
|
|
124
159
|
```ts
|
|
@@ -156,7 +191,8 @@ auth.on('sessionExpired', () => {
|
|
|
156
191
|
|
|
157
192
|
### Core (`@dloizides/auth-client`)
|
|
158
193
|
|
|
159
|
-
- `
|
|
194
|
+
- `BffAuthClient` — same-origin client for a per-app Backend-For-Frontend. `login()`, `logout()`, `getCurrentUser()`, `register()`, `forgotPassword()`, `resetPassword()`. No token handling — the BFF owns tokens, the browser owns only an httpOnly cookie. **The recommended auth surface (v3).**
|
|
195
|
+
- `AuthClient` — realm-aware orchestrator. `init()`, `refresh()`, `loginWithOtp()`, `loginWithPassword()`, `logout({ everywhere })`, `requestPasswordReset()`, `confirmPasswordReset()`, plus the v1 surface (`getAccessToken`, `getTokens`, `setTokens`, `clearTokens`, `buildAuthorizationUrl`, etc.). Direct-KC ROPC; deprecated in favour of `BffAuthClient`.
|
|
160
196
|
- `AuthApiClient` — typed wrapper for IdentityService auth endpoints.
|
|
161
197
|
- `AuthEventEmitter` — `sessionExpired` event.
|
|
162
198
|
- `RefreshInterceptor` — single-flight refresh queue.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { T as TokenStorage, c as AuthTokens } from './AuthClient-BGr8L03W.mjs';
|
|
2
2
|
export { A as AuthApiClient, d as AuthApiClientOptions, b as AuthClient, e as AuthClientCollaborators, f as AuthClientConfig, g as AuthClientFromIssuerInput, h as AuthEventEmitter, i as AuthEventListener, j as AuthEventName, k as AuthEventUnsubscribe, a as AuthSessionInfo, D as DirectKcOptions, F as ForgotPasswordRequest, I as InactivityStore, l as InactivityTracker, m as InactivityTrackerOptions, L as LoginOptions, n as LogoutOptions, O as OtpLoginRequest, P as PasswordLoginRequest, o as RawAuthLoginResponse, p as RefreshFn, q as RefreshInterceptor, r as RefreshInterceptorOptions, R as ResetPasswordRequest } from './AuthClient-BGr8L03W.mjs';
|
|
3
3
|
export { ExchangeAuthorizationCodeInput, FetchDiscoveryDocumentInput, OidcDiscoveryDocument, PkcePair, RefreshAccessTokenInput, clearDiscoveryCache, deriveCodeChallenge, exchangeAuthorizationCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, refreshAccessToken } from './oidc/index.mjs';
|
|
4
|
-
import { R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.mjs';
|
|
5
|
-
export {
|
|
4
|
+
import { H as HttpClient, R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.mjs';
|
|
5
|
+
export { a as HttpRequest, b as HttpResponse, c as createFetchHttpClient } from './TokenResponse-CY1CaU2l.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Roles emitted by Keycloak realms in the dloizides.com portfolio.
|
|
@@ -315,6 +315,109 @@ declare class BiometricGate {
|
|
|
315
315
|
unlock(): Promise<void>;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
/** Credentials posted to `POST /bff/login`. */
|
|
319
|
+
interface BffLoginRequest {
|
|
320
|
+
username: string;
|
|
321
|
+
password: string;
|
|
322
|
+
}
|
|
323
|
+
/** Payload for `POST /bff/register` — proxied by the BFF to TenantService. */
|
|
324
|
+
interface BffRegisterRequest {
|
|
325
|
+
firstName: string;
|
|
326
|
+
lastName: string;
|
|
327
|
+
username: string;
|
|
328
|
+
email: string;
|
|
329
|
+
password: string;
|
|
330
|
+
tenantName: string;
|
|
331
|
+
[key: string]: unknown;
|
|
332
|
+
}
|
|
333
|
+
/** Payload for `POST /bff/forgot-password` — proxied to TenantService. */
|
|
334
|
+
interface BffForgotPasswordRequest {
|
|
335
|
+
email: string;
|
|
336
|
+
/** Full URL with a `{token}` placeholder; the backend substitutes the token. */
|
|
337
|
+
resetUrlTemplate?: string;
|
|
338
|
+
[key: string]: unknown;
|
|
339
|
+
}
|
|
340
|
+
/** Payload for `POST /bff/reset-password` — proxied to TenantService. */
|
|
341
|
+
interface BffResetPasswordRequest {
|
|
342
|
+
token: string;
|
|
343
|
+
newPassword: string;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF
|
|
347
|
+
* returns the sanitised KC claims under a `user` envelope and **never** a
|
|
348
|
+
* token. Kept permissive so server-added claims flow through without a bump.
|
|
349
|
+
*/
|
|
350
|
+
interface BffUser {
|
|
351
|
+
sub?: string;
|
|
352
|
+
email?: string;
|
|
353
|
+
email_verified?: boolean;
|
|
354
|
+
name?: string;
|
|
355
|
+
preferred_username?: string;
|
|
356
|
+
given_name?: string;
|
|
357
|
+
family_name?: string;
|
|
358
|
+
tenantId?: string;
|
|
359
|
+
roles?: string[];
|
|
360
|
+
[key: string]: unknown;
|
|
361
|
+
}
|
|
362
|
+
interface BffAuthClientOptions {
|
|
363
|
+
/** Runtime-agnostic HTTP transport (wrap native `fetch` with `createFetchHttpClient`). */
|
|
364
|
+
http: HttpClient;
|
|
365
|
+
/**
|
|
366
|
+
* BFF origin. Defaults to `''` (same-origin) — the production wiring. An
|
|
367
|
+
* explicit origin is only useful for tests or a non-same-origin BFF.
|
|
368
|
+
*/
|
|
369
|
+
baseUrl?: string;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Same-origin client for a per-app BFF.
|
|
373
|
+
*
|
|
374
|
+
* No token storage, no refresh logic, no realm awareness — the BFF owns all of
|
|
375
|
+
* that server-side. The browser's only auth artefact is the httpOnly cookie.
|
|
376
|
+
*/
|
|
377
|
+
declare class BffAuthClient {
|
|
378
|
+
private readonly http;
|
|
379
|
+
private readonly baseUrl;
|
|
380
|
+
constructor(options: BffAuthClientOptions);
|
|
381
|
+
/**
|
|
382
|
+
* `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores
|
|
383
|
+
* the tokens in its Redis vault, and sets the httpOnly session cookie.
|
|
384
|
+
* Returns the sanitised user. Throws on a non-2xx response.
|
|
385
|
+
*/
|
|
386
|
+
login(request: BffLoginRequest): Promise<BffUser>;
|
|
387
|
+
/**
|
|
388
|
+
* `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis
|
|
389
|
+
* session, and clears the cookie. Non-fatal: a failed logout still leaves
|
|
390
|
+
* the SPA logged out client-side. Throws only on a non-2xx response.
|
|
391
|
+
*/
|
|
392
|
+
logout(): Promise<void>;
|
|
393
|
+
/**
|
|
394
|
+
* `GET /bff/me` — the live session's sanitised user, or `null` when there is
|
|
395
|
+
* no session (the BFF answers `401`). Used at app load to bootstrap auth
|
|
396
|
+
* state in place of the old token-in-storage check.
|
|
397
|
+
*/
|
|
398
|
+
getCurrentUser(): Promise<BffUser | null>;
|
|
399
|
+
/**
|
|
400
|
+
* `POST /bff/register` — the BFF proxies registration to TenantService and,
|
|
401
|
+
* on success, establishes a session exactly like `login`. Returns the user.
|
|
402
|
+
*/
|
|
403
|
+
register(request: BffRegisterRequest): Promise<BffUser>;
|
|
404
|
+
/**
|
|
405
|
+
* `POST /bff/forgot-password` — proxied to TenantService. The backend
|
|
406
|
+
* returns 200 unconditionally (no email enumeration); anything else throws.
|
|
407
|
+
*/
|
|
408
|
+
forgotPassword(request: BffForgotPasswordRequest): Promise<void>;
|
|
409
|
+
/**
|
|
410
|
+
* `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx
|
|
411
|
+
* response (e.g. `400` for an invalid / expired token).
|
|
412
|
+
*/
|
|
413
|
+
resetPassword(request: BffResetPasswordRequest): Promise<void>;
|
|
414
|
+
/**
|
|
415
|
+
* Shared POST for every state-changing `/bff/*` call: same-origin, cookie
|
|
416
|
+
* included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
|
|
417
|
+
*/
|
|
418
|
+
private postState;
|
|
419
|
+
}
|
|
420
|
+
|
|
318
421
|
/**
|
|
319
422
|
* Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.
|
|
320
423
|
*
|
|
@@ -488,4 +591,4 @@ declare function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse;
|
|
|
488
591
|
*/
|
|
489
592
|
declare function tokenResponseToAuthTokens(response: TokenResponse, now?: number): AuthTokens;
|
|
490
593
|
|
|
491
|
-
export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, type BiometricFlagStore, BiometricGate, type BiometricGateLike, type BiometricGateOptions, BrowserStorageTokenStorage, type BrowserStorageTokenStorageOptions, CookieTokenStorage, InMemoryTokenStorage, KeycloakRoles, type KeycloakUserInfo, type LocalAuthLike, type NormalizedUser, RawTokenResponse, type RefreshTokenBodyInput, type SecureStoreLike, SecureStoreTokenStorage, type SecureStoreTokenStorageOptions, type StorageLike, TokenResponse, TokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, computeExpiresAt, decodeJwt, extractAuthCode, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, tokenResponseToAuthTokens };
|
|
594
|
+
export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, BffAuthClient, type BffAuthClientOptions, type BffForgotPasswordRequest, type BffLoginRequest, type BffRegisterRequest, type BffResetPasswordRequest, type BffUser, type BiometricFlagStore, BiometricGate, type BiometricGateLike, type BiometricGateOptions, BrowserStorageTokenStorage, type BrowserStorageTokenStorageOptions, CookieTokenStorage, HttpClient, InMemoryTokenStorage, KeycloakRoles, type KeycloakUserInfo, type LocalAuthLike, type NormalizedUser, RawTokenResponse, type RefreshTokenBodyInput, type SecureStoreLike, SecureStoreTokenStorage, type SecureStoreTokenStorageOptions, type StorageLike, TokenResponse, TokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, computeExpiresAt, decodeJwt, extractAuthCode, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, tokenResponseToAuthTokens };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { T as TokenStorage, c as AuthTokens } from './AuthClient-D95OMajD.js';
|
|
2
2
|
export { A as AuthApiClient, d as AuthApiClientOptions, b as AuthClient, e as AuthClientCollaborators, f as AuthClientConfig, g as AuthClientFromIssuerInput, h as AuthEventEmitter, i as AuthEventListener, j as AuthEventName, k as AuthEventUnsubscribe, a as AuthSessionInfo, D as DirectKcOptions, F as ForgotPasswordRequest, I as InactivityStore, l as InactivityTracker, m as InactivityTrackerOptions, L as LoginOptions, n as LogoutOptions, O as OtpLoginRequest, P as PasswordLoginRequest, o as RawAuthLoginResponse, p as RefreshFn, q as RefreshInterceptor, r as RefreshInterceptorOptions, R as ResetPasswordRequest } from './AuthClient-D95OMajD.js';
|
|
3
3
|
export { ExchangeAuthorizationCodeInput, FetchDiscoveryDocumentInput, OidcDiscoveryDocument, PkcePair, RefreshAccessTokenInput, clearDiscoveryCache, deriveCodeChallenge, exchangeAuthorizationCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, refreshAccessToken } from './oidc/index.js';
|
|
4
|
-
import { R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.js';
|
|
5
|
-
export {
|
|
4
|
+
import { H as HttpClient, R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.js';
|
|
5
|
+
export { a as HttpRequest, b as HttpResponse, c as createFetchHttpClient } from './TokenResponse-CY1CaU2l.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Roles emitted by Keycloak realms in the dloizides.com portfolio.
|
|
@@ -315,6 +315,109 @@ declare class BiometricGate {
|
|
|
315
315
|
unlock(): Promise<void>;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
/** Credentials posted to `POST /bff/login`. */
|
|
319
|
+
interface BffLoginRequest {
|
|
320
|
+
username: string;
|
|
321
|
+
password: string;
|
|
322
|
+
}
|
|
323
|
+
/** Payload for `POST /bff/register` — proxied by the BFF to TenantService. */
|
|
324
|
+
interface BffRegisterRequest {
|
|
325
|
+
firstName: string;
|
|
326
|
+
lastName: string;
|
|
327
|
+
username: string;
|
|
328
|
+
email: string;
|
|
329
|
+
password: string;
|
|
330
|
+
tenantName: string;
|
|
331
|
+
[key: string]: unknown;
|
|
332
|
+
}
|
|
333
|
+
/** Payload for `POST /bff/forgot-password` — proxied to TenantService. */
|
|
334
|
+
interface BffForgotPasswordRequest {
|
|
335
|
+
email: string;
|
|
336
|
+
/** Full URL with a `{token}` placeholder; the backend substitutes the token. */
|
|
337
|
+
resetUrlTemplate?: string;
|
|
338
|
+
[key: string]: unknown;
|
|
339
|
+
}
|
|
340
|
+
/** Payload for `POST /bff/reset-password` — proxied to TenantService. */
|
|
341
|
+
interface BffResetPasswordRequest {
|
|
342
|
+
token: string;
|
|
343
|
+
newPassword: string;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF
|
|
347
|
+
* returns the sanitised KC claims under a `user` envelope and **never** a
|
|
348
|
+
* token. Kept permissive so server-added claims flow through without a bump.
|
|
349
|
+
*/
|
|
350
|
+
interface BffUser {
|
|
351
|
+
sub?: string;
|
|
352
|
+
email?: string;
|
|
353
|
+
email_verified?: boolean;
|
|
354
|
+
name?: string;
|
|
355
|
+
preferred_username?: string;
|
|
356
|
+
given_name?: string;
|
|
357
|
+
family_name?: string;
|
|
358
|
+
tenantId?: string;
|
|
359
|
+
roles?: string[];
|
|
360
|
+
[key: string]: unknown;
|
|
361
|
+
}
|
|
362
|
+
interface BffAuthClientOptions {
|
|
363
|
+
/** Runtime-agnostic HTTP transport (wrap native `fetch` with `createFetchHttpClient`). */
|
|
364
|
+
http: HttpClient;
|
|
365
|
+
/**
|
|
366
|
+
* BFF origin. Defaults to `''` (same-origin) — the production wiring. An
|
|
367
|
+
* explicit origin is only useful for tests or a non-same-origin BFF.
|
|
368
|
+
*/
|
|
369
|
+
baseUrl?: string;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Same-origin client for a per-app BFF.
|
|
373
|
+
*
|
|
374
|
+
* No token storage, no refresh logic, no realm awareness — the BFF owns all of
|
|
375
|
+
* that server-side. The browser's only auth artefact is the httpOnly cookie.
|
|
376
|
+
*/
|
|
377
|
+
declare class BffAuthClient {
|
|
378
|
+
private readonly http;
|
|
379
|
+
private readonly baseUrl;
|
|
380
|
+
constructor(options: BffAuthClientOptions);
|
|
381
|
+
/**
|
|
382
|
+
* `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores
|
|
383
|
+
* the tokens in its Redis vault, and sets the httpOnly session cookie.
|
|
384
|
+
* Returns the sanitised user. Throws on a non-2xx response.
|
|
385
|
+
*/
|
|
386
|
+
login(request: BffLoginRequest): Promise<BffUser>;
|
|
387
|
+
/**
|
|
388
|
+
* `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis
|
|
389
|
+
* session, and clears the cookie. Non-fatal: a failed logout still leaves
|
|
390
|
+
* the SPA logged out client-side. Throws only on a non-2xx response.
|
|
391
|
+
*/
|
|
392
|
+
logout(): Promise<void>;
|
|
393
|
+
/**
|
|
394
|
+
* `GET /bff/me` — the live session's sanitised user, or `null` when there is
|
|
395
|
+
* no session (the BFF answers `401`). Used at app load to bootstrap auth
|
|
396
|
+
* state in place of the old token-in-storage check.
|
|
397
|
+
*/
|
|
398
|
+
getCurrentUser(): Promise<BffUser | null>;
|
|
399
|
+
/**
|
|
400
|
+
* `POST /bff/register` — the BFF proxies registration to TenantService and,
|
|
401
|
+
* on success, establishes a session exactly like `login`. Returns the user.
|
|
402
|
+
*/
|
|
403
|
+
register(request: BffRegisterRequest): Promise<BffUser>;
|
|
404
|
+
/**
|
|
405
|
+
* `POST /bff/forgot-password` — proxied to TenantService. The backend
|
|
406
|
+
* returns 200 unconditionally (no email enumeration); anything else throws.
|
|
407
|
+
*/
|
|
408
|
+
forgotPassword(request: BffForgotPasswordRequest): Promise<void>;
|
|
409
|
+
/**
|
|
410
|
+
* `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx
|
|
411
|
+
* response (e.g. `400` for an invalid / expired token).
|
|
412
|
+
*/
|
|
413
|
+
resetPassword(request: BffResetPasswordRequest): Promise<void>;
|
|
414
|
+
/**
|
|
415
|
+
* Shared POST for every state-changing `/bff/*` call: same-origin, cookie
|
|
416
|
+
* included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
|
|
417
|
+
*/
|
|
418
|
+
private postState;
|
|
419
|
+
}
|
|
420
|
+
|
|
318
421
|
/**
|
|
319
422
|
* Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.
|
|
320
423
|
*
|
|
@@ -488,4 +591,4 @@ declare function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse;
|
|
|
488
591
|
*/
|
|
489
592
|
declare function tokenResponseToAuthTokens(response: TokenResponse, now?: number): AuthTokens;
|
|
490
593
|
|
|
491
|
-
export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, type BiometricFlagStore, BiometricGate, type BiometricGateLike, type BiometricGateOptions, BrowserStorageTokenStorage, type BrowserStorageTokenStorageOptions, CookieTokenStorage, InMemoryTokenStorage, KeycloakRoles, type KeycloakUserInfo, type LocalAuthLike, type NormalizedUser, RawTokenResponse, type RefreshTokenBodyInput, type SecureStoreLike, SecureStoreTokenStorage, type SecureStoreTokenStorageOptions, type StorageLike, TokenResponse, TokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, computeExpiresAt, decodeJwt, extractAuthCode, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, tokenResponseToAuthTokens };
|
|
594
|
+
export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, BffAuthClient, type BffAuthClientOptions, type BffForgotPasswordRequest, type BffLoginRequest, type BffRegisterRequest, type BffResetPasswordRequest, type BffUser, type BiometricFlagStore, BiometricGate, type BiometricGateLike, type BiometricGateOptions, BrowserStorageTokenStorage, type BrowserStorageTokenStorageOptions, CookieTokenStorage, HttpClient, InMemoryTokenStorage, KeycloakRoles, type KeycloakUserInfo, type LocalAuthLike, type NormalizedUser, RawTokenResponse, type RefreshTokenBodyInput, type SecureStoreLike, SecureStoreTokenStorage, type SecureStoreTokenStorageOptions, type StorageLike, TokenResponse, TokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, computeExpiresAt, decodeJwt, extractAuthCode, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, tokenResponseToAuthTokens };
|
package/dist/index.js
CHANGED
|
@@ -1059,6 +1059,121 @@ var AuthApiClient = class {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
};
|
|
1061
1061
|
|
|
1062
|
+
// src/bff/BffAuthClient.ts
|
|
1063
|
+
var CSRF_HEADER = "X-BFF-Csrf";
|
|
1064
|
+
var CSRF_HEADER_VALUE = "1";
|
|
1065
|
+
var JSON_CONTENT_TYPE = "application/json";
|
|
1066
|
+
var ENDPOINTS = {
|
|
1067
|
+
login: "/bff/login",
|
|
1068
|
+
logout: "/bff/logout",
|
|
1069
|
+
me: "/bff/me",
|
|
1070
|
+
register: "/bff/register",
|
|
1071
|
+
forgotPassword: "/bff/forgot-password",
|
|
1072
|
+
resetPassword: "/bff/reset-password"
|
|
1073
|
+
};
|
|
1074
|
+
function isRecord(value) {
|
|
1075
|
+
return typeof value === "object" && value !== null;
|
|
1076
|
+
}
|
|
1077
|
+
function extractUser(data) {
|
|
1078
|
+
if (!isRecord(data)) {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
const envelope = data;
|
|
1082
|
+
return isRecord(envelope.user) ? envelope.user : null;
|
|
1083
|
+
}
|
|
1084
|
+
var BffAuthClient = class {
|
|
1085
|
+
constructor(options) {
|
|
1086
|
+
this.http = options.http;
|
|
1087
|
+
this.baseUrl = (options.baseUrl ?? "").replace(/\/$/, "");
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores
|
|
1091
|
+
* the tokens in its Redis vault, and sets the httpOnly session cookie.
|
|
1092
|
+
* Returns the sanitised user. Throws on a non-2xx response.
|
|
1093
|
+
*/
|
|
1094
|
+
async login(request) {
|
|
1095
|
+
const data = await this.postState(ENDPOINTS.login, request, "login");
|
|
1096
|
+
const user = extractUser(data);
|
|
1097
|
+
if (user === null) {
|
|
1098
|
+
throw new Error("login: BFF response missing user");
|
|
1099
|
+
}
|
|
1100
|
+
return user;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis
|
|
1104
|
+
* session, and clears the cookie. Non-fatal: a failed logout still leaves
|
|
1105
|
+
* the SPA logged out client-side. Throws only on a non-2xx response.
|
|
1106
|
+
*/
|
|
1107
|
+
async logout() {
|
|
1108
|
+
await this.postState(ENDPOINTS.logout, void 0, "logout");
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* `GET /bff/me` — the live session's sanitised user, or `null` when there is
|
|
1112
|
+
* no session (the BFF answers `401`). Used at app load to bootstrap auth
|
|
1113
|
+
* state in place of the old token-in-storage check.
|
|
1114
|
+
*/
|
|
1115
|
+
async getCurrentUser() {
|
|
1116
|
+
const response = await this.http({
|
|
1117
|
+
url: `${this.baseUrl}${ENDPOINTS.me}`,
|
|
1118
|
+
method: "GET",
|
|
1119
|
+
headers: { Accept: JSON_CONTENT_TYPE },
|
|
1120
|
+
credentials: "include"
|
|
1121
|
+
});
|
|
1122
|
+
if (!response.ok) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
return extractUser(response.data);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* `POST /bff/register` — the BFF proxies registration to TenantService and,
|
|
1129
|
+
* on success, establishes a session exactly like `login`. Returns the user.
|
|
1130
|
+
*/
|
|
1131
|
+
async register(request) {
|
|
1132
|
+
const data = await this.postState(ENDPOINTS.register, request, "register");
|
|
1133
|
+
const user = extractUser(data);
|
|
1134
|
+
if (user === null) {
|
|
1135
|
+
throw new Error("register: BFF response missing user");
|
|
1136
|
+
}
|
|
1137
|
+
return user;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* `POST /bff/forgot-password` — proxied to TenantService. The backend
|
|
1141
|
+
* returns 200 unconditionally (no email enumeration); anything else throws.
|
|
1142
|
+
*/
|
|
1143
|
+
async forgotPassword(request) {
|
|
1144
|
+
await this.postState(ENDPOINTS.forgotPassword, request, "forgot-password");
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx
|
|
1148
|
+
* response (e.g. `400` for an invalid / expired token).
|
|
1149
|
+
*/
|
|
1150
|
+
async resetPassword(request) {
|
|
1151
|
+
await this.postState(ENDPOINTS.resetPassword, request, "reset-password");
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Shared POST for every state-changing `/bff/*` call: same-origin, cookie
|
|
1155
|
+
* included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
|
|
1156
|
+
*/
|
|
1157
|
+
async postState(path, body, label) {
|
|
1158
|
+
const headers = {
|
|
1159
|
+
"Content-Type": JSON_CONTENT_TYPE,
|
|
1160
|
+
Accept: JSON_CONTENT_TYPE,
|
|
1161
|
+
[CSRF_HEADER]: CSRF_HEADER_VALUE
|
|
1162
|
+
};
|
|
1163
|
+
const response = await this.http({
|
|
1164
|
+
url: `${this.baseUrl}${path}`,
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
headers,
|
|
1167
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
1168
|
+
credentials: "include"
|
|
1169
|
+
});
|
|
1170
|
+
if (!response.ok) {
|
|
1171
|
+
throw new Error(`${label} failed with status ${String(response.status)}`);
|
|
1172
|
+
}
|
|
1173
|
+
return response.data;
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1062
1177
|
// src/utils/normalizeKeycloakUser.ts
|
|
1063
1178
|
function isNonEmptyString(value) {
|
|
1064
1179
|
return typeof value === "string" && value !== "";
|
|
@@ -1172,6 +1287,7 @@ function decodeUtf8(binary) {
|
|
|
1172
1287
|
exports.AuthApiClient = AuthApiClient;
|
|
1173
1288
|
exports.AuthClient = AuthClient;
|
|
1174
1289
|
exports.AuthEventEmitter = AuthEventEmitter;
|
|
1290
|
+
exports.BffAuthClient = BffAuthClient;
|
|
1175
1291
|
exports.BiometricGate = BiometricGate;
|
|
1176
1292
|
exports.BrowserStorageTokenStorage = BrowserStorageTokenStorage;
|
|
1177
1293
|
exports.CookieTokenStorage = CookieTokenStorage;
|