@heyatom/bastion-client 0.7.0 → 0.7.2

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
@@ -4,7 +4,7 @@ Consumer SDK for [Bastion](https://github.com/heyatomdev/bastion), the HeyAtom i
4
4
  Replaces the `src/modules/bastion/` boilerplate every service used to copy by hand.
5
5
 
6
6
  - `@heyatom/bastion-client` — framework-free core: `JwksVerifier` (kid-indexed cache, unknown-kid refetch with cooldown, stale-on-error), `BastionHttp` (`POST /auth/client`, `POST /events`, any other route), `ServiceTokenProvider` (single-flight refresh ahead of `exp`), payload types.
7
- - `@heyatom/bastion-client` also carries Bastion's user-API contract: `BastionApiClient` (login, register, OAuth exchange, 2FA, single-flight refresh, logout, sessions, `/me`, profile, social accounts, email/password flows, export, delete), its types (`BastionMe`, `BastionTokenPair`, …) and `BastionErrorCode` (a stable code on every `BastionHttpError`: credentials, app context, refresh token, OAuth code, age gate, OAuth flow, unavailable, upstream). `refreshMemoryMs` tunes how long a refresh answer is remembered per token (default 60 s).
7
+ - `@heyatom/bastion-client` also carries Bastion's user-API contract: `BastionApiClient` (login, register, OAuth exchange, 2FA, single-flight refresh, logout, sessions, `/me`, profile, social accounts, email/password flows, export, delete), its types (`BastionMe`, `BastionTokenPair`, …) and `BastionErrorCode` (a stable code on every `BastionHttpError`: credentials, app context, refresh token, OAuth code, age gate, OAuth flow, unavailable, upstream). `refreshMemoryMs` tunes how long a refresh answer is remembered per token (default 60 s). Since 0.7.2 `completeAgeVerification(token, birthDate, { currentPassword?, twoFactorCode? })` sends the step-up Bastion requires (as `deleteSelf`), and `UserJwtPayload.ageVerificationRequired?: true` is set only while the account owes the app's age screen: a consumer API can refuse it from the token alone.
8
8
  - `@heyatom/bastion-client/webhooks` — `verifyWebhookSignature` (`X-Bastion-Signature-V2` with timestamp + delivery id, legacy fallback only with `allowLegacySignature: true` (off by default since 0.7.0), secret rotation on both sides), header constants, payload type.
9
9
  - `@heyatom/bastion-client/nest` — NestJS: `BastionModule`, `ServiceClientJwtGuard`, `BastionUserGuard`, `BastionAuditService`, `AuditInterceptor` (attributes to `req.adminUser`, any `AuditActor` shape: `sub` plus optional `tenantId`/`tenantSlug`/`role`/`appSlug`), `@Public()`, `@RequireScope()`, `@Audit()`, `@CurrentClient()`, `@CurrentAdminUser()`.
10
10
 
@@ -50,8 +50,13 @@ export declare class BastionApiClient {
50
50
  register(input: BastionRegisterInput, ctx?: BastionClientContext): Promise<{
51
51
  message: string;
52
52
  }>;
53
- /** Trades the one-time OAuth code for tokens. */
54
- exchange(code: string, ctx?: BastionClientContext): Promise<BastionLoginResult>;
53
+ /**
54
+ * Trades the one-time OAuth code for tokens. `state` is the value the app put
55
+ * on the initiate URL, read back from its own cookie/session (never from the
56
+ * callback query): Bastion refuses a code minted under a different one, which
57
+ * is what stops an attacker's code from being redeemed in a victim's browser.
58
+ */
59
+ exchange(code: string, ctx?: BastionClientContext, state?: string): Promise<BastionLoginResult>;
55
60
  completeTwoFactor(twoFactorToken: string, code: string, ctx?: BastionClientContext): Promise<BastionTokenPair>;
56
61
  refresh(refreshToken: string, ctx?: BastionClientContext): Promise<BastionTokenPair>;
57
62
  /** Revokes the token and any successor issued through `refresh` within the memory window. Never throws. */
@@ -64,7 +69,11 @@ export declare class BastionApiClient {
64
69
  me(accessToken: string): Promise<BastionMe>;
65
70
  /** Answers the updated columns only (`BastionProfile`), not the full `BastionMe`. */
66
71
  updateProfile(accessToken: string, update: BastionProfileUpdate): Promise<BastionProfile>;
67
- completeAgeVerification(accessToken: string, birthDate: string): Promise<BastionAgeVerificationResult>;
72
+ /** Step-up as `deleteSelf`: `currentPassword` when the account has one, `twoFactorCode` when 2FA is on. */
73
+ completeAgeVerification(accessToken: string, birthDate: string, stepUp?: {
74
+ currentPassword?: string;
75
+ twoFactorCode?: string;
76
+ }): Promise<BastionAgeVerificationResult>;
68
77
  /** Cached 5 min; a failed refetch serves the cached answer. */
69
78
  getAuthMethods(): Promise<BastionAuthMethods>;
70
79
  twoFactorStatus(accessToken: string): Promise<BastionTwoFactorStatus>;
@@ -61,9 +61,14 @@ class BastionApiClient {
61
61
  register(input, ctx) {
62
62
  return this.call('POST', '/auth/register', { ...input, ...this.appContext }, { headers: BastionApiClient.clientHeaders(ctx) });
63
63
  }
64
- /** Trades the one-time OAuth code for tokens. */
65
- exchange(code, ctx) {
66
- return this.call('POST', '/auth/exchange', { code, appSlug: this.appSlug }, { headers: BastionApiClient.clientHeaders(ctx) });
64
+ /**
65
+ * Trades the one-time OAuth code for tokens. `state` is the value the app put
66
+ * on the initiate URL, read back from its own cookie/session (never from the
67
+ * callback query): Bastion refuses a code minted under a different one, which
68
+ * is what stops an attacker's code from being redeemed in a victim's browser.
69
+ */
70
+ exchange(code, ctx, state) {
71
+ return this.call('POST', '/auth/exchange', { code, appSlug: this.appSlug, ...(state !== undefined ? { state } : {}) }, { headers: BastionApiClient.clientHeaders(ctx) });
67
72
  }
68
73
  completeTwoFactor(twoFactorToken, code, ctx) {
69
74
  return this.call('POST', '/auth/2fa/authenticate', { twoFactorToken, code }, { headers: BastionApiClient.clientHeaders(ctx) });
@@ -113,8 +118,9 @@ class BastionApiClient {
113
118
  updateProfile(accessToken, update) {
114
119
  return this.call('PATCH', '/auth/me', update, { token: accessToken });
115
120
  }
116
- completeAgeVerification(accessToken, birthDate) {
117
- return this.call('POST', '/auth/age-verification', { birthDate }, { token: accessToken });
121
+ /** Step-up as `deleteSelf`: `currentPassword` when the account has one, `twoFactorCode` when 2FA is on. */
122
+ completeAgeVerification(accessToken, birthDate, stepUp = {}) {
123
+ return this.call('POST', '/auth/age-verification', { birthDate, ...stepUp }, { token: accessToken });
118
124
  }
119
125
  /** Cached 5 min; a failed refetch serves the cached answer. */
120
126
  async getAuthMethods() {
@@ -20,6 +20,12 @@ export interface UserJwtPayload extends CommonClaims {
20
20
  role: string;
21
21
  permissions: string[];
22
22
  sessionId?: string;
23
+ /**
24
+ * Present (always `true`) only while the app has a minimum age and the account hasn't cleared
25
+ * it — an OAuth signup that hasn't answered `completeAgeVerification`. Absent on every other
26
+ * token. Recomputed at each issuance: refresh after the screen.
27
+ */
28
+ ageVerificationRequired?: true;
23
29
  }
24
30
  /** A machine token from `POST /auth/client`. Discriminated by `type`. */
25
31
  export interface ServiceClientJwtPayload extends CommonClaims {
@@ -50,8 +50,13 @@ export declare class BastionApiClient {
50
50
  register(input: BastionRegisterInput, ctx?: BastionClientContext): Promise<{
51
51
  message: string;
52
52
  }>;
53
- /** Trades the one-time OAuth code for tokens. */
54
- exchange(code: string, ctx?: BastionClientContext): Promise<BastionLoginResult>;
53
+ /**
54
+ * Trades the one-time OAuth code for tokens. `state` is the value the app put
55
+ * on the initiate URL, read back from its own cookie/session (never from the
56
+ * callback query): Bastion refuses a code minted under a different one, which
57
+ * is what stops an attacker's code from being redeemed in a victim's browser.
58
+ */
59
+ exchange(code: string, ctx?: BastionClientContext, state?: string): Promise<BastionLoginResult>;
55
60
  completeTwoFactor(twoFactorToken: string, code: string, ctx?: BastionClientContext): Promise<BastionTokenPair>;
56
61
  refresh(refreshToken: string, ctx?: BastionClientContext): Promise<BastionTokenPair>;
57
62
  /** Revokes the token and any successor issued through `refresh` within the memory window. Never throws. */
@@ -64,7 +69,11 @@ export declare class BastionApiClient {
64
69
  me(accessToken: string): Promise<BastionMe>;
65
70
  /** Answers the updated columns only (`BastionProfile`), not the full `BastionMe`. */
66
71
  updateProfile(accessToken: string, update: BastionProfileUpdate): Promise<BastionProfile>;
67
- completeAgeVerification(accessToken: string, birthDate: string): Promise<BastionAgeVerificationResult>;
72
+ /** Step-up as `deleteSelf`: `currentPassword` when the account has one, `twoFactorCode` when 2FA is on. */
73
+ completeAgeVerification(accessToken: string, birthDate: string, stepUp?: {
74
+ currentPassword?: string;
75
+ twoFactorCode?: string;
76
+ }): Promise<BastionAgeVerificationResult>;
68
77
  /** Cached 5 min; a failed refetch serves the cached answer. */
69
78
  getAuthMethods(): Promise<BastionAuthMethods>;
70
79
  twoFactorStatus(accessToken: string): Promise<BastionTwoFactorStatus>;
@@ -58,9 +58,14 @@ export class BastionApiClient {
58
58
  register(input, ctx) {
59
59
  return this.call('POST', '/auth/register', { ...input, ...this.appContext }, { headers: BastionApiClient.clientHeaders(ctx) });
60
60
  }
61
- /** Trades the one-time OAuth code for tokens. */
62
- exchange(code, ctx) {
63
- return this.call('POST', '/auth/exchange', { code, appSlug: this.appSlug }, { headers: BastionApiClient.clientHeaders(ctx) });
61
+ /**
62
+ * Trades the one-time OAuth code for tokens. `state` is the value the app put
63
+ * on the initiate URL, read back from its own cookie/session (never from the
64
+ * callback query): Bastion refuses a code minted under a different one, which
65
+ * is what stops an attacker's code from being redeemed in a victim's browser.
66
+ */
67
+ exchange(code, ctx, state) {
68
+ return this.call('POST', '/auth/exchange', { code, appSlug: this.appSlug, ...(state !== undefined ? { state } : {}) }, { headers: BastionApiClient.clientHeaders(ctx) });
64
69
  }
65
70
  completeTwoFactor(twoFactorToken, code, ctx) {
66
71
  return this.call('POST', '/auth/2fa/authenticate', { twoFactorToken, code }, { headers: BastionApiClient.clientHeaders(ctx) });
@@ -110,8 +115,9 @@ export class BastionApiClient {
110
115
  updateProfile(accessToken, update) {
111
116
  return this.call('PATCH', '/auth/me', update, { token: accessToken });
112
117
  }
113
- completeAgeVerification(accessToken, birthDate) {
114
- return this.call('POST', '/auth/age-verification', { birthDate }, { token: accessToken });
118
+ /** Step-up as `deleteSelf`: `currentPassword` when the account has one, `twoFactorCode` when 2FA is on. */
119
+ completeAgeVerification(accessToken, birthDate, stepUp = {}) {
120
+ return this.call('POST', '/auth/age-verification', { birthDate, ...stepUp }, { token: accessToken });
115
121
  }
116
122
  /** Cached 5 min; a failed refetch serves the cached answer. */
117
123
  async getAuthMethods() {
@@ -20,6 +20,12 @@ export interface UserJwtPayload extends CommonClaims {
20
20
  role: string;
21
21
  permissions: string[];
22
22
  sessionId?: string;
23
+ /**
24
+ * Present (always `true`) only while the app has a minimum age and the account hasn't cleared
25
+ * it — an OAuth signup that hasn't answered `completeAgeVerification`. Absent on every other
26
+ * token. Recomputed at each issuance: refresh after the screen.
27
+ */
28
+ ageVerificationRequired?: true;
23
29
  }
24
30
  /** A machine token from `POST /auth/client`. Discriminated by `type`. */
25
31
  export interface ServiceClientJwtPayload extends CommonClaims {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyatom/bastion-client",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Consumer SDK for the Bastion identity provider: JWKS verification, service-client auth, audit writes, NestJS guards",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Tombolato <hey@heyatom.dev> (https://heyatom.dev)",