@madauth/server 0.3.0-beta.1 → 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 +3 -3
- package/dist/{chunk-NPNM26J3.js → chunk-3QRKWLR7.js} +1 -0
- package/dist/{chunk-ZSBKNV5W.js → chunk-DVVBIFIV.js} +16 -11
- package/dist/{chunk-ODITZLMM.js → chunk-TI35AZ2U.js} +15 -8
- package/dist/{config-DEBYi8hb.d.ts → config-Dg2fZuWR.d.ts} +1 -1
- package/dist/entry/azure.d.ts +2 -2
- package/dist/entry/azure.js +3 -3
- package/dist/entry/lambda.d.ts +2 -2
- package/dist/entry/lambda.js +3 -3
- package/dist/entry/node.d.ts +2 -2
- package/dist/entry/node.js +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/standalone/azure.mjs +95 -80
- package/dist/standalone/lambda.mjs +95 -80
- package/dist/standalone/main.mjs +95 -80
- package/dist/{webhook-DCDP6SaE.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/bin.js
CHANGED
|
@@ -23,14 +23,14 @@ import {
|
|
|
23
23
|
parseClaims,
|
|
24
24
|
parseRoles,
|
|
25
25
|
passwordAccountKey
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-TI35AZ2U.js";
|
|
27
27
|
import {
|
|
28
28
|
generateSigningKey
|
|
29
29
|
} from "./chunk-JDQLSL36.js";
|
|
30
30
|
import {
|
|
31
31
|
DEV_WEBHOOK_RECEIVER_URL,
|
|
32
32
|
generateWebhookSecret
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-3QRKWLR7.js";
|
|
34
34
|
|
|
35
35
|
// src/cli.ts
|
|
36
36
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -293,7 +293,7 @@ ${name}=${value}
|
|
|
293
293
|
password && variable("WEBHOOK_SECRET", generateWebhookSecret(), "Signs every webhook call. Your receiver needs the same one."),
|
|
294
294
|
password && variable(
|
|
295
295
|
"WEBHOOK_EVENTS",
|
|
296
|
-
"email.verify,email.reset,email.already_registered",
|
|
296
|
+
"email.verify,email.reset,email.already_registered,email.no_password",
|
|
297
297
|
"The types your webhook receiver handles; only these are sent. Add e.g. signup.before or user.created."
|
|
298
298
|
),
|
|
299
299
|
variable("PORT", String(PORT), "Port the server listens on.")
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
toMadauthUser,
|
|
35
35
|
verifyAgainstDummy,
|
|
36
36
|
verifyPassword
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-TI35AZ2U.js";
|
|
38
38
|
import {
|
|
39
39
|
importSigningKeys
|
|
40
40
|
} from "./chunk-JDQLSL36.js";
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
WEBHOOK_TIMEOUT_MS,
|
|
43
43
|
createWebhookClient,
|
|
44
44
|
localeOf
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-3QRKWLR7.js";
|
|
46
46
|
|
|
47
47
|
// src/routes/google.ts
|
|
48
48
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
@@ -324,6 +324,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
324
324
|
locale
|
|
325
325
|
});
|
|
326
326
|
};
|
|
327
|
+
const sendAccountEmail = async (user, type, target, locale) => {
|
|
328
|
+
if (!webhook.wants(type)) return true;
|
|
329
|
+
const methods = await users.signInMethods(user.id);
|
|
330
|
+
return sendEmail(user, type, { link: target.href, site: target.host, locale, methods });
|
|
331
|
+
};
|
|
327
332
|
const consume = async (data, purpose) => {
|
|
328
333
|
if (typeof data.token === "string" && data.token) {
|
|
329
334
|
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
@@ -391,9 +396,7 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
391
396
|
const passwordHash = await hashPassword(data.password);
|
|
392
397
|
const existing = await users.findByEmail(normalizeEmail(email));
|
|
393
398
|
if (existing?.emailVerified) {
|
|
394
|
-
|
|
395
|
-
const sent = await sendEmail(existing, "email.already_registered", { link: target.href, site: target.host, locale });
|
|
396
|
-
return sent ? accepted(c) : unavailable(c);
|
|
399
|
+
return await sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c) : unavailable(c);
|
|
397
400
|
}
|
|
398
401
|
if (existing) {
|
|
399
402
|
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c);
|
|
@@ -442,8 +445,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
442
445
|
if (target instanceof Response) return target;
|
|
443
446
|
if (!isValidEmail(data.email)) return error(c, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
444
447
|
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
445
|
-
|
|
446
|
-
|
|
448
|
+
const locale = localeOf(data.locale);
|
|
449
|
+
if (user && await users.passwordAccount(user.id)) {
|
|
450
|
+
if (!await sendLinkEmail(user, "reset", target, locale)) return unavailable(c);
|
|
451
|
+
} else if (user) {
|
|
452
|
+
if (!await sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c);
|
|
447
453
|
}
|
|
448
454
|
return accepted(c);
|
|
449
455
|
});
|
|
@@ -453,11 +459,10 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
453
459
|
if (policy) return error(c, 400, "weak_password", policy);
|
|
454
460
|
const consumed = await consume(data, "reset");
|
|
455
461
|
const user = consumed.userId ? await users.findById(consumed.userId) : null;
|
|
456
|
-
|
|
462
|
+
const account = user ? await users.passwordAccount(user.id) : null;
|
|
463
|
+
if (!user || !account) return invalidVerification(c, consumed);
|
|
457
464
|
const secret = await hashPassword(data.password);
|
|
458
|
-
|
|
459
|
-
if (account) await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 });
|
|
460
|
-
else await users.linkAccount(user.id, { key: passwordAccountKey(user.id), secret });
|
|
465
|
+
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return invalidVerification(c, consumed);
|
|
461
466
|
const sessionVersion = user.sessionVersion + 1;
|
|
462
467
|
await users.updateUser(user.id, { emailVerified: true, sessionVersion, wrongCodes: 0 });
|
|
463
468
|
await users.clearVerifications(user.id);
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
WEBHOOK_TYPES,
|
|
9
9
|
checkWebhookSecret,
|
|
10
10
|
generateWebhookSecret
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3QRKWLR7.js";
|
|
12
12
|
|
|
13
13
|
// src/claims.ts
|
|
14
14
|
var ADMIN_ROLE = "admin";
|
|
@@ -216,7 +216,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
216
216
|
const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
|
|
217
217
|
if (!clientId && !password) {
|
|
218
218
|
throw new ConfigError(
|
|
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. See 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.`
|
|
220
220
|
);
|
|
221
221
|
}
|
|
222
222
|
const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
|
|
@@ -333,7 +333,7 @@ WEBHOOK_SECRET=${generateWebhookSecret()}
|
|
|
333
333
|
throw new ConfigError(
|
|
334
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
335
|
|
|
336
|
-
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered
|
|
336
|
+
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered,email.no_password
|
|
337
337
|
|
|
338
338
|
For one that only wants to know who signed in:
|
|
339
339
|
|
|
@@ -466,6 +466,12 @@ var Users = class {
|
|
|
466
466
|
async passwordAccount(userId) {
|
|
467
467
|
return this.findAccountByKey(passwordAccountKey(userId));
|
|
468
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
|
+
}
|
|
469
475
|
async findAccountByKey(key) {
|
|
470
476
|
return await this.store.findOne("account", { key });
|
|
471
477
|
}
|
|
@@ -540,8 +546,9 @@ var Users = class {
|
|
|
540
546
|
async updateUser(id, patch) {
|
|
541
547
|
await this.store.update("user", { id }, patch);
|
|
542
548
|
}
|
|
549
|
+
/** Changes an account. Resolves to false if it is gone meanwhile. */
|
|
543
550
|
async updateAccount(id, patch) {
|
|
544
|
-
await this.store.update("account", { id }, patch);
|
|
551
|
+
return await this.store.update("account", { id }, patch) === 1;
|
|
545
552
|
}
|
|
546
553
|
/**
|
|
547
554
|
* Counts a sign-in attempt: writes `patch` only if the account's `failedAttempts` is still the value that
|
|
@@ -635,15 +642,15 @@ export {
|
|
|
635
642
|
normalizeEmail,
|
|
636
643
|
isValidEmail,
|
|
637
644
|
deriveCodeKey,
|
|
638
|
-
passwordAccountKey,
|
|
639
|
-
googleAccountKey,
|
|
640
|
-
toMadauthUser,
|
|
641
|
-
Users,
|
|
642
645
|
SIGN_IN_METHODS,
|
|
643
646
|
parseMethodSettings,
|
|
644
647
|
Settings,
|
|
645
648
|
isOn,
|
|
646
649
|
anyOn,
|
|
650
|
+
passwordAccountKey,
|
|
651
|
+
googleAccountKey,
|
|
652
|
+
toMadauthUser,
|
|
653
|
+
Users,
|
|
647
654
|
envVars,
|
|
648
655
|
resolveEnv,
|
|
649
656
|
ConfigError,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JWK, JWTVerifyGetKey } from 'jose';
|
|
2
2
|
import { S as StoreAdapter } from './schema-3yhCc2jH.js';
|
|
3
|
-
import { W as WebhookSettings } from './webhook-
|
|
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). */
|
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
3
|
import '../schema-3yhCc2jH.js';
|
|
4
|
-
import '../webhook-
|
|
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-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
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
4
|
import '../schema-3yhCc2jH.js';
|
|
5
|
-
import '../webhook-
|
|
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-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
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
3
|
import '../schema-3yhCc2jH.js';
|
|
4
|
-
import '../webhook-
|
|
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-
|
|
3
|
+
} from "../chunk-DVVBIFIV.js";
|
|
4
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,11 +1,11 @@
|
|
|
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
5
|
import { a as Schema } from './schema-3yhCc2jH.js';
|
|
6
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
7
|
export { M as MadauthUser } from './user-BMWEWREH.js';
|
|
8
|
-
import './webhook-
|
|
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>;
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
REDIRECT_ERROR_PARAM,
|
|
12
12
|
createApp
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-DVVBIFIV.js";
|
|
14
14
|
import "./chunk-DCDTO5LG.js";
|
|
15
15
|
import {
|
|
16
16
|
ADMIN_ROLE,
|
|
@@ -20,11 +20,11 @@ import {
|
|
|
20
20
|
loadConfig,
|
|
21
21
|
parseClaims,
|
|
22
22
|
parseRoles
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-TI35AZ2U.js";
|
|
24
24
|
import {
|
|
25
25
|
generateSigningKey
|
|
26
26
|
} from "./chunk-JDQLSL36.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-3QRKWLR7.js";
|
|
28
28
|
export {
|
|
29
29
|
ADMIN_ROLE,
|
|
30
30
|
ConfigError,
|
|
@@ -7910,6 +7910,76 @@ function userFromClaims(claims) {
|
|
|
7910
7910
|
|
|
7911
7911
|
// src/users.ts
|
|
7912
7912
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
7913
|
+
|
|
7914
|
+
// src/settings.ts
|
|
7915
|
+
var SIGN_IN_METHODS = ["google", "password"];
|
|
7916
|
+
var METHODS_SETTING = "methods";
|
|
7917
|
+
function parseMethodSettings(input) {
|
|
7918
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
7919
|
+
const settings = {};
|
|
7920
|
+
for (const [key, value] of Object.entries(input)) {
|
|
7921
|
+
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
7922
|
+
settings[key] = value;
|
|
7923
|
+
}
|
|
7924
|
+
return settings;
|
|
7925
|
+
}
|
|
7926
|
+
var MAX_TRIES = 3;
|
|
7927
|
+
var Settings = class {
|
|
7928
|
+
constructor(store) {
|
|
7929
|
+
this.store = store;
|
|
7930
|
+
}
|
|
7931
|
+
/** The setting's value, or undefined if it was never set. */
|
|
7932
|
+
async get(id) {
|
|
7933
|
+
return (await this.read(id)).value;
|
|
7934
|
+
}
|
|
7935
|
+
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
7936
|
+
async read(id) {
|
|
7937
|
+
const record = await this.store.findOne("setting", { id });
|
|
7938
|
+
if (typeof record?.value !== "string") return {};
|
|
7939
|
+
try {
|
|
7940
|
+
return { json: record.value, value: JSON.parse(record.value) };
|
|
7941
|
+
} catch {
|
|
7942
|
+
return { json: record.value };
|
|
7943
|
+
}
|
|
7944
|
+
}
|
|
7945
|
+
/**
|
|
7946
|
+
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
7947
|
+
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
7948
|
+
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
7949
|
+
* `updatedBy` is the address of the admin, or null for the command line.
|
|
7950
|
+
*/
|
|
7951
|
+
async change(id, apply, updatedBy) {
|
|
7952
|
+
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
7953
|
+
const { json, value } = await this.read(id);
|
|
7954
|
+
const next = apply(value);
|
|
7955
|
+
if (next === null) return null;
|
|
7956
|
+
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
7957
|
+
const written = json === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json }, patch) === 1;
|
|
7958
|
+
if (written) return next;
|
|
7959
|
+
}
|
|
7960
|
+
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
7961
|
+
}
|
|
7962
|
+
/** Replaces the setting. */
|
|
7963
|
+
async set(id, value, updatedBy) {
|
|
7964
|
+
await this.change(id, () => value, updatedBy);
|
|
7965
|
+
}
|
|
7966
|
+
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
7967
|
+
async methods() {
|
|
7968
|
+
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
7969
|
+
}
|
|
7970
|
+
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
7971
|
+
async changeMethods(apply, updatedBy) {
|
|
7972
|
+
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
7973
|
+
}
|
|
7974
|
+
};
|
|
7975
|
+
function isOn(method, available, methods) {
|
|
7976
|
+
return available(method) && methods[method] !== false;
|
|
7977
|
+
}
|
|
7978
|
+
function anyOn(available, methods) {
|
|
7979
|
+
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
7980
|
+
}
|
|
7981
|
+
|
|
7982
|
+
// src/users.ts
|
|
7913
7983
|
var MAX_CODE_ATTEMPTS = 5;
|
|
7914
7984
|
var MAX_WRONG_CODES = 10;
|
|
7915
7985
|
var newId = (prefix) => `${prefix}_${randomBytes2(16).toString("base64url")}`;
|
|
@@ -7947,6 +8017,12 @@ var Users = class {
|
|
|
7947
8017
|
async passwordAccount(userId) {
|
|
7948
8018
|
return this.findAccountByKey(passwordAccountKey(userId));
|
|
7949
8019
|
}
|
|
8020
|
+
/** How the user signs in, from their accounts: e.g. `['google']` for a user without a password. */
|
|
8021
|
+
async signInMethods(userId) {
|
|
8022
|
+
const accounts = await this.store.findMany("account", { userId });
|
|
8023
|
+
const methods = accounts.map((account) => String(account.key).split(":")[0]);
|
|
8024
|
+
return SIGN_IN_METHODS.filter((method) => methods.includes(method));
|
|
8025
|
+
}
|
|
7950
8026
|
async findAccountByKey(key) {
|
|
7951
8027
|
return await this.store.findOne("account", { key });
|
|
7952
8028
|
}
|
|
@@ -8021,8 +8097,9 @@ var Users = class {
|
|
|
8021
8097
|
async updateUser(id, patch) {
|
|
8022
8098
|
await this.store.update("user", { id }, patch);
|
|
8023
8099
|
}
|
|
8100
|
+
/** Changes an account. Resolves to false if it is gone meanwhile. */
|
|
8024
8101
|
async updateAccount(id, patch) {
|
|
8025
|
-
await this.store.update("account", { id }, patch);
|
|
8102
|
+
return await this.store.update("account", { id }, patch) === 1;
|
|
8026
8103
|
}
|
|
8027
8104
|
/**
|
|
8028
8105
|
* Counts a sign-in attempt: writes `patch` only if the account's `failedAttempts` is still the value that
|
|
@@ -8107,6 +8184,7 @@ var WEBHOOK_TYPES = [
|
|
|
8107
8184
|
"email.verify",
|
|
8108
8185
|
"email.reset",
|
|
8109
8186
|
"email.already_registered",
|
|
8187
|
+
"email.no_password",
|
|
8110
8188
|
"signup.before",
|
|
8111
8189
|
"user.created",
|
|
8112
8190
|
"email.verified",
|
|
@@ -8416,6 +8494,11 @@ function passwordRoutes(app2, ctx, password, users, webhook) {
|
|
|
8416
8494
|
locale
|
|
8417
8495
|
});
|
|
8418
8496
|
};
|
|
8497
|
+
const sendAccountEmail = async (user, type, target, locale) => {
|
|
8498
|
+
if (!webhook.wants(type)) return true;
|
|
8499
|
+
const methods = await users.signInMethods(user.id);
|
|
8500
|
+
return sendEmail(user, type, { link: target.href, site: target.host, locale, methods });
|
|
8501
|
+
};
|
|
8419
8502
|
const consume = async (data, purpose) => {
|
|
8420
8503
|
if (typeof data.token === "string" && data.token) {
|
|
8421
8504
|
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
@@ -8483,9 +8566,7 @@ function passwordRoutes(app2, ctx, password, users, webhook) {
|
|
|
8483
8566
|
const passwordHash = await hashPassword(data.password);
|
|
8484
8567
|
const existing = await users.findByEmail(normalizeEmail(email));
|
|
8485
8568
|
if (existing?.emailVerified) {
|
|
8486
|
-
|
|
8487
|
-
const sent = await sendEmail(existing, "email.already_registered", { link: target.href, site: target.host, locale });
|
|
8488
|
-
return sent ? accepted(c) : unavailable(c);
|
|
8569
|
+
return await sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c) : unavailable(c);
|
|
8489
8570
|
}
|
|
8490
8571
|
if (existing) {
|
|
8491
8572
|
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c);
|
|
@@ -8534,8 +8615,11 @@ function passwordRoutes(app2, ctx, password, users, webhook) {
|
|
|
8534
8615
|
if (target instanceof Response) return target;
|
|
8535
8616
|
if (!isValidEmail(data.email)) return error(c, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
8536
8617
|
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
8537
|
-
|
|
8538
|
-
|
|
8618
|
+
const locale = localeOf(data.locale);
|
|
8619
|
+
if (user && await users.passwordAccount(user.id)) {
|
|
8620
|
+
if (!await sendLinkEmail(user, "reset", target, locale)) return unavailable(c);
|
|
8621
|
+
} else if (user) {
|
|
8622
|
+
if (!await sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c);
|
|
8539
8623
|
}
|
|
8540
8624
|
return accepted(c);
|
|
8541
8625
|
});
|
|
@@ -8545,11 +8629,10 @@ function passwordRoutes(app2, ctx, password, users, webhook) {
|
|
|
8545
8629
|
if (policy) return error(c, 400, "weak_password", policy);
|
|
8546
8630
|
const consumed = await consume(data, "reset");
|
|
8547
8631
|
const user = consumed.userId ? await users.findById(consumed.userId) : null;
|
|
8548
|
-
|
|
8632
|
+
const account = user ? await users.passwordAccount(user.id) : null;
|
|
8633
|
+
if (!user || !account) return invalidVerification(c, consumed);
|
|
8549
8634
|
const secret = await hashPassword(data.password);
|
|
8550
|
-
|
|
8551
|
-
if (account) await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 });
|
|
8552
|
-
else await users.linkAccount(user.id, { key: passwordAccountKey(user.id), secret });
|
|
8635
|
+
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return invalidVerification(c, consumed);
|
|
8553
8636
|
const sessionVersion = user.sessionVersion + 1;
|
|
8554
8637
|
await users.updateUser(user.id, { emailVerified: true, sessionVersion, wrongCodes: 0 });
|
|
8555
8638
|
await users.clearVerifications(user.id);
|
|
@@ -8560,74 +8643,6 @@ function passwordRoutes(app2, ctx, password, users, webhook) {
|
|
|
8560
8643
|
});
|
|
8561
8644
|
}
|
|
8562
8645
|
|
|
8563
|
-
// src/settings.ts
|
|
8564
|
-
var SIGN_IN_METHODS = ["google", "password"];
|
|
8565
|
-
var METHODS_SETTING = "methods";
|
|
8566
|
-
function parseMethodSettings(input) {
|
|
8567
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
8568
|
-
const settings = {};
|
|
8569
|
-
for (const [key, value] of Object.entries(input)) {
|
|
8570
|
-
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
8571
|
-
settings[key] = value;
|
|
8572
|
-
}
|
|
8573
|
-
return settings;
|
|
8574
|
-
}
|
|
8575
|
-
var MAX_TRIES = 3;
|
|
8576
|
-
var Settings = class {
|
|
8577
|
-
constructor(store) {
|
|
8578
|
-
this.store = store;
|
|
8579
|
-
}
|
|
8580
|
-
/** The setting's value, or undefined if it was never set. */
|
|
8581
|
-
async get(id) {
|
|
8582
|
-
return (await this.read(id)).value;
|
|
8583
|
-
}
|
|
8584
|
-
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
8585
|
-
async read(id) {
|
|
8586
|
-
const record = await this.store.findOne("setting", { id });
|
|
8587
|
-
if (typeof record?.value !== "string") return {};
|
|
8588
|
-
try {
|
|
8589
|
-
return { json: record.value, value: JSON.parse(record.value) };
|
|
8590
|
-
} catch {
|
|
8591
|
-
return { json: record.value };
|
|
8592
|
-
}
|
|
8593
|
-
}
|
|
8594
|
-
/**
|
|
8595
|
-
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
8596
|
-
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
8597
|
-
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
8598
|
-
* `updatedBy` is the address of the admin, or null for the command line.
|
|
8599
|
-
*/
|
|
8600
|
-
async change(id, apply, updatedBy) {
|
|
8601
|
-
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
8602
|
-
const { json, value } = await this.read(id);
|
|
8603
|
-
const next = apply(value);
|
|
8604
|
-
if (next === null) return null;
|
|
8605
|
-
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
8606
|
-
const written = json === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json }, patch) === 1;
|
|
8607
|
-
if (written) return next;
|
|
8608
|
-
}
|
|
8609
|
-
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
8610
|
-
}
|
|
8611
|
-
/** Replaces the setting. */
|
|
8612
|
-
async set(id, value, updatedBy) {
|
|
8613
|
-
await this.change(id, () => value, updatedBy);
|
|
8614
|
-
}
|
|
8615
|
-
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
8616
|
-
async methods() {
|
|
8617
|
-
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
8618
|
-
}
|
|
8619
|
-
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
8620
|
-
async changeMethods(apply, updatedBy) {
|
|
8621
|
-
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
8622
|
-
}
|
|
8623
|
-
};
|
|
8624
|
-
function isOn(method, available, methods) {
|
|
8625
|
-
return available(method) && methods[method] !== false;
|
|
8626
|
-
}
|
|
8627
|
-
function anyOn(available, methods) {
|
|
8628
|
-
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
8629
|
-
}
|
|
8630
|
-
|
|
8631
8646
|
// src/app.ts
|
|
8632
8647
|
var EVENT_TIMEOUT_MS = 5e3;
|
|
8633
8648
|
function createApp(config) {
|
|
@@ -8933,7 +8948,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
8933
8948
|
const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
|
|
8934
8949
|
if (!clientId && !password) {
|
|
8935
8950
|
throw new ConfigError(
|
|
8936
|
-
`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. See docs/server.md.`
|
|
8951
|
+
`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.`
|
|
8937
8952
|
);
|
|
8938
8953
|
}
|
|
8939
8954
|
const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
|
|
@@ -9042,7 +9057,7 @@ WEBHOOK_SECRET=${generateWebhookSecret()}
|
|
|
9042
9057
|
throw new ConfigError(
|
|
9043
9058
|
`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:
|
|
9044
9059
|
|
|
9045
|
-
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered
|
|
9060
|
+
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered,email.no_password
|
|
9046
9061
|
|
|
9047
9062
|
For one that only wants to know who signed in:
|
|
9048
9063
|
|
|
@@ -4062,6 +4062,76 @@ function userFromClaims(claims) {
|
|
|
4062
4062
|
|
|
4063
4063
|
// src/users.ts
|
|
4064
4064
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
4065
|
+
|
|
4066
|
+
// src/settings.ts
|
|
4067
|
+
var SIGN_IN_METHODS = ["google", "password"];
|
|
4068
|
+
var METHODS_SETTING = "methods";
|
|
4069
|
+
function parseMethodSettings(input) {
|
|
4070
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
4071
|
+
const settings = {};
|
|
4072
|
+
for (const [key, value] of Object.entries(input)) {
|
|
4073
|
+
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
4074
|
+
settings[key] = value;
|
|
4075
|
+
}
|
|
4076
|
+
return settings;
|
|
4077
|
+
}
|
|
4078
|
+
var MAX_TRIES = 3;
|
|
4079
|
+
var Settings = class {
|
|
4080
|
+
constructor(store) {
|
|
4081
|
+
this.store = store;
|
|
4082
|
+
}
|
|
4083
|
+
/** The setting's value, or undefined if it was never set. */
|
|
4084
|
+
async get(id) {
|
|
4085
|
+
return (await this.read(id)).value;
|
|
4086
|
+
}
|
|
4087
|
+
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
4088
|
+
async read(id) {
|
|
4089
|
+
const record = await this.store.findOne("setting", { id });
|
|
4090
|
+
if (typeof record?.value !== "string") return {};
|
|
4091
|
+
try {
|
|
4092
|
+
return { json: record.value, value: JSON.parse(record.value) };
|
|
4093
|
+
} catch {
|
|
4094
|
+
return { json: record.value };
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
/**
|
|
4098
|
+
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
4099
|
+
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
4100
|
+
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
4101
|
+
* `updatedBy` is the address of the admin, or null for the command line.
|
|
4102
|
+
*/
|
|
4103
|
+
async change(id, apply, updatedBy) {
|
|
4104
|
+
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
4105
|
+
const { json, value } = await this.read(id);
|
|
4106
|
+
const next = apply(value);
|
|
4107
|
+
if (next === null) return null;
|
|
4108
|
+
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
4109
|
+
const written = json === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json }, patch) === 1;
|
|
4110
|
+
if (written) return next;
|
|
4111
|
+
}
|
|
4112
|
+
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
4113
|
+
}
|
|
4114
|
+
/** Replaces the setting. */
|
|
4115
|
+
async set(id, value, updatedBy) {
|
|
4116
|
+
await this.change(id, () => value, updatedBy);
|
|
4117
|
+
}
|
|
4118
|
+
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
4119
|
+
async methods() {
|
|
4120
|
+
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
4121
|
+
}
|
|
4122
|
+
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
4123
|
+
async changeMethods(apply, updatedBy) {
|
|
4124
|
+
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
4125
|
+
}
|
|
4126
|
+
};
|
|
4127
|
+
function isOn(method, available, methods) {
|
|
4128
|
+
return available(method) && methods[method] !== false;
|
|
4129
|
+
}
|
|
4130
|
+
function anyOn(available, methods) {
|
|
4131
|
+
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
// src/users.ts
|
|
4065
4135
|
var MAX_CODE_ATTEMPTS = 5;
|
|
4066
4136
|
var MAX_WRONG_CODES = 10;
|
|
4067
4137
|
var newId = (prefix) => `${prefix}_${randomBytes2(16).toString("base64url")}`;
|
|
@@ -4099,6 +4169,12 @@ var Users = class {
|
|
|
4099
4169
|
async passwordAccount(userId) {
|
|
4100
4170
|
return this.findAccountByKey(passwordAccountKey(userId));
|
|
4101
4171
|
}
|
|
4172
|
+
/** How the user signs in, from their accounts: e.g. `['google']` for a user without a password. */
|
|
4173
|
+
async signInMethods(userId) {
|
|
4174
|
+
const accounts = await this.store.findMany("account", { userId });
|
|
4175
|
+
const methods = accounts.map((account) => String(account.key).split(":")[0]);
|
|
4176
|
+
return SIGN_IN_METHODS.filter((method) => methods.includes(method));
|
|
4177
|
+
}
|
|
4102
4178
|
async findAccountByKey(key) {
|
|
4103
4179
|
return await this.store.findOne("account", { key });
|
|
4104
4180
|
}
|
|
@@ -4173,8 +4249,9 @@ var Users = class {
|
|
|
4173
4249
|
async updateUser(id, patch) {
|
|
4174
4250
|
await this.store.update("user", { id }, patch);
|
|
4175
4251
|
}
|
|
4252
|
+
/** Changes an account. Resolves to false if it is gone meanwhile. */
|
|
4176
4253
|
async updateAccount(id, patch) {
|
|
4177
|
-
await this.store.update("account", { id }, patch);
|
|
4254
|
+
return await this.store.update("account", { id }, patch) === 1;
|
|
4178
4255
|
}
|
|
4179
4256
|
/**
|
|
4180
4257
|
* Counts a sign-in attempt: writes `patch` only if the account's `failedAttempts` is still the value that
|
|
@@ -4259,6 +4336,7 @@ var WEBHOOK_TYPES = [
|
|
|
4259
4336
|
"email.verify",
|
|
4260
4337
|
"email.reset",
|
|
4261
4338
|
"email.already_registered",
|
|
4339
|
+
"email.no_password",
|
|
4262
4340
|
"signup.before",
|
|
4263
4341
|
"user.created",
|
|
4264
4342
|
"email.verified",
|
|
@@ -4568,6 +4646,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
4568
4646
|
locale
|
|
4569
4647
|
});
|
|
4570
4648
|
};
|
|
4649
|
+
const sendAccountEmail = async (user, type, target, locale) => {
|
|
4650
|
+
if (!webhook.wants(type)) return true;
|
|
4651
|
+
const methods = await users.signInMethods(user.id);
|
|
4652
|
+
return sendEmail(user, type, { link: target.href, site: target.host, locale, methods });
|
|
4653
|
+
};
|
|
4571
4654
|
const consume = async (data, purpose) => {
|
|
4572
4655
|
if (typeof data.token === "string" && data.token) {
|
|
4573
4656
|
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
@@ -4635,9 +4718,7 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
4635
4718
|
const passwordHash = await hashPassword(data.password);
|
|
4636
4719
|
const existing = await users.findByEmail(normalizeEmail(email));
|
|
4637
4720
|
if (existing?.emailVerified) {
|
|
4638
|
-
|
|
4639
|
-
const sent = await sendEmail(existing, "email.already_registered", { link: target.href, site: target.host, locale });
|
|
4640
|
-
return sent ? accepted(c) : unavailable(c);
|
|
4721
|
+
return await sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c) : unavailable(c);
|
|
4641
4722
|
}
|
|
4642
4723
|
if (existing) {
|
|
4643
4724
|
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c);
|
|
@@ -4686,8 +4767,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
4686
4767
|
if (target instanceof Response) return target;
|
|
4687
4768
|
if (!isValidEmail(data.email)) return error(c, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
4688
4769
|
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
4689
|
-
|
|
4690
|
-
|
|
4770
|
+
const locale = localeOf(data.locale);
|
|
4771
|
+
if (user && await users.passwordAccount(user.id)) {
|
|
4772
|
+
if (!await sendLinkEmail(user, "reset", target, locale)) return unavailable(c);
|
|
4773
|
+
} else if (user) {
|
|
4774
|
+
if (!await sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c);
|
|
4691
4775
|
}
|
|
4692
4776
|
return accepted(c);
|
|
4693
4777
|
});
|
|
@@ -4697,11 +4781,10 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
4697
4781
|
if (policy) return error(c, 400, "weak_password", policy);
|
|
4698
4782
|
const consumed = await consume(data, "reset");
|
|
4699
4783
|
const user = consumed.userId ? await users.findById(consumed.userId) : null;
|
|
4700
|
-
|
|
4784
|
+
const account = user ? await users.passwordAccount(user.id) : null;
|
|
4785
|
+
if (!user || !account) return invalidVerification(c, consumed);
|
|
4701
4786
|
const secret = await hashPassword(data.password);
|
|
4702
|
-
|
|
4703
|
-
if (account) await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 });
|
|
4704
|
-
else await users.linkAccount(user.id, { key: passwordAccountKey(user.id), secret });
|
|
4787
|
+
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return invalidVerification(c, consumed);
|
|
4705
4788
|
const sessionVersion = user.sessionVersion + 1;
|
|
4706
4789
|
await users.updateUser(user.id, { emailVerified: true, sessionVersion, wrongCodes: 0 });
|
|
4707
4790
|
await users.clearVerifications(user.id);
|
|
@@ -4712,74 +4795,6 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
4712
4795
|
});
|
|
4713
4796
|
}
|
|
4714
4797
|
|
|
4715
|
-
// src/settings.ts
|
|
4716
|
-
var SIGN_IN_METHODS = ["google", "password"];
|
|
4717
|
-
var METHODS_SETTING = "methods";
|
|
4718
|
-
function parseMethodSettings(input) {
|
|
4719
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
4720
|
-
const settings = {};
|
|
4721
|
-
for (const [key, value] of Object.entries(input)) {
|
|
4722
|
-
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
4723
|
-
settings[key] = value;
|
|
4724
|
-
}
|
|
4725
|
-
return settings;
|
|
4726
|
-
}
|
|
4727
|
-
var MAX_TRIES = 3;
|
|
4728
|
-
var Settings = class {
|
|
4729
|
-
constructor(store) {
|
|
4730
|
-
this.store = store;
|
|
4731
|
-
}
|
|
4732
|
-
/** The setting's value, or undefined if it was never set. */
|
|
4733
|
-
async get(id) {
|
|
4734
|
-
return (await this.read(id)).value;
|
|
4735
|
-
}
|
|
4736
|
-
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
4737
|
-
async read(id) {
|
|
4738
|
-
const record = await this.store.findOne("setting", { id });
|
|
4739
|
-
if (typeof record?.value !== "string") return {};
|
|
4740
|
-
try {
|
|
4741
|
-
return { json: record.value, value: JSON.parse(record.value) };
|
|
4742
|
-
} catch {
|
|
4743
|
-
return { json: record.value };
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
/**
|
|
4747
|
-
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
4748
|
-
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
4749
|
-
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
4750
|
-
* `updatedBy` is the address of the admin, or null for the command line.
|
|
4751
|
-
*/
|
|
4752
|
-
async change(id, apply, updatedBy) {
|
|
4753
|
-
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
4754
|
-
const { json, value } = await this.read(id);
|
|
4755
|
-
const next = apply(value);
|
|
4756
|
-
if (next === null) return null;
|
|
4757
|
-
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
4758
|
-
const written = json === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json }, patch) === 1;
|
|
4759
|
-
if (written) return next;
|
|
4760
|
-
}
|
|
4761
|
-
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
4762
|
-
}
|
|
4763
|
-
/** Replaces the setting. */
|
|
4764
|
-
async set(id, value, updatedBy) {
|
|
4765
|
-
await this.change(id, () => value, updatedBy);
|
|
4766
|
-
}
|
|
4767
|
-
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
4768
|
-
async methods() {
|
|
4769
|
-
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
4770
|
-
}
|
|
4771
|
-
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
4772
|
-
async changeMethods(apply, updatedBy) {
|
|
4773
|
-
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
4774
|
-
}
|
|
4775
|
-
};
|
|
4776
|
-
function isOn(method, available, methods) {
|
|
4777
|
-
return available(method) && methods[method] !== false;
|
|
4778
|
-
}
|
|
4779
|
-
function anyOn(available, methods) {
|
|
4780
|
-
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
4781
|
-
}
|
|
4782
|
-
|
|
4783
4798
|
// src/app.ts
|
|
4784
4799
|
var EVENT_TIMEOUT_MS = 5e3;
|
|
4785
4800
|
function createApp(config) {
|
|
@@ -5085,7 +5100,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
5085
5100
|
const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
|
|
5086
5101
|
if (!clientId && !password) {
|
|
5087
5102
|
throw new ConfigError(
|
|
5088
|
-
`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. See docs/server.md.`
|
|
5103
|
+
`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.`
|
|
5089
5104
|
);
|
|
5090
5105
|
}
|
|
5091
5106
|
const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
|
|
@@ -5194,7 +5209,7 @@ WEBHOOK_SECRET=${generateWebhookSecret()}
|
|
|
5194
5209
|
throw new ConfigError(
|
|
5195
5210
|
`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:
|
|
5196
5211
|
|
|
5197
|
-
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered
|
|
5212
|
+
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered,email.no_password
|
|
5198
5213
|
|
|
5199
5214
|
For one that only wants to know who signed in:
|
|
5200
5215
|
|
package/dist/standalone/main.mjs
CHANGED
|
@@ -30020,6 +30020,7 @@ var WEBHOOK_TYPES = [
|
|
|
30020
30020
|
"email.verify",
|
|
30021
30021
|
"email.reset",
|
|
30022
30022
|
"email.already_registered",
|
|
30023
|
+
"email.no_password",
|
|
30023
30024
|
"signup.before",
|
|
30024
30025
|
"user.created",
|
|
30025
30026
|
"email.verified",
|
|
@@ -30158,7 +30159,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
30158
30159
|
const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
|
|
30159
30160
|
if (!clientId && !password) {
|
|
30160
30161
|
throw new ConfigError(
|
|
30161
|
-
`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. See docs/server.md.`
|
|
30162
|
+
`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.`
|
|
30162
30163
|
);
|
|
30163
30164
|
}
|
|
30164
30165
|
const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
|
|
@@ -30267,7 +30268,7 @@ WEBHOOK_SECRET=${generateWebhookSecret()}
|
|
|
30267
30268
|
throw new ConfigError(
|
|
30268
30269
|
`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:
|
|
30269
30270
|
|
|
30270
|
-
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered
|
|
30271
|
+
WEBHOOK_EVENTS=email.verify,email.reset,email.already_registered,email.no_password
|
|
30271
30272
|
|
|
30272
30273
|
For one that only wants to know who signed in:
|
|
30273
30274
|
|
|
@@ -33803,6 +33804,76 @@ function userFromClaims(claims) {
|
|
|
33803
33804
|
|
|
33804
33805
|
// src/users.ts
|
|
33805
33806
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
33807
|
+
|
|
33808
|
+
// src/settings.ts
|
|
33809
|
+
var SIGN_IN_METHODS = ["google", "password"];
|
|
33810
|
+
var METHODS_SETTING = "methods";
|
|
33811
|
+
function parseMethodSettings(input) {
|
|
33812
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
33813
|
+
const settings = {};
|
|
33814
|
+
for (const [key, value] of Object.entries(input)) {
|
|
33815
|
+
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
33816
|
+
settings[key] = value;
|
|
33817
|
+
}
|
|
33818
|
+
return settings;
|
|
33819
|
+
}
|
|
33820
|
+
var MAX_TRIES = 3;
|
|
33821
|
+
var Settings = class {
|
|
33822
|
+
constructor(store) {
|
|
33823
|
+
this.store = store;
|
|
33824
|
+
}
|
|
33825
|
+
/** The setting's value, or undefined if it was never set. */
|
|
33826
|
+
async get(id) {
|
|
33827
|
+
return (await this.read(id)).value;
|
|
33828
|
+
}
|
|
33829
|
+
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
33830
|
+
async read(id) {
|
|
33831
|
+
const record = await this.store.findOne("setting", { id });
|
|
33832
|
+
if (typeof record?.value !== "string") return {};
|
|
33833
|
+
try {
|
|
33834
|
+
return { json: record.value, value: JSON.parse(record.value) };
|
|
33835
|
+
} catch {
|
|
33836
|
+
return { json: record.value };
|
|
33837
|
+
}
|
|
33838
|
+
}
|
|
33839
|
+
/**
|
|
33840
|
+
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
33841
|
+
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
33842
|
+
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
33843
|
+
* `updatedBy` is the address of the admin, or null for the command line.
|
|
33844
|
+
*/
|
|
33845
|
+
async change(id, apply, updatedBy) {
|
|
33846
|
+
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
33847
|
+
const { json: json2, value } = await this.read(id);
|
|
33848
|
+
const next = apply(value);
|
|
33849
|
+
if (next === null) return null;
|
|
33850
|
+
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
33851
|
+
const written = json2 === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json2 }, patch) === 1;
|
|
33852
|
+
if (written) return next;
|
|
33853
|
+
}
|
|
33854
|
+
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
33855
|
+
}
|
|
33856
|
+
/** Replaces the setting. */
|
|
33857
|
+
async set(id, value, updatedBy) {
|
|
33858
|
+
await this.change(id, () => value, updatedBy);
|
|
33859
|
+
}
|
|
33860
|
+
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
33861
|
+
async methods() {
|
|
33862
|
+
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
33863
|
+
}
|
|
33864
|
+
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
33865
|
+
async changeMethods(apply, updatedBy) {
|
|
33866
|
+
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
33867
|
+
}
|
|
33868
|
+
};
|
|
33869
|
+
function isOn(method, available, methods) {
|
|
33870
|
+
return available(method) && methods[method] !== false;
|
|
33871
|
+
}
|
|
33872
|
+
function anyOn(available, methods) {
|
|
33873
|
+
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
33874
|
+
}
|
|
33875
|
+
|
|
33876
|
+
// src/users.ts
|
|
33806
33877
|
var MAX_CODE_ATTEMPTS = 5;
|
|
33807
33878
|
var MAX_WRONG_CODES = 10;
|
|
33808
33879
|
var newId = (prefix) => `${prefix}_${randomBytes3(16).toString("base64url")}`;
|
|
@@ -33840,6 +33911,12 @@ var Users = class {
|
|
|
33840
33911
|
async passwordAccount(userId) {
|
|
33841
33912
|
return this.findAccountByKey(passwordAccountKey(userId));
|
|
33842
33913
|
}
|
|
33914
|
+
/** How the user signs in, from their accounts: e.g. `['google']` for a user without a password. */
|
|
33915
|
+
async signInMethods(userId) {
|
|
33916
|
+
const accounts = await this.store.findMany("account", { userId });
|
|
33917
|
+
const methods = accounts.map((account) => String(account.key).split(":")[0]);
|
|
33918
|
+
return SIGN_IN_METHODS.filter((method) => methods.includes(method));
|
|
33919
|
+
}
|
|
33843
33920
|
async findAccountByKey(key) {
|
|
33844
33921
|
return await this.store.findOne("account", { key });
|
|
33845
33922
|
}
|
|
@@ -33914,8 +33991,9 @@ var Users = class {
|
|
|
33914
33991
|
async updateUser(id, patch) {
|
|
33915
33992
|
await this.store.update("user", { id }, patch);
|
|
33916
33993
|
}
|
|
33994
|
+
/** Changes an account. Resolves to false if it is gone meanwhile. */
|
|
33917
33995
|
async updateAccount(id, patch) {
|
|
33918
|
-
await this.store.update("account", { id }, patch);
|
|
33996
|
+
return await this.store.update("account", { id }, patch) === 1;
|
|
33919
33997
|
}
|
|
33920
33998
|
/**
|
|
33921
33999
|
* Counts a sign-in attempt: writes `patch` only if the account's `failedAttempts` is still the value that
|
|
@@ -34231,6 +34309,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34231
34309
|
locale
|
|
34232
34310
|
});
|
|
34233
34311
|
};
|
|
34312
|
+
const sendAccountEmail = async (user, type, target, locale) => {
|
|
34313
|
+
if (!webhook.wants(type)) return true;
|
|
34314
|
+
const methods = await users.signInMethods(user.id);
|
|
34315
|
+
return sendEmail(user, type, { link: target.href, site: target.host, locale, methods });
|
|
34316
|
+
};
|
|
34234
34317
|
const consume = async (data, purpose) => {
|
|
34235
34318
|
if (typeof data.token === "string" && data.token) {
|
|
34236
34319
|
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
@@ -34298,9 +34381,7 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34298
34381
|
const passwordHash = await hashPassword(data.password);
|
|
34299
34382
|
const existing = await users.findByEmail(normalizeEmail(email));
|
|
34300
34383
|
if (existing?.emailVerified) {
|
|
34301
|
-
|
|
34302
|
-
const sent = await sendEmail(existing, "email.already_registered", { link: target.href, site: target.host, locale });
|
|
34303
|
-
return sent ? accepted(c6) : unavailable(c6);
|
|
34384
|
+
return await sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34304
34385
|
}
|
|
34305
34386
|
if (existing) {
|
|
34306
34387
|
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c6);
|
|
@@ -34349,8 +34430,11 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34349
34430
|
if (target instanceof Response) return target;
|
|
34350
34431
|
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
34351
34432
|
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34352
|
-
|
|
34353
|
-
|
|
34433
|
+
const locale = localeOf(data.locale);
|
|
34434
|
+
if (user && await users.passwordAccount(user.id)) {
|
|
34435
|
+
if (!await sendLinkEmail(user, "reset", target, locale)) return unavailable(c6);
|
|
34436
|
+
} else if (user) {
|
|
34437
|
+
if (!await sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c6);
|
|
34354
34438
|
}
|
|
34355
34439
|
return accepted(c6);
|
|
34356
34440
|
});
|
|
@@ -34360,11 +34444,10 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34360
34444
|
if (policy) return error(c6, 400, "weak_password", policy);
|
|
34361
34445
|
const consumed = await consume(data, "reset");
|
|
34362
34446
|
const user = consumed.userId ? await users.findById(consumed.userId) : null;
|
|
34363
|
-
|
|
34447
|
+
const account = user ? await users.passwordAccount(user.id) : null;
|
|
34448
|
+
if (!user || !account) return invalidVerification(c6, consumed);
|
|
34364
34449
|
const secret = await hashPassword(data.password);
|
|
34365
|
-
|
|
34366
|
-
if (account) await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 });
|
|
34367
|
-
else await users.linkAccount(user.id, { key: passwordAccountKey(user.id), secret });
|
|
34450
|
+
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return invalidVerification(c6, consumed);
|
|
34368
34451
|
const sessionVersion = user.sessionVersion + 1;
|
|
34369
34452
|
await users.updateUser(user.id, { emailVerified: true, sessionVersion, wrongCodes: 0 });
|
|
34370
34453
|
await users.clearVerifications(user.id);
|
|
@@ -34375,74 +34458,6 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34375
34458
|
});
|
|
34376
34459
|
}
|
|
34377
34460
|
|
|
34378
|
-
// src/settings.ts
|
|
34379
|
-
var SIGN_IN_METHODS = ["google", "password"];
|
|
34380
|
-
var METHODS_SETTING = "methods";
|
|
34381
|
-
function parseMethodSettings(input) {
|
|
34382
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
34383
|
-
const settings = {};
|
|
34384
|
-
for (const [key, value] of Object.entries(input)) {
|
|
34385
|
-
if (!SIGN_IN_METHODS.includes(key) || typeof value !== "boolean") return null;
|
|
34386
|
-
settings[key] = value;
|
|
34387
|
-
}
|
|
34388
|
-
return settings;
|
|
34389
|
-
}
|
|
34390
|
-
var MAX_TRIES = 3;
|
|
34391
|
-
var Settings = class {
|
|
34392
|
-
constructor(store) {
|
|
34393
|
-
this.store = store;
|
|
34394
|
-
}
|
|
34395
|
-
/** The setting's value, or undefined if it was never set. */
|
|
34396
|
-
async get(id) {
|
|
34397
|
-
return (await this.read(id)).value;
|
|
34398
|
-
}
|
|
34399
|
-
/** The stored JSON and what it holds; both undefined if the setting was never set. */
|
|
34400
|
-
async read(id) {
|
|
34401
|
-
const record = await this.store.findOne("setting", { id });
|
|
34402
|
-
if (typeof record?.value !== "string") return {};
|
|
34403
|
-
try {
|
|
34404
|
-
return { json: record.value, value: JSON.parse(record.value) };
|
|
34405
|
-
} catch {
|
|
34406
|
-
return { json: record.value };
|
|
34407
|
-
}
|
|
34408
|
-
}
|
|
34409
|
-
/**
|
|
34410
|
-
* Changes the setting: `apply` gets the current value and returns the new one, or null to refuse the
|
|
34411
|
-
* change. The write only lands if nobody changed the setting since `apply` read it; otherwise `apply` runs
|
|
34412
|
-
* again with the newer value. Resolves to the new value, or null if `apply` refused.
|
|
34413
|
-
* `updatedBy` is the address of the admin, or null for the command line.
|
|
34414
|
-
*/
|
|
34415
|
-
async change(id, apply, updatedBy) {
|
|
34416
|
-
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
34417
|
-
const { json: json2, value } = await this.read(id);
|
|
34418
|
-
const next = apply(value);
|
|
34419
|
-
if (next === null) return null;
|
|
34420
|
-
const patch = { value: JSON.stringify(next), updatedAt: Date.now(), updatedBy };
|
|
34421
|
-
const written = json2 === void 0 ? await this.store.create("setting", { id, ...patch }) : await this.store.update("setting", { id, value: json2 }, patch) === 1;
|
|
34422
|
-
if (written) return next;
|
|
34423
|
-
}
|
|
34424
|
-
throw new Error(`Setting "${id}" kept changing; try again.`);
|
|
34425
|
-
}
|
|
34426
|
-
/** Replaces the setting. */
|
|
34427
|
-
async set(id, value, updatedBy) {
|
|
34428
|
-
await this.change(id, () => value, updatedBy);
|
|
34429
|
-
}
|
|
34430
|
-
/** Which sign-in methods are on; a method that was never switched off is on. */
|
|
34431
|
-
async methods() {
|
|
34432
|
-
return parseMethodSettings(await this.get(METHODS_SETTING)) ?? {};
|
|
34433
|
-
}
|
|
34434
|
-
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
34435
|
-
async changeMethods(apply, updatedBy) {
|
|
34436
|
-
return this.change(METHODS_SETTING, (current) => apply(parseMethodSettings(current) ?? {}), updatedBy);
|
|
34437
|
-
}
|
|
34438
|
-
};
|
|
34439
|
-
function isOn(method, available, methods) {
|
|
34440
|
-
return available(method) && methods[method] !== false;
|
|
34441
|
-
}
|
|
34442
|
-
function anyOn(available, methods) {
|
|
34443
|
-
return SIGN_IN_METHODS.some((method) => isOn(method, available, methods));
|
|
34444
|
-
}
|
|
34445
|
-
|
|
34446
34461
|
// src/app.ts
|
|
34447
34462
|
var EVENT_TIMEOUT_MS = 5e3;
|
|
34448
34463
|
function createApp(config) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Every `type` madAuth sends. */
|
|
2
|
-
declare const WEBHOOK_TYPES: readonly ["email.verify", "email.reset", "email.already_registered", "signup.before", "user.created", "email.verified", "email.password_reset", "user.signed_in", "user.deleted", "user.claims_changed"];
|
|
2
|
+
declare const WEBHOOK_TYPES: readonly ["email.verify", "email.reset", "email.already_registered", "email.no_password", "signup.before", "user.created", "email.verified", "email.password_reset", "user.signed_in", "user.deleted", "user.claims_changed"];
|
|
3
3
|
type WebhookType = (typeof WEBHOOK_TYPES)[number];
|
|
4
4
|
/** How old a call may be before receivers reject it, so a captured call can't be replayed. */
|
|
5
5
|
declare const WEBHOOK_TOLERANCE_SECONDS: number;
|
package/dist/webhook.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { a as WEBHOOK_TOLERANCE_SECONDS, b as WEBHOOK_TYPES, c as WebhookType, g as generateWebhookSecret, s as signWebhook, v as verifyWebhook } from './webhook-
|
|
1
|
+
export { a as WEBHOOK_TOLERANCE_SECONDS, b as WEBHOOK_TYPES, c as WebhookType, g as generateWebhookSecret, s as signWebhook, v as verifyWebhook } from './webhook-j5u9Kv9l.js';
|
package/dist/webhook.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madauth/server",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.2",
|
|
4
4
|
"description": "madAuth server: Google and e-mail & password sign-in with madAuth sessions, for Docker, AWS Lambda and Azure Functions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "David Neuy",
|