@lalternative/auth 0.5.0 → 0.7.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 +19 -13
- package/dist/index.js +417 -245
- package/dist/index.js.map +1 -1
- package/dist/{invitation-BpowqA66.d.ts → invitation-BUqM_BF8.d.ts} +49 -10
- package/dist/server.d.ts +3 -3
- package/dist/server.js +9 -3
- package/dist/server.js.map +1 -1
- package/dist/{types-BEldQPou.d.ts → types-SieiPwdd.d.ts} +87 -11
- package/package.json +1 -1
- package/dist/chunk-73BHM4PZ.js.map +0 -1
|
@@ -116,13 +116,34 @@ interface PlatformAuthClientConfig {
|
|
|
116
116
|
/** Additional client plugins */
|
|
117
117
|
plugins?: any[];
|
|
118
118
|
}
|
|
119
|
-
interface
|
|
119
|
+
interface VerifyEmailFormLabels {
|
|
120
|
+
title?: string;
|
|
121
|
+
subtitle?: string;
|
|
122
|
+
subtitleNoEmail?: string;
|
|
123
|
+
codePlaceholder?: string;
|
|
124
|
+
submit?: string;
|
|
125
|
+
submitPending?: string;
|
|
126
|
+
resend?: string;
|
|
127
|
+
resendPending?: string;
|
|
128
|
+
resent?: string;
|
|
129
|
+
alreadyVerified?: string;
|
|
130
|
+
login?: string;
|
|
131
|
+
codeRequired?: string;
|
|
132
|
+
invalidCode?: string;
|
|
133
|
+
resendFailed?: string;
|
|
134
|
+
missingEmail?: string;
|
|
135
|
+
}
|
|
136
|
+
interface VerifyEmailFormProps extends AuthHeadingProps {
|
|
120
137
|
/** Email to verify */
|
|
121
138
|
email: string;
|
|
122
139
|
/** Callback on successful verification */
|
|
123
140
|
onSuccess?: () => void;
|
|
124
141
|
/** URL to navigate to on success */
|
|
125
142
|
successUrl?: string;
|
|
143
|
+
/** Link to the login page */
|
|
144
|
+
loginUrl?: string;
|
|
145
|
+
/** Copy overrides; anything omitted keeps the French default */
|
|
146
|
+
labels?: VerifyEmailFormLabels;
|
|
126
147
|
/** Auth client instance */
|
|
127
148
|
authClient: any;
|
|
128
149
|
}
|
|
@@ -174,9 +195,24 @@ interface RegisterFormLabels {
|
|
|
174
195
|
passwordTooShort?: string;
|
|
175
196
|
signUpFailed?: string;
|
|
176
197
|
}
|
|
177
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Styling of the form's own heading. The package fixes the structure and a
|
|
200
|
+
* neutral default; the app fixes the type. Passing a class replaces the
|
|
201
|
+
* default size and weight, so pass the whole look, not an addition to it.
|
|
202
|
+
*/
|
|
203
|
+
interface AuthHeadingProps {
|
|
204
|
+
/** Replaces the default `text-2xl font-semibold tracking-tight` */
|
|
205
|
+
titleClassName?: string;
|
|
206
|
+
}
|
|
207
|
+
interface LoginFormProps extends AuthHeadingProps {
|
|
178
208
|
/** Callback once the session cookie is set and the core token minted */
|
|
179
209
|
onSuccess?: () => void;
|
|
210
|
+
/**
|
|
211
|
+
* Error raised outside the form — a failed OAuth round-trip coming back as
|
|
212
|
+
* `?error=`, a guard bouncing an unauthenticated visitor. Rendered in the
|
|
213
|
+
* same banner as the form's own errors, and superseded by them on submit.
|
|
214
|
+
*/
|
|
215
|
+
error?: string;
|
|
180
216
|
/** Link to the registration page */
|
|
181
217
|
registerUrl?: string;
|
|
182
218
|
/** Link to the password recovery page */
|
|
@@ -199,9 +235,11 @@ interface LoginFormProps {
|
|
|
199
235
|
/** Auth client instance */
|
|
200
236
|
authClient: any;
|
|
201
237
|
}
|
|
202
|
-
interface RegisterFormProps {
|
|
238
|
+
interface RegisterFormProps extends AuthHeadingProps {
|
|
203
239
|
/** Callback on successful sign-up, receives the email to verify */
|
|
204
240
|
onSuccess?: (email: string) => void;
|
|
241
|
+
/** Error raised outside the form, rendered in the same banner */
|
|
242
|
+
error?: string;
|
|
205
243
|
/** Link to the login page */
|
|
206
244
|
loginUrl?: string;
|
|
207
245
|
/** Rendered under the submit button — typically terms and privacy links */
|
|
@@ -215,32 +253,70 @@ interface RegisterFormProps {
|
|
|
215
253
|
/** Auth client instance */
|
|
216
254
|
authClient: any;
|
|
217
255
|
}
|
|
218
|
-
interface
|
|
256
|
+
interface ForgotPasswordFormLabels {
|
|
257
|
+
title?: string;
|
|
258
|
+
subtitle?: string;
|
|
259
|
+
emailPlaceholder?: string;
|
|
260
|
+
submit?: string;
|
|
261
|
+
submitPending?: string;
|
|
262
|
+
rememberPassword?: string;
|
|
263
|
+
login?: string;
|
|
264
|
+
emailRequired?: string;
|
|
265
|
+
sendFailed?: string;
|
|
266
|
+
}
|
|
267
|
+
interface ForgotPasswordFormProps extends AuthHeadingProps {
|
|
219
268
|
/** Callback on successful OTP send, receives the email */
|
|
220
269
|
onSuccess?: (email: string) => void;
|
|
221
270
|
/** Link to login page */
|
|
222
271
|
loginUrl?: string;
|
|
272
|
+
/** Copy overrides; anything omitted keeps the French default */
|
|
273
|
+
labels?: ForgotPasswordFormLabels;
|
|
223
274
|
/** Auth client instance */
|
|
224
275
|
authClient: any;
|
|
225
276
|
}
|
|
226
|
-
interface
|
|
277
|
+
interface ResetPasswordFormLabels {
|
|
278
|
+
title?: string;
|
|
279
|
+
subtitle?: string;
|
|
280
|
+
codePlaceholder?: string;
|
|
281
|
+
passwordPlaceholder?: string;
|
|
282
|
+
passwordHint?: string;
|
|
283
|
+
confirmPlaceholder?: string;
|
|
284
|
+
submit?: string;
|
|
285
|
+
submitPending?: string;
|
|
286
|
+
resend?: string;
|
|
287
|
+
resendPending?: string;
|
|
288
|
+
resent?: string;
|
|
289
|
+
rememberPassword?: string;
|
|
290
|
+
login?: string;
|
|
291
|
+
codeRequired?: string;
|
|
292
|
+
passwordTooShort?: string;
|
|
293
|
+
passwordMismatch?: string;
|
|
294
|
+
resetFailed?: string;
|
|
295
|
+
resendFailed?: string;
|
|
296
|
+
}
|
|
297
|
+
interface ResetPasswordFormProps extends AuthHeadingProps {
|
|
227
298
|
/** Email address to reset password for */
|
|
228
299
|
email: string;
|
|
229
300
|
/** Callback on successful password reset */
|
|
230
301
|
onSuccess?: () => void;
|
|
231
302
|
/** Link to login page */
|
|
232
303
|
loginUrl?: string;
|
|
304
|
+
/** Copy overrides; anything omitted keeps the French default */
|
|
305
|
+
labels?: ResetPasswordFormLabels;
|
|
233
306
|
/** Auth client instance */
|
|
234
307
|
authClient: any;
|
|
235
308
|
}
|
|
236
309
|
interface AuthLayoutProps {
|
|
237
|
-
/**
|
|
310
|
+
/** The app's mark, rendered above the form inside the card */
|
|
238
311
|
logo?: React.ReactNode;
|
|
239
|
-
/**
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Illustration filling the right half of the card from md up, dropped below
|
|
314
|
+
* it. Any node: an `<img className="h-full w-full object-cover">`, a
|
|
315
|
+
* gradient, a testimonial. Purely decorative — it is hidden from assistive
|
|
316
|
+
* technology, so it must not carry anything the form does not say.
|
|
317
|
+
*/
|
|
318
|
+
panel?: React.ReactNode;
|
|
319
|
+
/** The form. It renders its own title. */
|
|
244
320
|
children: React.ReactNode;
|
|
245
321
|
/** Footer content below the card (e.g. legal links) */
|
|
246
322
|
footer?: React.ReactNode;
|
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":[]}
|