@heyatom/bastion-client 0.3.0 → 0.3.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
@@ -6,7 +6,7 @@ Replaces the `src/modules/bastion/` boilerplate every service used to copy by ha
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
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`).
8
8
  - `@heyatom/bastion-client/webhooks` — `verifyWebhookSignature` (`X-Bastion-Signature-V2` with timestamp + delivery id, legacy fallback, secret rotation on both sides), header constants, payload type.
9
- - `@heyatom/bastion-client/nest` — NestJS: `BastionModule`, `ServiceClientJwtGuard`, `BastionUserGuard`, `BastionAuditService`, `AuditInterceptor`, `@Public()`, `@RequireScope()`, `@Audit()`, `@CurrentClient()`, `@CurrentAdminUser()`.
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
 
11
11
  Ships CJS and ESM. `jose` v6 is ESM-only, so the CJS build relies on Node's `require(esm)` — Node ≥ 20.19 / 22.12. Peer deps: `jose` (always), `@nestjs/common`, `@nestjs/core`, `rxjs`, `reflect-metadata` (for `/nest`).
12
12
 
@@ -1,7 +1,17 @@
1
1
  import { OnModuleInit } from '@nestjs/common';
2
- import type { UserJwtPayload } from '../core/types.js';
3
2
  import { BastionService } from './bastion.service.js';
4
3
  import { type BastionModuleOptions } from './options.js';
4
+ /**
5
+ * The fields `writeAsAdmin` reads from an actor. A `UserJwtPayload` fits, and
6
+ * so does a consumer's own admin payload that carries extra fields.
7
+ */
8
+ export interface AuditActor {
9
+ sub: string;
10
+ tenantId?: string;
11
+ tenantSlug?: string;
12
+ role?: string;
13
+ appSlug?: string;
14
+ }
5
15
  /**
6
16
  * Fire-and-forget audit writes to `POST /events` under this service's own
7
17
  * machine token. Never throws: a failed write is logged, the request that
@@ -12,6 +22,7 @@ export declare class BastionAuditService implements OnModuleInit {
12
22
  private readonly logger;
13
23
  private readonly tokens;
14
24
  private tenantMismatchWarned;
25
+ private noApiKeyWarned;
15
26
  constructor(bastion: BastionService, options: BastionModuleOptions);
16
27
  onModuleInit(): Promise<void>;
17
28
  /** Tenant of this service's machine token, once known. */
@@ -25,5 +36,5 @@ export declare class BastionAuditService implements OnModuleInit {
25
36
  * belongs to the same tenant as this service's token — Bastion rejects a
26
37
  * foreign userId silently — otherwise the actor goes into `metadata`.
27
38
  */
28
- writeAsAdmin(event: string, actor: UserJwtPayload | undefined, metadata?: Record<string, unknown>): Promise<void>;
39
+ writeAsAdmin(event: string, actor: AuditActor | undefined, metadata?: Record<string, unknown>): Promise<void>;
29
40
  }
@@ -29,6 +29,7 @@ let BastionAuditService = BastionAuditService_1 = class BastionAuditService {
29
29
  logger = new common_1.Logger(BastionAuditService_1.name);
30
30
  tokens;
31
31
  tenantMismatchWarned = false;
32
+ noApiKeyWarned = false;
32
33
  constructor(bastion, options) {
33
34
  this.bastion = bastion;
34
35
  this.tokens = options.apiKey
@@ -50,7 +51,12 @@ let BastionAuditService = BastionAuditService_1 = class BastionAuditService {
50
51
  }
51
52
  async write(event, opts = {}) {
52
53
  if (!this.tokens) {
53
- this.logger.warn(`audit write skipped, no apiKey configured event=${event}`);
54
+ // Once, not per event: a consumer that verifies tokens but never writes
55
+ // audit (no apiKey on purpose) would otherwise log every skipped write.
56
+ if (!this.noApiKeyWarned) {
57
+ this.noApiKeyWarned = true;
58
+ this.logger.warn(`audit writes skipped, no apiKey configured (first event=${event})`);
59
+ }
54
60
  return;
55
61
  }
56
62
  let token;
@@ -5,7 +5,7 @@ export { BastionJwksService } from './bastion-jwks.service.js';
5
5
  export { BastionService } from './bastion.service.js';
6
6
  export { BastionApiClientProvider } from './bastion-api-client.provider.js';
7
7
  export { BastionHttpException, NestBastionHttp } from './bastion-http-exception.js';
8
- export { BastionAuditService } from './bastion-audit.service.js';
8
+ export { BastionAuditService, type AuditActor } from './bastion-audit.service.js';
9
9
  export { ServiceClientJwtGuard } from './guards/service-client-jwt.guard.js';
10
10
  export { BastionUserGuard } from './guards/bastion-user.guard.js';
11
11
  export { AuditInterceptor } from './interceptors/audit.interceptor.js';
@@ -1,7 +1,17 @@
1
1
  import { OnModuleInit } from '@nestjs/common';
2
- import type { UserJwtPayload } from '../core/types.js';
3
2
  import { BastionService } from './bastion.service.js';
4
3
  import { type BastionModuleOptions } from './options.js';
4
+ /**
5
+ * The fields `writeAsAdmin` reads from an actor. A `UserJwtPayload` fits, and
6
+ * so does a consumer's own admin payload that carries extra fields.
7
+ */
8
+ export interface AuditActor {
9
+ sub: string;
10
+ tenantId?: string;
11
+ tenantSlug?: string;
12
+ role?: string;
13
+ appSlug?: string;
14
+ }
5
15
  /**
6
16
  * Fire-and-forget audit writes to `POST /events` under this service's own
7
17
  * machine token. Never throws: a failed write is logged, the request that
@@ -12,6 +22,7 @@ export declare class BastionAuditService implements OnModuleInit {
12
22
  private readonly logger;
13
23
  private readonly tokens;
14
24
  private tenantMismatchWarned;
25
+ private noApiKeyWarned;
15
26
  constructor(bastion: BastionService, options: BastionModuleOptions);
16
27
  onModuleInit(): Promise<void>;
17
28
  /** Tenant of this service's machine token, once known. */
@@ -25,5 +36,5 @@ export declare class BastionAuditService implements OnModuleInit {
25
36
  * belongs to the same tenant as this service's token — Bastion rejects a
26
37
  * foreign userId silently — otherwise the actor goes into `metadata`.
27
38
  */
28
- writeAsAdmin(event: string, actor: UserJwtPayload | undefined, metadata?: Record<string, unknown>): Promise<void>;
39
+ writeAsAdmin(event: string, actor: AuditActor | undefined, metadata?: Record<string, unknown>): Promise<void>;
29
40
  }
@@ -26,6 +26,7 @@ let BastionAuditService = BastionAuditService_1 = class BastionAuditService {
26
26
  logger = new Logger(BastionAuditService_1.name);
27
27
  tokens;
28
28
  tenantMismatchWarned = false;
29
+ noApiKeyWarned = false;
29
30
  constructor(bastion, options) {
30
31
  this.bastion = bastion;
31
32
  this.tokens = options.apiKey
@@ -47,7 +48,12 @@ let BastionAuditService = BastionAuditService_1 = class BastionAuditService {
47
48
  }
48
49
  async write(event, opts = {}) {
49
50
  if (!this.tokens) {
50
- this.logger.warn(`audit write skipped, no apiKey configured event=${event}`);
51
+ // Once, not per event: a consumer that verifies tokens but never writes
52
+ // audit (no apiKey on purpose) would otherwise log every skipped write.
53
+ if (!this.noApiKeyWarned) {
54
+ this.noApiKeyWarned = true;
55
+ this.logger.warn(`audit writes skipped, no apiKey configured (first event=${event})`);
56
+ }
51
57
  return;
52
58
  }
53
59
  let token;
@@ -5,7 +5,7 @@ export { BastionJwksService } from './bastion-jwks.service.js';
5
5
  export { BastionService } from './bastion.service.js';
6
6
  export { BastionApiClientProvider } from './bastion-api-client.provider.js';
7
7
  export { BastionHttpException, NestBastionHttp } from './bastion-http-exception.js';
8
- export { BastionAuditService } from './bastion-audit.service.js';
8
+ export { BastionAuditService, type AuditActor } from './bastion-audit.service.js';
9
9
  export { ServiceClientJwtGuard } from './guards/service-client-jwt.guard.js';
10
10
  export { BastionUserGuard } from './guards/bastion-user.guard.js';
11
11
  export { AuditInterceptor } from './interceptors/audit.interceptor.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyatom/bastion-client",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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)",