@madauth/server 0.2.0-beta.2 → 0.3.0-beta.2

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.
@@ -8,7 +8,132 @@ import {
8
8
  WEBHOOK_TYPES,
9
9
  checkWebhookSecret,
10
10
  generateWebhookSecret
11
- } from "./chunk-6EJDVZCD.js";
11
+ } from "./chunk-3QRKWLR7.js";
12
+
13
+ // src/claims.ts
14
+ var ADMIN_ROLE = "admin";
15
+ var MAX_ROLES = 20;
16
+ var MAX_CLAIMS_LENGTH = 2048;
17
+ var ROLE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
18
+ var CLAIM_NAME = /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/;
19
+ function parseRoles(input) {
20
+ if (!Array.isArray(input)) return null;
21
+ if (!input.every((role) => typeof role === "string" && ROLE_NAME.test(role))) return null;
22
+ const roles = [...new Set(input)].sort();
23
+ return roles.length <= MAX_ROLES ? roles : null;
24
+ }
25
+ function parseClaims(input) {
26
+ if (!isJsonObject(input)) return null;
27
+ const claims = {};
28
+ for (const key of Object.keys(input).sort()) {
29
+ const value = input[key];
30
+ if (!CLAIM_NAME.test(key) || !isJsonValue(value)) return null;
31
+ if (key === "roles") {
32
+ const roles = parseRoles(value);
33
+ if (!roles) return null;
34
+ claims[key] = roles;
35
+ } else {
36
+ claims[key] = value;
37
+ }
38
+ }
39
+ return JSON.stringify(claims).length <= MAX_CLAIMS_LENGTH ? claims : null;
40
+ }
41
+ function isJsonObject(value) {
42
+ return !!value && typeof value === "object" && !Array.isArray(value);
43
+ }
44
+ function isJsonValue(value) {
45
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
46
+ if (typeof value === "number") return Number.isFinite(value);
47
+ if (Array.isArray(value)) return value.every(isJsonValue);
48
+ return isJsonObject(value) && Object.values(value).every(isJsonValue);
49
+ }
50
+ function sameClaims(a, b) {
51
+ return JSON.stringify(a ?? {}) === JSON.stringify(b ?? {});
52
+ }
53
+ function claimsFromJson(json) {
54
+ if (!json) return {};
55
+ try {
56
+ const parsed = JSON.parse(json);
57
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+ function rolesOf(user) {
63
+ const roles = user.claims?.roles;
64
+ return Array.isArray(roles) ? roles.filter((role) => typeof role === "string") : [];
65
+ }
66
+ function isAdmin(user) {
67
+ return rolesOf(user).includes(ADMIN_ROLE);
68
+ }
69
+
70
+ // src/settings.ts
71
+ var SIGN_IN_METHODS = ["google", "password"];
72
+ var METHODS_SETTING = "methods";
73
+ function parseMethodSettings(input) {
74
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
75
+ const settings = {};
76
+ for (const [key, value] of Object.entries(input)) {
77
+ if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
78
+ settings[key] = value;
79
+ }
80
+ return settings;
81
+ }
82
+ var MAX_TRIES = 3;
83
+ var Settings = class {
84
+ constructor(store) {
85
+ this.store = store;
86
+ }
87
+ /** The setting's value, or undefined if it was never set. */
88
+ async get(id) {
89
+ return (await this.read(id)).value;
90
+ }
91
+ /** The stored JSON and what it holds; both undefined if the setting was never set. */
92
+ async read(id) {
93
+ const record = await this.store.findOne("setting", { id });
94
+ if (typeof record?.value !== "string") return {};
95
+ try {
96
+ return { json: record.value, value: JSON.parse(record.value) };
97
+ } catch {
98
+ return { json: record.value };
99
+ }
100
+ }
101
+ /**
102
+ * Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
103
+ * change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
104
+ * again with the newer value. Resolves to the new value, or null if `apply` refused.
105
+ * `updatedBy` is the address of the admin, or null for the command line.
106
+ */
107
+ async change(id, apply, updatedBy) {
108
+ for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
109
+ const { json, value } = await this.read(id);
110
+ const next = apply(value);
111
+ if (next === null) return null;
112
+ const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
113
+ const written = json === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json }, patch) === 1;
114
+ if (written) return next;
115
+ }
116
+ throw new Error(`Setting "${id}" kept changing; try again.`);
117
+ }
118
+ /** Replaces the setting. */
119
+ async set(id, value, updatedBy) {
120
+ await this.change(id, () => value, updatedBy);
121
+ }
122
+ /** Which sign-in methods are on; a method that was never switched off is on. */
123
+ async methods() {
124
+ return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
125
+ }
126
+ /** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
127
+ async changeMethods(apply, updatedBy) {
128
+ return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
129
+ }
130
+ };
131
+ function isOn(method, available, methods) {
132
+ return available(method) && methods[method] !== false;
133
+ }
134
+ function anyOn(available, methods) {
135
+ return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
136
+ }
12
137
 
13
138
  // src/config.ts
14
139
  var envVars = {
@@ -17,7 +142,7 @@ var envVars = {
17
142
  ALLOWED_ORIGINS: true,
18
143
  GOOGLE_CLIENT_ID: false,
19
144
  GOOGLE_CLIENT_SECRET: false,
20
- DATABASE_URL: false,
145
+ DATABASE_URL: true,
21
146
  PASSWORD_MIN_LENGTH: false,
22
147
  WEBHOOK_URL: false,
23
148
  WEBHOOK_SECRET: false,
@@ -86,19 +211,18 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
86
211
  if (clientId && !clientId.endsWith(".apps.googleusercontent.com")) {
87
212
  throw new ConfigError(`GOOGLE_CLIENT_ID must end with ".apps.googleusercontent.com" but is "${clientId}".`);
88
213
  }
89
- const sendsEmails = !!(overrides.store || read("DATABASE_URL"));
90
- const webhook = webhookFromEnv(read("WEBHOOK_URL"), read("WEBHOOK_SECRET"), read("WEBHOOK_EVENTS"), sendsEmails);
91
- if (sendsEmails && !webhook) {
214
+ const webhook = webhookFromEnv(read("WEBHOOK_URL"), read("WEBHOOK_SECRET"), read("WEBHOOK_EVENTS"));
215
+ const sendsEmails = !!webhook && REQUIRED_EMAIL_TYPES.every((type) => webhook.events.has(type));
216
+ const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
217
+ if (!clientId && !password) {
92
218
  throw new ConfigError(
93
- `WEBHOOK_URL is not set. E-mail & password sign-in sends its e-mails through your webhook. For development, run the receiver at ${DEV_WEBHOOK_RECEIVER_URL} and set WEBHOOK_URL=http://localhost:8790/webhook and WEBHOOK_EVENTS=${REQUIRED_EMAIL_TYPES.join(",")},email.already_registered. See "Webhooks" in docs/server.md.`
219
+ `No sign-in method is configured. Set GOOGLE_CLIENT_ID for Google sign-in, and/or a webhook that sends the e-mails for e-mail & password sign-in: for development, run the receiver at ${DEV_WEBHOOK_RECEIVER_URL} and set WEBHOOK_URL=http://localhost:8790/webhook and WEBHOOK_EVENTS=${REQUIRED_EMAIL_TYPES.join(",")},email.already_registered,email.no_password. See docs/server.md.`
94
220
  );
95
221
  }
96
222
  const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
97
- let password;
98
- if (store) password = { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")), store };
99
- if (!clientId && !password) {
223
+ if (!store) {
100
224
  throw new ConfigError(
101
- "No sign-in method is configured. Set GOOGLE_CLIENT_ID for Google sign-in and/or DATABASE_URL (e.g. sqlite:./madauth.db) for e-mail & password sign-in. See docs/server.md."
225
+ "DATABASE_URL is not set. madAuth stores every user, whichever way they sign in: set e.g. DATABASE_URL=sqlite:./madauth.db or DATABASE_URL=dynamodb:<table>. See docs/server.md."
102
226
  );
103
227
  }
104
228
  const ttlValue = read("SESSION_TTL");
@@ -121,6 +245,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
121
245
  renewalTtlSeconds,
122
246
  cookieDomain: read("COOKIE_DOMAIN"),
123
247
  google: clientId ? { clientId, clientSecret: read("GOOGLE_CLIENT_SECRET") } : void 0,
248
+ store,
124
249
  password,
125
250
  webhook
126
251
  };
@@ -174,8 +299,9 @@ async function dynamoDbStore(tableName) {
174
299
  const { createDynamoDbAdapter } = await import("./dynamodb.js");
175
300
  return createDynamoDbAdapter({ tableName });
176
301
  }
302
+ var RENAMED_TYPES = { "password.reset": "email.password_reset", "roles.changed": "user.claims_changed" };
177
303
  var LOCAL_HOSTS = ["localhost", "127.0.0.1", "[::1]", "host.docker.internal"];
178
- function webhookFromEnv(url, secret, events, sendsEmails) {
304
+ function webhookFromEnv(url, secret, events) {
179
305
  if (!url) {
180
306
  if (secret || events) throw new ConfigError("WEBHOOK_SECRET and WEBHOOK_EVENTS need WEBHOOK_URL.");
181
307
  return void 0;
@@ -205,24 +331,22 @@ WEBHOOK_SECRET=${generateWebhookSecret()}
205
331
  const allTypes = `All types: ${WEBHOOK_TYPES.join(", ")}. See "Webhooks" in docs/server.md.`;
206
332
  if (!selected.size) {
207
333
  throw new ConfigError(
208
- "WEBHOOK_EVENTS is not set. List the types your webhook receiver handles; only these are sent. " + (sendsEmails ? `E-mail & password sign-in needs ${REQUIRED_EMAIL_TYPES.join(" and ")}. For a receiver that sends the e-mails:
334
+ `WEBHOOK_EVENTS is not set. List the types your webhook receiver handles; only these are sent. E-mail & password sign-in is on when they include ${REQUIRED_EMAIL_TYPES.join(" and ")}. For a receiver that sends the e-mails:
335
+
336
+ WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered,email.no_password
209
337
 
210
- WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered
338
+ For one that only wants to know who signed in:
211
339
 
212
- ` : "For example:\n\nWEBHOOK_EVENTS=user.signed_in\n\n") + allTypes
340
+ WEBHOOK_EVENTS=user.signed_in
341
+
342
+ ` + allTypes
213
343
  );
214
344
  }
215
345
  const unknown = [...selected].filter((e) => !WEBHOOK_TYPES.includes(e));
216
346
  if (unknown.length) {
217
- throw new ConfigError(`WEBHOOK_EVENTS: unknown type ${unknown.join(", ")}. Known types: ${WEBHOOK_TYPES.join(", ")}.`);
218
- }
219
- const missing = sendsEmails ? REQUIRED_EMAIL_TYPES.filter((type) => !selected.has(type)) : [];
220
- if (missing.length) {
347
+ const renamed = unknown.filter((e) => e in RENAMED_TYPES).map((e) => `${e} is now ${RENAMED_TYPES[e]}`);
221
348
  throw new ConfigError(
222
- `WEBHOOK_EVENTS lacks ${missing.join(" and ")}. E-mail & password sign-in can't work without these e-mails, so your webhook receiver must send them:
223
-
224
- WEBHOOK_EVENTS=${[...selected, ...missing].join(",")}
225
- `
349
+ `WEBHOOK_EVENTS: unknown type ${unknown.join(", ")}. ${renamed.length ? `${renamed.join("; ")}. ` : ""}Known types: ${WEBHOOK_TYPES.join(", ")}.`
226
350
  );
227
351
  }
228
352
  return { url: parsed.href, secret, events: selected };
@@ -303,45 +427,6 @@ function safeEqual(a, b) {
303
427
  return x.length === y.length && timingSafeEqual(x, y);
304
428
  }
305
429
 
306
- // src/roles.ts
307
- var ADMIN_ROLE = "admin";
308
- var MAX_ROLES = 20;
309
- var ROLE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
310
- function parseRoles(input) {
311
- if (!Array.isArray(input)) return null;
312
- if (!input.every((role) => typeof role === "string" && ROLE_NAME.test(role))) return null;
313
- const roles = [...new Set(input)].sort();
314
- return roles.length <= MAX_ROLES ? roles : null;
315
- }
316
- function sameRoles(a, b) {
317
- return a.length === b.length && a.every((role, i) => role === b[i]);
318
- }
319
- var Roles = class {
320
- constructor(store) {
321
- this.store = store;
322
- }
323
- /** The address's roles, sorted; empty if it has none. */
324
- async get(emailNormalized) {
325
- const record = await this.store.findOne("role", { id: emailNormalized });
326
- return typeof record?.roles === "string" && record.roles ? record.roles.split(" ").filter(Boolean) : [];
327
- }
328
- /** Replaces the address's roles. `updatedBy` is the address of the admin, or null for the command line. */
329
- async set(emailNormalized, roles, updatedBy) {
330
- if (!roles.length) {
331
- await this.remove(emailNormalized);
332
- return;
333
- }
334
- const data = { roles: roles.join(" "), updatedAt: Date.now(), updatedBy };
335
- if (await this.store.update("role", { id: emailNormalized }, data) === 1) return;
336
- if (!await this.store.create("role", { id: emailNormalized, ...data })) {
337
- await this.store.update("role", { id: emailNormalized }, data);
338
- }
339
- }
340
- async remove(emailNormalized) {
341
- await this.store.delete("role", { id: emailNormalized });
342
- }
343
- };
344
-
345
430
  // src/users.ts
346
431
  import { randomBytes as randomBytes2 } from "crypto";
347
432
  var MAX_CODE_ATTEMPTS = 5;
@@ -350,6 +435,18 @@ var newId = (prefix) => `${prefix}_${randomBytes2(16).toString("base64url")}`;
350
435
  function passwordAccountKey(userId) {
351
436
  return `password:${userId}`;
352
437
  }
438
+ function googleAccountKey(sub) {
439
+ return `google:${sub}`;
440
+ }
441
+ function toMadauthUser(user, profile = {}) {
442
+ const result = { id: user.id, email: user.email };
443
+ const name = user.name ?? profile.name;
444
+ if (name) result.name = name;
445
+ if (profile.picture) result.picture = profile.picture;
446
+ const claims = claimsFromJson(user.claims);
447
+ if (Object.keys(claims).length) result.claims = claims;
448
+ return result;
449
+ }
353
450
  var Users = class {
354
451
  /** `codeKey` keys the e-mail codes (see deriveCodeKey); only needed to issue and check them. */
355
452
  constructor(store, codeKey) {
@@ -367,11 +464,22 @@ var Users = class {
367
464
  return await this.store.findOne("user", { id });
368
465
  }
369
466
  async passwordAccount(userId) {
370
- return await this.store.findOne("account", { key: passwordAccountKey(userId) });
467
+ return this.findAccountByKey(passwordAccountKey(userId));
468
+ }
469
+ /** How the user signs in, from their accounts: e.g. `['google']` for a user without a password. */
470
+ async signInMethods(userId) {
471
+ const accounts = await this.store.findMany("account", { userId });
472
+ const methods = accounts.map((account) => String(account.key).split(":")[0]);
473
+ return SIGN_IN_METHODS.filter((method) => methods.includes(method));
474
+ }
475
+ async findAccountByKey(key) {
476
+ return await this.store.findOne("account", { key });
371
477
  }
372
- /** Creates a user with a password. Resolves to null if the e-mail address is taken. */
373
- async createPasswordUser(data) {
374
- const now = Date.now();
478
+ /**
479
+ * Creates a user with their first account. Resolves to null if the e-mail address or the account key is
480
+ * taken. `account.key` is `password:<userId>` for a password (its hash as `secret`), or e.g. `google:<sub>`.
481
+ */
482
+ async createUser(data) {
375
483
  const user = {
376
484
  id: newId("usr"),
377
485
  email: data.email,
@@ -381,45 +489,66 @@ var Users = class {
381
489
  sessionVersion: 0,
382
490
  lastMailAt: 0,
383
491
  wrongCodes: 0,
384
- createdAt: now
492
+ claims: null,
493
+ createdAt: Date.now()
385
494
  };
386
495
  if (!await this.store.create("user", user)) return null;
387
- const account = {
388
- id: newId("acc"),
389
- userId: user.id,
390
- key: passwordAccountKey(user.id),
391
- secret: data.passwordHash,
392
- failedAttempts: 0,
393
- lockedUntil: 0,
394
- createdAt: now
395
- };
396
- if (!await this.store.create("account", account)) {
496
+ const account = typeof data.account === "function" ? data.account(user.id) : data.account;
497
+ if (!await this.linkAccount(user.id, account)) {
397
498
  await this.store.delete("user", { id: user.id });
398
499
  return null;
399
500
  }
400
501
  return user;
401
502
  }
402
503
  /**
403
- * Deletes the user with this e-mail address and everything stored for them. Resolves to the deleted
404
- * user's ID, or null if there is no such user (any more).
504
+ * A provider verified the user's address: an unconfirmed password sign-up with it, whose password nobody
505
+ * has proven, is dropped (the account and its pending links and codes), and the user counts as verified.
506
+ */
507
+ async verifyByProvider(user) {
508
+ if (user.emailVerified) return;
509
+ await this.store.delete("account", { key: passwordAccountKey(user.id) });
510
+ await this.clearVerifications(user.id);
511
+ await this.updateUser(user.id, { emailVerified: true, wrongCodes: 0 });
512
+ }
513
+ /** Removes an account record, e.g. one left behind by a deletion that did not finish. */
514
+ async deleteAccount(id) {
515
+ await this.store.delete("account", { id });
516
+ }
517
+ /** Adds a sign-in method to a user. Resolves to null if the account key is taken. */
518
+ async linkAccount(userId, account) {
519
+ const record = {
520
+ id: newId("acc"),
521
+ userId,
522
+ key: account.key,
523
+ secret: account.secret ?? null,
524
+ failedAttempts: 0,
525
+ lockedUntil: 0,
526
+ email: account.email ?? null,
527
+ createdAt: Date.now()
528
+ };
529
+ return await this.store.create("account", record) ? record : null;
530
+ }
531
+ /**
532
+ * Deletes the user and everything stored for them. Resolves to false if there is no such user (any more).
405
533
  */
406
- async deleteByEmail(emailNormalized) {
407
- const user = await this.findByEmail(emailNormalized);
408
- if (!user) return null;
409
- if (await this.store.delete("user", { id: user.id }) !== 1) return null;
534
+ async deleteById(id) {
535
+ const user = await this.findById(id);
536
+ if (!user) return false;
537
+ if (await this.store.delete("user", { id: user.id }) !== 1) return false;
410
538
  try {
411
539
  await this.store.delete("account", { userId: user.id });
412
540
  await this.store.delete("verification", { userId: user.id });
413
541
  } catch (e) {
414
542
  console.error(`[madauth] Deleted user ${user.id}, but not all of their records: ${e.message}`);
415
543
  }
416
- return user.id;
544
+ return true;
417
545
  }
418
546
  async updateUser(id, patch) {
419
547
  await this.store.update("user", { id }, patch);
420
548
  }
549
+ /** Changes an account. Resolves to false if it is gone meanwhile. */
421
550
  async updateAccount(id, patch) {
422
- await this.store.update("account", { id }, patch);
551
+ return await this.store.update("account", { id }, patch) === 1;
423
552
  }
424
553
  /**
425
554
  * Counts a sign-in attempt: writes `patch` only if the account's `failedAttempts` is still the value that
@@ -499,6 +628,12 @@ var Users = class {
499
628
  };
500
629
 
501
630
  export {
631
+ ADMIN_ROLE,
632
+ parseRoles,
633
+ parseClaims,
634
+ sameClaims,
635
+ claimsFromJson,
636
+ isAdmin,
502
637
  MAX_PASSWORD_LENGTH,
503
638
  hashPassword,
504
639
  verifyPassword,
@@ -507,10 +642,14 @@ export {
507
642
  normalizeEmail,
508
643
  isValidEmail,
509
644
  deriveCodeKey,
510
- ADMIN_ROLE,
511
- parseRoles,
512
- sameRoles,
513
- Roles,
645
+ SIGN_IN_METHODS,
646
+ parseMethodSettings,
647
+ Settings,
648
+ isOn,
649
+ anyOn,
650
+ passwordAccountKey,
651
+ googleAccountKey,
652
+ toMadauthUser,
514
653
  Users,
515
654
  envVars,
516
655
  resolveEnv,
@@ -1,6 +1,6 @@
1
1
  import { JWK, JWTVerifyGetKey } from 'jose';
2
- import { S as StoreAdapter } from './schema-BSgYVRpd.js';
3
- import { W as WebhookSettings } from './webhook-ltwulgcB.js';
2
+ import { S as StoreAdapter } from './schema-3yhCc2jH.js';
3
+ import { W as WebhookSettings } from './webhook-j5u9Kv9l.js';
4
4
 
5
5
  interface MadauthConfig {
6
6
  /** Public base URL of the server (MADAUTH_ISSUER). */
@@ -22,13 +22,14 @@ interface MadauthConfig {
22
22
  /** Enables the server-side code flow (GOOGLE_CLIENT_SECRET). */
23
23
  clientSecret?: string;
24
24
  };
25
- /** E-mail & password sign-in; off without a store (DATABASE_URL or the `store` option). */
25
+ /** Where the users, their accounts, claims and the settings are kept (DATABASE_URL or the `store` option). */
26
+ store: StoreAdapter;
27
+ /** E-mail & password sign-in; off unless the webhook sends the e-mails (`email.verify` and `email.reset`). */
26
28
  password?: {
27
29
  /** PASSWORD_MIN_LENGTH. */
28
30
  minLength: number;
29
- store: StoreAdapter;
30
31
  };
31
- /** Where madAuth sends e-mails, sign-up checks and events (WEBHOOK_URL); required for password sign-in. */
32
+ /** Where madAuth sends e-mails, sign-up checks and events (WEBHOOK_URL). */
32
33
  webhook?: WebhookSettings;
33
34
  /** Test hook: resolves Google's signing keys. Defaults to Google's published JWKS. */
34
35
  jwksResolver?: JWTVerifyGetKey;
@@ -42,7 +43,7 @@ declare const envVars: {
42
43
  readonly ALLOWED_ORIGINS: true;
43
44
  readonly GOOGLE_CLIENT_ID: false;
44
45
  readonly GOOGLE_CLIENT_SECRET: false;
45
- readonly DATABASE_URL: false;
46
+ readonly DATABASE_URL: true;
46
47
  readonly PASSWORD_MIN_LENGTH: false;
47
48
  readonly WEBHOOK_URL: false;
48
49
  readonly WEBHOOK_SECRET: false;
package/dist/dynamodb.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  madauthSchema
3
- } from "./chunk-XNBWZZHT.js";
3
+ } from "./chunk-CD7Y4T66.js";
4
4
 
5
5
  // src/store/dynamodb.ts
6
6
  var SORT_KEY = "#";
@@ -1,7 +1,7 @@
1
- import { E as EntryOptions } from '../config-BkxADgtp.js';
1
+ import { E as EntryOptions } from '../config-Dg2fZuWR.js';
2
2
  import 'jose';
3
- import '../schema-BSgYVRpd.js';
4
- import '../webhook-ltwulgcB.js';
3
+ import '../schema-3yhCc2jH.js';
4
+ import '../webhook-j5u9Kv9l.js';
5
5
 
6
6
  /**
7
7
  * Registers madAuth as an Azure Functions v4 HTTP function that handles every route. Pass `store` to use your
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-YALS7OMV.js";
4
- import "../chunk-7CTAB3QV.js";
3
+ } from "../chunk-DVVBIFIV.js";
4
+ import "../chunk-DCDTO5LG.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-5CKEMC6M.js";
8
+ } from "../chunk-TI35AZ2U.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-6EJDVZCD.js";
10
+ import "../chunk-3QRKWLR7.js";
11
11
 
12
12
  // src/entry/azure.ts
13
13
  import { app } from "@azure/functions";
@@ -1,8 +1,8 @@
1
1
  import { LambdaEvent, LambdaContext, handle } from 'hono/aws-lambda';
2
- import { E as EntryOptions } from '../config-BkxADgtp.js';
2
+ import { E as EntryOptions } from '../config-Dg2fZuWR.js';
3
3
  import 'jose';
4
- import '../schema-BSgYVRpd.js';
5
- import '../webhook-ltwulgcB.js';
4
+ import '../schema-3yhCc2jH.js';
5
+ import '../webhook-j5u9Kv9l.js';
6
6
 
7
7
  type LambdaHandler = (event: LambdaEvent, context: LambdaContext) => ReturnType<ReturnType<typeof handle>>;
8
8
  /**
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-YALS7OMV.js";
4
- import "../chunk-7CTAB3QV.js";
3
+ } from "../chunk-DVVBIFIV.js";
4
+ import "../chunk-DCDTO5LG.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-5CKEMC6M.js";
8
+ } from "../chunk-TI35AZ2U.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-6EJDVZCD.js";
10
+ import "../chunk-3QRKWLR7.js";
11
11
 
12
12
  // src/entry/lambda.ts
13
13
  import { handle } from "hono/aws-lambda";
@@ -1,7 +1,7 @@
1
- import { E as EntryOptions } from '../config-BkxADgtp.js';
1
+ import { E as EntryOptions } from '../config-Dg2fZuWR.js';
2
2
  import 'jose';
3
- import '../schema-BSgYVRpd.js';
4
- import '../webhook-ltwulgcB.js';
3
+ import '../schema-3yhCc2jH.js';
4
+ import '../webhook-j5u9Kv9l.js';
5
5
 
6
6
  type StartOptions = EntryOptions;
7
7
  /**
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-YALS7OMV.js";
4
- import "../chunk-7CTAB3QV.js";
3
+ } from "../chunk-DVVBIFIV.js";
4
+ import "../chunk-DCDTO5LG.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-5CKEMC6M.js";
8
+ } from "../chunk-TI35AZ2U.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-6EJDVZCD.js";
10
+ import "../chunk-3QRKWLR7.js";
11
11
 
12
12
  // src/entry/node.ts
13
13
  import { serve } from "@hono/node-server";
package/dist/index.d.ts CHANGED
@@ -1,15 +1,21 @@
1
1
  import { Hono } from 'hono';
2
- import { M as MadauthConfig } from './config-BkxADgtp.js';
3
- export { C as ConfigError, a as ConfigOverrides, E as EntryOptions, e as envVars, l as loadConfig } from './config-BkxADgtp.js';
2
+ import { M as MadauthConfig } from './config-Dg2fZuWR.js';
3
+ export { C as ConfigError, a as ConfigOverrides, E as EntryOptions, e as envVars, l as loadConfig } from './config-Dg2fZuWR.js';
4
4
  import { JWK } from 'jose';
5
- import { a as Schema } from './schema-BSgYVRpd.js';
6
- export { F as FieldDef, b as FieldType, R as Row, S as StoreAdapter, V as Value, W as Where, m as madauthSchema } from './schema-BSgYVRpd.js';
7
- export { M as MadauthUser } from './user-C1A3BfOo.js';
8
- import './webhook-ltwulgcB.js';
5
+ import { a as Schema } from './schema-3yhCc2jH.js';
6
+ export { F as FieldDef, b as FieldType, R as Row, S as StoreAdapter, V as Value, W as Where, m as madauthSchema } from './schema-3yhCc2jH.js';
7
+ export { M as MadauthUser } from './user-BMWEWREH.js';
8
+ import './webhook-j5u9Kv9l.js';
9
9
 
10
10
  /** Creates a new private ES256 JWK for MADAUTH_SIGNING_KEY. */
11
11
  declare function generateSigningKey(): Promise<JWK>;
12
12
 
13
+ /** The sign-in methods madAuth has. */
14
+ declare const SIGN_IN_METHODS: readonly ["google", "password"];
15
+ type SignInMethod = (typeof SIGN_IN_METHODS)[number];
16
+ /** Which sign-in methods are on. A method that is not mentioned is on. */
17
+ type MethodSettings = Partial<Record<SignInMethod, boolean>>;
18
+
13
19
  /** Hash parameter that carries a sign-in error back to the app after the code flow. */
14
20
  declare const REDIRECT_ERROR_PARAM = "madauth_error";
15
21
 
@@ -32,4 +38,21 @@ declare function upgradeTablesSql(dialect: SqlDialect, fromVersion: number): str
32
38
  /** The SQL that creates madAuth's tables and indexes in an empty database. */
33
39
  declare function createTablesSql(dialect: SqlDialect, schema?: Schema): string;
34
40
 
35
- export { MadauthConfig, REDIRECT_ERROR_PARAM, Schema, type SqlDialect, columnName, createApp, createTablesSql, generateSigningKey, tableName, upgradeTablesSql };
41
+ /** A user's claims: a JSON object. */
42
+ type Claims = Record<string, unknown>;
43
+ /** The role that may manage claims and settings through the HTTP API. */
44
+ declare const ADMIN_ROLE = "admin";
45
+ /**
46
+ * Checks role names and returns them sorted and without duplicates, or null if `input` is not a list of
47
+ * valid names: lower-case letters, digits, `-` and `_`, starting with a letter, at most 32 characters.
48
+ */
49
+ declare function parseRoles(input: unknown): string[] | null;
50
+ /**
51
+ * Checks a claims object and returns it in canonical form (keys sorted, `roles` sorted and without
52
+ * duplicates), or null if `input` is not one: a JSON object whose keys are names (letters, digits and `_`,
53
+ * starting with a letter, at most 64 characters), whose `roles`, if any, is a valid list of role names,
54
+ * and which is at most {@link MAX_CLAIMS_LENGTH} characters as JSON.
55
+ */
56
+ declare function parseClaims(input: unknown): Claims | null;
57
+
58
+ export { ADMIN_ROLE, type Claims, MadauthConfig, type MethodSettings, REDIRECT_ERROR_PARAM, SIGN_IN_METHODS, Schema, type SignInMethod, type SqlDialect, columnName, createApp, createTablesSql, generateSigningKey, parseClaims, parseRoles, tableName, upgradeTablesSql };