@atlasauth/backend 0.4.0 → 0.6.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.
Files changed (119) hide show
  1. package/README.md +187 -0
  2. package/dist/client/activity.d.ts +104 -0
  3. package/dist/client/activity.js +29 -0
  4. package/dist/client/activity.js.map +1 -0
  5. package/dist/client/audit-events.d.ts +103 -0
  6. package/dist/client/audit-events.js +43 -0
  7. package/dist/client/audit-events.js.map +1 -0
  8. package/dist/client/billing-plans.d.ts +43 -0
  9. package/dist/client/billing-plans.js +26 -0
  10. package/dist/client/billing-plans.js.map +1 -1
  11. package/dist/client/connectors.d.ts +174 -0
  12. package/dist/client/connectors.js +54 -0
  13. package/dist/client/connectors.js.map +1 -0
  14. package/dist/client/data-subject-requests.d.ts +21 -0
  15. package/dist/client/data-subject-requests.js +15 -0
  16. package/dist/client/data-subject-requests.js.map +1 -1
  17. package/dist/client/design.d.ts +295 -0
  18. package/dist/client/design.js +76 -0
  19. package/dist/client/design.js.map +1 -0
  20. package/dist/client/diagnostics.d.ts +53 -0
  21. package/dist/client/diagnostics.js +24 -0
  22. package/dist/client/diagnostics.js.map +1 -0
  23. package/dist/client/email-addresses.d.ts +22 -0
  24. package/dist/client/email-addresses.js +17 -0
  25. package/dist/client/email-addresses.js.map +1 -0
  26. package/dist/client/entitlement-overrides.d.ts +50 -0
  27. package/dist/client/entitlement-overrides.js +20 -0
  28. package/dist/client/entitlement-overrides.js.map +1 -0
  29. package/dist/client/error.d.ts +28 -0
  30. package/dist/client/error.js +27 -1
  31. package/dist/client/error.js.map +1 -1
  32. package/dist/client/index.d.ts +458 -16
  33. package/dist/client/index.js +76 -2
  34. package/dist/client/index.js.map +1 -1
  35. package/dist/client/instance.d.ts +54 -0
  36. package/dist/client/instance.js +18 -0
  37. package/dist/client/instance.js.map +1 -1
  38. package/dist/client/invitations.d.ts +14 -0
  39. package/dist/client/invitations.js +9 -0
  40. package/dist/client/invitations.js.map +1 -1
  41. package/dist/client/legacy-auth.d.ts +45 -0
  42. package/dist/client/legacy-auth.js +18 -0
  43. package/dist/client/legacy-auth.js.map +1 -0
  44. package/dist/client/mcp.d.ts +86 -0
  45. package/dist/client/mcp.js +47 -0
  46. package/dist/client/mcp.js.map +1 -0
  47. package/dist/client/notifications.d.ts +92 -0
  48. package/dist/client/notifications.js +55 -0
  49. package/dist/client/notifications.js.map +1 -0
  50. package/dist/client/oauth-clients.d.ts +46 -2
  51. package/dist/client/oauth-clients.js +4 -0
  52. package/dist/client/oauth-clients.js.map +1 -1
  53. package/dist/client/oauth-providers.d.ts +67 -7
  54. package/dist/client/oauth-providers.js +7 -2
  55. package/dist/client/oauth-providers.js.map +1 -1
  56. package/dist/client/openfga.d.ts +137 -0
  57. package/dist/client/openfga.js +72 -0
  58. package/dist/client/openfga.js.map +1 -0
  59. package/dist/client/org-ldap-connections.d.ts +75 -0
  60. package/dist/client/org-ldap-connections.js +36 -0
  61. package/dist/client/org-ldap-connections.js.map +1 -0
  62. package/dist/client/organization-settings-schema.d.ts +24 -0
  63. package/dist/client/organization-settings-schema.js +30 -0
  64. package/dist/client/organization-settings-schema.js.map +1 -0
  65. package/dist/client/organizations.d.ts +205 -4
  66. package/dist/client/organizations.js +109 -0
  67. package/dist/client/organizations.js.map +1 -1
  68. package/dist/client/pca.d.ts +59 -0
  69. package/dist/client/pca.js +40 -0
  70. package/dist/client/pca.js.map +1 -0
  71. package/dist/client/platform.d.ts +328 -0
  72. package/dist/client/platform.js +187 -0
  73. package/dist/client/platform.js.map +1 -0
  74. package/dist/client/redirect-urls.d.ts +23 -0
  75. package/dist/client/redirect-urls.js +22 -0
  76. package/dist/client/redirect-urls.js.map +1 -0
  77. package/dist/client/request.d.ts +45 -0
  78. package/dist/client/request.js +35 -2
  79. package/dist/client/request.js.map +1 -1
  80. package/dist/client/session-exchange-apps.d.ts +43 -0
  81. package/dist/client/session-exchange-apps.js +25 -0
  82. package/dist/client/session-exchange-apps.js.map +1 -0
  83. package/dist/client/sessions.d.ts +12 -0
  84. package/dist/client/sessions.js +10 -0
  85. package/dist/client/sessions.js.map +1 -1
  86. package/dist/client/sign-in-tokens.d.ts +13 -1
  87. package/dist/client/sign-in-tokens.js +11 -0
  88. package/dist/client/sign-in-tokens.js.map +1 -1
  89. package/dist/client/test-mode.d.ts +44 -0
  90. package/dist/client/test-mode.js +26 -0
  91. package/dist/client/test-mode.js.map +1 -0
  92. package/dist/client/tickets.d.ts +49 -0
  93. package/dist/client/tickets.js +19 -0
  94. package/dist/client/tickets.js.map +1 -0
  95. package/dist/client/users.d.ts +346 -0
  96. package/dist/client/users.js +228 -0
  97. package/dist/client/users.js.map +1 -1
  98. package/dist/client/waitlist.d.ts +21 -0
  99. package/dist/client/waitlist.js +16 -0
  100. package/dist/client/waitlist.js.map +1 -1
  101. package/dist/client/webhooks.d.ts +60 -6
  102. package/dist/client/webhooks.js +36 -2
  103. package/dist/client/webhooks.js.map +1 -1
  104. package/dist/discovery.d.ts +49 -0
  105. package/dist/discovery.js +143 -0
  106. package/dist/discovery.js.map +1 -0
  107. package/dist/index.d.ts +3 -0
  108. package/dist/index.js +15 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/jwks-cache.d.ts +8 -0
  111. package/dist/jwks-cache.js +20 -3
  112. package/dist/jwks-cache.js.map +1 -1
  113. package/dist/logout-token.d.ts +97 -0
  114. package/dist/logout-token.js +182 -0
  115. package/dist/logout-token.js.map +1 -0
  116. package/dist/verify.d.ts +26 -3
  117. package/dist/verify.js +40 -5
  118. package/dist/verify.js.map +1 -1
  119. package/package.json +1 -1
@@ -5,6 +5,8 @@ export interface WebhookEndpoint {
5
5
  url: string;
6
6
  enabled_events: string[];
7
7
  active: boolean;
8
+ /** Start of the current failure streak (epoch ms), or null when the last delivery succeeded. */
9
+ failing_since?: number | null;
8
10
  disabled_at: number | null;
9
11
  created_at: number;
10
12
  }
@@ -16,7 +18,12 @@ export interface WebhookDelivery {
16
18
  object: 'webhook_delivery';
17
19
  id: string;
18
20
  event_id: string;
21
+ /** The event type, or null once the event has passed its retention. */
22
+ event_type?: string | null;
19
23
  attempt_number: number;
24
+ max_attempts?: number;
25
+ /** The payload that was sent (null once the event has passed its retention). */
26
+ payload?: unknown;
20
27
  status: string;
21
28
  http_status: number | null;
22
29
  response_snippet: string | null;
@@ -29,12 +36,56 @@ export interface CreateWebhookEndpointBody {
29
36
  /** Each entry is `"*"` or a known event name. */
30
37
  enabled_events?: string[];
31
38
  }
39
+ /** Change an endpoint in place. The signing secret is kept. */
40
+ export interface UpdateWebhookEndpointBody {
41
+ url?: string;
42
+ enabled_events?: string[];
43
+ active?: boolean;
44
+ }
45
+ /** The endpoint list plus the delivery settings that apply to every endpoint. */
46
+ export interface WebhookEndpointList {
47
+ object?: 'list';
48
+ data: WebhookEndpoint[];
49
+ has_more: boolean;
50
+ next_cursor?: string | null;
51
+ /** Every event type an endpoint can subscribe to. */
52
+ available_events?: string[];
53
+ /** The delay before each retry, in milliseconds. */
54
+ retry_schedule_ms?: number[];
55
+ /** Delivery attempts per event before it is marked failed. */
56
+ max_attempts?: number;
57
+ }
58
+ export interface WebhookSigningSecret {
59
+ object: 'webhook_secret';
60
+ /** The `whsec_...` signing secret. */
61
+ secret: string;
62
+ warning?: string;
63
+ }
64
+ /** A queued delivery (from `test` or `redeliver`). */
65
+ export interface WebhookDeliveryQueued {
66
+ object: 'webhook_delivery';
67
+ id: string;
68
+ event_id: string;
69
+ status: string;
70
+ note: string;
71
+ }
72
+ export interface ListWebhookDeliveriesParams {
73
+ /** How many deliveries to return (1–100, default 50). */
74
+ limit?: number;
75
+ }
32
76
  export declare function webhooksResource(request: RequestFn): {
33
77
  endpoints: {
34
- list(): Promise<{
35
- data: WebhookEndpoint[];
36
- has_more: false;
37
- }>;
78
+ list(): Promise<WebhookEndpointList>;
79
+ /** Update url, enabled_events or active. The signing secret is kept. */
80
+ update(id: string, body: UpdateWebhookEndpointBody): Promise<WebhookEndpoint>;
81
+ /** Show the current signing secret again. Audited. */
82
+ revealSecret(id: string): Promise<WebhookSigningSecret>;
83
+ /** Rotate the signing secret; the new one is revealed exactly once. */
84
+ rotateSecret(id: string): Promise<WebhookSigningSecret>;
85
+ /** Send a synthetic `webhook.test` event to this endpoint only. */
86
+ test(id: string): Promise<WebhookDeliveryQueued>;
87
+ /** Redeliver a past event as a fresh attempt (409 once past its retention). */
88
+ redeliver(id: string, deliveryId: string): Promise<WebhookDeliveryQueued>;
38
89
  create(body: CreateWebhookEndpointBody, idempotencyKey?: string): Promise<WebhookEndpointWithSecret>;
39
90
  delete(id: string): Promise<{
40
91
  object: "webhook_endpoint";
@@ -43,8 +94,11 @@ export declare function webhooksResource(request: RequestFn): {
43
94
  }>;
44
95
  };
45
96
  /** Delivery log for an endpoint. */
46
- deliveries(id: string): Promise<{
97
+ deliveries(id: string, params?: ListWebhookDeliveriesParams): Promise<{
47
98
  data: WebhookDelivery[];
48
- has_more: false;
99
+ has_more: boolean;
100
+ next_cursor?: string | null;
49
101
  }>;
102
+ /** Redeliver a past event (same as `endpoints.redeliver`). */
103
+ redeliver(endpointId: string, deliveryId: string): Promise<WebhookDeliveryQueued>;
50
104
  };
@@ -8,6 +8,29 @@ function webhooksResource(request) {
8
8
  list() {
9
9
  return request({ method: 'GET', path: '/v1/webhook_endpoints' });
10
10
  },
11
+ /** Update url, enabled_events or active. The signing secret is kept. */
12
+ update(id, body) {
13
+ return request({ method: 'PATCH', path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}`, body });
14
+ },
15
+ /** Show the current signing secret again. Audited. */
16
+ revealSecret(id) {
17
+ return request({ method: 'POST', path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/reveal_secret` });
18
+ },
19
+ /** Rotate the signing secret; the new one is revealed exactly once. */
20
+ rotateSecret(id) {
21
+ return request({ method: 'POST', path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/rotate_secret` });
22
+ },
23
+ /** Send a synthetic `webhook.test` event to this endpoint only. */
24
+ test(id) {
25
+ return request({ method: 'POST', path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/test` });
26
+ },
27
+ /** Redeliver a past event as a fresh attempt (409 once past its retention). */
28
+ redeliver(id, deliveryId) {
29
+ return request({
30
+ method: 'POST',
31
+ path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/deliveries/${(0, request_1.enc)(deliveryId)}/redeliver`,
32
+ });
33
+ },
11
34
  create(body, idempotencyKey) {
12
35
  return request({ method: 'POST', path: '/v1/webhook_endpoints', body, idempotencyKey });
13
36
  },
@@ -16,8 +39,19 @@ function webhooksResource(request) {
16
39
  },
17
40
  },
18
41
  /** Delivery log for an endpoint. */
19
- deliveries(id) {
20
- return request({ method: 'GET', path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/deliveries` });
42
+ deliveries(id, params = {}) {
43
+ return request({
44
+ method: 'GET',
45
+ path: `/v1/webhook_endpoints/${(0, request_1.enc)(id)}/deliveries`,
46
+ query: { ...params },
47
+ });
48
+ },
49
+ /** Redeliver a past event (same as `endpoints.redeliver`). */
50
+ redeliver(endpointId, deliveryId) {
51
+ return request({
52
+ method: 'POST',
53
+ path: `/v1/webhook_endpoints/${(0, request_1.enc)(endpointId)}/deliveries/${(0, request_1.enc)(deliveryId)}/redeliver`,
54
+ });
21
55
  },
22
56
  };
23
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/client/webhooks.ts"],"names":[],"mappings":";;AAoCA,4CAuBC;AA3DD,uCAAgD;AAoChD,SAAgB,gBAAgB,CAAC,OAAkB;IACjD,OAAO;QACL,SAAS,EAAE;YACT,IAAI;gBACF,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,CACJ,IAA+B,EAC/B,cAAuB;gBAEvB,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;YAC1F,CAAC;YACD,MAAM,CACJ,EAAU;gBAEV,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACjF,CAAC;SACF;QACD,oCAAoC;QACpC,UAAU,CAAC,EAAU;YACnB,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;QACzF,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/client/webhooks.ts"],"names":[],"mappings":";;AAqFA,4CA4DC;AAjJD,uCAAgD;AAqFhD,SAAgB,gBAAgB,CAAC,OAAkB;IACjD,OAAO;QACL,SAAS,EAAE;YACT,IAAI;gBACF,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC;YACnE,CAAC;YACD,wEAAwE;YACxE,MAAM,CAAC,EAAU,EAAE,IAA+B;gBAChD,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YACtF,CAAC;YACD,sDAAsD;YACtD,YAAY,CAAC,EAAU;gBACrB,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,uEAAuE;YACvE,YAAY,CAAC,EAAU;gBACrB,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,mEAAmE;YACnE,IAAI,CAAC,EAAU;gBACb,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YACpF,CAAC;YACD,+EAA+E;YAC/E,SAAS,CAAC,EAAU,EAAE,UAAkB;gBACtC,OAAO,OAAO,CAAC;oBACb,MAAM,EAAE,MAAM;oBACd,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,eAAe,IAAA,aAAG,EAAC,UAAU,CAAC,YAAY;iBACjF,CAAC,CAAC;YACL,CAAC;YACD,MAAM,CACJ,IAA+B,EAC/B,cAAuB;gBAEvB,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;YAC1F,CAAC;YACD,MAAM,CACJ,EAAU;gBAEV,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACjF,CAAC;SACF;QACD,oCAAoC;QACpC,UAAU,CACR,EAAU,EACV,SAAsC,EAAE;YAExC,OAAO,OAAO,CAAC;gBACb,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,EAAE,CAAC,aAAa;gBACnD,KAAK,EAAE,EAAE,GAAG,MAAM,EAAE;aACrB,CAAC,CAAC;QACL,CAAC;QACD,8DAA8D;QAC9D,SAAS,CAAC,UAAkB,EAAE,UAAkB;YAC9C,OAAO,OAAO,CAAC;gBACb,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,yBAAyB,IAAA,aAAG,EAAC,UAAU,CAAC,eAAe,IAAA,aAAG,EAAC,UAAU,CAAC,YAAY;aACzF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * OIDC discovery of an instance's JWKS URL, so `new AtlasBackend({ issuer })`
3
+ * works without a `jwksUrl`.
4
+ *
5
+ * Where the document lives:
6
+ * - an instance with its own frontend API host (every instance created from
7
+ * the dashboard): `${issuer}/.well-known/openid-configuration`;
8
+ * - an instance on the SHARED host: its tokens carry the shared issuer, and the
9
+ * shared host's root discovery document describes a different instance's key
10
+ * set. Such an instance must name itself (`instanceId`, or a `publishableKey`
11
+ * it is derived from) so discovery uses the per-instance document at
12
+ * `${issuer}/instances/${instanceId}/.well-known/openid-configuration`.
13
+ *
14
+ * Caching mirrors the JWKS cache: the discovered URL is reused for the JWKS TTL
15
+ * (an hour), a failed refresh keeps serving the last good URL, and a failing
16
+ * endpoint is retried at most once a minute — so a broken or hostile discovery
17
+ * endpoint cannot turn verification traffic into outbound request floods.
18
+ */
19
+ export declare class DiscoveryError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ export interface JwksDiscoveryDeps {
23
+ /** The `iss` tokens carry. The discovery document's `issuer` must equal it. */
24
+ issuer: string;
25
+ /** Per-instance discovery for shared-host instances. */
26
+ instanceId?: string;
27
+ fetchImpl?: typeof fetch;
28
+ now?: () => number;
29
+ ttlMs?: number;
30
+ refetchIntervalMs?: number;
31
+ }
32
+ /** The instance id inside a publishable key (`pk_live_ins_…` / `pk_test_ins_…`), if it has one. */
33
+ export declare function instanceIdFromPublishableKey(publishableKey: string): string | undefined;
34
+ /** The discovery document URL for an issuer (and optional instance id). */
35
+ export declare function discoveryUrlFor(issuer: string, instanceId?: string): string;
36
+ export declare class JwksDiscovery {
37
+ private readonly deps;
38
+ readonly url: string;
39
+ private cachedUri;
40
+ private fetchedAt;
41
+ private lastAttemptAt;
42
+ private lastError;
43
+ private inflight;
44
+ constructor(deps: JwksDiscoveryDeps);
45
+ private get now();
46
+ /** The JWKS URL, from cache when fresh. Throws {@link DiscoveryError} when none can be had. */
47
+ jwksUri(): Promise<string>;
48
+ private fetchUri;
49
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JwksDiscovery = exports.DiscoveryError = void 0;
4
+ exports.instanceIdFromPublishableKey = instanceIdFromPublishableKey;
5
+ exports.discoveryUrlFor = discoveryUrlFor;
6
+ const jwks_cache_1 = require("./jwks-cache");
7
+ /**
8
+ * OIDC discovery of an instance's JWKS URL, so `new AtlasBackend({ issuer })`
9
+ * works without a `jwksUrl`.
10
+ *
11
+ * Where the document lives:
12
+ * - an instance with its own frontend API host (every instance created from
13
+ * the dashboard): `${issuer}/.well-known/openid-configuration`;
14
+ * - an instance on the SHARED host: its tokens carry the shared issuer, and the
15
+ * shared host's root discovery document describes a different instance's key
16
+ * set. Such an instance must name itself (`instanceId`, or a `publishableKey`
17
+ * it is derived from) so discovery uses the per-instance document at
18
+ * `${issuer}/instances/${instanceId}/.well-known/openid-configuration`.
19
+ *
20
+ * Caching mirrors the JWKS cache: the discovered URL is reused for the JWKS TTL
21
+ * (an hour), a failed refresh keeps serving the last good URL, and a failing
22
+ * endpoint is retried at most once a minute — so a broken or hostile discovery
23
+ * endpoint cannot turn verification traffic into outbound request floods.
24
+ */
25
+ class DiscoveryError extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'DiscoveryError';
29
+ }
30
+ }
31
+ exports.DiscoveryError = DiscoveryError;
32
+ const stripSlash = (value) => value.replace(/\/+$/, '');
33
+ /** The instance id inside a publishable key (`pk_live_ins_…` / `pk_test_ins_…`), if it has one. */
34
+ function instanceIdFromPublishableKey(publishableKey) {
35
+ const match = /^pk_(?:live|test)_(ins_[A-Za-z0-9]+)$/.exec(publishableKey.trim());
36
+ return match?.[1];
37
+ }
38
+ /** The discovery document URL for an issuer (and optional instance id). */
39
+ function discoveryUrlFor(issuer, instanceId) {
40
+ const base = stripSlash(issuer);
41
+ return instanceId
42
+ ? `${base}/instances/${encodeURIComponent(instanceId)}/.well-known/openid-configuration`
43
+ : `${base}/.well-known/openid-configuration`;
44
+ }
45
+ function isLoopback(hostname) {
46
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
47
+ }
48
+ class JwksDiscovery {
49
+ deps;
50
+ url;
51
+ cachedUri = null;
52
+ fetchedAt = 0;
53
+ lastAttemptAt = 0;
54
+ lastError = null;
55
+ inflight = null;
56
+ constructor(deps) {
57
+ this.deps = deps;
58
+ this.url = discoveryUrlFor(deps.issuer, deps.instanceId);
59
+ }
60
+ get now() {
61
+ return (this.deps.now ?? Date.now)();
62
+ }
63
+ /** The JWKS URL, from cache when fresh. Throws {@link DiscoveryError} when none can be had. */
64
+ async jwksUri() {
65
+ const now = this.now;
66
+ const ttl = this.deps.ttlMs ?? jwks_cache_1.DEFAULT_TTL_MS;
67
+ const interval = this.deps.refetchIntervalMs ?? jwks_cache_1.REFETCH_INTERVAL_MS;
68
+ if (this.cachedUri && now - this.fetchedAt < ttl)
69
+ return this.cachedUri;
70
+ // Throttle: inside the retry window, answer from what we have (or the last error).
71
+ if (this.lastAttemptAt && now - this.lastAttemptAt < interval) {
72
+ if (this.cachedUri)
73
+ return this.cachedUri;
74
+ if (this.lastError)
75
+ throw this.lastError;
76
+ }
77
+ if (this.inflight)
78
+ return this.inflight;
79
+ this.lastAttemptAt = now;
80
+ this.inflight = this.fetchUri()
81
+ .then((uri) => {
82
+ this.cachedUri = uri;
83
+ this.fetchedAt = now;
84
+ this.lastError = null;
85
+ return uri;
86
+ })
87
+ .catch((error) => {
88
+ this.lastError =
89
+ error instanceof DiscoveryError
90
+ ? error
91
+ : new DiscoveryError(`OIDC discovery at ${this.url} failed: ${error?.message ?? String(error)}`);
92
+ // Keep serving the last good URL through a discovery outage.
93
+ if (this.cachedUri)
94
+ return this.cachedUri;
95
+ throw this.lastError;
96
+ })
97
+ .finally(() => {
98
+ this.inflight = null;
99
+ });
100
+ return this.inflight;
101
+ }
102
+ async fetchUri() {
103
+ const fail = (why) => new DiscoveryError(`OIDC discovery at ${this.url} failed: ${why}. Pass \`jwksUrl\` explicitly` +
104
+ (this.deps.instanceId
105
+ ? '.'
106
+ : ', or — for an instance without its own frontend API domain — pass `instanceId` (or `publishableKey`).'));
107
+ const response = await (this.deps.fetchImpl ?? fetch)(this.url, {
108
+ headers: { accept: 'application/json' },
109
+ });
110
+ if (!response.ok)
111
+ throw fail(`HTTP ${response.status}`);
112
+ let body;
113
+ try {
114
+ body = await response.json();
115
+ }
116
+ catch {
117
+ throw fail('the response is not JSON');
118
+ }
119
+ const doc = body;
120
+ if (!doc || typeof doc !== 'object')
121
+ throw fail('the response is not a discovery document');
122
+ // OIDC Discovery §4.3: the document's issuer MUST match the issuer it was
123
+ // fetched for — otherwise its keys belong to someone else.
124
+ if (typeof doc.issuer !== 'string' || stripSlash(doc.issuer) !== stripSlash(this.deps.issuer)) {
125
+ throw fail(`its issuer (${String(doc.issuer)}) does not match the configured issuer (${this.deps.issuer})`);
126
+ }
127
+ if (typeof doc.jwks_uri !== 'string')
128
+ throw fail('it has no jwks_uri');
129
+ let jwks;
130
+ try {
131
+ jwks = new URL(doc.jwks_uri);
132
+ }
133
+ catch {
134
+ throw fail('its jwks_uri is not an absolute URL');
135
+ }
136
+ if (jwks.protocol !== 'https:' && !(jwks.protocol === 'http:' && isLoopback(jwks.hostname))) {
137
+ throw fail('its jwks_uri is not https');
138
+ }
139
+ return jwks.toString();
140
+ }
141
+ }
142
+ exports.JwksDiscovery = JwksDiscovery;
143
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './jwks-cache';
2
+ export { DiscoveryError, JwksDiscovery, discoveryUrlFor, instanceIdFromPublishableKey } from './discovery';
2
3
  export * from './verify';
3
4
  export * from './handshake';
5
+ export { BACKCHANNEL_LOGOUT_EVENT, LOGOUT_TOKEN_CLOCK_SKEW_SECONDS, LOGOUT_TOKEN_MAX_AGE_SECONDS, LogoutTokenVerifier, memoryLogoutReplayStore, verifyLogoutToken, } from './logout-token';
6
+ export type { LogoutReplayStore, LogoutTokenCheckOptions, LogoutTokenClaims, LogoutTokenResult, LogoutTokenVerifierOptions, } from './logout-token';
4
7
  export * from './webhooks';
5
8
  export { createAtlasClient } from './client';
6
9
  export type { AtlasClient } from './client';
package/dist/index.js CHANGED
@@ -14,11 +14,25 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.memoryPcaStore = exports.defaultExtract = exports.requirePCA = exports.verifyPCActn = exports.createTestIssuer = exports.createAtlasClient = void 0;
17
+ exports.memoryPcaStore = exports.defaultExtract = exports.requirePCA = exports.verifyPCActn = exports.createTestIssuer = exports.createAtlasClient = exports.verifyLogoutToken = exports.memoryLogoutReplayStore = exports.LogoutTokenVerifier = exports.LOGOUT_TOKEN_MAX_AGE_SECONDS = exports.LOGOUT_TOKEN_CLOCK_SKEW_SECONDS = exports.BACKCHANNEL_LOGOUT_EVENT = exports.instanceIdFromPublishableKey = exports.discoveryUrlFor = exports.JwksDiscovery = exports.DiscoveryError = void 0;
18
18
  // §7.3 token verification — the original surface, unchanged.
19
19
  __exportStar(require("./jwks-cache"), exports);
20
+ // OIDC discovery of the JWKS URL when only `issuer` is configured.
21
+ var discovery_1 = require("./discovery");
22
+ Object.defineProperty(exports, "DiscoveryError", { enumerable: true, get: function () { return discovery_1.DiscoveryError; } });
23
+ Object.defineProperty(exports, "JwksDiscovery", { enumerable: true, get: function () { return discovery_1.JwksDiscovery; } });
24
+ Object.defineProperty(exports, "discoveryUrlFor", { enumerable: true, get: function () { return discovery_1.discoveryUrlFor; } });
25
+ Object.defineProperty(exports, "instanceIdFromPublishableKey", { enumerable: true, get: function () { return discovery_1.instanceIdFromPublishableKey; } });
20
26
  __exportStar(require("./verify"), exports);
21
27
  __exportStar(require("./handshake"), exports);
28
+ // OIDC Back-Channel Logout: verify the logout_token Atlas POSTs to your app.
29
+ var logout_token_1 = require("./logout-token");
30
+ Object.defineProperty(exports, "BACKCHANNEL_LOGOUT_EVENT", { enumerable: true, get: function () { return logout_token_1.BACKCHANNEL_LOGOUT_EVENT; } });
31
+ Object.defineProperty(exports, "LOGOUT_TOKEN_CLOCK_SKEW_SECONDS", { enumerable: true, get: function () { return logout_token_1.LOGOUT_TOKEN_CLOCK_SKEW_SECONDS; } });
32
+ Object.defineProperty(exports, "LOGOUT_TOKEN_MAX_AGE_SECONDS", { enumerable: true, get: function () { return logout_token_1.LOGOUT_TOKEN_MAX_AGE_SECONDS; } });
33
+ Object.defineProperty(exports, "LogoutTokenVerifier", { enumerable: true, get: function () { return logout_token_1.LogoutTokenVerifier; } });
34
+ Object.defineProperty(exports, "memoryLogoutReplayStore", { enumerable: true, get: function () { return logout_token_1.memoryLogoutReplayStore; } });
35
+ Object.defineProperty(exports, "verifyLogoutToken", { enumerable: true, get: function () { return logout_token_1.verifyLogoutToken; } });
22
36
  // §12.2 inbound webhook verification for consumers.
23
37
  __exportStar(require("./webhooks"), exports);
24
38
  // The typed management client for the secret-key Backend API.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6DAA6D;AAC7D,+CAA6B;AAC7B,2CAAyB;AACzB,8CAA4B;AAE5B,oDAAoD;AACpD,6CAA2B;AAE3B,8DAA8D;AAC9D,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAE1B,2CAAyB;AAEzB,yEAAyE;AACzE,2EAA2E;AAC3E,iEAAiE;AACjE,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AAGzB,wFAAwF;AACxF,2CAA2C;AAC3C,uCAA4C;AAAnC,sGAAA,YAAY,OAAA;AAErB,iDAA+E;AAAtE,yGAAA,UAAU,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,6GAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6DAA6D;AAC7D,+CAA6B;AAC7B,mEAAmE;AACnE,yCAA2G;AAAlG,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,yHAAA,4BAA4B,OAAA;AACrF,2CAAyB;AACzB,8CAA4B;AAE5B,6EAA6E;AAC7E,+CAOwB;AANtB,wHAAA,wBAAwB,OAAA;AACxB,+HAAA,+BAA+B,OAAA;AAC/B,4HAAA,4BAA4B,OAAA;AAC5B,mHAAA,mBAAmB,OAAA;AACnB,uHAAA,uBAAuB,OAAA;AACvB,iHAAA,iBAAiB,OAAA;AAUnB,oDAAoD;AACpD,6CAA2B;AAE3B,8DAA8D;AAC9D,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAE1B,2CAAyB;AAEzB,yEAAyE;AACzE,2EAA2E;AAC3E,iEAAiE;AACjE,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AAGzB,wFAAwF;AACxF,2CAA2C;AAC3C,uCAA4C;AAAnC,sGAAA,YAAY,OAAA;AAErB,iDAA+E;AAAtE,yGAAA,UAAU,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,6GAAA,cAAc,OAAA"}
@@ -34,6 +34,12 @@ export interface JwksCacheDeps {
34
34
  * when both are given the static set wins and no network call is ever made.
35
35
  */
36
36
  staticJwks?: Jwks;
37
+ /**
38
+ * Resolve the JWKS URL lazily (OIDC discovery) when no fixed `url` is given.
39
+ * Called on each fetch attempt; it does its own caching. A rejection is
40
+ * recorded as {@link JwksCache.lastError} and treated like a failed fetch.
41
+ */
42
+ resolveUrl?: () => Promise<string>;
37
43
  fetchImpl?: typeof fetch;
38
44
  now?: () => number;
39
45
  ttlMs?: number;
@@ -47,6 +53,8 @@ export declare class JwksCache {
47
53
  private lastAttemptAt;
48
54
  /** Exposed so a caller can assert the rate limit actually bit. */
49
55
  lastOutcome: FetchOutcome;
56
+ /** Why the last attempt produced no keys (e.g. a discovery failure), for diagnostics. */
57
+ lastError: string | null;
50
58
  constructor(deps: JwksCacheDeps);
51
59
  private get now();
52
60
  private has;
@@ -29,6 +29,8 @@ class JwksCache {
29
29
  lastAttemptAt = 0;
30
30
  /** Exposed so a caller can assert the rate limit actually bit. */
31
31
  lastOutcome = 'fresh';
32
+ /** Why the last attempt produced no keys (e.g. a discovery failure), for diagnostics. */
33
+ lastError = null;
32
34
  constructor(deps) {
33
35
  this.deps = deps;
34
36
  }
@@ -62,13 +64,12 @@ class JwksCache {
62
64
  this.lastOutcome = 'cached';
63
65
  return this.deps.staticJwks;
64
66
  }
65
- if (this.deps.url === undefined) {
67
+ if (this.deps.url === undefined && this.deps.resolveUrl === undefined) {
66
68
  // Neither a URL nor a static set: nothing to verify against. Report
67
69
  // "no keys" rather than attempting `fetch(undefined)`.
68
70
  this.lastOutcome = 'failed';
69
71
  return null;
70
72
  }
71
- const url = this.deps.url;
72
73
  const now = this.now;
73
74
  const ttl = this.deps.ttlMs ?? exports.DEFAULT_TTL_MS;
74
75
  const interval = this.deps.refetchIntervalMs ?? exports.REFETCH_INTERVAL_MS;
@@ -88,6 +89,20 @@ class JwksCache {
88
89
  return this.cached;
89
90
  }
90
91
  this.lastAttemptAt = now;
92
+ let url;
93
+ if (this.deps.url !== undefined) {
94
+ url = this.deps.url;
95
+ }
96
+ else {
97
+ try {
98
+ url = await this.deps.resolveUrl();
99
+ }
100
+ catch (error) {
101
+ this.lastOutcome = 'failed';
102
+ this.lastError = error instanceof Error ? error.message : String(error);
103
+ return this.cached;
104
+ }
105
+ }
91
106
  try {
92
107
  const response = await (this.deps.fetchImpl ?? fetch)(url, {
93
108
  headers: { accept: 'application/json' },
@@ -99,13 +114,15 @@ class JwksCache {
99
114
  throw new Error('JWKS is malformed');
100
115
  this.cached = body;
101
116
  this.fetchedAt = now;
117
+ this.lastError = null;
102
118
  this.lastOutcome = kidMiss ? 'refetched' : 'fresh';
103
119
  return this.cached;
104
120
  }
105
- catch {
121
+ catch (error) {
106
122
  // Keep serving what we have. A JWKS outage should degrade to "new keys
107
123
  // do not work yet", not "nobody can authenticate".
108
124
  this.lastOutcome = 'failed';
125
+ this.lastError = `JWKS fetch from ${url} failed: ${error instanceof Error ? error.message : String(error)}`;
109
126
  return this.cached;
110
127
  }
111
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jwks-cache.js","sourceRoot":"","sources":["../src/jwks-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jwks-cache.js","sourceRoot":"","sources":["../src/jwks-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { JwksCache, type Jwks } from './jwks-cache';
2
+ /**
3
+ * OpenID Connect Back-Channel Logout 1.0 — verifying the `logout_token` Atlas
4
+ * POSTs to your `backchannel_logout_uri` when a user's session ends.
5
+ *
6
+ * The checks are those of §2.6 of the spec: RS256 signature from the issuer's
7
+ * JWKS, `iss`, `aud` containing your client id, `iat` present (and not in the
8
+ * future), `exp` when present, a `jti`, the back-channel-logout event, a `sid`
9
+ * and/or `sub`, NO `nonce`, and a `typ` of `logout+jwt` when the header has one.
10
+ */
11
+ /** The event a logout_token carries (and an id_token never does). */
12
+ export declare const BACKCHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
13
+ /** Seconds of clock skew tolerated on `iat` / `exp`. */
14
+ export declare const LOGOUT_TOKEN_CLOCK_SKEW_SECONDS = 5;
15
+ /** Default maximum age of a logout_token (`iat`), in seconds. Atlas mints them with a 2-minute life. */
16
+ export declare const LOGOUT_TOKEN_MAX_AGE_SECONDS = 300;
17
+ /** What a verified logout_token tells you: whose session(s) to end. */
18
+ export interface LogoutTokenClaims {
19
+ /** The user whose sessions end. At least one of `sub` / `sid` is present. */
20
+ sub?: string;
21
+ /** The session that ended — the same `sid` your id_token for it carried. */
22
+ sid?: string;
23
+ /** Unique token id; remember it to refuse a replay. */
24
+ jti: string;
25
+ /** Issued-at, epoch seconds. */
26
+ iat: number;
27
+ }
28
+ export type LogoutTokenResult = {
29
+ ok: true;
30
+ claims: LogoutTokenClaims;
31
+ } | {
32
+ ok: false;
33
+ /**
34
+ * `malformed`: not a JWT. `no_keys`: the issuer's keys could not be loaded.
35
+ * `invalid`: a check failed (`detail` says which). `replayed`: the `jti`
36
+ * was already seen by the replay store.
37
+ */
38
+ reason: 'malformed' | 'no_keys' | 'invalid' | 'replayed';
39
+ detail?: string;
40
+ };
41
+ /**
42
+ * Remembers logout_token `jti`s so a captured token cannot be replayed. Return
43
+ * `true` the first time a jti is seen, `false` when it was seen before. Back it
44
+ * with a shared store (Redis `SET NX PX`, a DB unique key) when you run more
45
+ * than one server process.
46
+ */
47
+ export interface LogoutReplayStore {
48
+ remember(jti: string, expiresAtMs: number): boolean | Promise<boolean>;
49
+ }
50
+ /** An in-process {@link LogoutReplayStore}; entries expire after the token's max age. */
51
+ export declare function memoryLogoutReplayStore(now?: () => number): LogoutReplayStore;
52
+ export interface LogoutTokenVerifierOptions {
53
+ /** Expected `iss`: your instance's issuer, or `https://atlasauth.net` for Sign in with Atlas apps. */
54
+ issuer: string;
55
+ /** Your client id (the `aud` the token must contain). One string or several accepted values. */
56
+ clientId: string | readonly string[];
57
+ /** The issuer's JWKS URL. Optional: discovered from the issuer when omitted. */
58
+ jwksUrl?: string;
59
+ /** A static key set, for offline verification (tests). Exclusive with `jwksUrl`. */
60
+ jwks?: Jwks;
61
+ /** For discovery on the shared host: the instance id (`ins_...`). */
62
+ instanceId?: string;
63
+ /** Alternative to `instanceId`: read it from the publishable key. */
64
+ publishableKey?: string;
65
+ /** Reject tokens whose `iat` is older than this many seconds. Default 300. */
66
+ maxAgeSeconds?: number;
67
+ /** Optional replay protection by `jti`. */
68
+ replayStore?: LogoutReplayStore;
69
+ fetchImpl?: typeof fetch;
70
+ now?: () => number;
71
+ }
72
+ /** Per-call settings for checking a logout_token against already-loaded keys. */
73
+ export interface LogoutTokenCheckOptions {
74
+ issuer: string;
75
+ clientId: string | readonly string[];
76
+ maxAgeSeconds?: number;
77
+ replayStore?: LogoutReplayStore;
78
+ now?: () => number;
79
+ }
80
+ /**
81
+ * Verifies logout_tokens. Create one and reuse it: it caches the issuer's keys
82
+ * (refetching at most once a minute on an unknown `kid`).
83
+ */
84
+ export declare class LogoutTokenVerifier {
85
+ private readonly options;
86
+ private readonly cache;
87
+ constructor(options: LogoutTokenVerifierOptions);
88
+ /** Verify a logout_token (the `logout_token` form field of the POST). */
89
+ verify(token: string): Promise<LogoutTokenResult>;
90
+ }
91
+ /**
92
+ * One-off verification. Prefer a long-lived {@link LogoutTokenVerifier} on a
93
+ * server, so the issuer's keys are cached between requests.
94
+ */
95
+ export declare function verifyLogoutToken(token: string, options: LogoutTokenVerifierOptions): Promise<LogoutTokenResult>;
96
+ /** Shared by {@link LogoutTokenVerifier} and `AtlasBackend.verifyLogoutToken`. */
97
+ export declare function checkLogoutToken(token: string, cache: JwksCache, options: LogoutTokenCheckOptions): Promise<LogoutTokenResult>;