@dloizides/auth-client 3.0.0 → 3.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.2.0 (2026-05-22)
4
+
5
+ Additive release for Phase 3d of the unified-auth plan — event-scoped PIN
6
+ login. Extends `BffAuthClient` with the browser-facing PIN call so the new
7
+ `<PinForm>` in `@dloizides/auth-web` has a same-origin client. No breaking
8
+ changes.
9
+
10
+ ### Added
11
+
12
+ - `BffAuthClient.pinLogin({ pin, eventExternalId })` → `POST /bff/pin/login`.
13
+ The BFF runs the event-scoped PIN direct-grant against Keycloak server-side
14
+ (the `(event, pin)` pair resolves to the staff member's KC account + their
15
+ event-scoped role) and sets the httpOnly session cookie. Returns the
16
+ sanitised `BffUser`, exactly like `login` / `verifyOtp`. Throws on a non-2xx
17
+ (`401` for a bad / expired / locked-out PIN or an unknown event, `501` when
18
+ PIN login is not an enabled method). Carries the `X-BFF-Csrf` header like
19
+ every other state-changing call. No `username` / `password` ever leaves the
20
+ browser.
21
+ - Type: `BffPinLoginRequest`.
22
+
23
+ ## 3.1.0 (2026-05-22)
24
+
25
+ Additive release for Phase 2d of the unified-auth plan — email-OTP. Extends
26
+ `BffAuthClient` with the two browser-facing OTP calls so the new `<OtpForm>` in
27
+ `@dloizides/auth-web` has a same-origin client. No breaking changes.
28
+
29
+ ### Added
30
+
31
+ - `BffAuthClient.requestOtp({ identifier })` → `POST /bff/otp/request`. The BFF
32
+ proxies to TenantService, which emails a short-TTL code. The endpoint is
33
+ anti-enumeration (a `200` is the normal path), so the method **returns** the
34
+ relayed `{ success, expiresIn, code }` body — the UI uses `expiresIn` for a
35
+ countdown. It still throws on a non-2xx (`501` OTP not enabled, `502` upstream
36
+ down). Carries the `X-BFF-Csrf` header like every other state-changing call.
37
+ - `BffAuthClient.verifyOtp({ username, otp })` → `POST /bff/otp/verify`. The BFF
38
+ runs the OTP direct-grant against Keycloak server-side and sets the httpOnly
39
+ session cookie. Returns the sanitised `BffUser`, exactly like `login`. Throws
40
+ on a non-2xx (e.g. `401` for a bad / expired code).
41
+ - Types: `BffOtpRequestRequest`, `BffOtpVerifyRequest`, `BffOtpRequestResult`.
42
+
3
43
  ## 3.0.0 (2026-05-19)
4
44
 
5
45
  Major release for Phase 2 of the identity-hardening initiative. Adds the
@@ -457,4 +457,4 @@ declare class AuthClient {
457
457
  private resolveScope;
458
458
  }
459
459
 
460
- export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type ResetPasswordRequest as R, type TokenStorage as T, type AuthSessionInfo as a, AuthClient as b, type AuthTokens as c, type AuthApiClientOptions as d, type AuthClientCollaborators as e, type AuthClientConfig as f, type AuthClientFromIssuerInput as g, AuthEventEmitter as h, type AuthEventListener as i, type AuthEventName as j, type AuthEventUnsubscribe as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RawAuthLoginResponse as o, type RefreshFn as p, RefreshInterceptor as q, type RefreshInterceptorOptions as r };
460
+ export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type RawAuthLoginResponse as R, type TokenStorage as T, type AuthApiClientOptions as a, AuthClient as b, type AuthClientCollaborators as c, type AuthClientConfig as d, type AuthClientFromIssuerInput as e, AuthEventEmitter as f, type AuthEventListener as g, type AuthEventName as h, type AuthEventUnsubscribe as i, type AuthSessionInfo as j, type AuthTokens as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RefreshFn as o, RefreshInterceptor as p, type RefreshInterceptorOptions as q, type ResetPasswordRequest as r };
@@ -457,4 +457,4 @@ declare class AuthClient {
457
457
  private resolveScope;
458
458
  }
459
459
 
460
- export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type ResetPasswordRequest as R, type TokenStorage as T, type AuthSessionInfo as a, AuthClient as b, type AuthTokens as c, type AuthApiClientOptions as d, type AuthClientCollaborators as e, type AuthClientConfig as f, type AuthClientFromIssuerInput as g, AuthEventEmitter as h, type AuthEventListener as i, type AuthEventName as j, type AuthEventUnsubscribe as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RawAuthLoginResponse as o, type RefreshFn as p, RefreshInterceptor as q, type RefreshInterceptorOptions as r };
460
+ export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type RawAuthLoginResponse as R, type TokenStorage as T, type AuthApiClientOptions as a, AuthClient as b, type AuthClientCollaborators as c, type AuthClientConfig as d, type AuthClientFromIssuerInput as e, AuthEventEmitter as f, type AuthEventListener as g, type AuthEventName as h, type AuthEventUnsubscribe as i, type AuthSessionInfo as j, type AuthTokens as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RefreshFn as o, RefreshInterceptor as p, type RefreshInterceptorOptions as q, type ResetPasswordRequest as r };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TokenStorage, c as AuthTokens } from './AuthClient-BGr8L03W.mjs';
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';
1
+ import { T as TokenStorage, k as AuthTokens } from './AuthClient-D8Ul-aGa.mjs';
2
+ export { A as AuthApiClient, a as AuthApiClientOptions, b as AuthClient, c as AuthClientCollaborators, d as AuthClientConfig, e as AuthClientFromIssuerInput, f as AuthEventEmitter, g as AuthEventListener, h as AuthEventName, i as AuthEventUnsubscribe, j 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, R as RawAuthLoginResponse, o as RefreshFn, p as RefreshInterceptor, q as RefreshInterceptorOptions, r as ResetPasswordRequest } from './AuthClient-D8Ul-aGa.mjs';
3
3
  export { ExchangeAuthorizationCodeInput, FetchDiscoveryDocumentInput, OidcDiscoveryDocument, PkcePair, RefreshAccessTokenInput, clearDiscoveryCache, deriveCodeChallenge, exchangeAuthorizationCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, refreshAccessToken } from './oidc/index.mjs';
4
4
  import { H as HttpClient, R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.mjs';
5
5
  export { a as HttpRequest, b as HttpResponse, c as createFetchHttpClient } from './TokenResponse-CY1CaU2l.mjs';
@@ -342,6 +342,51 @@ interface BffResetPasswordRequest {
342
342
  token: string;
343
343
  newPassword: string;
344
344
  }
345
+ /**
346
+ * Payload for `POST /bff/otp/request` — the BFF proxies it to TenantService,
347
+ * which generates a short-TTL code and emails it.
348
+ */
349
+ interface BffOtpRequestRequest {
350
+ /** The email address (or username) the one-time code is sent to. */
351
+ identifier: string;
352
+ }
353
+ /** Payload for `POST /bff/otp/verify` — the BFF exchanges it for a session. */
354
+ interface BffOtpVerifyRequest {
355
+ /** The email / username the code was requested for. */
356
+ username: string;
357
+ /** The one-time code the user entered. */
358
+ otp: string;
359
+ }
360
+ /**
361
+ * Payload for `POST /bff/pin/login` — the BFF exchanges an event-scoped PIN
362
+ * for a session.
363
+ *
364
+ * The `(event, pin)` pair alone identifies the staff member: no `username` /
365
+ * `password` ever leaves the browser. A PIN entered in an event's context
366
+ * grants that staff member their event-scoped role for that event only
367
+ * (the unified-auth plan §4.4 — event-scoped, per-individual PINs).
368
+ */
369
+ interface BffPinLoginRequest {
370
+ /** The numeric PIN the staff member entered. */
371
+ pin: string;
372
+ /** External id of the event the PIN is scoped to (supplied by the page/route). */
373
+ eventExternalId: string;
374
+ }
375
+ /**
376
+ * The body `POST /bff/otp/request` relays from TenantService.
377
+ *
378
+ * Anti-enumeration: the shape is identical whether or not the identifier is
379
+ * registered. `code` is non-null only outside production (a dev convenience);
380
+ * the UI must never depend on it being present.
381
+ */
382
+ interface BffOtpRequestResult {
383
+ /** Always `true` on a relayed 200 — the request was accepted. */
384
+ success: boolean;
385
+ /** Seconds until the emitted code expires — drives a countdown in the UI. */
386
+ expiresIn: number;
387
+ /** The code itself, non-production only; `null` (or absent) in production. */
388
+ code: string | null;
389
+ }
345
390
  /**
346
391
  * The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF
347
392
  * returns the sanitised KC claims under a `user` envelope and **never** a
@@ -411,6 +456,34 @@ declare class BffAuthClient {
411
456
  * response (e.g. `400` for an invalid / expired token).
412
457
  */
413
458
  resetPassword(request: BffResetPasswordRequest): Promise<void>;
459
+ /**
460
+ * `POST /bff/otp/request` — the BFF proxies to TenantService, which generates
461
+ * a short-TTL code and emails it.
462
+ *
463
+ * The endpoint is anti-enumeration: a `200` is the normal path whether or not
464
+ * the identifier is registered. This method therefore **returns** the relayed
465
+ * `{ success, expiresIn, code }` body (so the UI can show the expiry) rather
466
+ * than treating a 200 as opaque. It still throws on a non-2xx — a `501`
467
+ * (OTP not enabled) or `502` (upstream down) is a real failure to surface.
468
+ */
469
+ requestOtp(request: BffOtpRequestRequest): Promise<BffOtpRequestResult>;
470
+ /**
471
+ * `POST /bff/otp/verify` — the BFF runs the OTP direct-grant against Keycloak
472
+ * server-side, stores the tokens in its Redis vault, and sets the httpOnly
473
+ * session cookie. Returns the sanitised user, exactly like `login`. Throws on
474
+ * a non-2xx (e.g. `401` for a bad / expired code).
475
+ */
476
+ verifyOtp(request: BffOtpVerifyRequest): Promise<BffUser>;
477
+ /**
478
+ * `POST /bff/pin/login` — the BFF runs the event-scoped PIN direct-grant
479
+ * against Keycloak server-side (the `(event, pin)` pair resolves to the
480
+ * staff member's KC account + event-scoped role), stores the tokens in its
481
+ * Redis vault, and sets the httpOnly session cookie. Returns the sanitised
482
+ * user, exactly like `login` / `verifyOtp`. Throws on a non-2xx — `401` for
483
+ * a bad / expired / locked-out PIN or an unknown event, `501` when PIN login
484
+ * is not an enabled method for this BFF.
485
+ */
486
+ pinLogin(request: BffPinLoginRequest): Promise<BffUser>;
414
487
  /**
415
488
  * Shared POST for every state-changing `/bff/*` call: same-origin, cookie
416
489
  * included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
@@ -591,4 +664,4 @@ declare function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse;
591
664
  */
592
665
  declare function tokenResponseToAuthTokens(response: TokenResponse, now?: number): AuthTokens;
593
666
 
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 };
667
+ export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, BffAuthClient, type BffAuthClientOptions, type BffForgotPasswordRequest, type BffLoginRequest, type BffOtpRequestRequest, type BffOtpRequestResult, type BffOtpVerifyRequest, type BffPinLoginRequest, 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,5 +1,5 @@
1
- import { T as TokenStorage, c as AuthTokens } from './AuthClient-D95OMajD.js';
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';
1
+ import { T as TokenStorage, k as AuthTokens } from './AuthClient-Cv7btBX0.js';
2
+ export { A as AuthApiClient, a as AuthApiClientOptions, b as AuthClient, c as AuthClientCollaborators, d as AuthClientConfig, e as AuthClientFromIssuerInput, f as AuthEventEmitter, g as AuthEventListener, h as AuthEventName, i as AuthEventUnsubscribe, j 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, R as RawAuthLoginResponse, o as RefreshFn, p as RefreshInterceptor, q as RefreshInterceptorOptions, r as ResetPasswordRequest } from './AuthClient-Cv7btBX0.js';
3
3
  export { ExchangeAuthorizationCodeInput, FetchDiscoveryDocumentInput, OidcDiscoveryDocument, PkcePair, RefreshAccessTokenInput, clearDiscoveryCache, deriveCodeChallenge, exchangeAuthorizationCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, refreshAccessToken } from './oidc/index.js';
4
4
  import { H as HttpClient, R as RawTokenResponse, T as TokenResponse } from './TokenResponse-CY1CaU2l.js';
5
5
  export { a as HttpRequest, b as HttpResponse, c as createFetchHttpClient } from './TokenResponse-CY1CaU2l.js';
@@ -342,6 +342,51 @@ interface BffResetPasswordRequest {
342
342
  token: string;
343
343
  newPassword: string;
344
344
  }
345
+ /**
346
+ * Payload for `POST /bff/otp/request` — the BFF proxies it to TenantService,
347
+ * which generates a short-TTL code and emails it.
348
+ */
349
+ interface BffOtpRequestRequest {
350
+ /** The email address (or username) the one-time code is sent to. */
351
+ identifier: string;
352
+ }
353
+ /** Payload for `POST /bff/otp/verify` — the BFF exchanges it for a session. */
354
+ interface BffOtpVerifyRequest {
355
+ /** The email / username the code was requested for. */
356
+ username: string;
357
+ /** The one-time code the user entered. */
358
+ otp: string;
359
+ }
360
+ /**
361
+ * Payload for `POST /bff/pin/login` — the BFF exchanges an event-scoped PIN
362
+ * for a session.
363
+ *
364
+ * The `(event, pin)` pair alone identifies the staff member: no `username` /
365
+ * `password` ever leaves the browser. A PIN entered in an event's context
366
+ * grants that staff member their event-scoped role for that event only
367
+ * (the unified-auth plan §4.4 — event-scoped, per-individual PINs).
368
+ */
369
+ interface BffPinLoginRequest {
370
+ /** The numeric PIN the staff member entered. */
371
+ pin: string;
372
+ /** External id of the event the PIN is scoped to (supplied by the page/route). */
373
+ eventExternalId: string;
374
+ }
375
+ /**
376
+ * The body `POST /bff/otp/request` relays from TenantService.
377
+ *
378
+ * Anti-enumeration: the shape is identical whether or not the identifier is
379
+ * registered. `code` is non-null only outside production (a dev convenience);
380
+ * the UI must never depend on it being present.
381
+ */
382
+ interface BffOtpRequestResult {
383
+ /** Always `true` on a relayed 200 — the request was accepted. */
384
+ success: boolean;
385
+ /** Seconds until the emitted code expires — drives a countdown in the UI. */
386
+ expiresIn: number;
387
+ /** The code itself, non-production only; `null` (or absent) in production. */
388
+ code: string | null;
389
+ }
345
390
  /**
346
391
  * The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF
347
392
  * returns the sanitised KC claims under a `user` envelope and **never** a
@@ -411,6 +456,34 @@ declare class BffAuthClient {
411
456
  * response (e.g. `400` for an invalid / expired token).
412
457
  */
413
458
  resetPassword(request: BffResetPasswordRequest): Promise<void>;
459
+ /**
460
+ * `POST /bff/otp/request` — the BFF proxies to TenantService, which generates
461
+ * a short-TTL code and emails it.
462
+ *
463
+ * The endpoint is anti-enumeration: a `200` is the normal path whether or not
464
+ * the identifier is registered. This method therefore **returns** the relayed
465
+ * `{ success, expiresIn, code }` body (so the UI can show the expiry) rather
466
+ * than treating a 200 as opaque. It still throws on a non-2xx — a `501`
467
+ * (OTP not enabled) or `502` (upstream down) is a real failure to surface.
468
+ */
469
+ requestOtp(request: BffOtpRequestRequest): Promise<BffOtpRequestResult>;
470
+ /**
471
+ * `POST /bff/otp/verify` — the BFF runs the OTP direct-grant against Keycloak
472
+ * server-side, stores the tokens in its Redis vault, and sets the httpOnly
473
+ * session cookie. Returns the sanitised user, exactly like `login`. Throws on
474
+ * a non-2xx (e.g. `401` for a bad / expired code).
475
+ */
476
+ verifyOtp(request: BffOtpVerifyRequest): Promise<BffUser>;
477
+ /**
478
+ * `POST /bff/pin/login` — the BFF runs the event-scoped PIN direct-grant
479
+ * against Keycloak server-side (the `(event, pin)` pair resolves to the
480
+ * staff member's KC account + event-scoped role), stores the tokens in its
481
+ * Redis vault, and sets the httpOnly session cookie. Returns the sanitised
482
+ * user, exactly like `login` / `verifyOtp`. Throws on a non-2xx — `401` for
483
+ * a bad / expired / locked-out PIN or an unknown event, `501` when PIN login
484
+ * is not an enabled method for this BFF.
485
+ */
486
+ pinLogin(request: BffPinLoginRequest): Promise<BffUser>;
414
487
  /**
415
488
  * Shared POST for every state-changing `/bff/*` call: same-origin, cookie
416
489
  * included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
@@ -591,4 +664,4 @@ declare function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse;
591
664
  */
592
665
  declare function tokenResponseToAuthTokens(response: TokenResponse, now?: number): AuthTokens;
593
666
 
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 };
667
+ export { AuthTokens, type AuthorizationCodeBodyInput, type AuthorizationResponseLike, type AuthorizationUrlInput, BffAuthClient, type BffAuthClientOptions, type BffForgotPasswordRequest, type BffLoginRequest, type BffOtpRequestRequest, type BffOtpRequestResult, type BffOtpVerifyRequest, type BffPinLoginRequest, 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
@@ -1069,7 +1069,10 @@ var ENDPOINTS = {
1069
1069
  me: "/bff/me",
1070
1070
  register: "/bff/register",
1071
1071
  forgotPassword: "/bff/forgot-password",
1072
- resetPassword: "/bff/reset-password"
1072
+ resetPassword: "/bff/reset-password",
1073
+ otpRequest: "/bff/otp/request",
1074
+ otpVerify: "/bff/otp/verify",
1075
+ pinLogin: "/bff/pin/login"
1073
1076
  };
1074
1077
  function isRecord(value) {
1075
1078
  return typeof value === "object" && value !== null;
@@ -1081,6 +1084,16 @@ function extractUser(data) {
1081
1084
  const envelope = data;
1082
1085
  return isRecord(envelope.user) ? envelope.user : null;
1083
1086
  }
1087
+ function toOtpRequestResult(data) {
1088
+ if (!isRecord(data)) {
1089
+ return { success: true, expiresIn: 0, code: null };
1090
+ }
1091
+ return {
1092
+ success: typeof data.success === "boolean" ? data.success : true,
1093
+ expiresIn: typeof data.expiresIn === "number" ? data.expiresIn : 0,
1094
+ code: typeof data.code === "string" ? data.code : null
1095
+ };
1096
+ }
1084
1097
  var BffAuthClient = class {
1085
1098
  constructor(options) {
1086
1099
  this.http = options.http;
@@ -1150,6 +1163,51 @@ var BffAuthClient = class {
1150
1163
  async resetPassword(request) {
1151
1164
  await this.postState(ENDPOINTS.resetPassword, request, "reset-password");
1152
1165
  }
1166
+ /**
1167
+ * `POST /bff/otp/request` — the BFF proxies to TenantService, which generates
1168
+ * a short-TTL code and emails it.
1169
+ *
1170
+ * The endpoint is anti-enumeration: a `200` is the normal path whether or not
1171
+ * the identifier is registered. This method therefore **returns** the relayed
1172
+ * `{ success, expiresIn, code }` body (so the UI can show the expiry) rather
1173
+ * than treating a 200 as opaque. It still throws on a non-2xx — a `501`
1174
+ * (OTP not enabled) or `502` (upstream down) is a real failure to surface.
1175
+ */
1176
+ async requestOtp(request) {
1177
+ const data = await this.postState(ENDPOINTS.otpRequest, request, "otp-request");
1178
+ return toOtpRequestResult(data);
1179
+ }
1180
+ /**
1181
+ * `POST /bff/otp/verify` — the BFF runs the OTP direct-grant against Keycloak
1182
+ * server-side, stores the tokens in its Redis vault, and sets the httpOnly
1183
+ * session cookie. Returns the sanitised user, exactly like `login`. Throws on
1184
+ * a non-2xx (e.g. `401` for a bad / expired code).
1185
+ */
1186
+ async verifyOtp(request) {
1187
+ const data = await this.postState(ENDPOINTS.otpVerify, request, "otp-verify");
1188
+ const user = extractUser(data);
1189
+ if (user === null) {
1190
+ throw new Error("otp-verify: BFF response missing user");
1191
+ }
1192
+ return user;
1193
+ }
1194
+ /**
1195
+ * `POST /bff/pin/login` — the BFF runs the event-scoped PIN direct-grant
1196
+ * against Keycloak server-side (the `(event, pin)` pair resolves to the
1197
+ * staff member's KC account + event-scoped role), stores the tokens in its
1198
+ * Redis vault, and sets the httpOnly session cookie. Returns the sanitised
1199
+ * user, exactly like `login` / `verifyOtp`. Throws on a non-2xx — `401` for
1200
+ * a bad / expired / locked-out PIN or an unknown event, `501` when PIN login
1201
+ * is not an enabled method for this BFF.
1202
+ */
1203
+ async pinLogin(request) {
1204
+ const data = await this.postState(ENDPOINTS.pinLogin, request, "pin-login");
1205
+ const user = extractUser(data);
1206
+ if (user === null) {
1207
+ throw new Error("pin-login: BFF response missing user");
1208
+ }
1209
+ return user;
1210
+ }
1153
1211
  /**
1154
1212
  * Shared POST for every state-changing `/bff/*` call: same-origin, cookie
1155
1213
  * included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.