@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.
- package/CHANGELOG.md +16 -0
- package/dist/better-auth.d.ts +157 -0
- package/dist/better-auth.js +245 -0
- package/dist/better-auth.js.map +1 -0
- package/dist/client/better-auth-imports.d.ts +205 -0
- package/dist/client/better-auth-imports.js +107 -0
- package/dist/client/better-auth-imports.js.map +1 -0
- package/dist/client/index.d.ts +116 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/policies.d.ts +316 -0
- package/dist/client/policies.js +103 -0
- package/dist/client/policies.js.map +1 -0
- package/dist/client/users.d.ts +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy-issuers.d.ts +65 -0
- package/dist/legacy-issuers.js +140 -0
- package/dist/legacy-issuers.js.map +1 -0
- package/dist/policy-source.d.ts +104 -0
- package/dist/policy-source.js +99 -0
- package/dist/policy-source.js.map +1 -0
- package/dist/verify.d.ts +22 -0
- package/dist/verify.js +32 -0
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlasauth/backend changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 — 2026-10-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Policy acceptance: `policies` (`list`, `create`, `get`, `update`, `delete`, `versions.list/create/get/update/delete/publish/retire`, `listAcceptances`, `exportAcceptancesCsv`, `forUser`, `settings.get/update` for the `pol` session claim) and `policySource` (`get`, `update`, `delete`, `test`, `sync`, `listDeliveries`) for the external policy source.
|
|
8
|
+
- Policy source protocol v1 helpers for the system that serves policies: `verifyPolicyFeedRequest()` (Atlas's signed `GET {endpoint}/policies`) and `signPolicySourceEvent()` (`policy.published` / `policy.retired`), with the `PolicyFeed` types.
|
|
9
|
+
- `RecordUserConsentBody` / `UserConsent`: `policy_version_id`, `content_sha256`, `display`, `scrolled_to_end`, `view_ms`, `locale`.
|
|
10
|
+
|
|
11
|
+
## 0.17.0 — 2026-10-24
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Better Auth session bridge: `exchangeBetterAuthSession()` exchanges a Better Auth session for an Atlas session server to server with your secret key (forwarding the user's IP and user agent), plus `migrations.betterAuthBridge` to read and change the bridge settings.
|
|
16
|
+
- `legacyIssuers` on `AtlasBackend`: accept Better Auth-issued JWTs for a bounded window during a migration, mapping `sub` to the Atlas user through `external_id`.
|
|
17
|
+
- `betterAuthImports`: migrate from Better Auth with a sealed bundle (`getPublicKey`, `rotatePublicKey`, `create`, `uploadPart`, `finalize`, `upload` for all three, `dryRun`, `apply` to import or resume, `get`, `list`, `waitFor`, `delete`, `listJwks`, `deleteJwk`), with `BetterAuthImport`, `BetterAuthImportOptions`, the report types and `splitBundleParts`.
|
|
18
|
+
|
|
3
19
|
## 0.16.0 — 2026-10-24
|
|
4
20
|
|
|
5
21
|
### Added
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving from Better Auth to Atlas without signing anyone out.
|
|
3
|
+
*
|
|
4
|
+
* During the migration window a returning user still carries their Better Auth
|
|
5
|
+
* session cookie (`better-auth.session_token`, `__Secure-` prefixed over
|
|
6
|
+
* https). It is HttpOnly, so only your server can read it. The bridge reads it,
|
|
7
|
+
* exchanges it ONCE with Atlas (`POST /v1/client/migrations/better_auth/exchange`),
|
|
8
|
+
* sets the Atlas session as your own first-party cookies and clears the Better
|
|
9
|
+
* Auth cookies. Atlas finds the session in your import (or asks your old app,
|
|
10
|
+
* with the live fallback on) and runs the normal sign-in path on it — MFA
|
|
11
|
+
* policy included: a user who still owes a second factor is sent to finish it.
|
|
12
|
+
*
|
|
13
|
+
* Framework-neutral: {@link createBetterAuthBridge} returns a decision with the
|
|
14
|
+
* cookies to set and clear; the Next.js middleware and the Express / Fastify /
|
|
15
|
+
* Hono adapters render it.
|
|
16
|
+
*/
|
|
17
|
+
/** Better Auth's default cookie prefix (`advanced.cookiePrefix`). */
|
|
18
|
+
export declare const BETTER_AUTH_COOKIE_PREFIX = "better-auth";
|
|
19
|
+
/** The FAPI path of the exchange (publishable key; per-IP limited — the browser / fallback path). */
|
|
20
|
+
export declare const BETTER_AUTH_EXCHANGE_PATH = "/v1/client/migrations/better_auth/exchange";
|
|
21
|
+
/** The Backend API path (secret key; limited per instance for a bulk cutover — the server path). */
|
|
22
|
+
export declare const BETTER_AUTH_BAPI_EXCHANGE_PATH = "/v1/migrations/better_auth/exchange";
|
|
23
|
+
/** A cookie to set (or, with `maxAge: 0`, clear) on the response. */
|
|
24
|
+
export interface BridgeCookie {
|
|
25
|
+
name: string;
|
|
26
|
+
value: string;
|
|
27
|
+
options: {
|
|
28
|
+
path?: string;
|
|
29
|
+
domain?: string;
|
|
30
|
+
httpOnly?: boolean;
|
|
31
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
32
|
+
secure?: boolean;
|
|
33
|
+
maxAge?: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The Better Auth cookies the bridge reads (session_token) and clears (all of them). */
|
|
37
|
+
export declare function betterAuthCookieNames(prefix?: string): {
|
|
38
|
+
sessionToken: string[];
|
|
39
|
+
all: string[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The raw Better Auth session cookie value from a `Cookie` header (the
|
|
43
|
+
* `__Secure-` name first), or undefined. `cookieName` overrides the name when
|
|
44
|
+
* the old app set `advanced.cookies.session_token.name`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readBetterAuthSessionCookie(cookieHeader: string | null | undefined, options?: {
|
|
47
|
+
cookiePrefix?: string;
|
|
48
|
+
cookieName?: string;
|
|
49
|
+
}): string | undefined;
|
|
50
|
+
/** Why Atlas refused an exchange (`meta.bridge_outcome`). */
|
|
51
|
+
export type BetterAuthRefusal = 'closed' | 'invalid' | 'signature_invalid' | 'expired' | 'reused' | 'user_unavailable' | 'rate_limited' | 'unavailable';
|
|
52
|
+
export type BetterAuthExchangeResult = {
|
|
53
|
+
kind: 'session';
|
|
54
|
+
jwt: string;
|
|
55
|
+
refreshToken: string;
|
|
56
|
+
sessionId: string;
|
|
57
|
+
userId: string;
|
|
58
|
+
expiresIn: number;
|
|
59
|
+
} | {
|
|
60
|
+
/** The user still owes a second factor (or MFA enrolment) — the Better Auth session is spent. */
|
|
61
|
+
kind: 'needs_mfa';
|
|
62
|
+
attemptId: string;
|
|
63
|
+
status: 'needs_second_factor' | 'needs_mfa_enrollment';
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'refused';
|
|
66
|
+
outcome: BetterAuthRefusal;
|
|
67
|
+
status: number;
|
|
68
|
+
};
|
|
69
|
+
export interface ExchangeBetterAuthSessionOptions {
|
|
70
|
+
/** The instance's Frontend API origin, e.g. `https://clerk.example.com` or your FAPI host. */
|
|
71
|
+
fapiOrigin: string;
|
|
72
|
+
publishableKey: string;
|
|
73
|
+
/**
|
|
74
|
+
* The instance secret key. When set (recommended on a server), the exchange
|
|
75
|
+
* goes to the Backend API, which is limited per instance with a ceiling sized
|
|
76
|
+
* for a bulk cutover instead of per IP — your server speaks for every user —
|
|
77
|
+
* and takes the user's IP and user agent from `clientIp` / `userAgent`.
|
|
78
|
+
*/
|
|
79
|
+
secretKey?: string;
|
|
80
|
+
/** Backend API origin for the secret-key path. Default `https://api.atlasauth.net`. */
|
|
81
|
+
bapiBaseUrl?: string;
|
|
82
|
+
/** The end user's IP (secret-key path), so Atlas's risk and geo checks see the user, not your server. */
|
|
83
|
+
clientIp?: string;
|
|
84
|
+
/** The raw Better Auth session cookie value (or the bare token). */
|
|
85
|
+
token: string;
|
|
86
|
+
/** false → the Atlas refresh cookie should be a browser-session cookie. */
|
|
87
|
+
remember?: boolean;
|
|
88
|
+
fetchImpl?: typeof fetch;
|
|
89
|
+
/** Forwarded so the session's device record shows the user's browser, not your server. */
|
|
90
|
+
userAgent?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Exchange a Better Auth session for an Atlas session, server to server
|
|
94
|
+
* (`delivery: "body"`). Never throws: a transport failure is
|
|
95
|
+
* `{ kind: 'refused', outcome: 'unavailable' }`, so the caller keeps the Better
|
|
96
|
+
* Auth cookie and tries again on a later request.
|
|
97
|
+
*/
|
|
98
|
+
export declare function exchangeBetterAuthSession(options: ExchangeBetterAuthSessionOptions): Promise<BetterAuthExchangeResult>;
|
|
99
|
+
export interface BetterAuthBridgeOptions {
|
|
100
|
+
fapiOrigin: string;
|
|
101
|
+
publishableKey: string;
|
|
102
|
+
/** The instance secret key: exchanges go through the Backend API (recommended; see {@link ExchangeBetterAuthSessionOptions.secretKey}). */
|
|
103
|
+
secretKey?: string;
|
|
104
|
+
bapiBaseUrl?: string;
|
|
105
|
+
/** Better Auth's `advanced.cookiePrefix` (default `better-auth`). */
|
|
106
|
+
cookiePrefix?: string;
|
|
107
|
+
/** The session cookie's full name, when the old app overrode it. */
|
|
108
|
+
cookieName?: string;
|
|
109
|
+
/** The `domain` the Better Auth cookies were set on (its `crossSubDomainCookies.domain`), so they clear. */
|
|
110
|
+
betterAuthCookieDomain?: string;
|
|
111
|
+
/** Mark the Atlas cookies `Secure`. Default true; set false only for plain-http local development. */
|
|
112
|
+
secure?: boolean;
|
|
113
|
+
fetchImpl?: typeof fetch;
|
|
114
|
+
}
|
|
115
|
+
/** What the bridge decided for one request. */
|
|
116
|
+
export type BetterAuthBridgeDecision =
|
|
117
|
+
/** No Better Auth cookie, or an Atlas session already present: carry on. */
|
|
118
|
+
{
|
|
119
|
+
action: 'none';
|
|
120
|
+
}
|
|
121
|
+
/** Exchanged: set these cookies and re-enter (redirect to the same URL) so the new session is read. */
|
|
122
|
+
| {
|
|
123
|
+
action: 'signed_in';
|
|
124
|
+
cookies: BridgeCookie[];
|
|
125
|
+
session: Extract<BetterAuthExchangeResult, {
|
|
126
|
+
kind: 'session';
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
/** A second factor is owed: send the browser to your sign-in page carrying the attempt (see `continuationUrl`). */
|
|
130
|
+
| {
|
|
131
|
+
action: 'needs_mfa';
|
|
132
|
+
cookies: BridgeCookie[];
|
|
133
|
+
attemptId: string;
|
|
134
|
+
status: string;
|
|
135
|
+
continuationUrl: (url: string) => string;
|
|
136
|
+
}
|
|
137
|
+
/** Refused. `cookies` clears the Better Auth cookies when the refusal is final (invalid, expired, reused). */
|
|
138
|
+
| {
|
|
139
|
+
action: 'refused';
|
|
140
|
+
cookies: BridgeCookie[];
|
|
141
|
+
outcome: BetterAuthRefusal;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* The bridge as a function of a request's headers. Call it from middleware
|
|
145
|
+
* before your normal auth check; it does nothing (no network) for a request
|
|
146
|
+
* without a Better Auth cookie or with an Atlas session (`__session` /
|
|
147
|
+
* `__atlas_rt`) already present.
|
|
148
|
+
*/
|
|
149
|
+
export declare function createBetterAuthBridge(options: BetterAuthBridgeOptions): (request: {
|
|
150
|
+
headers: {
|
|
151
|
+
get(name: string): string | null;
|
|
152
|
+
} | Record<string, string | string[] | undefined>;
|
|
153
|
+
/** The end user's IP as your framework resolved it (forwarded on the secret-key path). */
|
|
154
|
+
ip?: string;
|
|
155
|
+
}) => Promise<BetterAuthBridgeDecision>;
|
|
156
|
+
/** Serialize a {@link BridgeCookie} as a `Set-Cookie` header value. */
|
|
157
|
+
export declare function serializeBridgeCookie(cookie: BridgeCookie): string;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Moving from Better Auth to Atlas without signing anyone out.
|
|
4
|
+
*
|
|
5
|
+
* During the migration window a returning user still carries their Better Auth
|
|
6
|
+
* session cookie (`better-auth.session_token`, `__Secure-` prefixed over
|
|
7
|
+
* https). It is HttpOnly, so only your server can read it. The bridge reads it,
|
|
8
|
+
* exchanges it ONCE with Atlas (`POST /v1/client/migrations/better_auth/exchange`),
|
|
9
|
+
* sets the Atlas session as your own first-party cookies and clears the Better
|
|
10
|
+
* Auth cookies. Atlas finds the session in your import (or asks your old app,
|
|
11
|
+
* with the live fallback on) and runs the normal sign-in path on it — MFA
|
|
12
|
+
* policy included: a user who still owes a second factor is sent to finish it.
|
|
13
|
+
*
|
|
14
|
+
* Framework-neutral: {@link createBetterAuthBridge} returns a decision with the
|
|
15
|
+
* cookies to set and clear; the Next.js middleware and the Express / Fastify /
|
|
16
|
+
* Hono adapters render it.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.BETTER_AUTH_BAPI_EXCHANGE_PATH = exports.BETTER_AUTH_EXCHANGE_PATH = exports.BETTER_AUTH_COOKIE_PREFIX = void 0;
|
|
20
|
+
exports.betterAuthCookieNames = betterAuthCookieNames;
|
|
21
|
+
exports.readBetterAuthSessionCookie = readBetterAuthSessionCookie;
|
|
22
|
+
exports.exchangeBetterAuthSession = exchangeBetterAuthSession;
|
|
23
|
+
exports.createBetterAuthBridge = createBetterAuthBridge;
|
|
24
|
+
exports.serializeBridgeCookie = serializeBridgeCookie;
|
|
25
|
+
/** Better Auth's default cookie prefix (`advanced.cookiePrefix`). */
|
|
26
|
+
exports.BETTER_AUTH_COOKIE_PREFIX = 'better-auth';
|
|
27
|
+
/** The FAPI path of the exchange (publishable key; per-IP limited — the browser / fallback path). */
|
|
28
|
+
exports.BETTER_AUTH_EXCHANGE_PATH = '/v1/client/migrations/better_auth/exchange';
|
|
29
|
+
/** The Backend API path (secret key; limited per instance for a bulk cutover — the server path). */
|
|
30
|
+
exports.BETTER_AUTH_BAPI_EXCHANGE_PATH = '/v1/migrations/better_auth/exchange';
|
|
31
|
+
/** Default Backend API origin. */
|
|
32
|
+
const DEFAULT_BAPI_URL = 'https://api.atlasauth.net';
|
|
33
|
+
/** The Better Auth cookies the bridge reads (session_token) and clears (all of them). */
|
|
34
|
+
function betterAuthCookieNames(prefix = exports.BETTER_AUTH_COOKIE_PREFIX) {
|
|
35
|
+
const base = ['session_token', 'session_data', 'account_data', 'dont_remember'].map((n) => `${prefix}.${n}`);
|
|
36
|
+
const secure = base.map((n) => `__Secure-${n}`);
|
|
37
|
+
return {
|
|
38
|
+
sessionToken: [`__Secure-${prefix}.session_token`, `${prefix}.session_token`],
|
|
39
|
+
all: [...secure, ...base],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function readCookie(header, name) {
|
|
43
|
+
if (!header)
|
|
44
|
+
return undefined;
|
|
45
|
+
for (const part of header.split(';')) {
|
|
46
|
+
const [key, ...rest] = part.trim().split('=');
|
|
47
|
+
if (key === name)
|
|
48
|
+
return rest.join('=');
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The raw Better Auth session cookie value from a `Cookie` header (the
|
|
54
|
+
* `__Secure-` name first), or undefined. `cookieName` overrides the name when
|
|
55
|
+
* the old app set `advanced.cookies.session_token.name`.
|
|
56
|
+
*/
|
|
57
|
+
function readBetterAuthSessionCookie(cookieHeader, options = {}) {
|
|
58
|
+
const names = options.cookieName
|
|
59
|
+
? [`__Secure-${options.cookieName}`, options.cookieName]
|
|
60
|
+
: betterAuthCookieNames(options.cookiePrefix).sessionToken;
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const value = readCookie(cookieHeader, name);
|
|
63
|
+
if (value)
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Exchange a Better Auth session for an Atlas session, server to server
|
|
70
|
+
* (`delivery: "body"`). Never throws: a transport failure is
|
|
71
|
+
* `{ kind: 'refused', outcome: 'unavailable' }`, so the caller keeps the Better
|
|
72
|
+
* Auth cookie and tries again on a later request.
|
|
73
|
+
*/
|
|
74
|
+
async function exchangeBetterAuthSession(options) {
|
|
75
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
76
|
+
const viaSecretKey = Boolean(options.secretKey);
|
|
77
|
+
const url = viaSecretKey
|
|
78
|
+
? `${(options.bapiBaseUrl ?? DEFAULT_BAPI_URL).replace(/\/+$/, '')}${exports.BETTER_AUTH_BAPI_EXCHANGE_PATH}`
|
|
79
|
+
: new URL(exports.BETTER_AUTH_EXCHANGE_PATH, options.fapiOrigin).toString();
|
|
80
|
+
const headers = { 'content-type': 'application/json' };
|
|
81
|
+
if (viaSecretKey) {
|
|
82
|
+
headers.authorization = `Bearer ${options.secretKey}`;
|
|
83
|
+
if (options.clientIp)
|
|
84
|
+
headers['x-atlas-client-ip'] = options.clientIp;
|
|
85
|
+
if (options.userAgent)
|
|
86
|
+
headers['x-atlas-client-user-agent'] = options.userAgent;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
headers['x-publishable-key'] = options.publishableKey;
|
|
90
|
+
if (options.userAgent)
|
|
91
|
+
headers['user-agent'] = options.userAgent;
|
|
92
|
+
}
|
|
93
|
+
let res;
|
|
94
|
+
try {
|
|
95
|
+
res = await doFetch(url, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers,
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
token: options.token,
|
|
100
|
+
...(viaSecretKey ? {} : { delivery: 'body' }),
|
|
101
|
+
...(options.remember === false ? { remember: false } : {}),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { kind: 'refused', outcome: 'unavailable', status: 0 };
|
|
107
|
+
}
|
|
108
|
+
const data = (await res.json().catch(() => null));
|
|
109
|
+
if (res.ok && data?.object === 'session_tokens' && data.jwt && data.refresh_token) {
|
|
110
|
+
return {
|
|
111
|
+
kind: 'session',
|
|
112
|
+
jwt: data.jwt,
|
|
113
|
+
refreshToken: data.refresh_token,
|
|
114
|
+
sessionId: data.session_id ?? '',
|
|
115
|
+
userId: data.user_id ?? '',
|
|
116
|
+
expiresIn: data.expires_in ?? 0,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (res.ok &&
|
|
120
|
+
data?.object === 'sign_in_attempt' &&
|
|
121
|
+
data.id &&
|
|
122
|
+
(data.status === 'needs_second_factor' || data.status === 'needs_mfa_enrollment')) {
|
|
123
|
+
return { kind: 'needs_mfa', attemptId: data.id, status: data.status };
|
|
124
|
+
}
|
|
125
|
+
if (res.status === 429)
|
|
126
|
+
return { kind: 'refused', outcome: 'rate_limited', status: 429 };
|
|
127
|
+
const outcome = data?.errors?.[0]?.meta?.bridge_outcome;
|
|
128
|
+
const known = ['closed', 'invalid', 'signature_invalid', 'expired', 'reused', 'user_unavailable', 'rate_limited'];
|
|
129
|
+
if (outcome && known.includes(outcome)) {
|
|
130
|
+
return { kind: 'refused', outcome: outcome, status: res.status };
|
|
131
|
+
}
|
|
132
|
+
// Anything else (a 5xx, a guard refusal, an unparsable body) is not a verdict
|
|
133
|
+
// on the Better Auth session: keep it and retry later.
|
|
134
|
+
return { kind: 'refused', outcome: 'unavailable', status: res.status };
|
|
135
|
+
}
|
|
136
|
+
/** Refusals that say the Better Auth session will never exchange: its cookies are cleared. */
|
|
137
|
+
const FINAL = ['invalid', 'signature_invalid', 'expired', 'reused', 'user_unavailable'];
|
|
138
|
+
/**
|
|
139
|
+
* The bridge as a function of a request's headers. Call it from middleware
|
|
140
|
+
* before your normal auth check; it does nothing (no network) for a request
|
|
141
|
+
* without a Better Auth cookie or with an Atlas session (`__session` /
|
|
142
|
+
* `__atlas_rt`) already present.
|
|
143
|
+
*/
|
|
144
|
+
function createBetterAuthBridge(options) {
|
|
145
|
+
const secure = options.secure !== false;
|
|
146
|
+
const clearBetterAuth = () => betterAuthCookieNames(options.cookiePrefix).all.concat(options.cookieName ? [options.cookieName, `__Secure-${options.cookieName}`] : []).map((name) => ({
|
|
147
|
+
name,
|
|
148
|
+
value: '',
|
|
149
|
+
options: {
|
|
150
|
+
path: '/',
|
|
151
|
+
maxAge: 0,
|
|
152
|
+
...(options.betterAuthCookieDomain ? { domain: options.betterAuthCookieDomain } : {}),
|
|
153
|
+
// A `__Secure-` cookie can only be overwritten by a Secure Set-Cookie.
|
|
154
|
+
...(name.startsWith('__Secure-') ? { secure: true } : {}),
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
return async function bridge(request) {
|
|
158
|
+
const cookieHeader = header(request.headers, 'cookie');
|
|
159
|
+
const token = readBetterAuthSessionCookie(cookieHeader, options);
|
|
160
|
+
if (!token)
|
|
161
|
+
return { action: 'none' };
|
|
162
|
+
// Already on Atlas: leave the old cookie for the old app's own expiry.
|
|
163
|
+
if (readCookie(cookieHeader, '__session') || readCookie(cookieHeader, '__atlas_rt'))
|
|
164
|
+
return { action: 'none' };
|
|
165
|
+
const result = await exchangeBetterAuthSession({
|
|
166
|
+
fapiOrigin: options.fapiOrigin,
|
|
167
|
+
publishableKey: options.publishableKey,
|
|
168
|
+
token,
|
|
169
|
+
fetchImpl: options.fetchImpl,
|
|
170
|
+
userAgent: header(request.headers, 'user-agent'),
|
|
171
|
+
secretKey: options.secretKey,
|
|
172
|
+
bapiBaseUrl: options.bapiBaseUrl,
|
|
173
|
+
clientIp: request.ip ?? clientIpFrom(request.headers),
|
|
174
|
+
});
|
|
175
|
+
if (result.kind === 'session') {
|
|
176
|
+
return {
|
|
177
|
+
action: 'signed_in',
|
|
178
|
+
session: result,
|
|
179
|
+
cookies: [
|
|
180
|
+
{ name: '__session', value: result.jwt, options: { path: '/', sameSite: 'lax', secure } },
|
|
181
|
+
{
|
|
182
|
+
name: '__atlas_rt',
|
|
183
|
+
value: result.refreshToken,
|
|
184
|
+
options: { path: '/', httpOnly: true, sameSite: 'lax', secure },
|
|
185
|
+
},
|
|
186
|
+
...clearBetterAuth(),
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (result.kind === 'needs_mfa') {
|
|
191
|
+
return {
|
|
192
|
+
action: 'needs_mfa',
|
|
193
|
+
attemptId: result.attemptId,
|
|
194
|
+
status: result.status,
|
|
195
|
+
cookies: clearBetterAuth(),
|
|
196
|
+
continuationUrl: (url) => {
|
|
197
|
+
const next = new URL(url);
|
|
198
|
+
next.searchParams.set('__atlas_attempt', result.attemptId);
|
|
199
|
+
next.searchParams.set('__atlas_status', result.status);
|
|
200
|
+
return next.toString();
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
action: 'refused',
|
|
206
|
+
outcome: result.outcome,
|
|
207
|
+
cookies: FINAL.includes(result.outcome) ? clearBetterAuth() : [],
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function header(headers, name) {
|
|
212
|
+
if (typeof headers.get === 'function') {
|
|
213
|
+
return headers.get(name) ?? undefined;
|
|
214
|
+
}
|
|
215
|
+
const record = headers;
|
|
216
|
+
const value = record[name] ?? record[name.toLowerCase()];
|
|
217
|
+
return Array.isArray(value) ? value.join('; ') : value;
|
|
218
|
+
}
|
|
219
|
+
/** Serialize a {@link BridgeCookie} as a `Set-Cookie` header value. */
|
|
220
|
+
function serializeBridgeCookie(cookie) {
|
|
221
|
+
const o = cookie.options;
|
|
222
|
+
const parts = [`${cookie.name}=${cookie.value}`];
|
|
223
|
+
parts.push(`Path=${o.path ?? '/'}`);
|
|
224
|
+
if (o.domain)
|
|
225
|
+
parts.push(`Domain=${o.domain}`);
|
|
226
|
+
if (o.maxAge !== undefined) {
|
|
227
|
+
parts.push(`Max-Age=${o.maxAge}`);
|
|
228
|
+
if (o.maxAge === 0)
|
|
229
|
+
parts.push('Expires=Thu, 01 Jan 1970 00:00:00 GMT');
|
|
230
|
+
}
|
|
231
|
+
if (o.httpOnly)
|
|
232
|
+
parts.push('HttpOnly');
|
|
233
|
+
if (o.secure)
|
|
234
|
+
parts.push('Secure');
|
|
235
|
+
if (o.sameSite)
|
|
236
|
+
parts.push(`SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`);
|
|
237
|
+
return parts.join('; ');
|
|
238
|
+
}
|
|
239
|
+
/** The client IP a proxy in front of your server recorded (first X-Forwarded-For hop, or X-Real-IP). */
|
|
240
|
+
function clientIpFrom(headers) {
|
|
241
|
+
const xff = header(headers, 'x-forwarded-for');
|
|
242
|
+
const first = xff?.split(',')[0]?.trim();
|
|
243
|
+
return first || header(headers, 'x-real-ip')?.trim() || undefined;
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=better-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../src/better-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { type RequestFn } from './request';
|
|
2
|
+
import type { CursorPage, CursorParams } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Migrate from Better Auth: import a sealed bundle made by
|
|
5
|
+
* `@atlasauth/better-auth-migrate` (`npx atlas-better-auth-export`).
|
|
6
|
+
*
|
|
7
|
+
* 1. `getPublicKey()` — the key the exporter seals the bundle to.
|
|
8
|
+
* 2. `create()`, `uploadPart()` for each part, `finalize()` — or `upload()`
|
|
9
|
+
* for all three. Atlas opens the bundle and runs the dry run.
|
|
10
|
+
* 3. Read the import's `report`, then `apply()`. Applying again after a
|
|
11
|
+
* failure resumes where it stopped; every record is an idempotent upsert.
|
|
12
|
+
*/
|
|
13
|
+
export type BetterAuthImportStatus = 'uploading' | 'analyzing' | 'ready' | 'invalid' | 'queued' | 'applying' | 'completed' | 'failed';
|
|
14
|
+
export interface BetterAuthImportKey {
|
|
15
|
+
object: 'better_auth_import_key';
|
|
16
|
+
kid: string;
|
|
17
|
+
/** base64url raw X25519 public key. */
|
|
18
|
+
public_key: string;
|
|
19
|
+
alg: 'X25519-HKDF-SHA256-A256GCM';
|
|
20
|
+
status: 'active' | 'retired';
|
|
21
|
+
/** SHA-256 of the raw key, first 16 bytes as hex pairs. */
|
|
22
|
+
fingerprint: string;
|
|
23
|
+
/** `kid:public_key`, the form `--public-key` takes. */
|
|
24
|
+
compact: string;
|
|
25
|
+
created_at: number;
|
|
26
|
+
retired_at: number | null;
|
|
27
|
+
}
|
|
28
|
+
export interface BetterAuthImportOptions {
|
|
29
|
+
models?: string[];
|
|
30
|
+
on_email_conflict?: 'skip' | 'link';
|
|
31
|
+
default_role?: string;
|
|
32
|
+
create_app_roles?: boolean;
|
|
33
|
+
import_oauth_tokens?: boolean;
|
|
34
|
+
passkeys_on_rp_mismatch?: 'skip' | 'import';
|
|
35
|
+
import_subscriptions?: boolean;
|
|
36
|
+
plan_map?: Record<string, string>;
|
|
37
|
+
import_jwks?: boolean;
|
|
38
|
+
jwks_cutover_days?: number;
|
|
39
|
+
import_sessions?: boolean;
|
|
40
|
+
retain_bundle?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface BetterAuthImportActionItem {
|
|
43
|
+
code: string;
|
|
44
|
+
severity: 'required' | 'recommended' | 'info';
|
|
45
|
+
message: string;
|
|
46
|
+
count: number;
|
|
47
|
+
items: string[];
|
|
48
|
+
}
|
|
49
|
+
interface ReportCommon {
|
|
50
|
+
skipped: Array<{
|
|
51
|
+
model: string;
|
|
52
|
+
row_id: string | null;
|
|
53
|
+
reason_code: string;
|
|
54
|
+
reason: string;
|
|
55
|
+
}>;
|
|
56
|
+
skipped_total: number;
|
|
57
|
+
skipped_by_code: Record<string, number>;
|
|
58
|
+
conflicts: Array<{
|
|
59
|
+
model: string;
|
|
60
|
+
row_id: string | null;
|
|
61
|
+
code: string;
|
|
62
|
+
message: string;
|
|
63
|
+
resolution: 'merge' | 'skip';
|
|
64
|
+
existing_id: string | null;
|
|
65
|
+
value?: string;
|
|
66
|
+
}>;
|
|
67
|
+
conflicts_total: number;
|
|
68
|
+
action_items: BetterAuthImportActionItem[];
|
|
69
|
+
warnings: string[];
|
|
70
|
+
notes: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
export interface BetterAuthImportReport extends ReportCommon {
|
|
73
|
+
models: Array<{
|
|
74
|
+
model: string;
|
|
75
|
+
label: string;
|
|
76
|
+
found: number;
|
|
77
|
+
will_create: number;
|
|
78
|
+
will_update: number;
|
|
79
|
+
will_skip: number;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
export interface BetterAuthImportResult extends ReportCommon {
|
|
83
|
+
models: Array<{
|
|
84
|
+
model: string;
|
|
85
|
+
label: string;
|
|
86
|
+
found: number;
|
|
87
|
+
created: number;
|
|
88
|
+
updated: number;
|
|
89
|
+
unchanged: number;
|
|
90
|
+
skipped: number;
|
|
91
|
+
failed: number;
|
|
92
|
+
}>;
|
|
93
|
+
completed_at: number | null;
|
|
94
|
+
}
|
|
95
|
+
export interface BetterAuthImport {
|
|
96
|
+
object: 'better_auth_import';
|
|
97
|
+
id: string;
|
|
98
|
+
status: BetterAuthImportStatus;
|
|
99
|
+
key_id: string | null;
|
|
100
|
+
parts: number;
|
|
101
|
+
bytes: number;
|
|
102
|
+
bundle_sha256: string | null;
|
|
103
|
+
bundle_purged: boolean;
|
|
104
|
+
options: BetterAuthImportOptions;
|
|
105
|
+
source: {
|
|
106
|
+
exported_at: string;
|
|
107
|
+
exporter: {
|
|
108
|
+
name: string;
|
|
109
|
+
version: string;
|
|
110
|
+
};
|
|
111
|
+
better_auth_version: string | null;
|
|
112
|
+
base_url: string | null;
|
|
113
|
+
plugins: string[];
|
|
114
|
+
models: string[];
|
|
115
|
+
rp_id: string | null;
|
|
116
|
+
social_providers: string[];
|
|
117
|
+
} | null;
|
|
118
|
+
total: number;
|
|
119
|
+
progress: {
|
|
120
|
+
total: number;
|
|
121
|
+
processed: number;
|
|
122
|
+
succeeded: number;
|
|
123
|
+
failed: number;
|
|
124
|
+
percent: number;
|
|
125
|
+
} | null;
|
|
126
|
+
report: BetterAuthImportReport | null;
|
|
127
|
+
result: BetterAuthImportResult | null;
|
|
128
|
+
error: {
|
|
129
|
+
code: string;
|
|
130
|
+
message: string;
|
|
131
|
+
} | null;
|
|
132
|
+
created_at: number;
|
|
133
|
+
updated_at: number;
|
|
134
|
+
analyzed_at: number | null;
|
|
135
|
+
apply_started_at: number | null;
|
|
136
|
+
completed_at: number | null;
|
|
137
|
+
}
|
|
138
|
+
export interface ImportedJwk {
|
|
139
|
+
object: 'imported_jwk';
|
|
140
|
+
kid: string;
|
|
141
|
+
source: string;
|
|
142
|
+
jwk: Record<string, unknown>;
|
|
143
|
+
import_id: string | null;
|
|
144
|
+
expires_at: number | null;
|
|
145
|
+
created_at: number;
|
|
146
|
+
}
|
|
147
|
+
/** Bytes per uploaded part (the API takes at most 10 MiB). Parts end on a line boundary. */
|
|
148
|
+
export declare const BETTER_AUTH_IMPORT_PART_BYTES: number;
|
|
149
|
+
/** Split a sealed bundle's text into upload parts on line boundaries. */
|
|
150
|
+
export declare function splitBundleParts(text: string, maxBytes?: number): string[];
|
|
151
|
+
export declare function betterAuthImportsResource(request: RequestFn): {
|
|
152
|
+
/** The import public key (created on first read). */
|
|
153
|
+
getPublicKey(): Promise<BetterAuthImportKey>;
|
|
154
|
+
/** Replace the import key; bundles sealed to the old one no longer finalize. */
|
|
155
|
+
rotatePublicKey(): Promise<BetterAuthImportKey>;
|
|
156
|
+
create(body?: {
|
|
157
|
+
options?: BetterAuthImportOptions;
|
|
158
|
+
}, idempotencyKey?: string): Promise<BetterAuthImport>;
|
|
159
|
+
list(params?: CursorParams): Promise<CursorPage<BetterAuthImport>>;
|
|
160
|
+
get(id: string): Promise<BetterAuthImport>;
|
|
161
|
+
/** Upload part `part` (1-based) of the sealed bundle text. */
|
|
162
|
+
uploadPart(id: string, part: number, data: string): Promise<{
|
|
163
|
+
object: "better_auth_import_part";
|
|
164
|
+
import_id: string;
|
|
165
|
+
part: number;
|
|
166
|
+
bytes: number;
|
|
167
|
+
sha256: string;
|
|
168
|
+
}>;
|
|
169
|
+
/** Close the upload; Atlas checks the key and runs the dry run. */
|
|
170
|
+
finalize(id: string): Promise<BetterAuthImport>;
|
|
171
|
+
/** Run the dry run again, optionally with new options. */
|
|
172
|
+
dryRun(id: string, body?: {
|
|
173
|
+
options?: BetterAuthImportOptions;
|
|
174
|
+
}): Promise<BetterAuthImport>;
|
|
175
|
+
/** Import (or resume). Pass confirm_conflicts when the dry run reported conflicts. */
|
|
176
|
+
apply(id: string, body?: {
|
|
177
|
+
confirm_conflicts?: boolean;
|
|
178
|
+
}): Promise<BetterAuthImport>;
|
|
179
|
+
delete(id: string): Promise<{
|
|
180
|
+
object: "better_auth_import";
|
|
181
|
+
id: string;
|
|
182
|
+
deleted: true;
|
|
183
|
+
}>;
|
|
184
|
+
/** Better Auth JWT keys published in the instance JWKS for the cutover. */
|
|
185
|
+
listJwks(): Promise<CursorPage<ImportedJwk>>;
|
|
186
|
+
deleteJwk(kid: string): Promise<{
|
|
187
|
+
object: "imported_jwk";
|
|
188
|
+
kid: string;
|
|
189
|
+
deleted: true;
|
|
190
|
+
}>;
|
|
191
|
+
/** Create an import, upload the sealed bundle text in parts and finalize it. */
|
|
192
|
+
upload(bundleText: string, opts?: {
|
|
193
|
+
options?: BetterAuthImportOptions;
|
|
194
|
+
partBytes?: number;
|
|
195
|
+
onPart?: (n: number, of: number) => void;
|
|
196
|
+
}): Promise<BetterAuthImport>;
|
|
197
|
+
/** Poll until the import leaves analyzing / queued / applying. */
|
|
198
|
+
waitFor(id: string, opts?: {
|
|
199
|
+
intervalMs?: number;
|
|
200
|
+
timeoutMs?: number;
|
|
201
|
+
onProgress?: (imp: BetterAuthImport) => void;
|
|
202
|
+
sleep?: (ms: number) => Promise<void>;
|
|
203
|
+
}): Promise<BetterAuthImport>;
|
|
204
|
+
};
|
|
205
|
+
export {};
|