@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.
- package/dist/bin.js +99 -32
- package/dist/{chunk-6EJDVZCD.js → chunk-3QRKWLR7.js} +10 -2
- package/dist/{chunk-CNL562LZ.js → chunk-56MOVFO4.js} +24 -2
- package/dist/{chunk-XNBWZZHT.js → chunk-CD7Y4T66.js} +11 -9
- package/dist/{chunk-7CTAB3QV.js → chunk-DCDTO5LG.js} +2 -2
- package/dist/{chunk-YALS7OMV.js → chunk-DVVBIFIV.js} +222 -122
- package/dist/{chunk-5CKEMC6M.js → chunk-TI35AZ2U.js} +227 -88
- package/dist/{config-BkxADgtp.d.ts → config-Dg2fZuWR.d.ts} +7 -6
- package/dist/dynamodb.js +1 -1
- package/dist/entry/azure.d.ts +3 -3
- package/dist/entry/azure.js +4 -4
- package/dist/entry/lambda.d.ts +3 -3
- package/dist/entry/lambda.js +4 -4
- package/dist/entry/node.d.ts +3 -3
- package/dist/entry/node.js +4 -4
- package/dist/index.d.ts +30 -7
- package/dist/index.js +15 -7
- package/dist/{schema-BSgYVRpd.d.ts → schema-3yhCc2jH.d.ts} +17 -9
- package/dist/sqlite.js +2 -2
- package/dist/standalone/azure.mjs +847 -597
- package/dist/standalone/lambda.mjs +847 -597
- package/dist/standalone/main.mjs +594 -344
- package/dist/store/dynamodb.d.ts +1 -1
- package/dist/store/sqlite.d.ts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -0
- package/dist/user-BMWEWREH.d.ts +12 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/{webhook-ltwulgcB.d.ts → webhook-j5u9Kv9l.d.ts} +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.js +1 -1
- package/package.json +1 -1
- package/dist/user-C1A3BfOo.d.ts +0 -12
|
@@ -8,7 +8,132 @@ import {
|
|
|
8
8
|
WEBHOOK_TYPES,
|
|
9
9
|
checkWebhookSecret,
|
|
10
10
|
generateWebhookSecret
|
|
11
|
-
} from "./chunk-
|
|
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:
|
|
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
|
|
90
|
-
const
|
|
91
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
98
|
-
if (store) password = { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")), store };
|
|
99
|
-
if (!clientId && !password) {
|
|
223
|
+
if (!store) {
|
|
100
224
|
throw new ConfigError(
|
|
101
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
338
|
+
For one that only wants to know who signed in:
|
|
211
339
|
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
492
|
+
claims: null,
|
|
493
|
+
createdAt: Date.now()
|
|
385
494
|
};
|
|
386
495
|
if (!await this.store.create("user", user)) return null;
|
|
387
|
-
const account =
|
|
388
|
-
|
|
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
|
-
*
|
|
404
|
-
*
|
|
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
|
|
407
|
-
const user = await this.
|
|
408
|
-
if (!user) return
|
|
409
|
-
if (await this.store.delete("user", { id: user.id }) !== 1) return
|
|
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
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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-
|
|
3
|
-
import { W as WebhookSettings } from './webhook-
|
|
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
|
-
/**
|
|
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)
|
|
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:
|
|
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
package/dist/entry/azure.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as EntryOptions } from '../config-
|
|
1
|
+
import { E as EntryOptions } from '../config-Dg2fZuWR.js';
|
|
2
2
|
import 'jose';
|
|
3
|
-
import '../schema-
|
|
4
|
-
import '../webhook-
|
|
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
|
package/dist/entry/azure.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApp
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
|
+
import "../chunk-DCDTO5LG.js";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
7
7
|
resolveEnv
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-TI35AZ2U.js";
|
|
9
9
|
import "../chunk-JDQLSL36.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-3QRKWLR7.js";
|
|
11
11
|
|
|
12
12
|
// src/entry/azure.ts
|
|
13
13
|
import { app } from "@azure/functions";
|
package/dist/entry/lambda.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LambdaEvent, LambdaContext, handle } from 'hono/aws-lambda';
|
|
2
|
-
import { E as EntryOptions } from '../config-
|
|
2
|
+
import { E as EntryOptions } from '../config-Dg2fZuWR.js';
|
|
3
3
|
import 'jose';
|
|
4
|
-
import '../schema-
|
|
5
|
-
import '../webhook-
|
|
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
|
/**
|
package/dist/entry/lambda.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApp
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
|
+
import "../chunk-DCDTO5LG.js";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
7
7
|
resolveEnv
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-TI35AZ2U.js";
|
|
9
9
|
import "../chunk-JDQLSL36.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-3QRKWLR7.js";
|
|
11
11
|
|
|
12
12
|
// src/entry/lambda.ts
|
|
13
13
|
import { handle } from "hono/aws-lambda";
|
package/dist/entry/node.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as EntryOptions } from '../config-
|
|
1
|
+
import { E as EntryOptions } from '../config-Dg2fZuWR.js';
|
|
2
2
|
import 'jose';
|
|
3
|
-
import '../schema-
|
|
4
|
-
import '../webhook-
|
|
3
|
+
import '../schema-3yhCc2jH.js';
|
|
4
|
+
import '../webhook-j5u9Kv9l.js';
|
|
5
5
|
|
|
6
6
|
type StartOptions = EntryOptions;
|
|
7
7
|
/**
|
package/dist/entry/node.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApp
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
|
+
import "../chunk-DCDTO5LG.js";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig,
|
|
7
7
|
resolveEnv
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-TI35AZ2U.js";
|
|
9
9
|
import "../chunk-JDQLSL36.js";
|
|
10
|
-
import "../chunk-
|
|
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-
|
|
3
|
-
export { C as ConfigError, a as ConfigOverrides, E as EntryOptions, e as envVars, l as loadConfig } from './config-
|
|
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-
|
|
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-
|
|
7
|
-
export { M as MadauthUser } from './user-
|
|
8
|
-
import './webhook-
|
|
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
|
-
|
|
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 };
|