@bitbaum/accountkit 0.0.0-stage → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -2
  3. package/dist/AccountMenu.d.ts +76 -0
  4. package/dist/AccountMenu.d.ts.map +1 -0
  5. package/dist/AccountMenu.js +156 -0
  6. package/dist/AccountMenu.js.map +1 -0
  7. package/dist/SignInError.d.ts +50 -0
  8. package/dist/SignInError.d.ts.map +1 -0
  9. package/dist/SignInError.js +34 -0
  10. package/dist/SignInError.js.map +1 -0
  11. package/dist/auth/client.d.ts +19 -0
  12. package/dist/auth/client.d.ts.map +1 -0
  13. package/dist/auth/client.js +17 -0
  14. package/dist/auth/client.js.map +1 -0
  15. package/dist/auth/factor.d.ts +9 -0
  16. package/dist/auth/factor.d.ts.map +1 -0
  17. package/dist/auth/factor.js +107 -0
  18. package/dist/auth/factor.js.map +1 -0
  19. package/dist/auth/index.d.ts +6 -0
  20. package/dist/auth/index.d.ts.map +1 -0
  21. package/dist/auth/index.js +6 -0
  22. package/dist/auth/index.js.map +1 -0
  23. package/dist/auth/labels.d.ts +114 -0
  24. package/dist/auth/labels.d.ts.map +1 -0
  25. package/dist/auth/labels.js +132 -0
  26. package/dist/auth/labels.js.map +1 -0
  27. package/dist/auth/limits.d.ts +28 -0
  28. package/dist/auth/limits.d.ts.map +1 -0
  29. package/dist/auth/limits.js +90 -0
  30. package/dist/auth/limits.js.map +1 -0
  31. package/dist/auth/mail.d.ts +10 -0
  32. package/dist/auth/mail.d.ts.map +1 -0
  33. package/dist/auth/mail.js +45 -0
  34. package/dist/auth/mail.js.map +1 -0
  35. package/dist/auth/methods.d.ts +71 -0
  36. package/dist/auth/methods.d.ts.map +1 -0
  37. package/dist/auth/methods.js +92 -0
  38. package/dist/auth/methods.js.map +1 -0
  39. package/dist/auth/next.d.ts +73 -0
  40. package/dist/auth/next.d.ts.map +1 -0
  41. package/dist/auth/next.js +131 -0
  42. package/dist/auth/next.js.map +1 -0
  43. package/dist/auth/react.d.ts +174 -0
  44. package/dist/auth/react.d.ts.map +1 -0
  45. package/dist/auth/react.js +396 -0
  46. package/dist/auth/react.js.map +1 -0
  47. package/dist/auth/recovery.d.ts +17 -0
  48. package/dist/auth/recovery.d.ts.map +1 -0
  49. package/dist/auth/recovery.js +85 -0
  50. package/dist/auth/recovery.js.map +1 -0
  51. package/dist/auth/roles.d.ts +19 -0
  52. package/dist/auth/roles.d.ts.map +1 -0
  53. package/dist/auth/roles.js +29 -0
  54. package/dist/auth/roles.js.map +1 -0
  55. package/dist/auth/schema.d.ts +1923 -0
  56. package/dist/auth/schema.d.ts.map +1 -0
  57. package/dist/auth/schema.js +120 -0
  58. package/dist/auth/schema.js.map +1 -0
  59. package/dist/auth/server.d.ts +56 -0
  60. package/dist/auth/server.d.ts.map +1 -0
  61. package/dist/auth/server.js +229 -0
  62. package/dist/auth/server.js.map +1 -0
  63. package/dist/auth/translations.d.ts +9 -0
  64. package/dist/auth/translations.d.ts.map +1 -0
  65. package/dist/auth/translations.js +513 -0
  66. package/dist/auth/translations.js.map +1 -0
  67. package/dist/identity.d.ts +50 -0
  68. package/dist/identity.d.ts.map +1 -0
  69. package/dist/identity.js +76 -0
  70. package/dist/identity.js.map +1 -0
  71. package/dist/index.d.ts +4 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +4 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/orangecat.d.ts +333 -0
  76. package/dist/orangecat.d.ts.map +1 -0
  77. package/dist/orangecat.js +508 -0
  78. package/dist/orangecat.js.map +1 -0
  79. package/dist/paths.d.ts +3 -0
  80. package/dist/paths.d.ts.map +1 -0
  81. package/dist/paths.js +11 -0
  82. package/dist/paths.js.map +1 -0
  83. package/package.json +145 -4
  84. package/styles.css +383 -0
@@ -0,0 +1,508 @@
1
+ /**
2
+ * "Sign in with OrangeCat" — the one copy.
3
+ *
4
+ * Nine apps wrote this themselves (loki, solon, heidi, skif, substrata,
5
+ * petvity, surf-your-life, evig, datacat), and each learned something the
6
+ * others had not. This module is all of it, once:
7
+ *
8
+ * - the provider (Auth.js v5, and next-auth v4) with the two settings every
9
+ * app paid a debugging cycle for: `client_secret_post` and PKCE;
10
+ * - a profile with NO `email`, so Auth.js never looks a user up, or links
11
+ * one, by an address OrangeCat did not verify;
12
+ * - the session refresh that ends a session OrangeCat revoked (heidi, solon);
13
+ * - the entry parameters: create-account, login hint, Google/GitHub (solon);
14
+ * - the protections the user-table apps each wrote: identity keyed on `sub`,
15
+ * a `.invalid` placeholder when the address is taken, no ADMIN_EMAILS
16
+ * promotion and no local password reset on the strength of an OrangeCat
17
+ * email (petvity, surf-your-life, datacat, evig);
18
+ * - evig's "Connect OrangeCat" link token, for linking an account the person
19
+ * is already signed in to.
20
+ *
21
+ * Server-only (it signs with node:crypto) and free of any next-auth import:
22
+ * the types below are structural, so the same file serves Auth.js v5 and
23
+ * next-auth v4, and every rule is testable under plain `node --test`.
24
+ *
25
+ * Why email is never the key: OrangeCat's auth backend auto-confirms
26
+ * addresses, so `email_verified` is not proof that the person owns the
27
+ * address. Anyone can register someone else's email on OrangeCat. The `sub`
28
+ * (OrangeCat's actor id) is the only identity an app may key on.
29
+ */
30
+ import { createHmac, timingSafeEqual } from "node:crypto";
31
+ // ── Where OrangeCat is ──────────────────────────────────────────────────────
32
+ export const ORANGECAT_PROVIDER_ID = "orangecat";
33
+ export const ORANGECAT_DEFAULT_ISSUER = "https://orangecat.ch";
34
+ /** Identity only. An app that acts on OrangeCat's behalf passes its own. */
35
+ export const ORANGECAT_IDENTITY_SCOPES = "openid profile email";
36
+ /**
37
+ * The issuer every OrangeCat token's `iss` carries. Not normalised: OIDC
38
+ * compares `iss` byte for byte, so a trailing slash in the env is a
39
+ * different issuer, exactly as discovery would treat it.
40
+ */
41
+ export function orangecatIssuer(env = process.env) {
42
+ return env.ORANGECAT_OAUTH_ISSUER?.trim() || ORANGECAT_DEFAULT_ISSUER;
43
+ }
44
+ /**
45
+ * The client pair from the env names every app already uses, or null. Null
46
+ * means the provider is ABSENT — an app hides the button rather than mount a
47
+ * half-configured provider that fails opaquely at the code exchange.
48
+ */
49
+ export function orangecatClient(env = process.env) {
50
+ const clientId = env.ORANGECAT_OAUTH_CLIENT_ID?.trim();
51
+ const clientSecret = env.ORANGECAT_OAUTH_CLIENT_SECRET?.trim();
52
+ if (!clientId || !clientSecret)
53
+ return null;
54
+ return { clientId, clientSecret, issuer: orangecatIssuer(env) };
55
+ }
56
+ /**
57
+ * Deliberately has NO `email` key. For an account it has not seen, Auth.js
58
+ * calls `getUserByEmail(user.email)` and then either links the two (account
59
+ * takeover, since the address is unverified) or refuses the sign-in
60
+ * (OAuthAccountNotLinked). Without the key that lookup never runs, so a new
61
+ * `sub` always becomes a new person. The address rides along as
62
+ * `contactEmail`, for an app that wants to store it — see `contactEmailFor`.
63
+ */
64
+ export function orangecatProfile(claims) {
65
+ const sub = typeof claims.sub === "string" ? claims.sub.trim() : "";
66
+ if (!sub)
67
+ throw new Error("OrangeCat id_token has no sub");
68
+ const text = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
69
+ const email = text(claims.email);
70
+ return {
71
+ id: sub,
72
+ name: text(claims.name) ?? text(claims.preferred_username),
73
+ image: text(claims.picture),
74
+ orangecatSub: sub,
75
+ username: text(claims.preferred_username)?.replace(/^@/, "") ?? null,
76
+ contactEmail: email ? email.toLowerCase() : null,
77
+ };
78
+ }
79
+ /**
80
+ * The two settings that are not taste:
81
+ *
82
+ * - `client_secret_post`. OrangeCat's token endpoint accepts only that.
83
+ * Auth.js defaults to `client_secret_basic`, which OrangeCat answers at the
84
+ * code exchange with a 400 reading "client_id is required" — a message that
85
+ * points at the wrong problem entirely.
86
+ * - PKCE, even for a confidential client that holds a secret.
87
+ *
88
+ * A silent revert of either surfaces as an opaque failure at the code
89
+ * exchange, the least debuggable place for it; the contract tests pin both.
90
+ */
91
+ const TOKEN_AUTH = {
92
+ token_endpoint_auth_method: "client_secret_post",
93
+ };
94
+ const CHECKS = ["pkce", "state"];
95
+ /** Auth.js v5 (`next-auth@5`). Spread into `providers: [...]`. */
96
+ export function orangecatProvider(options) {
97
+ return {
98
+ id: ORANGECAT_PROVIDER_ID,
99
+ name: "OrangeCat",
100
+ type: "oidc",
101
+ issuer: options.issuer ?? orangecatIssuer(),
102
+ clientId: options.clientId,
103
+ clientSecret: options.clientSecret,
104
+ client: { ...TOKEN_AUTH },
105
+ checks: [...CHECKS],
106
+ authorization: {
107
+ params: { scope: options.scopes ?? ORANGECAT_IDENTITY_SCOPES },
108
+ },
109
+ profile: orangecatProfile,
110
+ // Never let anyone flip this on: the address is not verified.
111
+ allowDangerousEmailAccountLinking: false,
112
+ };
113
+ }
114
+ /** next-auth v4 (`next-auth@4`): an "oauth" provider with OIDC discovery. */
115
+ export function orangecatProviderV4(options) {
116
+ const issuer = (options.issuer ?? orangecatIssuer()).replace(/\/+$/, "");
117
+ return {
118
+ id: ORANGECAT_PROVIDER_ID,
119
+ name: "OrangeCat",
120
+ type: "oauth",
121
+ wellKnown: `${issuer}/.well-known/openid-configuration`,
122
+ issuer,
123
+ idToken: true,
124
+ clientId: options.clientId,
125
+ clientSecret: options.clientSecret,
126
+ client: { ...TOKEN_AUTH },
127
+ checks: [...CHECKS],
128
+ authorization: {
129
+ params: { scope: options.scopes ?? ORANGECAT_IDENTITY_SCOPES },
130
+ },
131
+ profile: orangecatProfile,
132
+ allowDangerousEmailAccountLinking: false,
133
+ };
134
+ }
135
+ /** The providers OrangeCat has switched on that an app may offer a button for. */
136
+ export const SOCIAL_PROVIDERS = ["google", "github"];
137
+ const EMAIL = /^[^\s@<>"]{1,64}@[^\s@<>"]{1,255}$/;
138
+ export function isSocialProvider(value) {
139
+ return (typeof value === "string" &&
140
+ SOCIAL_PROVIDERS.includes(value));
141
+ }
142
+ /**
143
+ * The third argument to `signIn("orangecat", options, params)`. OrangeCat's
144
+ * screen opens already in the state the person chose:
145
+ *
146
+ * prompt=create on "Create account" (OIDC Prompt Create 1.0)
147
+ * login_hint the email they typed, pre-filled
148
+ * idp_hint straight to Google or GitHub
149
+ *
150
+ * Auth.js merges these into the authorization URL verbatim, including over
151
+ * its own parameters, so only these three keys are ever produced and every
152
+ * value is checked first.
153
+ */
154
+ export function authorizationParams(input) {
155
+ const params = {};
156
+ if (input.mode === "sign-up")
157
+ params.prompt = "create";
158
+ const email = input.email?.trim();
159
+ if (email && EMAIL.test(email))
160
+ params.login_hint = email;
161
+ if (isSocialProvider(input.provider))
162
+ params.idp_hint = input.provider;
163
+ return params;
164
+ }
165
+ /**
166
+ * Where to land after signing in: `from`, if it is a path on this site.
167
+ * `from` arrives in a URL anyone can write, so `//evil.example` and
168
+ * `/\evil.example` (which browsers read as protocol-relative) are refused.
169
+ */
170
+ export { returnPath } from "./paths.js";
171
+ // ── The session: ends when OrangeCat says so ────────────────────────────────
172
+ /**
173
+ * A JWT session would otherwise outlive anything OrangeCat does: a person who
174
+ * clicks Disconnect on OrangeCat's Connected apps page, or Sign out
175
+ * everywhere, stayed signed in until the cookie expired weeks later (observed
176
+ * on production 2026-09-29). So the JWT carries the refresh token and the
177
+ * access token's expiry. While the access token is valid the session is
178
+ * trusted as is — OrangeCat's documented bound: a revocation reaches a
179
+ * session within the access-token lifetime, one hour. Once it expires, the
180
+ * app refreshes. `invalid_grant` means the access was taken back and the
181
+ * session ends; anything else (network, 5xx) keeps the session and retries a
182
+ * minute later, so an OrangeCat outage does not sign everyone out.
183
+ *
184
+ * Safe inside `jwt()` since orangecat#1212: a confidential client's refresh
185
+ * token is echoed back unchanged, so a render that discards the rewritten
186
+ * cookie loses nothing but the new expiry.
187
+ */
188
+ const REFRESH_MARGIN_SECONDS = 60;
189
+ const RETRY_AFTER_SECONDS = 60;
190
+ /** On sign-in: remember what OrangeCat handed over. */
191
+ export function bindOcTokens(token, account) {
192
+ if (account.refresh_token)
193
+ token.ocRefreshToken = account.refresh_token;
194
+ if (typeof account.expires_at === "number")
195
+ token.ocExpiresAt = account.expires_at;
196
+ delete token.ocRetryAt;
197
+ return token;
198
+ }
199
+ /** True when the access token has (nearly) expired and a retry is not on hold. */
200
+ export function ocRefreshDue(token, nowSeconds) {
201
+ if (!token.actorId ||
202
+ !token.ocRefreshToken ||
203
+ typeof token.ocExpiresAt !== "number") {
204
+ return false;
205
+ }
206
+ if (typeof token.ocRetryAt === "number" && nowSeconds < token.ocRetryAt)
207
+ return false;
208
+ return nowSeconds >= token.ocExpiresAt - REFRESH_MARGIN_SECONDS;
209
+ }
210
+ /** One refresh_token grant against OrangeCat's token endpoint. */
211
+ export async function refreshOcTokens(refreshToken, deps) {
212
+ const body = new URLSearchParams({
213
+ grant_type: "refresh_token",
214
+ refresh_token: refreshToken,
215
+ client_id: deps.clientId,
216
+ client_secret: deps.clientSecret,
217
+ });
218
+ let res;
219
+ try {
220
+ res = await deps.fetch(`${deps.issuer.replace(/\/+$/, "")}/oauth/token`, {
221
+ method: "POST",
222
+ headers: { "content-type": "application/x-www-form-urlencoded" },
223
+ body,
224
+ });
225
+ }
226
+ catch (e) {
227
+ return {
228
+ ok: false,
229
+ revoked: false,
230
+ reason: e instanceof Error ? e.message : "fetch failed",
231
+ };
232
+ }
233
+ let json = {};
234
+ try {
235
+ json = (await res.json());
236
+ }
237
+ catch {
238
+ // a non-JSON body is judged by its status below
239
+ }
240
+ if (res.ok && typeof json.expires_in === "number") {
241
+ return {
242
+ ok: true,
243
+ // Confidential clients get the same token back (orangecat#1212);
244
+ // public clients still rotate. Keep the old one only if none came.
245
+ refreshToken: json.refresh_token ?? refreshToken,
246
+ expiresAt: deps.nowSeconds + json.expires_in,
247
+ };
248
+ }
249
+ // invalid_grant is OrangeCat's word for "revoked, expired or reused" — the
250
+ // only answer that means the person (or OrangeCat) took the access back.
251
+ if (res.status === 400 && json.error === "invalid_grant")
252
+ return { ok: false, revoked: true };
253
+ return {
254
+ ok: false,
255
+ revoked: false,
256
+ reason: `${res.status} ${json.error ?? ""}`.trim(),
257
+ };
258
+ }
259
+ /**
260
+ * Apply a refresh outcome. Revoked → the identity is dropped, which is how
261
+ * an app reads "signed out" (read `token.actorId`, never `token.sub`).
262
+ * Transient → hold the retry for a minute.
263
+ */
264
+ export function applyOcRefresh(token, result, nowSeconds) {
265
+ if (result.ok) {
266
+ token.ocRefreshToken = result.refreshToken;
267
+ token.ocExpiresAt = result.expiresAt;
268
+ delete token.ocRetryAt;
269
+ return token;
270
+ }
271
+ if (result.revoked) {
272
+ delete token.actorId;
273
+ delete token.ocRefreshToken;
274
+ delete token.ocExpiresAt;
275
+ delete token.ocRetryAt;
276
+ return token;
277
+ }
278
+ token.ocRetryAt = nowSeconds + RETRY_AFTER_SECONDS;
279
+ return token;
280
+ }
281
+ /**
282
+ * The whole of an app's `jwt()` callback for OrangeCat, in one call:
283
+ *
284
+ * async jwt({ token, account, profile }) {
285
+ * return syncOcSession({ token, account, profile }, orangecat);
286
+ * }
287
+ *
288
+ * Sign-in: records the actor id and the tokens. Afterwards: refreshes when
289
+ * due, and drops `actorId` when OrangeCat revoked the grant.
290
+ */
291
+ export async function syncOcSession(input, client, deps = {}) {
292
+ const { token, account, profile } = input;
293
+ if (account?.provider === ORANGECAT_PROVIDER_ID) {
294
+ // id_token.sub is the actor id: the identity boundary, never the email.
295
+ if (profile?.sub)
296
+ token.actorId = profile.sub;
297
+ return bindOcTokens(token, account);
298
+ }
299
+ const nowSeconds = deps.nowSeconds ?? Math.floor(Date.now() / 1000);
300
+ if (!client || !ocRefreshDue(token, nowSeconds))
301
+ return token;
302
+ const result = await refreshOcTokens(token.ocRefreshToken, {
303
+ ...client,
304
+ fetch: deps.fetch ?? fetch,
305
+ nowSeconds,
306
+ });
307
+ return applyOcRefresh(token, result, nowSeconds);
308
+ }
309
+ // ── Apps with their own user table ──────────────────────────────────────────
310
+ /**
311
+ * The address stored when the OrangeCat email already belongs to another
312
+ * local user (or there is none). `.invalid` is reserved (RFC 2606): it can
313
+ * never be delivered to, registered, or sent a reset link.
314
+ */
315
+ export function placeholderEmail(sub) {
316
+ return `orangecat-${sub.replace(/[^a-zA-Z0-9-]/g, "")}@users.invalid`;
317
+ }
318
+ export function isPlaceholderEmail(email) {
319
+ return (typeof email === "string" &&
320
+ /^orangecat-[a-zA-Z0-9-]*@users\.invalid$/i.test(email));
321
+ }
322
+ /** The contact address to store for a new OrangeCat user: theirs when free. */
323
+ export function contactEmailFor(sub, wanted, taken) {
324
+ const email = wanted?.trim().toLowerCase();
325
+ return email && !taken ? email : placeholderEmail(sub);
326
+ }
327
+ /**
328
+ * Whether a sign-in's email may earn ADMIN_EMAILS (or any email-list)
329
+ * promotion. Never for OrangeCat: promoting on an unverified address makes
330
+ * admin a matter of typing one into OrangeCat.
331
+ */
332
+ export function emailEarnsPromotion(provider) {
333
+ return provider !== ORANGECAT_PROVIDER_ID;
334
+ }
335
+ /**
336
+ * Whether "forgot password" may email this account a reset link.
337
+ *
338
+ * A user OrangeCat created has no local password, and its address came from
339
+ * OrangeCat unverified: a reset link would hand whoever really owns that
340
+ * address a password on the OrangeCat person's account. A placeholder can
341
+ * never receive mail. Anyone with a password of their own may reset it.
342
+ */
343
+ export function mayReceivePasswordReset(user) {
344
+ if (!user.email || isPlaceholderEmail(user.email))
345
+ return false;
346
+ if (user.hasPassword)
347
+ return true;
348
+ return !user.orangecatLinked;
349
+ }
350
+ /**
351
+ * What an OrangeCat sign-in means for a local account. Pure; the adapter
352
+ * wrapper and the JWT-only apps (evig) both decide through it.
353
+ *
354
+ * - An explicit connect from a signed-in account wins: the person's own
355
+ * decision, made from inside their session.
356
+ * - A known `sub` signs in as its user.
357
+ * - Anything else creates a new user — never a match on email.
358
+ */
359
+ export function decideOrangecatSignIn(input, policy = {}) {
360
+ const { sub, byActor, linkFor } = input;
361
+ if (linkFor) {
362
+ if (byActor && byActor.id !== linkFor.id)
363
+ return { kind: "refuse", reason: "already-linked" };
364
+ if (linkFor.orangecatSub && linkFor.orangecatSub !== sub) {
365
+ return { kind: "refuse", reason: "already-linked" };
366
+ }
367
+ return { kind: "link", userId: linkFor.id };
368
+ }
369
+ if (byActor)
370
+ return { kind: "existing", userId: byActor.id };
371
+ const email = input.contactEmail?.trim().toLowerCase() || null;
372
+ if (!email && policy.whenNoEmail === "refuse")
373
+ return { kind: "refuse", reason: "no-email" };
374
+ if (email && input.emailTaken && policy.whenEmailTaken === "refuse") {
375
+ return { kind: "refuse", reason: "email-taken" };
376
+ }
377
+ return {
378
+ kind: "create",
379
+ email: contactEmailFor(sub, email, input.emailTaken),
380
+ };
381
+ }
382
+ /**
383
+ * Find or create the local user for an OrangeCat identity — for apps that
384
+ * resolve in a callback rather than through an adapter (datacat). Survives
385
+ * the insert race datacat found: a concurrent sign-in of the same sub returns
386
+ * that row; an address claimed between check and insert falls back to the
387
+ * placeholder.
388
+ */
389
+ export async function resolveOrangecatUser(store, identity) {
390
+ const sub = identity.sub?.trim();
391
+ if (!sub)
392
+ throw new Error("OrangeCat identity has no sub");
393
+ const existing = await store.findBySub(sub);
394
+ if (existing)
395
+ return existing;
396
+ const wanted = identity.contactEmail?.trim().toLowerCase() || null;
397
+ const taken = wanted ? await store.emailTaken(wanted) : false;
398
+ const row = {
399
+ orangecatSub: sub,
400
+ email: contactEmailFor(sub, wanted, taken),
401
+ name: identity.name ?? null,
402
+ image: identity.image ?? null,
403
+ };
404
+ try {
405
+ return await store.insert(row);
406
+ }
407
+ catch (err) {
408
+ const raced = await store.findBySub(sub);
409
+ if (raced)
410
+ return raced;
411
+ if (isPlaceholderEmail(row.email))
412
+ throw err;
413
+ return store.insert({ ...row, email: placeholderEmail(sub) });
414
+ }
415
+ }
416
+ /**
417
+ * Wrap an Auth.js adapter so an OrangeCat sign-in resolves its user by
418
+ * `sub` alone. Every other provider passes straight through.
419
+ *
420
+ * `keepTokens: false` (default) stores no OrangeCat tokens — identity only,
421
+ * and the sub column is the single source of truth for the link. An app that
422
+ * calls OrangeCat on the person's behalf (loki) passes `true`, and the base
423
+ * adapter also writes the accounts row.
424
+ *
425
+ * Linking an existing account is Auth.js's own path: sign in to the app,
426
+ * then sign in with OrangeCat — `linkAccount` runs for the signed-in user,
427
+ * never on the strength of an email match.
428
+ */
429
+ export function withOrangecatIdentity(base, store, options = {}) {
430
+ const call = (name, arg) => {
431
+ const fn = base[name];
432
+ if (!fn)
433
+ throw new Error(`base adapter has no ${name}`);
434
+ return fn.call(base, arg);
435
+ };
436
+ return {
437
+ ...base,
438
+ async getUserByAccount(ref) {
439
+ if (ref.provider === ORANGECAT_PROVIDER_ID)
440
+ return store.findBySub(ref.providerAccountId);
441
+ return base.getUserByAccount ? call("getUserByAccount", ref) : null;
442
+ },
443
+ async createUser(user) {
444
+ const sub = typeof user.orangecatSub === "string" ? user.orangecatSub : "";
445
+ if (!sub)
446
+ return call("createUser", user);
447
+ return resolveOrangecatUser(store, {
448
+ sub,
449
+ contactEmail: typeof user.contactEmail === "string" ? user.contactEmail : null,
450
+ name: typeof user.name === "string" ? user.name : null,
451
+ image: typeof user.image === "string" ? user.image : null,
452
+ });
453
+ },
454
+ async linkAccount(account) {
455
+ if (account.provider === ORANGECAT_PROVIDER_ID) {
456
+ const ok = await store.attachSub(account.userId, account.providerAccountId);
457
+ if (!ok)
458
+ throw new Error("This account is already connected to a different OrangeCat login");
459
+ if (!options.keepTokens)
460
+ return null;
461
+ }
462
+ if (base.linkAccount)
463
+ await call("linkAccount", account);
464
+ return null;
465
+ },
466
+ };
467
+ }
468
+ // ── "Connect OrangeCat" from a signed-in account (evig's pattern) ───────────
469
+ /**
470
+ * For an app without an adapter, proof that THIS signed-in user asked to
471
+ * connect OrangeCat: a cookie set by a POST from inside their session,
472
+ * read back by the sign-in callback, good for ten minutes. The OrangeCat
473
+ * identity that comes back attaches to that account and no other.
474
+ *
475
+ * POST /api/user/orangecat → set cookie mintLinkToken(user.id, AUTH_SECRET)
476
+ * signIn("orangecat") → OrangeCat → callback
477
+ * signIn/jwt callback → readLinkToken(cookie) → decideOrangecatSignIn({ linkFor })
478
+ */
479
+ export const ORANGECAT_LINK_TTL_SECONDS = 10 * 60;
480
+ function hmac(payload, secret) {
481
+ return createHmac("sha256", secret).update(payload).digest("base64url");
482
+ }
483
+ /** `userId.expiresAt.signature`, minted for one signed-in user. */
484
+ export function mintLinkToken(userId, secret, nowMs = Date.now()) {
485
+ if (!secret)
486
+ throw new Error("mintLinkToken needs a secret");
487
+ if (!userId || userId.includes("."))
488
+ throw new Error("mintLinkToken: bad user id");
489
+ const payload = `${userId}.${Math.floor(nowMs / 1000) + ORANGECAT_LINK_TTL_SECONDS}`;
490
+ return `${payload}.${hmac(payload, secret)}`;
491
+ }
492
+ /** The user id the token was minted for, or null if forged, altered or expired. */
493
+ export function readLinkToken(token, secret, nowMs = Date.now()) {
494
+ if (!token || !secret)
495
+ return null;
496
+ const parts = token.split(".");
497
+ if (parts.length !== 3)
498
+ return null;
499
+ const [userId, expires, signature] = parts;
500
+ const a = Buffer.from(signature);
501
+ const b = Buffer.from(hmac(`${userId}.${expires}`, secret));
502
+ if (a.length !== b.length || !timingSafeEqual(a, b))
503
+ return null;
504
+ if (!/^\d+$/.test(expires) || Number(expires) * 1000 < nowMs)
505
+ return null;
506
+ return userId || null;
507
+ }
508
+ //# sourceMappingURL=orangecat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orangecat.js","sourceRoot":"","sources":["../src/orangecat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Browser-safe local return path; never accepts an external redirect. */
2
+ export declare function returnPath(from: unknown, fallback?: string): string;
3
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,SAAM,GAAG,MAAM,CAKhE"}
package/dist/paths.js ADDED
@@ -0,0 +1,11 @@
1
+ /** Browser-safe local return path; never accepts an external redirect. */
2
+ export function returnPath(from, fallback = "/") {
3
+ if (typeof from !== "string" || !from.startsWith("/"))
4
+ return fallback;
5
+ if (from.startsWith("//") || from.includes("\\"))
6
+ return fallback;
7
+ if ([...from].some((c) => c.charCodeAt(0) < 0x20))
8
+ return fallback;
9
+ return from;
10
+ }
11
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,IAAa,EAAE,QAAQ,GAAG,GAAG;IACtD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvE,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClE,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IACnE,OAAO,IAAI,CAAC;AACd,CAAC"}