@krizaka/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/index.d.ts CHANGED
@@ -1,133 +1,54 @@
1
- /**
2
- * The account PORT (ADR-0008 of Yomizaka, now shared). A product's account — name, language, theme, avatar, e-mail change with
3
- * verification, password (changed when signed in, or reset by a mailed link), sessions, export, deletion — behind one interface. Every product keeps its OWN accounts: no central
4
- * service, no runtime dependency between products.
5
- */
6
- type Theme = "light" | "dark" | "system";
7
- interface AccountProfile {
8
- id: string;
9
- email: string;
10
- name: string | null;
11
- locale: "en" | "fr";
12
- /** The theme the person chose; null = follow the device. */
13
- theme: Theme | null;
14
- /** A seed of the default avatars (the UI draws it); null = derived from the e-mail address. */
15
- avatarSeed: string | null;
16
- emailVerified: boolean;
17
- /** An address awaiting its verification link, if the person asked to change it. */
18
- pendingEmail: string | null;
19
- }
20
- interface ProfilePatch {
21
- name?: string | null;
22
- locale?: "en" | "fr";
23
- theme?: Theme | null;
24
- avatarSeed?: string | null;
25
- }
26
- /** A sign-in of the person: where from and when. Never the token. */
27
- interface SessionSummary {
28
- id: string;
29
- createdAt: Date;
30
- expiresAt: Date;
31
- ip: string | null;
32
- userAgent: string | null;
33
- current: boolean;
34
- }
35
- interface AccountService {
36
- profile(accountId: string): Promise<AccountProfile>;
37
- updateProfile(accountId: string, patch: ProfilePatch): Promise<AccountProfile>;
38
- /** Asks to change the address: the password is checked, a link is mailed to the NEW address. Answers the same whether or not it is already taken (no enumeration). */
39
- requestEmailChange(accountId: string, input: {
40
- newEmail: string;
41
- password: string;
42
- }, now: Date): Promise<void>;
43
- /** Applies the change from the mailed link (single use, expires); the new address is verified by that very act. */
44
- confirmEmailChange(token: string, now: Date): Promise<"CHANGED" | "INVALID">;
45
- /** Changes the password (the current one is checked) and signs every other session out; `keepSessionId` stays. */
46
- changePassword(accountId: string, input: {
47
- current: string;
48
- next: string;
49
- }, keepSessionId?: string): Promise<void>;
50
- /** Asks for a reset link, mailed (in the person's language) when an account with a password has this address; answers the same either way (no enumeration). */
51
- requestPasswordReset(email: string, now: Date): Promise<void>;
52
- /** Sets a new password from the mailed link — single use, expires — and signs every session out. The new password is checked before the link is used up. */
53
- resetPassword(token: string, next: string, now: Date): Promise<"CHANGED" | "INVALID">;
54
- /** Checks the password. Throws `AccountError(403, "WRONG_PASSWORD")`. */
55
- confirmPassword(accountId: string, password: string): Promise<void>;
56
- /** The person's live sign-ins, newest first; `currentSessionId` is flagged. */
57
- sessions(accountId: string, now: Date, currentSessionId?: string): Promise<SessionSummary[]>;
58
- /** Signs every other session out (and returns how many); `keepSessionId` stays. */
59
- signOutOthers(accountId: string, keepSessionId: string): Promise<number>;
60
- /** What the product keeps about the person, as one JSON-able document: the profile, the live sign-ins, and what `extra` adds. */
61
- exportAccount(accountId: string, now: Date, extra?: () => Promise<Record<string, unknown>>): Promise<Record<string, unknown>>;
62
- /** Deletes the identity: the account, its sessions and everything that hangs on it (the product erases its own data first). */
63
- deleteAccount(accountId: string): Promise<void>;
64
- }
65
- declare class AccountError extends Error {
66
- readonly status: number;
67
- readonly code: string;
68
- constructor(status: number, code: string, message: string);
69
- }
1
+ import { E as ExternalIdentity, I as IdentityLinkStore, a as IdentityProvider } from './identity-links-C5AG7Fbp.js';
2
+ export { A as AccountDeps, b as AccountError, c as AccountProfile, d as AccountService, C as CompleteOptions, e as EMAIL_CHANGE_TTL_HOURS, F as FEDERATED_PROVIDERS, f as FEDERATION_CODE, g as FederatedProvider, h as FederationCode, i as FederationError, j as IdentityLinkError, M as Mail, k as MailLink, P as PASSWORD_RESET_MAX_MINUTES, l as PASSWORD_RESET_MIN_MINUTES, m as PASSWORD_RESET_TTL_MINUTES, n as ProfilePatch, Q as Query, S as SessionSummary, o as StartOptions, p as StartedSignIn, T as Theme, q as dbAccounts, r as dbIdentityLinks, s as emailChangeMail, t as emailChangeToken, u as identityLinksMigration, v as isEmailChangeToken, w as isFederatedProvider, x as isLinkToken, y as isLocalReturn, z as linkToken, B as memoryIdentityLinks, D as memoryIdentityProvider, G as passwordResetMail, H as pkceChallenge, J as postgresAccounts } from './identity-links-C5AG7Fbp.js';
70
3
 
71
- /** A SQL executor: the rows of a statement and its parameters (the product's pool). */
72
- type Query = <T = Record<string, unknown>>(sql: string, params?: unknown[]) => Promise<T[]>;
73
- interface Mail {
74
- to: string;
75
- /** The language of the mail (the person's). */
76
- locale: "en" | "fr";
77
- subject: string;
78
- text: string;
4
+ /** What federated sign-in needs from the product's accounts: no second user table, just a look-up and, when registration is open, a creation. */
5
+ interface FederatedAccounts {
6
+ /** The account that holds this address (lower case), and whether the product has verified it; null when none. */
7
+ findByEmail(email: string): Promise<{
8
+ id: string;
9
+ emailVerified: boolean;
10
+ } | null>;
11
+ /** Creates an account for a person the provider vouches for (address verified, no password). Absent when registration is closed. */
12
+ create?(identity: ExternalIdentity & {
13
+ email: string;
14
+ }): Promise<string>;
79
15
  }
80
- interface AccountDeps {
81
- query: Query;
82
- /**
83
- * Runs `work` in one transaction on one connection (BEGIN … COMMIT, rolled back when `work` throws) — the product's pool
84
- * knows how. The password reset runs under it: the account row is locked, the links retired and issued, the token claimed,
85
- * the password set and the sessions ended as one. Without it, the statements run one by one on `query` (one process, no
86
- * concurrent requests): give it in production.
87
- */
88
- transaction?: <T>(work: (query: Query) => Promise<T>) => Promise<T>;
89
- mail: (mail: Mail) => Promise<void>;
90
- /** `https://www.akinaza.com`: the e-mail change link is `<baseUrl>/<locale>/confirm-email?token=…`, the reset link `<baseUrl>/<locale>/reset-password?token=…`. */
91
- baseUrl: string;
92
- /** The product's name, in the words of the mail (« Akinaza »). */
93
- product: string;
94
- /** The product's token prefix, two or three letters (`ye1.` for Yomizaka: `ye`). The tokens are `<prefix>1.<43 base64url>`. */
95
- tokenPrefix: string;
96
- passwords: {
97
- hash: (password: string) => Promise<string>;
98
- verify: (password: string, stored: string) => Promise<boolean>;
16
+ /** Why a sign-in was refused: a stable code the product maps to its words. */
17
+ declare const FEDERATED_REFUSAL: {
18
+ /** The provider did not verify the address (or gave none): nothing is linked, nothing is created. */
19
+ readonly emailNotVerified: "EMAIL_NOT_VERIFIED";
20
+ /** An account holds the address but the product never verified it: linking would let a stranger take it over. */
21
+ readonly accountEmailUnverified: "ACCOUNT_EMAIL_UNVERIFIED";
22
+ /** No account, and registration is closed. */
23
+ readonly noAccount: "NO_ACCOUNT";
24
+ };
25
+ type FederatedRefusal = (typeof FEDERATED_REFUSAL)[keyof typeof FEDERATED_REFUSAL];
26
+ type FederatedSignInResult =
27
+ /** `via`: `link` already linked, `linked` attached to the account of that address now, `created` a new account. */
28
+ {
29
+ status: "signed-in";
30
+ accountId: string;
31
+ via: "link" | "linked" | "created";
32
+ } | {
33
+ status: "refused";
34
+ code: FederatedRefusal;
35
+ };
36
+ interface FederatedSignInDeps {
37
+ links: IdentityLinkStore;
38
+ accounts: FederatedAccounts;
39
+ clock: {
40
+ now(): Date;
99
41
  };
100
- /** How a token is kept: the product's own keyed hash, the one its sessions use. */
101
- tokenHash: (token: string) => string;
102
- /**
103
- * How long a password reset link lives, in minutes: a whole number from 5 to 1440 (`PASSWORD_RESET_MIN_MINUTES`–`PASSWORD_RESET_MAX_MINUTES`),
104
- * default `PASSWORD_RESET_TTL_MINUTES` (30). The product reads it from its settings; the expiry and the mailed text both use it.
105
- * Out of range, `postgresAccounts` throws.
106
- */
107
- passwordResetTtlMinutes?: number;
108
- /** Test seam: the token to issue. */
109
- token?: () => string;
110
42
  }
111
- declare const EMAIL_CHANGE_TTL_HOURS = 24;
112
- /** The default lifetime of a password reset link, in minutes (`AccountDeps.passwordResetTtlMinutes` changes it). */
113
- declare const PASSWORD_RESET_TTL_MINUTES = 30;
114
- declare const PASSWORD_RESET_MIN_MINUTES = 5;
115
- declare const PASSWORD_RESET_MAX_MINUTES = 1440;
116
- /** One format for every mailed link (an e-mail change, a password reset): the product's prefix, a version, 256 random bits. */
117
- declare const linkToken: (prefix: string) => string;
118
- declare const isLinkToken: (prefix: string, value: string | undefined | null) => value is string;
119
- declare const emailChangeToken: (prefix: string) => string;
120
- declare const isEmailChangeToken: (prefix: string, value: string | undefined | null) => value is string;
121
- declare function passwordResetMail(product: string, locale: "en" | "fr", url: string, ttlMinutes?: number): {
122
- subject: string;
123
- text: string;
124
- };
125
- declare function emailChangeMail(product: string, locale: "en" | "fr", url: string): {
126
- subject: string;
127
- text: string;
43
+ /**
44
+ * From a verified external identity to an account of the product. The rule that matters: an identity attaches to an EXISTING account
45
+ * only when the address is verified on both sides — by the provider and by the product. Otherwise a stranger who registers at
46
+ * Facebook with your address would sign in as you.
47
+ */
48
+ declare function createFederatedSignIn({ links, accounts, clock }: FederatedSignInDeps): {
49
+ signIn(identity: ExternalIdentity): Promise<FederatedSignInResult>;
128
50
  };
129
- /** The adapter over the product's own PostgreSQL (`accounts`, `sessions`, `email_changes`, `password_resets`; see `accountsMigration`). */
130
- declare function postgresAccounts(deps: AccountDeps): AccountService;
51
+ type FederatedSignIn = ReturnType<typeof createFederatedSignIn>;
131
52
 
132
53
  /**
133
54
  * What the account adapter needs from a product's database, safe to run on a database that already has `accounts` and `sessions`:
@@ -250,4 +171,29 @@ declare function createRoles<Role extends string, Permission extends string>(dep
250
171
  };
251
172
  type Roles<Role extends string = string, Permission extends string = string> = ReturnType<typeof createRoles<Role, Permission>>;
252
173
 
253
- export { type AccountDeps, AccountError, type AccountProfile, type AccountService, EMAIL_CHANGE_TTL_HOURS, type Invitation, type Mail, type Member, PASSWORD_RESET_MAX_MINUTES, PASSWORD_RESET_MIN_MINUTES, PASSWORD_RESET_TTL_MINUTES, type ProfilePatch, type Query, ROLES_CODE, type RoleModel, type Roles, type RolesCode, type RolesDeps, RolesError, type RolesQuery, type SessionSummary, type Theme, accountsMigration, can, createRoles, defineRoles, emailChangeMail, emailChangeToken, isEmailChangeToken, isLinkToken, linkToken, passwordResetMail, postgresAccounts, rolesMigration };
174
+ /** The `fetch` the adapter calls krizaka-users with (injected: no global, no network in tests). */
175
+ type Fetcher = (url: string, init: {
176
+ method: "POST";
177
+ headers: Record<string, string>;
178
+ body: string;
179
+ }) => Promise<{
180
+ ok: boolean;
181
+ status: number;
182
+ json(): Promise<unknown>;
183
+ }>;
184
+ interface UsersFederationOptions {
185
+ /** The tenant host of the product: `https://auth.dev.akinaza.com`. */
186
+ baseUrl: string;
187
+ /** The product's public address: the start URL sends the person back to it, so `returnTo` is absolute from it. */
188
+ appUrl: string;
189
+ fetch: Fetcher;
190
+ }
191
+ /** Reads krizaka-users' answer into an identity; anything that is not a complete identity is refused. */
192
+ declare function parseExternalIdentity(body: unknown): ExternalIdentity;
193
+ /**
194
+ * The adapter for krizaka-users (`auth.<product>` tenant host, krizaka/krizaka-users#31): `start` builds the tenant's start address,
195
+ * `complete` exchanges the one-time code (and the PKCE verifier of a native app) for the verified identity.
196
+ */
197
+ declare function usersIdentityProvider({ baseUrl, appUrl, fetch }: UsersFederationOptions): IdentityProvider;
198
+
199
+ export { ExternalIdentity, FEDERATED_REFUSAL, type FederatedAccounts, type FederatedRefusal, type FederatedSignIn, type FederatedSignInDeps, type FederatedSignInResult, type Fetcher, IdentityLinkStore, IdentityProvider, type Invitation, type Member, ROLES_CODE, type RoleModel, type Roles, type RolesCode, type RolesDeps, RolesError, type RolesQuery, type UsersFederationOptions, accountsMigration, can, createFederatedSignIn, createRoles, defineRoles, parseExternalIdentity, rolesMigration, usersIdentityProvider };
package/dist/index.js CHANGED
@@ -1,5 +1,19 @@
1
+ import {
2
+ FEDERATED_PROVIDERS,
3
+ FEDERATION_CODE,
4
+ FederationError,
5
+ IdentityLinkError,
6
+ dbIdentityLinks,
7
+ identityLinksMigration,
8
+ isFederatedProvider,
9
+ isLocalReturn,
10
+ memoryIdentityLinks,
11
+ memoryIdentityProvider,
12
+ pkceChallenge
13
+ } from "./chunk-H7AKE2OH.js";
14
+
1
15
  // src/accounts.ts
2
- import crypto from "crypto";
16
+ import crypto2 from "crypto";
3
17
  import { addHours, addMinutes } from "date-fns";
4
18
 
5
19
  // src/port.ts
@@ -29,7 +43,7 @@ var NAME_MAX = 80;
29
43
  var PASSWORD_MIN = 12;
30
44
  var PASSWORD_MAX = 256;
31
45
  var THEMES = ["light", "dark", "system"];
32
- var linkToken = (prefix) => `${prefix}1.${crypto.randomBytes(32).toString("base64url")}`;
46
+ var linkToken = (prefix) => `${prefix}1.${crypto2.randomBytes(32).toString("base64url")}`;
33
47
  var isLinkToken = (prefix, value) => !!value && new RegExp(`^${prefix}1\\.[A-Za-z0-9_-]{43}$`).test(value);
34
48
  var emailChangeToken = linkToken;
35
49
  var isEmailChangeToken = isLinkToken;
@@ -77,7 +91,7 @@ var cleanName = (raw) => {
77
91
  if (name.length > NAME_MAX) throw new AccountError(400, "BAD_REQUEST", `A name is at most ${NAME_MAX} characters`);
78
92
  return name;
79
93
  };
80
- function postgresAccounts(deps) {
94
+ function dbAccounts(deps) {
81
95
  const { query } = deps;
82
96
  const resetTtlMinutes = checkResetTtl(deps.passwordResetTtlMinutes ?? PASSWORD_RESET_TTL_MINUTES);
83
97
  const transaction = deps.transaction ?? ((work) => work(query));
@@ -120,7 +134,7 @@ function postgresAccounts(deps) {
120
134
  const token = (deps.token ?? (() => emailChangeToken(deps.tokenPrefix)))();
121
135
  await query("INSERT INTO email_changes (account_id, new_email, token_hash, expires_at) VALUES ($1, $2, $3, $4)", [id, address, deps.tokenHash(token), addHours(now, EMAIL_CHANGE_TTL_HOURS)]);
122
136
  const url = `${deps.baseUrl.replace(/\/$/, "")}/${profile.locale}/confirm-email?token=${encodeURIComponent(token)}`;
123
- await deps.mail({ to: address, locale: profile.locale, ...emailChangeMail(deps.product, profile.locale, url) });
137
+ await deps.mail({ to: address, locale: profile.locale, ...emailChangeMail(deps.product, profile.locale, url), link: { kind: "emailChange", url, hours: EMAIL_CHANGE_TTL_HOURS } });
124
138
  },
125
139
  async confirmEmailChange(token, now) {
126
140
  if (!isEmailChangeToken(deps.tokenPrefix, token)) return "INVALID";
@@ -154,7 +168,7 @@ function postgresAccounts(deps) {
154
168
  });
155
169
  if (!issued) return;
156
170
  const url = `${deps.baseUrl.replace(/\/$/, "")}/${issued.locale}/reset-password?token=${encodeURIComponent(issued.token)}`;
157
- await deps.mail({ to: address, locale: issued.locale, ...passwordResetMail(deps.product, issued.locale, url, resetTtlMinutes) });
171
+ await deps.mail({ to: address, locale: issued.locale, ...passwordResetMail(deps.product, issued.locale, url, resetTtlMinutes), link: { kind: "passwordReset", url, minutes: resetTtlMinutes } });
158
172
  },
159
173
  async resetPassword(token, next, now) {
160
174
  if (next.length < PASSWORD_MIN) throw new AccountError(400, "PASSWORD_TOO_SHORT", `At least ${PASSWORD_MIN} characters`);
@@ -186,6 +200,37 @@ function postgresAccounts(deps) {
186
200
  }
187
201
  };
188
202
  }
203
+ var postgresAccounts = dbAccounts;
204
+
205
+ // src/federated-sign-in.ts
206
+ var FEDERATED_REFUSAL = {
207
+ /** The provider did not verify the address (or gave none): nothing is linked, nothing is created. */
208
+ emailNotVerified: "EMAIL_NOT_VERIFIED",
209
+ /** An account holds the address but the product never verified it: linking would let a stranger take it over. */
210
+ accountEmailUnverified: "ACCOUNT_EMAIL_UNVERIFIED",
211
+ /** No account, and registration is closed. */
212
+ noAccount: "NO_ACCOUNT"
213
+ };
214
+ function createFederatedSignIn({ links, accounts, clock }) {
215
+ return {
216
+ async signIn(identity) {
217
+ const known = await links.find(identity.issuer, identity.subject);
218
+ if (known) return { status: "signed-in", accountId: known, via: "link" };
219
+ const email = identity.email?.trim().toLowerCase();
220
+ if (!email || !identity.emailVerified) return { status: "refused", code: FEDERATED_REFUSAL.emailNotVerified };
221
+ const existing = await accounts.findByEmail(email);
222
+ if (existing) {
223
+ if (!existing.emailVerified) return { status: "refused", code: FEDERATED_REFUSAL.accountEmailUnverified };
224
+ await links.link(existing.id, identity.issuer, identity.subject, clock.now());
225
+ return { status: "signed-in", accountId: existing.id, via: "linked" };
226
+ }
227
+ if (!accounts.create) return { status: "refused", code: FEDERATED_REFUSAL.noAccount };
228
+ const accountId = await accounts.create({ ...identity, email });
229
+ await links.link(accountId, identity.issuer, identity.subject, clock.now());
230
+ return { status: "signed-in", accountId, via: "created" };
231
+ }
232
+ };
233
+ }
189
234
 
190
235
  // src/migration.ts
191
236
  function accountsMigration(options = {}) {
@@ -248,7 +293,7 @@ CREATE INDEX IF NOT EXISTS invitations_scope ON invitations(scope_id) WHERE used
248
293
  }
249
294
 
250
295
  // src/roles.ts
251
- import crypto2 from "crypto";
296
+ import crypto3 from "crypto";
252
297
  import { addHours as addHours2 } from "date-fns";
253
298
  var defineRoles = (model) => model;
254
299
  var can = (model, role, permission) => !!role && model.permissions[role].includes(permission);
@@ -279,7 +324,7 @@ function createRoles(deps) {
279
324
  if (!known(role)) throw new RolesError(ROLES_CODE.unknownRole);
280
325
  return role;
281
326
  };
282
- const newToken = deps.token ?? (() => `${deps.tokenPrefix}1.${crypto2.randomBytes(32).toString("base64url")}`);
327
+ const newToken = deps.token ?? (() => `${deps.tokenPrefix}1.${crypto3.randomBytes(32).toString("base64url")}`);
283
328
  const owners = (scopeId) => query("SELECT account_id FROM memberships WHERE scope_id = $1 AND role = $2", [scopeId, ownerRole()]);
284
329
  const ownerRole = () => model.roles.find((r) => model.roles.every((x) => model.manages[r].includes(x) || x === r)) ?? model.roles[0];
285
330
  async function roleOf(accountId, scopeId) {
@@ -345,7 +390,7 @@ function createRoles(deps) {
345
390
  const token = newToken();
346
391
  const [row] = await query(
347
392
  "INSERT INTO invitations (id, scope_id, email, role, token_hash, invited_by, created_at, expires_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, expires_at",
348
- [crypto2.randomUUID(), scopeId, email, role, deps.tokenHash(token), actor.id, now, addHours2(now, deps.inviteTtlHours)]
393
+ [crypto3.randomUUID(), scopeId, email, role, deps.tokenHash(token), actor.id, now, addHours2(now, deps.inviteTtlHours)]
349
394
  );
350
395
  return { token, invitation: { id: row.id, email, role, createdAt: row.created_at, expiresAt: row.expires_at } };
351
396
  },
@@ -376,9 +421,53 @@ function createRoles(deps) {
376
421
  }
377
422
  };
378
423
  }
424
+
425
+ // src/users-federation.ts
426
+ var START = (base, provider) => `${base}/api/v1/auth/web/${provider}/start`;
427
+ var EXCHANGE = (base) => `${base}/api/v1/auth/web/exchange`;
428
+ var text = (value) => typeof value === "string" && value.length > 0 ? value : null;
429
+ function parseExternalIdentity(body) {
430
+ const o = body ?? {};
431
+ const issuer = text(o.issuer);
432
+ const subject = text(o.subject);
433
+ if (!issuer || !subject) throw new FederationError(FEDERATION_CODE.unavailable, "krizaka-users answered without an issuer and a subject");
434
+ return { issuer, subject, email: text(o.email), emailVerified: o.email_verified === true, name: text(o.name) };
435
+ }
436
+ function usersIdentityProvider({ baseUrl, appUrl, fetch }) {
437
+ const base = baseUrl.replace(/\/+$/, "");
438
+ const app = appUrl.replace(/\/+$/, "");
439
+ return {
440
+ async start(provider, returnTo, options = {}) {
441
+ if (!isFederatedProvider(provider)) throw new FederationError(FEDERATION_CODE.unknownProvider, `Unknown provider ${String(provider)}`);
442
+ if (!isLocalReturn(returnTo)) throw new FederationError(FEDERATION_CODE.badReturn, "That return address is not allowed");
443
+ const state = crypto.randomUUID();
444
+ const query = new URLSearchParams({ return_to: `${app}${returnTo}`, state });
445
+ if (options.codeChallenge) {
446
+ query.set("code_challenge", options.codeChallenge);
447
+ query.set("code_challenge_method", "S256");
448
+ }
449
+ return { url: `${START(base, provider)}?${query}`, state };
450
+ },
451
+ async complete(code, options = {}) {
452
+ const response = await fetch(EXCHANGE(base), {
453
+ method: "POST",
454
+ headers: { "content-type": "application/json" },
455
+ body: JSON.stringify({ code, ...options.codeVerifier ? { code_verifier: options.codeVerifier } : {} })
456
+ });
457
+ if (response.status === 400 || response.status === 401 || response.status === 404 || response.status === 410) throw new FederationError(FEDERATION_CODE.invalidCode, "That code is not valid");
458
+ if (!response.ok) throw new FederationError(FEDERATION_CODE.unavailable, `krizaka-users answered ${response.status}`);
459
+ return parseExternalIdentity(await response.json());
460
+ }
461
+ };
462
+ }
379
463
  export {
380
464
  AccountError,
381
465
  EMAIL_CHANGE_TTL_HOURS,
466
+ FEDERATED_PROVIDERS,
467
+ FEDERATED_REFUSAL,
468
+ FEDERATION_CODE,
469
+ FederationError,
470
+ IdentityLinkError,
382
471
  PASSWORD_RESET_MAX_MINUTES,
383
472
  PASSWORD_RESET_MIN_MINUTES,
384
473
  PASSWORD_RESET_TTL_MINUTES,
@@ -386,15 +475,26 @@ export {
386
475
  RolesError,
387
476
  accountsMigration,
388
477
  can,
478
+ createFederatedSignIn,
389
479
  createRoles,
480
+ dbAccounts,
481
+ dbIdentityLinks,
390
482
  defineRoles,
391
483
  emailChangeMail,
392
484
  emailChangeToken,
485
+ identityLinksMigration,
393
486
  isEmailChangeToken,
487
+ isFederatedProvider,
394
488
  isLinkToken,
489
+ isLocalReturn,
395
490
  linkToken,
491
+ memoryIdentityLinks,
492
+ memoryIdentityProvider,
493
+ parseExternalIdentity,
396
494
  passwordResetMail,
495
+ pkceChallenge,
397
496
  postgresAccounts,
398
- rolesMigration
497
+ rolesMigration,
498
+ usersIdentityProvider
399
499
  };
400
500
  //# sourceMappingURL=index.js.map