@atlasauth/backend 0.16.0 → 0.18.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.
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.policiesResource = policiesResource;
4
+ exports.policySourceResource = policySourceResource;
5
+ const request_1 = require("./request");
6
+ function policiesResource(request) {
7
+ return {
8
+ /** `GET /v1/policies` — active policies (`include_retired` for all). */
9
+ list(params = {}) {
10
+ return request({ method: 'GET', path: '/v1/policies', query: { ...params } });
11
+ },
12
+ /** `POST /v1/policies`. */
13
+ create(body) {
14
+ return request({ method: 'POST', path: '/v1/policies', body });
15
+ },
16
+ /** `GET /v1/policies/:id` — by id or key. */
17
+ get(idOrKey) {
18
+ return request({ method: 'GET', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}` });
19
+ },
20
+ /** `PATCH /v1/policies/:id`. */
21
+ update(idOrKey, body) {
22
+ return request({ method: 'PATCH', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}`, body });
23
+ },
24
+ /** `DELETE /v1/policies/:id` — deletes a never-published policy, retires one with history. */
25
+ delete(idOrKey) {
26
+ return request({ method: 'DELETE', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}` });
27
+ },
28
+ versions: {
29
+ list(idOrKey) {
30
+ return request({ method: 'GET', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions` });
31
+ },
32
+ /** `POST /v1/policies/:id/versions` — a draft (or published at once with `publish: true`). */
33
+ create(idOrKey, body) {
34
+ return request({ method: 'POST', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions`, body });
35
+ },
36
+ get(idOrKey, versionIdOrLabel) {
37
+ return request({ method: 'GET', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions/${(0, request_1.enc)(versionIdOrLabel)}` });
38
+ },
39
+ /** Drafts only. */
40
+ update(idOrKey, versionIdOrLabel, body) {
41
+ return request({ method: 'PATCH', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions/${(0, request_1.enc)(versionIdOrLabel)}`, body });
42
+ },
43
+ /** Drafts only. */
44
+ delete(idOrKey, versionIdOrLabel) {
45
+ return request({ method: 'DELETE', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions/${(0, request_1.enc)(versionIdOrLabel)}` });
46
+ },
47
+ /** `POST …/publish` — `effective_at` (epoch ms or ISO; default now) is when the gate starts asking for it. */
48
+ publish(idOrKey, versionIdOrLabel, body = {}) {
49
+ return request({ method: 'POST', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions/${(0, request_1.enc)(versionIdOrLabel)}/publish`, body });
50
+ },
51
+ retire(idOrKey, versionIdOrLabel) {
52
+ return request({ method: 'POST', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/versions/${(0, request_1.enc)(versionIdOrLabel)}/retire` });
53
+ },
54
+ },
55
+ /** `GET /v1/policies/:id/acceptances` — who accepted, newest first. */
56
+ listAcceptances(idOrKey, params = {}) {
57
+ return request({ method: 'GET', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/acceptances`, query: { ...params } });
58
+ },
59
+ /** The same as CSV (`format=csv`, up to 100,000 rows). */
60
+ exportAcceptancesCsv(idOrKey, params = {}) {
61
+ return request({ method: 'GET', path: `/v1/policies/${(0, request_1.enc)(idOrKey)}/acceptances`, query: { ...params, format: 'csv' }, raw: true });
62
+ },
63
+ /** `GET /v1/users/:id/policies` — a user's standing on every policy. */
64
+ forUser(userId) {
65
+ return request({ method: 'GET', path: `/v1/users/${(0, request_1.enc)(userId)}/policies` });
66
+ },
67
+ settings: {
68
+ get() {
69
+ return request({ method: 'GET', path: '/v1/instance/policy_settings' });
70
+ },
71
+ update(body) {
72
+ return request({ method: 'PATCH', path: '/v1/instance/policy_settings', body });
73
+ },
74
+ },
75
+ };
76
+ }
77
+ function policySourceResource(request) {
78
+ return {
79
+ get() {
80
+ return request({ method: 'GET', path: '/v1/instance/policy_source' });
81
+ },
82
+ /** `PUT /v1/instance/policy_source` — partial; `signing_secret` is write-only. */
83
+ update(body) {
84
+ return request({ method: 'PUT', path: '/v1/instance/policy_source', body });
85
+ },
86
+ /** Disconnect the source (its policies and history stay). */
87
+ delete() {
88
+ return request({ method: 'DELETE', path: '/v1/instance/policy_source' });
89
+ },
90
+ /** A signed fetch of the feed, validated but not stored. */
91
+ test() {
92
+ return request({ method: 'POST', path: '/v1/instance/policy_source/test' });
93
+ },
94
+ /** Fetch and store now. */
95
+ sync() {
96
+ return request({ method: 'POST', path: '/v1/instance/policy_source/sync' });
97
+ },
98
+ listDeliveries(params = {}) {
99
+ return request({ method: 'GET', path: '/v1/instance/policy_source/deliveries', query: { ...params } });
100
+ },
101
+ };
102
+ }
103
+ //# sourceMappingURL=policies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.js","sourceRoot":"","sources":["../../src/client/policies.ts"],"names":[],"mappings":"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"}
@@ -664,6 +664,14 @@ export interface RecordUserConsentBody {
664
664
  status?: 'accepted' | 'withdrawn';
665
665
  source?: string;
666
666
  metadata?: Record<string, unknown>;
667
+ /** Policy acceptance evidence: the exact version (must be that version of that policy). */
668
+ policy_version_id?: string;
669
+ /** Must equal the version's `content_sha256` when given. */
670
+ content_sha256?: string;
671
+ display?: 'checkbox' | 'scroll' | 'link';
672
+ scrolled_to_end?: boolean;
673
+ view_ms?: number;
674
+ locale?: string;
667
675
  }
668
676
  export interface UserConsent {
669
677
  object: 'consent';
@@ -673,6 +681,13 @@ export interface UserConsent {
673
681
  status: 'accepted' | 'withdrawn' | (string & {});
674
682
  source: string | null;
675
683
  metadata?: unknown;
684
+ /** Policy acceptance evidence (null on records made outside a policy flow). */
685
+ policy_version_id?: string | null;
686
+ content_sha256?: string | null;
687
+ display?: 'checkbox' | 'scroll' | 'link' | null;
688
+ scrolled_to_end?: boolean | null;
689
+ view_ms?: number | null;
690
+ locale?: string | null;
676
691
  created_at: number;
677
692
  }
678
693
  export interface CreateUserApprovalRequestBody {
package/dist/index.d.ts CHANGED
@@ -3,12 +3,16 @@ export { DiscoveryError, JwksDiscovery, discoveryUrlFor, instanceIdFromPublishab
3
3
  export * from './verify';
4
4
  export * from './access-control';
5
5
  export * from './handshake';
6
+ export * from './better-auth';
7
+ export { BETTER_AUTH_JWT_ALGS, peekIssuer } from './legacy-issuers';
8
+ export type { LegacyIssuer } from './legacy-issuers';
6
9
  export { BACKCHANNEL_LOGOUT_EVENT, LOGOUT_TOKEN_CLOCK_SKEW_SECONDS, LOGOUT_TOKEN_MAX_AGE_SECONDS, LogoutTokenVerifier, memoryLogoutReplayStore, verifyLogoutToken, } from './logout-token';
7
10
  export type { LogoutReplayStore, LogoutTokenCheckOptions, LogoutTokenClaims, LogoutTokenResult, LogoutTokenVerifierOptions, } from './logout-token';
8
11
  export { AtlasOidcError, OIDC_FLOW_COOKIE, OIDC_FLOW_MAX_AGE_SECONDS, oidc, oidcCallback, oidcClearCookie, oidcLogoutUrl, oidcStart, oidcVerifyIdToken, resetOidcCaches, } from './oidc';
9
12
  export type { AtlasOidc, AtlasOidcErrorCode, OidcCallbackOptions, OidcClientAuth, OidcIdentity, OidcLogoutUrlOptions, OidcStartOptions, OidcStartResult, VerifyIdTokenOptions, } from './oidc';
10
13
  export * from './webhooks';
11
14
  export * from './referrals';
15
+ export * from './policy-source';
12
16
  export * from './botid';
13
17
  export { createAtlasClient } from './client';
14
18
  export type { AtlasClient } from './client';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ 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.verifyAccessToken = exports.protectedResourceMetadataPath = exports.protectedResourceMetadata = exports.memoryDpopReplayStore = exports.memoryDpopNonceSource = exports.hmacDpopNonceSource = exports.createIntrospector = exports.createAccessTokenVerifier = exports.buildWwwAuthenticate = exports.DPOP_PROOF_MAX_AGE_SECONDS = exports.ACCESS_TOKEN_CLOCK_SKEW_SECONDS = exports.memoryPcaStore = exports.defaultExtract = exports.requirePCA = exports.verifyPCActn = exports.REFERALLY_API_VERSION = exports.referralEmailHash = exports.referallySignature = exports.signReferralUpdatedEvent = exports.signReferallyCall = exports.signAtlasReferralCall = exports.TEST_TOKEN_TAMPERS = exports.createTestIssuer = exports.createAtlasClient = exports.resetOidcCaches = exports.oidcVerifyIdToken = exports.oidcStart = exports.oidcLogoutUrl = exports.oidcClearCookie = exports.oidcCallback = exports.oidc = exports.OIDC_FLOW_MAX_AGE_SECONDS = exports.OIDC_FLOW_COOKIE = exports.AtlasOidcError = 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;
17
+ exports.verifyAccessToken = exports.protectedResourceMetadataPath = exports.protectedResourceMetadata = exports.memoryDpopReplayStore = exports.memoryDpopNonceSource = exports.hmacDpopNonceSource = exports.createIntrospector = exports.createAccessTokenVerifier = exports.buildWwwAuthenticate = exports.DPOP_PROOF_MAX_AGE_SECONDS = exports.ACCESS_TOKEN_CLOCK_SKEW_SECONDS = exports.memoryPcaStore = exports.defaultExtract = exports.requirePCA = exports.verifyPCActn = exports.REFERALLY_API_VERSION = exports.referralEmailHash = exports.referallySignature = exports.signReferralUpdatedEvent = exports.signReferallyCall = exports.signAtlasReferralCall = exports.TEST_TOKEN_TAMPERS = exports.createTestIssuer = exports.createAtlasClient = exports.resetOidcCaches = exports.oidcVerifyIdToken = exports.oidcStart = exports.oidcLogoutUrl = exports.oidcClearCookie = exports.oidcCallback = exports.oidc = exports.OIDC_FLOW_MAX_AGE_SECONDS = exports.OIDC_FLOW_COOKIE = exports.AtlasOidcError = exports.verifyLogoutToken = exports.memoryLogoutReplayStore = exports.LogoutTokenVerifier = exports.LOGOUT_TOKEN_MAX_AGE_SECONDS = exports.LOGOUT_TOKEN_CLOCK_SKEW_SECONDS = exports.BACKCHANNEL_LOGOUT_EVENT = exports.peekIssuer = exports.BETTER_AUTH_JWT_ALGS = 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
20
  // OIDC discovery of the JWKS URL when only `issuer` is configured.
@@ -26,6 +26,11 @@ Object.defineProperty(exports, "instanceIdFromPublishableKey", { enumerable: tru
26
26
  __exportStar(require("./verify"), exports);
27
27
  __exportStar(require("./access-control"), exports);
28
28
  __exportStar(require("./handshake"), exports);
29
+ // Moving from Better Auth: exchange its session cookie once, accept its JWTs for a window.
30
+ __exportStar(require("./better-auth"), exports);
31
+ var legacy_issuers_1 = require("./legacy-issuers");
32
+ Object.defineProperty(exports, "BETTER_AUTH_JWT_ALGS", { enumerable: true, get: function () { return legacy_issuers_1.BETTER_AUTH_JWT_ALGS; } });
33
+ Object.defineProperty(exports, "peekIssuer", { enumerable: true, get: function () { return legacy_issuers_1.peekIssuer; } });
29
34
  // OIDC Back-Channel Logout: verify the logout_token Atlas POSTs to your app.
30
35
  var logout_token_1 = require("./logout-token");
31
36
  Object.defineProperty(exports, "BACKCHANNEL_LOGOUT_EVENT", { enumerable: true, get: function () { return logout_token_1.BACKCHANNEL_LOGOUT_EVENT; } });
@@ -51,6 +56,7 @@ Object.defineProperty(exports, "resetOidcCaches", { enumerable: true, get: funct
51
56
  __exportStar(require("./webhooks"), exports);
52
57
  // The Atlas referral protocol, for a referral platform receiving Atlas's signed calls.
53
58
  __exportStar(require("./referrals"), exports);
59
+ __exportStar(require("./policy-source"), exports);
54
60
  // Vercel BotID: sign checkBotId() verdicts on your Vercel route for Atlas's captcha gate.
55
61
  __exportStar(require("./botid"), exports);
56
62
  // 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,mEAAmE;AACnE,yCAA2G;AAAlG,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,yHAAA,4BAA4B,OAAA;AACrF,2CAAyB;AACzB,mDAAiC;AACjC,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,sEAAsE;AACtE,iFAAiF;AACjF,+BAWgB;AAVd,sGAAA,cAAc,OAAA;AACd,wGAAA,gBAAgB,OAAA;AAChB,iHAAA,yBAAyB,OAAA;AACzB,4FAAA,IAAI,OAAA;AACJ,oGAAA,YAAY,OAAA;AACZ,uGAAA,eAAe,OAAA;AACf,qGAAA,aAAa,OAAA;AACb,iGAAA,SAAS,OAAA;AACT,yGAAA,iBAAiB,OAAA;AACjB,uGAAA,eAAe,OAAA;AAcjB,oDAAoD;AACpD,6CAA2B;AAE3B,uFAAuF;AACvF,8CAA4B;AAE5B,0FAA0F;AAC1F,0CAAwB;AAExB,8DAA8D;AAC9D,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAE1B,2CAAyB;AAEzB,yEAAyE;AACzE,2EAA2E;AAC3E,iEAAiE;AACjE,qCAAiE;AAAxD,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAS7C,6DAO+B;AAN7B,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AACjB,+HAAA,wBAAwB,OAAA;AACxB,yHAAA,kBAAkB,OAAA;AAClB,wHAAA,iBAAiB,OAAA;AACjB,4HAAA,qBAAqB,OAAA;AAWvB,wFAAwF;AACxF,2CAA2C;AAC3C,uCAA4C;AAAnC,sGAAA,YAAY,OAAA;AAErB,iDAA+E;AAAtE,yGAAA,UAAU,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,6GAAA,cAAc,OAAA;AAGnD,8EAA8E;AAC9E,gFAAgF;AAChF,mDAY0B;AAXxB,iIAAA,+BAA+B,OAAA;AAC/B,4HAAA,0BAA0B,OAAA;AAC1B,sHAAA,oBAAoB,OAAA;AACpB,2HAAA,yBAAyB,OAAA;AACzB,oHAAA,kBAAkB,OAAA;AAClB,qHAAA,mBAAmB,OAAA;AACnB,uHAAA,qBAAqB,OAAA;AACrB,uHAAA,qBAAqB,OAAA;AACrB,2HAAA,yBAAyB,OAAA;AACzB,+HAAA,6BAA6B,OAAA;AAC7B,mHAAA,iBAAiB,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,mDAAiC;AACjC,8CAA4B;AAC5B,2FAA2F;AAC3F,gDAA8B;AAC9B,mDAAoE;AAA3D,sHAAA,oBAAoB,OAAA;AAAE,4GAAA,UAAU,OAAA;AAGzC,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,sEAAsE;AACtE,iFAAiF;AACjF,+BAWgB;AAVd,sGAAA,cAAc,OAAA;AACd,wGAAA,gBAAgB,OAAA;AAChB,iHAAA,yBAAyB,OAAA;AACzB,4FAAA,IAAI,OAAA;AACJ,oGAAA,YAAY,OAAA;AACZ,uGAAA,eAAe,OAAA;AACf,qGAAA,aAAa,OAAA;AACb,iGAAA,SAAS,OAAA;AACT,yGAAA,iBAAiB,OAAA;AACjB,uGAAA,eAAe,OAAA;AAcjB,oDAAoD;AACpD,6CAA2B;AAE3B,uFAAuF;AACvF,8CAA4B;AAC5B,kDAAgC;AAEhC,0FAA0F;AAC1F,0CAAwB;AAExB,8DAA8D;AAC9D,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAE1B,2CAAyB;AAEzB,yEAAyE;AACzE,2EAA2E;AAC3E,iEAAiE;AACjE,qCAAiE;AAAxD,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAS7C,6DAO+B;AAN7B,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AACjB,+HAAA,wBAAwB,OAAA;AACxB,yHAAA,kBAAkB,OAAA;AAClB,wHAAA,iBAAiB,OAAA;AACjB,4HAAA,qBAAqB,OAAA;AAWvB,wFAAwF;AACxF,2CAA2C;AAC3C,uCAA4C;AAAnC,sGAAA,YAAY,OAAA;AAErB,iDAA+E;AAAtE,yGAAA,UAAU,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,6GAAA,cAAc,OAAA;AAGnD,8EAA8E;AAC9E,gFAAgF;AAChF,mDAY0B;AAXxB,iIAAA,+BAA+B,OAAA;AAC/B,4HAAA,0BAA0B,OAAA;AAC1B,sHAAA,oBAAoB,OAAA;AACpB,2HAAA,yBAAyB,OAAA;AACzB,oHAAA,kBAAkB,OAAA;AAClB,qHAAA,mBAAmB,OAAA;AACnB,uHAAA,qBAAqB,OAAA;AACrB,uHAAA,qBAAqB,OAAA;AACrB,2HAAA,yBAAyB,OAAA;AACzB,+HAAA,6BAA6B,OAAA;AAC7B,mHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,65 @@
1
+ import { type Jwks } from './jwks-cache';
2
+ /**
3
+ * Accepting your OLD auth provider's JWTs during a cutover — today, Better
4
+ * Auth's `jwt` plugin tokens.
5
+ *
6
+ * When you move to Atlas, API clients may still hold short-lived JWTs your
7
+ * Better Auth app minted. With `legacyIssuers` on an {@link AtlasBackend}, a
8
+ * token whose `iss` is one of these issuers is verified against THAT issuer's
9
+ * key set (the JWKS you imported into Atlas, or the old app's `/api/auth/jwks`)
10
+ * for a bounded window, and its `sub` (the Better Auth user id) is mapped to the
11
+ * Atlas user id — Atlas keeps the Better Auth id as the user's `external_id`.
12
+ * After `until`, those tokens are refused like any other foreign token.
13
+ */
14
+ export interface LegacyIssuer {
15
+ /** The old provider. Only `better_auth` today. */
16
+ kind?: 'better_auth';
17
+ /** The `iss` the old tokens carry (Better Auth: its `baseURL`, unless `jwt.issuer` was set). */
18
+ issuer: string;
19
+ /** The imported key set. Exactly one of `jwks` / `jwksUrl`. */
20
+ jwks?: Jwks;
21
+ /** Where to fetch the old key set (e.g. `https://app.example.com/api/auth/jwks`). */
22
+ jwksUrl?: string;
23
+ /** Expected `aud` (Better Auth: its `baseURL`, unless `jwt.audience` was set). Unchecked when unset. */
24
+ audience?: string | readonly string[];
25
+ /** The end of the acceptance window. Required: a legacy issuer is never accepted forever. */
26
+ until: Date | string | number;
27
+ /** Signing algorithms to accept. Default: Better Auth's (EdDSA, ES256, ES512, RS256, PS256). */
28
+ algorithms?: readonly string[];
29
+ /**
30
+ * Map the old `sub` to the Atlas user id. Default: look the user up by
31
+ * `external_id` through the Backend API (needs `secretKey` + `bapiBaseUrl`
32
+ * on the backend), cached in memory. Return null to refuse the token.
33
+ */
34
+ resolveSubject?: (legacySub: string, claims: Record<string, unknown>) => Promise<string | null> | string | null;
35
+ }
36
+ /** Better Auth's `jwt` plugin key algorithms. */
37
+ export declare const BETTER_AUTH_JWT_ALGS: string[];
38
+ export interface LegacyVerifierDeps {
39
+ issuers: readonly LegacyIssuer[];
40
+ secretKey?: string;
41
+ bapiBaseUrl?: string;
42
+ fetchImpl?: typeof fetch;
43
+ now?: () => number;
44
+ clockToleranceSeconds: number;
45
+ }
46
+ export type LegacyVerifyOutcome = {
47
+ ok: true;
48
+ claims: Record<string, unknown>;
49
+ } | {
50
+ ok: false;
51
+ reason: 'invalid' | 'no_keys';
52
+ };
53
+ /** The unverified `iss` of a compact JWT, for routing only — never trusted on its own. */
54
+ export declare function peekIssuer(token: string): string | undefined;
55
+ export declare class LegacyIssuerVerifier {
56
+ private readonly deps;
57
+ private readonly caches;
58
+ private readonly subjects;
59
+ constructor(deps: LegacyVerifierDeps);
60
+ private get now();
61
+ /** The configured entry for this `iss`, when its window is still open. */
62
+ match(iss: string | undefined): LegacyIssuer | undefined;
63
+ verify(token: string, entry: LegacyIssuer): Promise<LegacyVerifyOutcome>;
64
+ private resolve;
65
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LegacyIssuerVerifier = exports.BETTER_AUTH_JWT_ALGS = void 0;
4
+ exports.peekIssuer = peekIssuer;
5
+ const jose_1 = require("jose");
6
+ const jwks_cache_1 = require("./jwks-cache");
7
+ /** Better Auth's `jwt` plugin key algorithms. */
8
+ exports.BETTER_AUTH_JWT_ALGS = ['EdDSA', 'ES256', 'ES512', 'RS256', 'PS256'];
9
+ /** How long a resolved sub → Atlas id mapping is reused. */
10
+ const SUBJECT_TTL_MS = 5 * 60_000;
11
+ /** Cap on cached subjects, so a stream of distinct tokens can't grow memory without bound. */
12
+ const SUBJECT_CACHE_MAX = 10_000;
13
+ /** The unverified `iss` of a compact JWT, for routing only — never trusted on its own. */
14
+ function peekIssuer(token) {
15
+ try {
16
+ const iss = (0, jose_1.decodeJwt)(token).iss;
17
+ return typeof iss === 'string' ? iss : undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ function untilMs(until) {
24
+ const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(until);
25
+ return Number.isFinite(t) ? t : 0;
26
+ }
27
+ class LegacyIssuerVerifier {
28
+ deps;
29
+ caches = new Map();
30
+ subjects = new Map();
31
+ constructor(deps) {
32
+ this.deps = deps;
33
+ for (const entry of deps.issuers) {
34
+ const hasUrl = typeof entry.jwksUrl === 'string' && entry.jwksUrl.length > 0;
35
+ if (hasUrl === (entry.jwks !== undefined)) {
36
+ throw new Error(`legacyIssuers: "${entry.issuer}" needs exactly one of \`jwks\` or \`jwksUrl\`.`);
37
+ }
38
+ if (!entry.issuer)
39
+ throw new Error('legacyIssuers: every entry needs `issuer`.');
40
+ if (!untilMs(entry.until))
41
+ throw new Error(`legacyIssuers: "${entry.issuer}" needs a valid \`until\` (the end of the window).`);
42
+ if (!entry.resolveSubject && (!deps.secretKey || !deps.bapiBaseUrl)) {
43
+ throw new Error(`legacyIssuers: "${entry.issuer}" maps its \`sub\` to an Atlas user through the Backend API, which needs \`secretKey\` and \`bapiBaseUrl\` on AtlasBackend — or give the issuer a \`resolveSubject\`.`);
44
+ }
45
+ this.caches.set(entry.issuer, new jwks_cache_1.JwksCache({
46
+ url: hasUrl ? entry.jwksUrl : undefined,
47
+ staticJwks: entry.jwks,
48
+ fetchImpl: deps.fetchImpl,
49
+ now: deps.now,
50
+ }));
51
+ }
52
+ }
53
+ get now() {
54
+ return (this.deps.now ?? Date.now)();
55
+ }
56
+ /** The configured entry for this `iss`, when its window is still open. */
57
+ match(iss) {
58
+ if (!iss)
59
+ return undefined;
60
+ const entry = this.deps.issuers.find((e) => e.issuer === iss);
61
+ if (!entry)
62
+ return undefined;
63
+ return untilMs(entry.until) > this.now ? entry : undefined;
64
+ }
65
+ async verify(token, entry) {
66
+ const cache = this.caches.get(entry.issuer);
67
+ const keys = await cache.get((0, jwks_cache_1.readKid)(token));
68
+ if (!keys || keys.keys.length === 0)
69
+ return { ok: false, reason: 'no_keys' };
70
+ let payload;
71
+ try {
72
+ const verified = await (0, jose_1.jwtVerify)(token, (0, jose_1.createLocalJWKSet)(keys), {
73
+ algorithms: [...(entry.algorithms ?? exports.BETTER_AUTH_JWT_ALGS)],
74
+ issuer: entry.issuer,
75
+ ...(entry.audience !== undefined ? { audience: entry.audience } : {}),
76
+ clockTolerance: this.deps.clockToleranceSeconds,
77
+ currentDate: this.deps.now ? new Date(this.deps.now()) : undefined,
78
+ });
79
+ payload = verified.payload;
80
+ }
81
+ catch {
82
+ return { ok: false, reason: 'invalid' };
83
+ }
84
+ // A legacy token without an expiry would outlive the window it is accepted in.
85
+ if (typeof payload.exp !== 'number')
86
+ return { ok: false, reason: 'invalid' };
87
+ const legacySub = typeof payload.sub === 'string' && payload.sub ? payload.sub : typeof payload.id === 'string' ? payload.id : '';
88
+ if (!legacySub)
89
+ return { ok: false, reason: 'invalid' };
90
+ const atlasId = await this.resolve(legacySub, payload, entry);
91
+ if (!atlasId)
92
+ return { ok: false, reason: 'invalid' };
93
+ return {
94
+ ok: true,
95
+ claims: {
96
+ ...payload,
97
+ sub: atlasId,
98
+ sid: typeof payload.sid === 'string' ? payload.sid : '',
99
+ external_id: legacySub,
100
+ legacy: { kind: entry.kind ?? 'better_auth', issuer: entry.issuer, sub: legacySub },
101
+ },
102
+ };
103
+ }
104
+ async resolve(legacySub, claims, entry) {
105
+ if (entry.resolveSubject) {
106
+ try {
107
+ return (await entry.resolveSubject(legacySub, claims)) || null;
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ const key = `${entry.issuer}\u0000${legacySub}`;
114
+ const hit = this.subjects.get(key);
115
+ if (hit && this.now - hit.at < SUBJECT_TTL_MS)
116
+ return hit.id;
117
+ let id = null;
118
+ try {
119
+ const url = new URL('/v1/users', this.deps.bapiBaseUrl);
120
+ url.searchParams.set('external_id', legacySub);
121
+ url.searchParams.set('limit', '1');
122
+ const res = await (this.deps.fetchImpl ?? fetch)(url.toString(), {
123
+ headers: { authorization: `Bearer ${this.deps.secretKey}` },
124
+ });
125
+ if (!res.ok)
126
+ return null; // not cached: a transient failure should not stick
127
+ const body = (await res.json());
128
+ id = body.data?.[0]?.id ?? null;
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ if (this.subjects.size >= SUBJECT_CACHE_MAX)
134
+ this.subjects.clear();
135
+ this.subjects.set(key, { id, at: this.now });
136
+ return id;
137
+ }
138
+ }
139
+ exports.LegacyIssuerVerifier = LegacyIssuerVerifier;
140
+ //# sourceMappingURL=legacy-issuers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-issuers.js","sourceRoot":"","sources":["../src/legacy-issuers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Atlas policy source protocol v1 — helpers for the system that SERVES an
3
+ * Atlas instance's policy documents (a CMS, a docs platform, your own service).
4
+ *
5
+ * Atlas → you: GET {endpoint_url}/policies (full feed)
6
+ * GET {endpoint_url}/policies?key=<key> (one policy, after your event)
7
+ * signed with the source's signing secret: `atlas-id`, `atlas-timestamp`
8
+ * (epoch ms) and `atlas-signature: v1,<base64 HMAC-SHA256>` over
9
+ * `{atlas-id}.{atlas-timestamp}.GET\n{path+query}`. Verify it with
10
+ * {@link verifyPolicyFeedRequest}, then answer a {@link PolicyFeed}.
11
+ * You → Atlas: POST {webhook_url} (from GET /v1/instance/policy_source), a
12
+ * {@link PolicySourceEvent} signed with the same secret —
13
+ * {@link signPolicySourceEvent} (Atlas-style) or any Standard Webhooks library.
14
+ * Atlas then re-fetches that policy; it never stores content from the event.
15
+ */
16
+ export declare const POLICY_SOURCE_PROTOCOL_VERSION = "2026-10-24";
17
+ export declare const POLICY_SOURCE_VERSION_HEADER = "atlas-policies-version";
18
+ export interface PolicyFeedVersion {
19
+ version: string;
20
+ /** Epoch ms or ISO 8601. */
21
+ published_at?: number | string;
22
+ /** When Atlas starts asking users for it (defaults to published_at). */
23
+ effective_at?: number | string;
24
+ /** One of content_html / content_markdown, or content_url for Atlas to snapshot. At most 512 KB. */
25
+ content_html?: string;
26
+ content_markdown?: string;
27
+ content_url?: string;
28
+ summary_of_changes?: string;
29
+ locale?: string;
30
+ }
31
+ export interface PolicyFeedPolicy {
32
+ /** Slug: lowercase letters, digits, `_` and `-`. */
33
+ key: string;
34
+ title: string;
35
+ display?: 'checkbox' | 'scroll' | 'link';
36
+ required?: boolean;
37
+ applies_to?: 'sign_up' | 'sign_in_and_sign_up';
38
+ versions: PolicyFeedVersion[];
39
+ }
40
+ /** The answer to `GET {endpoint_url}/policies`. */
41
+ export interface PolicyFeed {
42
+ object: 'policy_list';
43
+ policies: PolicyFeedPolicy[];
44
+ }
45
+ export type PolicySourceEvent = {
46
+ type: 'policy.published';
47
+ key: string;
48
+ version: string;
49
+ occurred_at?: string | number;
50
+ } | {
51
+ type: 'policy.retired';
52
+ key: string;
53
+ occurred_at?: string | number;
54
+ };
55
+ type HeadersLike = Headers | Record<string, string | string[] | undefined>;
56
+ export type PolicyFeedVerifyResult = {
57
+ verified: true;
58
+ deliveryId: string;
59
+ timestamp: number;
60
+ key: string | null;
61
+ protocolVersion: string | undefined;
62
+ } | {
63
+ verified: false;
64
+ reason: 'missing_headers' | 'malformed_signature' | 'stale_timestamp' | 'signature_mismatch' | 'wrong_method';
65
+ };
66
+ /**
67
+ * Verify Atlas's signed GET of your feed. Pass the request's path AND query
68
+ * exactly as received (e.g. `req.originalUrl`), and the method.
69
+ *
70
+ * ```ts
71
+ * app.get('/atlas/policies', (req, res) => {
72
+ * const v = verifyPolicyFeedRequest({ method: req.method, pathAndQuery: req.originalUrl, headers: req.headers, secret });
73
+ * if (!v.verified) return res.sendStatus(401);
74
+ * res.json(buildFeed(v.key)); // PolicyFeed, one policy when v.key is set
75
+ * });
76
+ * ```
77
+ */
78
+ export declare function verifyPolicyFeedRequest(input: {
79
+ method?: string;
80
+ pathAndQuery: string;
81
+ headers: HeadersLike;
82
+ secret: string;
83
+ now?: Date;
84
+ toleranceMs?: number;
85
+ }): PolicyFeedVerifyResult;
86
+ /**
87
+ * Sign an event for `POST {webhook_url}` (Atlas-style headers). Send `body`
88
+ * byte-for-byte as returned.
89
+ *
90
+ * ```ts
91
+ * const { body, headers } = signPolicySourceEvent({ event: { type: 'policy.published', key: 'tos', version: '2026-10-24' }, secret });
92
+ * await fetch(webhookUrl, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
93
+ * ```
94
+ */
95
+ export declare function signPolicySourceEvent(input: {
96
+ event: PolicySourceEvent;
97
+ secret: string;
98
+ id?: string;
99
+ timestampMs?: number;
100
+ }): {
101
+ body: string;
102
+ headers: Record<string, string>;
103
+ };
104
+ export {};
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POLICY_SOURCE_VERSION_HEADER = exports.POLICY_SOURCE_PROTOCOL_VERSION = void 0;
4
+ exports.verifyPolicyFeedRequest = verifyPolicyFeedRequest;
5
+ exports.signPolicySourceEvent = signPolicySourceEvent;
6
+ const node_crypto_1 = require("node:crypto");
7
+ /**
8
+ * Atlas policy source protocol v1 — helpers for the system that SERVES an
9
+ * Atlas instance's policy documents (a CMS, a docs platform, your own service).
10
+ *
11
+ * Atlas → you: GET {endpoint_url}/policies (full feed)
12
+ * GET {endpoint_url}/policies?key=<key> (one policy, after your event)
13
+ * signed with the source's signing secret: `atlas-id`, `atlas-timestamp`
14
+ * (epoch ms) and `atlas-signature: v1,<base64 HMAC-SHA256>` over
15
+ * `{atlas-id}.{atlas-timestamp}.GET\n{path+query}`. Verify it with
16
+ * {@link verifyPolicyFeedRequest}, then answer a {@link PolicyFeed}.
17
+ * You → Atlas: POST {webhook_url} (from GET /v1/instance/policy_source), a
18
+ * {@link PolicySourceEvent} signed with the same secret —
19
+ * {@link signPolicySourceEvent} (Atlas-style) or any Standard Webhooks library.
20
+ * Atlas then re-fetches that policy; it never stores content from the event.
21
+ */
22
+ exports.POLICY_SOURCE_PROTOCOL_VERSION = '2026-10-24';
23
+ exports.POLICY_SOURCE_VERSION_HEADER = 'atlas-policies-version';
24
+ const TOLERANCE_MS = 5 * 60_000;
25
+ function header(headers, name) {
26
+ if (typeof headers.get === 'function')
27
+ return headers.get(name) ?? undefined;
28
+ const bag = headers;
29
+ const direct = bag[name] ?? bag[Object.keys(bag).find((k) => k.toLowerCase() === name) ?? ''];
30
+ return Array.isArray(direct) ? direct[0] : direct;
31
+ }
32
+ function mac(secret, id, timestamp, payload) {
33
+ return (0, node_crypto_1.createHmac)('sha256', secret).update(`${id}.${timestamp}.${payload}`).digest();
34
+ }
35
+ /**
36
+ * Verify Atlas's signed GET of your feed. Pass the request's path AND query
37
+ * exactly as received (e.g. `req.originalUrl`), and the method.
38
+ *
39
+ * ```ts
40
+ * app.get('/atlas/policies', (req, res) => {
41
+ * const v = verifyPolicyFeedRequest({ method: req.method, pathAndQuery: req.originalUrl, headers: req.headers, secret });
42
+ * if (!v.verified) return res.sendStatus(401);
43
+ * res.json(buildFeed(v.key)); // PolicyFeed, one policy when v.key is set
44
+ * });
45
+ * ```
46
+ */
47
+ function verifyPolicyFeedRequest(input) {
48
+ if (input.method && input.method.toUpperCase() !== 'GET')
49
+ return { verified: false, reason: 'wrong_method' };
50
+ const id = header(input.headers, 'atlas-id');
51
+ const ts = header(input.headers, 'atlas-timestamp');
52
+ const sig = header(input.headers, 'atlas-signature');
53
+ if (!id || !ts || !sig)
54
+ return { verified: false, reason: 'missing_headers' };
55
+ const provided = sig
56
+ .trim()
57
+ .split(/\s+/)
58
+ .filter((e) => e.startsWith('v1,'))
59
+ .map((e) => Buffer.from(e.slice(3), 'base64'));
60
+ if (provided.length === 0)
61
+ return { verified: false, reason: 'malformed_signature' };
62
+ const timestamp = Number(ts);
63
+ const now = (input.now ?? new Date()).getTime();
64
+ if (!Number.isFinite(timestamp) || Math.abs(now - timestamp) > (input.toleranceMs ?? TOLERANCE_MS)) {
65
+ return { verified: false, reason: 'stale_timestamp' };
66
+ }
67
+ const expected = mac(input.secret, id, ts, `GET\n${input.pathAndQuery}`);
68
+ const ok = provided.some((p) => p.length === expected.length && (0, node_crypto_1.timingSafeEqual)(p, expected));
69
+ if (!ok)
70
+ return { verified: false, reason: 'signature_mismatch' };
71
+ let key = null;
72
+ const q = input.pathAndQuery.indexOf('?');
73
+ if (q !== -1)
74
+ key = new URLSearchParams(input.pathAndQuery.slice(q + 1)).get('key');
75
+ return { verified: true, deliveryId: id, timestamp, key, protocolVersion: header(input.headers, exports.POLICY_SOURCE_VERSION_HEADER) };
76
+ }
77
+ /**
78
+ * Sign an event for `POST {webhook_url}` (Atlas-style headers). Send `body`
79
+ * byte-for-byte as returned.
80
+ *
81
+ * ```ts
82
+ * const { body, headers } = signPolicySourceEvent({ event: { type: 'policy.published', key: 'tos', version: '2026-10-24' }, secret });
83
+ * await fetch(webhookUrl, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
84
+ * ```
85
+ */
86
+ function signPolicySourceEvent(input) {
87
+ const body = JSON.stringify({ occurred_at: new Date().toISOString(), ...input.event });
88
+ const id = input.id ?? `evt_${(0, node_crypto_1.randomUUID)()}`;
89
+ const ts = String(input.timestampMs ?? Date.now());
90
+ return {
91
+ body,
92
+ headers: {
93
+ 'atlas-id': id,
94
+ 'atlas-timestamp': ts,
95
+ 'atlas-signature': `v1,${mac(input.secret, id, ts, body).toString('base64')}`,
96
+ },
97
+ };
98
+ }
99
+ //# sourceMappingURL=policy-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-source.js","sourceRoot":"","sources":["../src/policy-source.ts"],"names":[],"mappings":"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"}