@lalternative/auth 0.4.6 → 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.
- package/dist/{chunk-73BHM4PZ.js → chunk-6URDBMQY.js} +29 -1
- package/dist/chunk-6URDBMQY.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{invitation-Bucmin-z.d.ts → invitation-aZz_6M7B.d.ts} +49 -10
- package/dist/server.d.ts +3 -3
- package/dist/server.js +14 -3
- package/dist/server.js.map +1 -1
- package/dist/{types-BXyGxKtF.d.ts → types-BEldQPou.d.ts} +11 -0
- package/package.json +1 -1
- package/dist/chunk-73BHM4PZ.js.map +0 -1
|
@@ -37,6 +37,8 @@ async function claimInvitation({
|
|
|
37
37
|
function inviteTokenFrom(request, param = "invite") {
|
|
38
38
|
const direct = new URL(request.url).searchParams.get(param);
|
|
39
39
|
if (direct && direct.trim() !== "") return direct;
|
|
40
|
+
const held = inviteTokenCookie(request);
|
|
41
|
+
if (held) return held;
|
|
40
42
|
const referer = request.headers.get("referer");
|
|
41
43
|
if (!referer) return null;
|
|
42
44
|
try {
|
|
@@ -46,6 +48,29 @@ function inviteTokenFrom(request, param = "invite") {
|
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
var INVITE_TOKEN_COOKIE = "invite_token";
|
|
52
|
+
function pinInviteToken(request, param = "invite") {
|
|
53
|
+
if (inviteTokenCookie(request)) return null;
|
|
54
|
+
const token = inviteTokenFrom(request, param);
|
|
55
|
+
return token ? holdInviteTokenCookie(token) : null;
|
|
56
|
+
}
|
|
57
|
+
function holdInviteTokenCookie(token, maxAgeSeconds = 1800) {
|
|
58
|
+
return `${INVITE_TOKEN_COOKIE}=${encodeURIComponent(token)}; Path=/; Max-Age=${maxAgeSeconds}; HttpOnly; SameSite=Lax`;
|
|
59
|
+
}
|
|
60
|
+
function releaseInviteTokenCookie() {
|
|
61
|
+
return `${INVITE_TOKEN_COOKIE}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax`;
|
|
62
|
+
}
|
|
63
|
+
function inviteTokenCookie(request) {
|
|
64
|
+
const header = request.headers.get("cookie");
|
|
65
|
+
if (!header) return null;
|
|
66
|
+
for (const part of header.split(";")) {
|
|
67
|
+
const [name, ...rest] = part.trim().split("=");
|
|
68
|
+
if (name !== INVITE_TOKEN_COOKIE) continue;
|
|
69
|
+
const value = decodeURIComponent(rest.join("=")).trim();
|
|
70
|
+
return value === "" ? null : value;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
49
74
|
var SIGNUP_COMPLETING = [
|
|
50
75
|
"/sign-up/email",
|
|
51
76
|
"/sign-in/email-otp",
|
|
@@ -65,8 +90,11 @@ function isInvitationFailure(outcome) {
|
|
|
65
90
|
export {
|
|
66
91
|
claimInvitation,
|
|
67
92
|
inviteTokenFrom,
|
|
93
|
+
pinInviteToken,
|
|
94
|
+
holdInviteTokenCookie,
|
|
95
|
+
releaseInviteTokenCookie,
|
|
68
96
|
completesSignup,
|
|
69
97
|
invitationOutcomeCookie,
|
|
70
98
|
isInvitationFailure
|
|
71
99
|
};
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
100
|
+
//# sourceMappingURL=chunk-6URDBMQY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/invitation.ts"],"sourcesContent":["import type { InvitationFailure } from \"./types\"\n\n/**\n * What became of a claim, in terms the invitee can be told.\n *\n * 'expired' and 'claimed' are kept apart from 'unknown' because only they say\n * the offer was real, which is what tells someone that asking for a new link is\n * worth it rather than doubting the address they were invited at. The three\n * failures match InvitationFailure, so an outcome feeds InvitationNotice\n * directly.\n */\nexport type ClaimOutcome = \"granted\" | InvitationFailure | \"failed\"\n\nconst OUTCOME_BY_STATUS: Record<number, ClaimOutcome> = {\n 404: \"unknown\",\n 409: \"claimed\",\n 410: \"expired\",\n}\n\nexport interface ClaimInvitationOptions {\n /** Absolute URL of the endpoint that redeems a token. */\n endpoint: string\n token: string\n /** The account the app just created, which the grant is attached to. */\n externalUserId: string\n /** Sent as the Authorization bearer — typically the app's API key. */\n apiKey?: string\n /** Merged into the request body, for backends wanting more than the token. */\n extra?: Record<string, unknown>\n /** Headers merged last, so a caller can pass a cookie-based credential. */\n headers?: Record<string, string>\n /** Bounds the call so a slow API never stalls the sign-in response. */\n timeoutMs?: number\n}\n\n/**\n * Redeems an invitation token for a user who has just signed in, turning the\n * offer into a grant on their account.\n *\n * Why this belongs on the SERVER, on the auth callback rather than in the page:\n * the invitation link lands on /register?invite=<token>, but the sign-up that\n * follows can complete through any of three flows (password + OTP, OAuth\n * redirect, email verification), and only two of them return to the page that\n * held the token. Claiming where the session is established covers every flow\n * with one code path.\n *\n * Best-effort by design: a sign-in must never fail because an invitation could\n * not be redeemed. A failed claim leaves the invitation unclaimed and the user\n * on their default tier — recoverable by following the link again, since a\n * refused claim consumes nothing.\n */\nexport async function claimInvitation({\n endpoint,\n token,\n externalUserId,\n apiKey,\n extra,\n headers,\n timeoutMs = 5000,\n}: ClaimInvitationOptions): Promise<ClaimOutcome> {\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),\n ...headers,\n },\n body: JSON.stringify({\n token,\n external_user_id: externalUserId,\n ...extra,\n }),\n signal: AbortSignal.timeout(timeoutMs),\n })\n\n if (res.ok) return \"granted\"\n return OUTCOME_BY_STATUS[res.status] ?? \"failed\"\n } catch {\n return \"failed\"\n }\n}\n\n/**\n * Extracts the invitation token from an auth request.\n *\n * The token lives on the page the invitee landed on (/register?invite=…), never\n * on the auth endpoints themselves, so it has to be recovered from the request\n * that completes the sign-up. Three sources, in order of trust:\n *\n * - the URL, for a callback reached through a redirect whose query string\n * the app controls;\n * - the cookie held since the link was opened, which is the only one that\n * survives an OAuth round trip or an OTP screen that never carried the\n * token (see holdInviteTokenCookie);\n * - the Referer, for the password flow, whose XHR is issued BY the page\n * holding it.\n *\n * The Referer stays last and stays supported: it covers a caller that never\n * held the cookie. It is attacker-controlled input on a best-effort path, so a\n * malformed one is ignored rather than thrown on.\n */\nexport function inviteTokenFrom(\n request: Request,\n param = \"invite\",\n): string | null {\n const direct = new URL(request.url).searchParams.get(param)\n if (direct && direct.trim() !== \"\") return direct\n\n const held = inviteTokenCookie(request)\n if (held) return held\n\n const referer = request.headers.get(\"referer\")\n if (!referer) return null\n try {\n const token = new URL(referer).searchParams.get(param)\n return token && token.trim() !== \"\" ? token : null\n } catch {\n return null\n }\n}\n\n/** Name of the cookie holding the token between the link and the claim. */\nconst INVITE_TOKEN_COOKIE = \"invite_token\"\n\n/**\n * Pins the token onto the browser the first time a request carries it, so the\n * rest of the sign-up can find it.\n *\n * Called on every auth request, not only the ones completing a sign-up: the\n * token is legible on the FIRST call of a flow (the page holding it issues that\n * XHR, so the Referer still has it) and gone by the last (verified from a\n * screen that never held it, or returned from Google). Waiting for the moment\n * the account exists is waiting one request too long.\n *\n * Returns null when there is nothing to pin — no token in the request, or one\n * already held — so a caller can skip the Set-Cookie entirely.\n */\nexport function pinInviteToken(\n request: Request,\n param = \"invite\",\n): string | null {\n if (inviteTokenCookie(request)) return null\n const token = inviteTokenFrom(request, param)\n return token ? holdInviteTokenCookie(token) : null\n}\n\n/**\n * Holds the token from the moment the link is opened until the account exists.\n *\n * The URL and the Referer each cover only part of the ground: the OTP flow\n * verifies from a screen that never carried the token, and an OAuth sign-up\n * comes back from Google with no Referer of ours at all. Both lose it, and the\n * invitee lands on the default tier with the offer still pending.\n *\n * SameSite=Lax rather than Strict: the return from Google is a cross-site\n * top-level navigation, which Strict would refuse — the one case this exists\n * for. HttpOnly because the page has no reason to read it, and short-lived\n * because signing up takes minutes: a single-use invitation has no business\n * sitting in a browser for longer.\n */\nexport function holdInviteTokenCookie(\n token: string,\n maxAgeSeconds = 1800,\n): string {\n return `${INVITE_TOKEN_COOKIE}=${encodeURIComponent(token)}; Path=/; Max-Age=${maxAgeSeconds}; HttpOnly; SameSite=Lax`\n}\n\n/**\n * Clears the held token. Sent once the claim has been attempted: the token is\n * single-use, so keeping it would only replay a call that can no longer\n * succeed.\n */\nexport function releaseInviteTokenCookie(): string {\n return `${INVITE_TOKEN_COOKIE}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax`\n}\n\nfunction inviteTokenCookie(request: Request): string | null {\n const header = request.headers.get(\"cookie\")\n if (!header) return null\n for (const part of header.split(\";\")) {\n const [name, ...rest] = part.trim().split(\"=\")\n if (name !== INVITE_TOKEN_COOKIE) continue\n const value = decodeURIComponent(rest.join(\"=\")).trim()\n return value === \"\" ? null : value\n }\n return null\n}\n\n/**\n * BetterAuth paths that complete a sign-up. Email/password returns the user\n * straight from sign-up/email; the OTP and OAuth flows only produce a usable\n * account once the verification/callback succeeds, so those are the moments\n * worth reacting to.\n */\nconst SIGNUP_COMPLETING = [\n \"/sign-up/email\",\n \"/sign-in/email-otp\",\n \"/email-otp/verify-email\",\n \"/callback/\",\n]\n\n/**\n * Whether this request is the one that just created a usable account — the\n * moment to provision, claim an invitation, or greet someone. Matching on the\n * path rather than on a response body keeps it flow-agnostic: the three\n * sign-up flows return three different shapes.\n */\nexport function completesSignup(pathname: string): boolean {\n return SIGNUP_COMPLETING.some((p) => pathname.includes(p))\n}\n\n/**\n * Carries a failed claim to the next page. The claim happens inside an auth\n * response nobody renders, so its result would otherwise reach only the server\n * log — leaving an invitee on the default tier with no idea their link had\n * lapsed. Short-lived and readable by the page, which reports it and clears it.\n */\nexport function invitationOutcomeCookie(\n outcome: ClaimOutcome,\n name = \"invite_claim\",\n): string {\n return `${name}=${outcome}; Path=/; Max-Age=120; SameSite=Lax`\n}\n\n/**\n * Whether an outcome is one the invitee should be shown a reason for.\n * 'failed' is excluded: it means the call did not complete, so the offer may\n * still be good and telling someone their invitation is invalid would be wrong.\n */\nexport function isInvitationFailure(\n outcome: ClaimOutcome,\n): outcome is InvitationFailure {\n return outcome === \"expired\" || outcome === \"claimed\" || outcome === \"unknown\"\n}\n"],"mappings":";AAaA,IAAM,oBAAkD;AAAA,EACtD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAkCA,eAAsB,gBAAgB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAAkD;AAChD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,UAAU;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG,IAAI,CAAC;AAAA,QACtD,GAAG;AAAA,MACL;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,kBAAkB;AAAA,QAClB,GAAG;AAAA,MACL,CAAC;AAAA,MACD,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AAED,QAAI,IAAI,GAAI,QAAO;AACnB,WAAO,kBAAkB,IAAI,MAAM,KAAK;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAqBO,SAAS,gBACd,SACA,QAAQ,UACO;AACf,QAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,KAAK;AAC1D,MAAI,UAAU,OAAO,KAAK,MAAM,GAAI,QAAO;AAE3C,QAAM,OAAO,kBAAkB,OAAO;AACtC,MAAI,KAAM,QAAO;AAEjB,QAAM,UAAU,QAAQ,QAAQ,IAAI,SAAS;AAC7C,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,UAAM,QAAQ,IAAI,IAAI,OAAO,EAAE,aAAa,IAAI,KAAK;AACrD,WAAO,SAAS,MAAM,KAAK,MAAM,KAAK,QAAQ;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAM,sBAAsB;AAerB,SAAS,eACd,SACA,QAAQ,UACO;AACf,MAAI,kBAAkB,OAAO,EAAG,QAAO;AACvC,QAAM,QAAQ,gBAAgB,SAAS,KAAK;AAC5C,SAAO,QAAQ,sBAAsB,KAAK,IAAI;AAChD;AAgBO,SAAS,sBACd,OACA,gBAAgB,MACR;AACR,SAAO,GAAG,mBAAmB,IAAI,mBAAmB,KAAK,CAAC,qBAAqB,aAAa;AAC9F;AAOO,SAAS,2BAAmC;AACjD,SAAO,GAAG,mBAAmB;AAC/B;AAEA,SAAS,kBAAkB,SAAiC;AAC1D,QAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,CAAC,MAAM,GAAG,IAAI,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AAC7C,QAAI,SAAS,oBAAqB;AAClC,UAAM,QAAQ,mBAAmB,KAAK,KAAK,GAAG,CAAC,EAAE,KAAK;AACtD,WAAO,UAAU,KAAK,OAAO;AAAA,EAC/B;AACA,SAAO;AACT;AAQA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,SAAS,gBAAgB,UAA2B;AACzD,SAAO,kBAAkB,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAC3D;AAQO,SAAS,wBACd,SACA,OAAO,gBACC;AACR,SAAO,GAAG,IAAI,IAAI,OAAO;AAC3B;AAOO,SAAS,oBACd,SAC8B;AAC9B,SAAO,YAAY,aAAa,YAAY,aAAa,YAAY;AACvE;","names":[]}
|
package/dist/client.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps } from './types-
|
|
2
|
-
export { b as InvitationFailure, c as LoginFormLabels, P as PlatformAuthClientConfig, d as PlatformAuthConfig, e as PlatformAuthMailer, f as PlatformAuthMailerArgs, g as PlatformAuthMailerType, h as PlatformSession, i as PlatformSessionData, j as PlatformUser, k as RegisterFormLabels } from './types-
|
|
1
|
+
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps } from './types-BEldQPou.js';
|
|
2
|
+
export { b as InvitationFailure, c as LoginFormLabels, P as PlatformAuthClientConfig, d as PlatformAuthConfig, e as PlatformAuthMailer, f as PlatformAuthMailerArgs, g as PlatformAuthMailerType, h as PlatformSession, i as PlatformSessionData, j as PlatformUser, k as RegisterFormLabels } from './types-BEldQPou.js';
|
|
3
3
|
import * as better_auth_react from 'better-auth/react';
|
|
4
4
|
import * as better_auth from 'better-auth';
|
|
5
5
|
import { PlatformAuthClient } from './client.js';
|
|
6
6
|
import * as react from 'react';
|
|
7
7
|
import { InputHTMLAttributes, ReactNode } from 'react';
|
|
8
|
-
export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-
|
|
8
|
+
export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-aZz_6M7B.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Returns a useSession hook bound to the given auth client.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as InvitationFailure } from './types-
|
|
1
|
+
import { b as InvitationFailure } from './types-BEldQPou.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* What became of a claim, in terms the invitee can be told.
|
|
@@ -47,17 +47,56 @@ declare function claimInvitation({ endpoint, token, externalUserId, apiKey, extr
|
|
|
47
47
|
*
|
|
48
48
|
* The token lives on the page the invitee landed on (/register?invite=…), never
|
|
49
49
|
* on the auth endpoints themselves, so it has to be recovered from the request
|
|
50
|
-
* that completes the sign-up.
|
|
51
|
-
* flow: the URL, for the OAuth callback reached through a redirect whose query
|
|
52
|
-
* string the app controls; and the Referer, for the password and OTP flows,
|
|
53
|
-
* which are XHR calls issued BY that page and therefore carry it.
|
|
50
|
+
* that completes the sign-up. Three sources, in order of trust:
|
|
54
51
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
52
|
+
* - the URL, for a callback reached through a redirect whose query string
|
|
53
|
+
* the app controls;
|
|
54
|
+
* - the cookie held since the link was opened, which is the only one that
|
|
55
|
+
* survives an OAuth round trip or an OTP screen that never carried the
|
|
56
|
+
* token (see holdInviteTokenCookie);
|
|
57
|
+
* - the Referer, for the password flow, whose XHR is issued BY the page
|
|
58
|
+
* holding it.
|
|
59
|
+
*
|
|
60
|
+
* The Referer stays last and stays supported: it covers a caller that never
|
|
61
|
+
* held the cookie. It is attacker-controlled input on a best-effort path, so a
|
|
62
|
+
* malformed one is ignored rather than thrown on.
|
|
59
63
|
*/
|
|
60
64
|
declare function inviteTokenFrom(request: Request, param?: string): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Pins the token onto the browser the first time a request carries it, so the
|
|
67
|
+
* rest of the sign-up can find it.
|
|
68
|
+
*
|
|
69
|
+
* Called on every auth request, not only the ones completing a sign-up: the
|
|
70
|
+
* token is legible on the FIRST call of a flow (the page holding it issues that
|
|
71
|
+
* XHR, so the Referer still has it) and gone by the last (verified from a
|
|
72
|
+
* screen that never held it, or returned from Google). Waiting for the moment
|
|
73
|
+
* the account exists is waiting one request too long.
|
|
74
|
+
*
|
|
75
|
+
* Returns null when there is nothing to pin — no token in the request, or one
|
|
76
|
+
* already held — so a caller can skip the Set-Cookie entirely.
|
|
77
|
+
*/
|
|
78
|
+
declare function pinInviteToken(request: Request, param?: string): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Holds the token from the moment the link is opened until the account exists.
|
|
81
|
+
*
|
|
82
|
+
* The URL and the Referer each cover only part of the ground: the OTP flow
|
|
83
|
+
* verifies from a screen that never carried the token, and an OAuth sign-up
|
|
84
|
+
* comes back from Google with no Referer of ours at all. Both lose it, and the
|
|
85
|
+
* invitee lands on the default tier with the offer still pending.
|
|
86
|
+
*
|
|
87
|
+
* SameSite=Lax rather than Strict: the return from Google is a cross-site
|
|
88
|
+
* top-level navigation, which Strict would refuse — the one case this exists
|
|
89
|
+
* for. HttpOnly because the page has no reason to read it, and short-lived
|
|
90
|
+
* because signing up takes minutes: a single-use invitation has no business
|
|
91
|
+
* sitting in a browser for longer.
|
|
92
|
+
*/
|
|
93
|
+
declare function holdInviteTokenCookie(token: string, maxAgeSeconds?: number): string;
|
|
94
|
+
/**
|
|
95
|
+
* Clears the held token. Sent once the claim has been attempted: the token is
|
|
96
|
+
* single-use, so keeping it would only replay a call that can no longer
|
|
97
|
+
* succeed.
|
|
98
|
+
*/
|
|
99
|
+
declare function releaseInviteTokenCookie(): string;
|
|
61
100
|
/**
|
|
62
101
|
* Whether this request is the one that just created a usable account — the
|
|
63
102
|
* moment to provision, claim an invitation, or greet someone. Matching on the
|
|
@@ -79,4 +118,4 @@ declare function invitationOutcomeCookie(outcome: ClaimOutcome, name?: string):
|
|
|
79
118
|
*/
|
|
80
119
|
declare function isInvitationFailure(outcome: ClaimOutcome): outcome is InvitationFailure;
|
|
81
120
|
|
|
82
|
-
export { type ClaimOutcome as C, type ClaimInvitationOptions as a, completesSignup as b, claimInvitation as c, invitationOutcomeCookie as d, inviteTokenFrom as e, isInvitationFailure as i };
|
|
121
|
+
export { type ClaimOutcome as C, type ClaimInvitationOptions as a, completesSignup as b, claimInvitation as c, invitationOutcomeCookie as d, inviteTokenFrom as e, holdInviteTokenCookie as h, isInvitationFailure as i, pinInviteToken as p, releaseInviteTokenCookie as r };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Auth, BetterAuthOptions } from 'better-auth';
|
|
2
|
-
import { d as PlatformAuthConfig } from './types-
|
|
3
|
-
export { h as PlatformSession, i as PlatformSessionData, j as PlatformUser } from './types-
|
|
4
|
-
export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure } from './invitation-
|
|
2
|
+
import { d as PlatformAuthConfig } from './types-BEldQPou.js';
|
|
3
|
+
export { h as PlatformSession, i as PlatformSessionData, j as PlatformUser } from './types-BEldQPou.js';
|
|
4
|
+
export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, h as holdInviteTokenCookie, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure, p as pinInviteToken, r as releaseInviteTokenCookie } from './invitation-aZz_6M7B.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a Better Auth instance with platform defaults.
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
claimInvitation,
|
|
3
3
|
completesSignup,
|
|
4
|
+
holdInviteTokenCookie,
|
|
4
5
|
invitationOutcomeCookie,
|
|
5
6
|
inviteTokenFrom,
|
|
6
|
-
isInvitationFailure
|
|
7
|
-
|
|
7
|
+
isInvitationFailure,
|
|
8
|
+
pinInviteToken,
|
|
9
|
+
releaseInviteTokenCookie
|
|
10
|
+
} from "./chunk-6URDBMQY.js";
|
|
8
11
|
|
|
9
12
|
// src/server.ts
|
|
10
13
|
import { betterAuth, APIError } from "better-auth";
|
|
@@ -36,6 +39,7 @@ function createPlatformAuth(config) {
|
|
|
36
39
|
google,
|
|
37
40
|
github,
|
|
38
41
|
plugins = [],
|
|
42
|
+
databaseHooks,
|
|
39
43
|
betaMode = false,
|
|
40
44
|
isInvited,
|
|
41
45
|
emailSubjects,
|
|
@@ -61,6 +65,10 @@ function createPlatformAuth(config) {
|
|
|
61
65
|
enabled: false
|
|
62
66
|
}
|
|
63
67
|
},
|
|
68
|
+
// Passed through as given. The platform defines none of its own today, so
|
|
69
|
+
// there is nothing to merge; should it ever add one, this becomes a merge
|
|
70
|
+
// rather than a hand-off, or an app silently switches a platform hook off.
|
|
71
|
+
...databaseHooks ? { databaseHooks } : {},
|
|
64
72
|
hooks: {
|
|
65
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
74
|
before: async (ctx) => {
|
|
@@ -125,8 +133,11 @@ export {
|
|
|
125
133
|
claimInvitation,
|
|
126
134
|
completesSignup,
|
|
127
135
|
createPlatformAuth,
|
|
136
|
+
holdInviteTokenCookie,
|
|
128
137
|
invitationOutcomeCookie,
|
|
129
138
|
inviteTokenFrom,
|
|
130
|
-
isInvitationFailure
|
|
139
|
+
isInvitationFailure,
|
|
140
|
+
pinInviteToken,
|
|
141
|
+
releaseInviteTokenCookie
|
|
131
142
|
};
|
|
132
143
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n admin(),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n ...(google\n ? {\n google: {\n clientId: google.clientId,\n clientSecret: google.clientSecret,\n },\n }\n : {}),\n ...(github\n ? {\n github: {\n clientId: github.clientId,\n clientSecret: github.clientSecret,\n },\n }\n : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n // Passed through as given. The platform defines none of its own today, so\n // there is nothing to merge; should it ever add one, this becomes a merge\n // rather than a hand-off, or an app silently switches a platform hook off.\n ...(databaseHooks ? { databaseHooks } : {}),\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n admin(),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n ...(google\n ? {\n google: {\n clientId: google.clientId,\n clientSecret: google.clientSecret,\n },\n }\n : {}),\n ...(github\n ? {\n github: {\n clientId: github.clientId,\n clientSecret: github.clientSecret,\n },\n }\n : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,YAAY,gBAAmD;AACxE,SAAS,UAAU,aAAa;AAGhC,IAAM,yBAAiD;AAAA,EACrD,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAEA,SAAS,sBAAsB,KAAqB;AAClD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKW,GAAG;AAAA;AAAA;AAAA;AAAA;AAKvB;AAMO,SAAS,mBACd,QACyB;AACzB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,CAAC;AAAA,IACX;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,WAAW,EAAE,GAAG,wBAAwB,GAAG,cAAc;AAC/D,QAAM,cAAc,kBAAkB;AAOtC,SAAO,WAAW;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,MAChB,SAAS;AAAA,MACT,0BAA0B;AAAA,IAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAAS;AAAA,MACP,gBAAgB;AAAA,QACd,SAAS;AAAA,MACX;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAIA,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;AAAA,IACzC,OAAO;AAAA;AAAA,MAEL,QAAQ,OAAO,QAAa;AAC1B,YAAI,CAAC,SAAU;AACf,YAAI,IAAI,SAAS,iBAAkB;AACnC,cAAM,OAAO,IAAI;AACjB,cAAM,QAAQ,MAAM;AACpB,cAAM,cAAc,MAAM;AAC1B,YAAI,SAAS,eAAe,WAAW;AACrC,gBAAM,KAAK,MAAM,UAAU,OAAO,WAAW;AAC7C,cAAI,GAAI;AAAA,QACV;AACA,cAAM,IAAI,SAAS,aAAa;AAAA,UAC9B,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA,QACP,MAAM,oBAAoB,EAAE,OAAO,KAAK,KAAK,GAAG;AAC9C,gBAAM,UAAU,SAAS,IAAI,IACzB,GAAG,SAAS,IAAI,CAAC,MAAM,OAAO,KAC9B,QAAQ,OAAO;AACnB,gBAAM,OAAO,YAAY,KAAK,IAA8B;AAE5D,cAAI,QAAQ;AACV,kBAAM,OAAO;AAAA,cACX,IAAI;AAAA,cACJ;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AACD;AAAA,UACF;AAEA,kBAAQ;AAAA,YACN,iEAA4D,KAAK,KAAK,IAAI,MAAM,GAAG;AAAA,UACrF;AAAA,QACF;AAAA,QACA,WAAW;AAAA,QACX,WAAW;AAAA,QACX,kCAAkC;AAAA,MACpC,CAAC;AAAA,MACD,MAAM;AAAA,MACN,GAAG;AAAA;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,MACf,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,MACL,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,IACP;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -92,6 +92,17 @@ interface PlatformAuthConfig {
|
|
|
92
92
|
* default branded template is used.
|
|
93
93
|
*/
|
|
94
94
|
renderOtpEmail?: (otp: string, type: PlatformAuthMailerType) => string;
|
|
95
|
+
/**
|
|
96
|
+
* Better Auth database hooks, passed through unchanged.
|
|
97
|
+
*
|
|
98
|
+
* `user.create.after` is the seam an app uses to react to a signup — record
|
|
99
|
+
* the invite token it carried, queue the work that puts the account on a
|
|
100
|
+
* plan. It runs AFTER the insert commits (Better Auth queues it as an
|
|
101
|
+
* after-transaction hook), so it cannot be made atomic with the user row:
|
|
102
|
+
* a crash between the two leaves an account nothing reacted to. Anything
|
|
103
|
+
* durable therefore needs its own repair path.
|
|
104
|
+
*/
|
|
105
|
+
databaseHooks?: BetterAuthOptions["databaseHooks"];
|
|
95
106
|
/** Additional Better Auth plugins to append */
|
|
96
107
|
plugins?: BetterAuthOptions["plugins"];
|
|
97
108
|
/** Enable private beta mode (blocks public registration) */
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/invitation.ts"],"sourcesContent":["import type { InvitationFailure } from \"./types\"\n\n/**\n * What became of a claim, in terms the invitee can be told.\n *\n * 'expired' and 'claimed' are kept apart from 'unknown' because only they say\n * the offer was real, which is what tells someone that asking for a new link is\n * worth it rather than doubting the address they were invited at. The three\n * failures match InvitationFailure, so an outcome feeds InvitationNotice\n * directly.\n */\nexport type ClaimOutcome = \"granted\" | InvitationFailure | \"failed\"\n\nconst OUTCOME_BY_STATUS: Record<number, ClaimOutcome> = {\n 404: \"unknown\",\n 409: \"claimed\",\n 410: \"expired\",\n}\n\nexport interface ClaimInvitationOptions {\n /** Absolute URL of the endpoint that redeems a token. */\n endpoint: string\n token: string\n /** The account the app just created, which the grant is attached to. */\n externalUserId: string\n /** Sent as the Authorization bearer — typically the app's API key. */\n apiKey?: string\n /** Merged into the request body, for backends wanting more than the token. */\n extra?: Record<string, unknown>\n /** Headers merged last, so a caller can pass a cookie-based credential. */\n headers?: Record<string, string>\n /** Bounds the call so a slow API never stalls the sign-in response. */\n timeoutMs?: number\n}\n\n/**\n * Redeems an invitation token for a user who has just signed in, turning the\n * offer into a grant on their account.\n *\n * Why this belongs on the SERVER, on the auth callback rather than in the page:\n * the invitation link lands on /register?invite=<token>, but the sign-up that\n * follows can complete through any of three flows (password + OTP, OAuth\n * redirect, email verification), and only two of them return to the page that\n * held the token. Claiming where the session is established covers every flow\n * with one code path.\n *\n * Best-effort by design: a sign-in must never fail because an invitation could\n * not be redeemed. A failed claim leaves the invitation unclaimed and the user\n * on their default tier — recoverable by following the link again, since a\n * refused claim consumes nothing.\n */\nexport async function claimInvitation({\n endpoint,\n token,\n externalUserId,\n apiKey,\n extra,\n headers,\n timeoutMs = 5000,\n}: ClaimInvitationOptions): Promise<ClaimOutcome> {\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),\n ...headers,\n },\n body: JSON.stringify({\n token,\n external_user_id: externalUserId,\n ...extra,\n }),\n signal: AbortSignal.timeout(timeoutMs),\n })\n\n if (res.ok) return \"granted\"\n return OUTCOME_BY_STATUS[res.status] ?? \"failed\"\n } catch {\n return \"failed\"\n }\n}\n\n/**\n * Extracts the invitation token from an auth request.\n *\n * The token lives on the page the invitee landed on (/register?invite=…), never\n * on the auth endpoints themselves, so it has to be recovered from the request\n * that completes the sign-up. Two sources, because no single one covers every\n * flow: the URL, for the OAuth callback reached through a redirect whose query\n * string the app controls; and the Referer, for the password and OTP flows,\n * which are XHR calls issued BY that page and therefore carry it.\n *\n * Reading it per-request keeps the claim stateless: nothing associates a\n * browser with a pending invitation, and a request with no token claims\n * nothing. A malformed Referer is ignored rather than thrown on — it is\n * attacker-controlled input on a best-effort path.\n */\nexport function inviteTokenFrom(\n request: Request,\n param = \"invite\",\n): string | null {\n const direct = new URL(request.url).searchParams.get(param)\n if (direct && direct.trim() !== \"\") return direct\n\n const referer = request.headers.get(\"referer\")\n if (!referer) return null\n try {\n const token = new URL(referer).searchParams.get(param)\n return token && token.trim() !== \"\" ? token : null\n } catch {\n return null\n }\n}\n\n/**\n * BetterAuth paths that complete a sign-up. Email/password returns the user\n * straight from sign-up/email; the OTP and OAuth flows only produce a usable\n * account once the verification/callback succeeds, so those are the moments\n * worth reacting to.\n */\nconst SIGNUP_COMPLETING = [\n \"/sign-up/email\",\n \"/sign-in/email-otp\",\n \"/email-otp/verify-email\",\n \"/callback/\",\n]\n\n/**\n * Whether this request is the one that just created a usable account — the\n * moment to provision, claim an invitation, or greet someone. Matching on the\n * path rather than on a response body keeps it flow-agnostic: the three\n * sign-up flows return three different shapes.\n */\nexport function completesSignup(pathname: string): boolean {\n return SIGNUP_COMPLETING.some((p) => pathname.includes(p))\n}\n\n/**\n * Carries a failed claim to the next page. The claim happens inside an auth\n * response nobody renders, so its result would otherwise reach only the server\n * log — leaving an invitee on the default tier with no idea their link had\n * lapsed. Short-lived and readable by the page, which reports it and clears it.\n */\nexport function invitationOutcomeCookie(\n outcome: ClaimOutcome,\n name = \"invite_claim\",\n): string {\n return `${name}=${outcome}; Path=/; Max-Age=120; SameSite=Lax`\n}\n\n/**\n * Whether an outcome is one the invitee should be shown a reason for.\n * 'failed' is excluded: it means the call did not complete, so the offer may\n * still be good and telling someone their invitation is invalid would be wrong.\n */\nexport function isInvitationFailure(\n outcome: ClaimOutcome,\n): outcome is InvitationFailure {\n return outcome === \"expired\" || outcome === \"claimed\" || outcome === \"unknown\"\n}\n"],"mappings":";AAaA,IAAM,oBAAkD;AAAA,EACtD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAkCA,eAAsB,gBAAgB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAAkD;AAChD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,UAAU;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG,IAAI,CAAC;AAAA,QACtD,GAAG;AAAA,MACL;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,kBAAkB;AAAA,QAClB,GAAG;AAAA,MACL,CAAC;AAAA,MACD,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AAED,QAAI,IAAI,GAAI,QAAO;AACnB,WAAO,kBAAkB,IAAI,MAAM,KAAK;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBO,SAAS,gBACd,SACA,QAAQ,UACO;AACf,QAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,KAAK;AAC1D,MAAI,UAAU,OAAO,KAAK,MAAM,GAAI,QAAO;AAE3C,QAAM,UAAU,QAAQ,QAAQ,IAAI,SAAS;AAC7C,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,UAAM,QAAQ,IAAI,IAAI,OAAO,EAAE,aAAa,IAAI,KAAK;AACrD,WAAO,SAAS,MAAM,KAAK,MAAM,KAAK,QAAQ;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,SAAS,gBAAgB,UAA2B;AACzD,SAAO,kBAAkB,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAC3D;AAQO,SAAS,wBACd,SACA,OAAO,gBACC;AACR,SAAO,GAAG,IAAI,IAAI,OAAO;AAC3B;AAOO,SAAS,oBACd,SAC8B;AAC9B,SAAO,YAAY,aAAa,YAAY,aAAa,YAAY;AACvE;","names":[]}
|