@madauth/server 0.3.0-beta.2 → 0.4.0-beta.1
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/README.md +3 -2
- package/dist/bin.js +104 -42
- package/dist/chunk-3PACOYTG.js +1185 -0
- package/dist/{chunk-DCDTO5LG.js → chunk-A7UYZRBL.js} +1 -0
- package/dist/{chunk-CD7Y4T66.js → chunk-GA4TF7EI.js} +24 -2
- package/dist/{chunk-TI35AZ2U.js → chunk-ISJGOCF3.js} +250 -83
- package/dist/{chunk-56MOVFO4.js → chunk-OMIX3ERC.js} +11 -2
- package/dist/{chunk-3QRKWLR7.js → chunk-Y7ZSUROX.js} +4 -1
- package/dist/{config-Dg2fZuWR.d.ts → config-DkE_2D3-.d.ts} +5 -2
- 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 +29 -14
- package/dist/index.js +10 -6
- package/dist/{schema-3yhCc2jH.d.ts → schema-DTeiSuQB.d.ts} +39 -2
- package/dist/sqlite.js +2 -2
- package/dist/standalone/azure.mjs +1316 -676
- package/dist/standalone/lambda.mjs +1311 -671
- package/dist/standalone/main.mjs +1142 -502
- 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 → user-BZs84sui.d.ts} +5 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/{webhook-j5u9Kv9l.d.ts → webhook-B6P50ocX.d.ts} +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-DVVBIFIV.js +0 -718
package/dist/standalone/main.mjs
CHANGED
|
@@ -38,7 +38,7 @@ var init_schema = __esm({
|
|
|
38
38
|
"src/store/schema.ts"() {
|
|
39
39
|
"use strict";
|
|
40
40
|
madauthSchema = {
|
|
41
|
-
version:
|
|
41
|
+
version: 5,
|
|
42
42
|
models: {
|
|
43
43
|
/** Someone who signed in, whichever way: the e-mail address is the identity shared by the sign-in methods. */
|
|
44
44
|
user: {
|
|
@@ -57,7 +57,10 @@ var init_schema = __esm({
|
|
|
57
57
|
createdAt: { type: "number" }
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* How a user signs in. `key` is `password:<userId>`, `google:<sub>` or `totp:<userId>` (the authenticator
|
|
62
|
+
* app, whose `secret` is encrypted).
|
|
63
|
+
*/
|
|
61
64
|
account: {
|
|
62
65
|
fields: {
|
|
63
66
|
id: { type: "string", primaryKey: true },
|
|
@@ -68,6 +71,25 @@ var init_schema = __esm({
|
|
|
68
71
|
lockedUntil: { type: "number" },
|
|
69
72
|
/** The address the provider reported at the last sign-in, e.g. Google's current primary address (since version 4). */
|
|
70
73
|
email: { type: "string", nullable: true },
|
|
74
|
+
/** The time step of the last authenticator code that was accepted, so a code works once; 0 before (since version 5). */
|
|
75
|
+
lastUsedStep: { type: "number" },
|
|
76
|
+
/**
|
|
77
|
+
* The setup an authenticator secret came from; its recovery codes carry the same id, so the app and
|
|
78
|
+
* its codes always belong together. Empty for other accounts (since version 5).
|
|
79
|
+
*/
|
|
80
|
+
setupId: { type: "string", nullable: true },
|
|
81
|
+
createdAt: { type: "number" }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* A recovery code of a user's authenticator app (since version 5). `id` is the code's keyed hash; the
|
|
86
|
+
* record goes when the code is used. It works only while the account's `setupId` is its own.
|
|
87
|
+
*/
|
|
88
|
+
recoveryCode: {
|
|
89
|
+
fields: {
|
|
90
|
+
id: { type: "string", primaryKey: true },
|
|
91
|
+
userId: { type: "string", index: true },
|
|
92
|
+
setupId: { type: "string" },
|
|
71
93
|
createdAt: { type: "number" }
|
|
72
94
|
}
|
|
73
95
|
},
|
|
@@ -99,7 +121,7 @@ var init_schema = __esm({
|
|
|
99
121
|
|
|
100
122
|
// src/store/sql.ts
|
|
101
123
|
function tableName(model) {
|
|
102
|
-
return `madauth_${model}`;
|
|
124
|
+
return `madauth_${columnName(model)}`;
|
|
103
125
|
}
|
|
104
126
|
function columnName(field) {
|
|
105
127
|
return field.replace(/[A-Z]/g, (c6) => `_${c6.toLowerCase()}`);
|
|
@@ -167,6 +189,15 @@ var init_sql = __esm({
|
|
|
167
189
|
`DROP TABLE ${tableName("role")};`,
|
|
168
190
|
createTablesSql(dialect, { version: 4, models: { setting: madauthSchema.models.setting } })
|
|
169
191
|
].join("\n")
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
// The authenticator app: which code was used last, and the recovery codes.
|
|
195
|
+
version: 5,
|
|
196
|
+
sql: (dialect) => [
|
|
197
|
+
`ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("lastUsedStep")} ${columnTypes[dialect].number} NOT NULL DEFAULT 0;`,
|
|
198
|
+
`ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("setupId")} ${columnTypes[dialect].string};`,
|
|
199
|
+
createTablesSql(dialect, { version: 5, models: { recoveryCode: madauthSchema.models.recoveryCode } })
|
|
200
|
+
].join("\n")
|
|
170
201
|
}
|
|
171
202
|
];
|
|
172
203
|
}
|
|
@@ -1210,17 +1241,17 @@ var init_schemaDeserializationMiddleware = __esm({
|
|
|
1210
1241
|
response,
|
|
1211
1242
|
output: parsed
|
|
1212
1243
|
};
|
|
1213
|
-
} catch (
|
|
1214
|
-
Object.defineProperty(
|
|
1244
|
+
} catch (error2) {
|
|
1245
|
+
Object.defineProperty(error2, "$response", {
|
|
1215
1246
|
value: response,
|
|
1216
1247
|
enumerable: false,
|
|
1217
1248
|
writable: false,
|
|
1218
1249
|
configurable: false
|
|
1219
1250
|
});
|
|
1220
|
-
if (!("$metadata" in
|
|
1251
|
+
if (!("$metadata" in error2)) {
|
|
1221
1252
|
const hint = `Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.`;
|
|
1222
1253
|
try {
|
|
1223
|
-
|
|
1254
|
+
error2.message += "\n " + hint;
|
|
1224
1255
|
} catch (ignored) {
|
|
1225
1256
|
if (!context.logger || context.logger?.constructor?.name === "NoOpLogger") {
|
|
1226
1257
|
console.warn(hint);
|
|
@@ -1228,16 +1259,16 @@ var init_schemaDeserializationMiddleware = __esm({
|
|
|
1228
1259
|
context.logger?.warn?.(hint);
|
|
1229
1260
|
}
|
|
1230
1261
|
}
|
|
1231
|
-
if (typeof
|
|
1232
|
-
if (
|
|
1233
|
-
|
|
1262
|
+
if (typeof error2.$responseBodyText !== "undefined") {
|
|
1263
|
+
if (error2.$response) {
|
|
1264
|
+
error2.$response.body = error2.$responseBodyText;
|
|
1234
1265
|
}
|
|
1235
1266
|
}
|
|
1236
1267
|
try {
|
|
1237
1268
|
if (HttpResponse.isInstance(response)) {
|
|
1238
1269
|
const { headers = {}, statusCode } = response;
|
|
1239
1270
|
const headerEntries = Object.entries(headers);
|
|
1240
|
-
|
|
1271
|
+
error2.$metadata = {
|
|
1241
1272
|
httpStatusCode: statusCode,
|
|
1242
1273
|
requestId: findHeader(/^x-[\w-]+-request-?id$/, headerEntries),
|
|
1243
1274
|
extendedRequestId: findHeader(/^x-[\w-]+-id-2$/, headerEntries),
|
|
@@ -1247,7 +1278,7 @@ var init_schemaDeserializationMiddleware = __esm({
|
|
|
1247
1278
|
} catch (ignored) {
|
|
1248
1279
|
}
|
|
1249
1280
|
}
|
|
1250
|
-
throw
|
|
1281
|
+
throw error2;
|
|
1251
1282
|
}
|
|
1252
1283
|
};
|
|
1253
1284
|
findHeader = (pattern, headers) => {
|
|
@@ -3203,26 +3234,26 @@ var calculateBodyLength;
|
|
|
3203
3234
|
var init_calculateBodyLength = __esm({
|
|
3204
3235
|
"../../node_modules/@smithy/core/dist-es/submodules/serde/util-body-length/calculateBodyLength.js"() {
|
|
3205
3236
|
"use strict";
|
|
3206
|
-
calculateBodyLength = (
|
|
3207
|
-
if (!
|
|
3237
|
+
calculateBodyLength = (body2) => {
|
|
3238
|
+
if (!body2) {
|
|
3208
3239
|
return 0;
|
|
3209
3240
|
}
|
|
3210
|
-
if (typeof
|
|
3211
|
-
return Buffer.byteLength(
|
|
3212
|
-
} else if (typeof
|
|
3213
|
-
return
|
|
3214
|
-
} else if (typeof
|
|
3215
|
-
return
|
|
3216
|
-
} else if (typeof
|
|
3217
|
-
return
|
|
3218
|
-
} else if (
|
|
3219
|
-
if (
|
|
3220
|
-
return lstatSync(
|
|
3221
|
-
} else if (typeof
|
|
3222
|
-
return fstatSync(
|
|
3241
|
+
if (typeof body2 === "string") {
|
|
3242
|
+
return Buffer.byteLength(body2);
|
|
3243
|
+
} else if (typeof body2.byteLength === "number") {
|
|
3244
|
+
return body2.byteLength;
|
|
3245
|
+
} else if (typeof body2.size === "number") {
|
|
3246
|
+
return body2.size;
|
|
3247
|
+
} else if (typeof body2.start === "number" && typeof body2.end === "number") {
|
|
3248
|
+
return body2.end + 1 - body2.start;
|
|
3249
|
+
} else if (body2 instanceof ReadStream) {
|
|
3250
|
+
if (body2.path != null) {
|
|
3251
|
+
return lstatSync(body2.path).size;
|
|
3252
|
+
} else if (typeof body2.fd === "number") {
|
|
3253
|
+
return fstatSync(body2.fd).size;
|
|
3223
3254
|
}
|
|
3224
3255
|
}
|
|
3225
|
-
throw new Error(`Body Length computation failed for ${
|
|
3256
|
+
throw new Error(`Body Length computation failed for ${body2}`);
|
|
3226
3257
|
};
|
|
3227
3258
|
}
|
|
3228
3259
|
});
|
|
@@ -3293,8 +3324,8 @@ var init_ProviderError = __esm({
|
|
|
3293
3324
|
Object.setPrototypeOf(this, _ProviderError.prototype);
|
|
3294
3325
|
logger2?.debug?.(`@smithy/property-provider ${tryNextLink ? "->" : "(!)"} ${message2}`);
|
|
3295
3326
|
}
|
|
3296
|
-
static from(
|
|
3297
|
-
return Object.assign(new this(
|
|
3327
|
+
static from(error2, options = true) {
|
|
3328
|
+
return Object.assign(new this(error2.message, options), error2);
|
|
3298
3329
|
}
|
|
3299
3330
|
};
|
|
3300
3331
|
}
|
|
@@ -5221,9 +5252,9 @@ var init_getEndpointUrl = __esm({
|
|
|
5221
5252
|
if (typeof expression === "string") {
|
|
5222
5253
|
try {
|
|
5223
5254
|
return new URL(expression);
|
|
5224
|
-
} catch (
|
|
5225
|
-
console.error(`Failed to construct URL with ${expression}`,
|
|
5226
|
-
throw
|
|
5255
|
+
} catch (error2) {
|
|
5256
|
+
console.error(`Failed to construct URL with ${expression}`, error2);
|
|
5257
|
+
throw error2;
|
|
5227
5258
|
}
|
|
5228
5259
|
}
|
|
5229
5260
|
throw new EndpointError(`Endpoint URL must be a string, got ${typeof expression}`);
|
|
@@ -6391,9 +6422,9 @@ var init_EventStreamCodec = __esm({
|
|
|
6391
6422
|
}
|
|
6392
6423
|
};
|
|
6393
6424
|
}
|
|
6394
|
-
encode({ headers: rawHeaders, body }) {
|
|
6425
|
+
encode({ headers: rawHeaders, body: body2 }) {
|
|
6395
6426
|
const headers = this.headerMarshaller.format(rawHeaders);
|
|
6396
|
-
const length = headers.byteLength +
|
|
6427
|
+
const length = headers.byteLength + body2.byteLength + 16;
|
|
6397
6428
|
const out = new Uint8Array(length);
|
|
6398
6429
|
const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
6399
6430
|
const checksum = new Crc32Node();
|
|
@@ -6402,14 +6433,14 @@ var init_EventStreamCodec = __esm({
|
|
|
6402
6433
|
checksum.update(out.subarray(0, 8));
|
|
6403
6434
|
view.setUint32(8, checksum.digestSync(), false);
|
|
6404
6435
|
out.set(headers, 12);
|
|
6405
|
-
out.set(
|
|
6436
|
+
out.set(body2, headers.byteLength + 12);
|
|
6406
6437
|
checksum.update(out.subarray(8, length - 4));
|
|
6407
6438
|
view.setUint32(length - 4, checksum.digestSync(), false);
|
|
6408
6439
|
return out;
|
|
6409
6440
|
}
|
|
6410
6441
|
decode(message2) {
|
|
6411
|
-
const { headers, body } = splitMessage(message2);
|
|
6412
|
-
return { headers: this.headerMarshaller.parse(headers), body };
|
|
6442
|
+
const { headers, body: body2 } = splitMessage(message2);
|
|
6443
|
+
return { headers: this.headerMarshaller.parse(headers), body: body2 };
|
|
6413
6444
|
}
|
|
6414
6445
|
formatHeaders(rawHeaders) {
|
|
6415
6446
|
return this.headerMarshaller.format(rawHeaders);
|
|
@@ -6613,9 +6644,9 @@ function getMessageUnmarshaller(deserializer, toUtf83) {
|
|
|
6613
6644
|
const exception = { [code]: message2 };
|
|
6614
6645
|
const deserializedException = await deserializer(exception);
|
|
6615
6646
|
if (deserializedException.$unknown) {
|
|
6616
|
-
const
|
|
6617
|
-
|
|
6618
|
-
throw
|
|
6647
|
+
const error2 = new Error(toUtf83(message2.body));
|
|
6648
|
+
error2.name = code;
|
|
6649
|
+
throw error2;
|
|
6619
6650
|
}
|
|
6620
6651
|
throw deserializedException[code];
|
|
6621
6652
|
} else if (messageType === "event") {
|
|
@@ -6656,8 +6687,8 @@ var init_EventStreamMarshaller = __esm({
|
|
|
6656
6687
|
this.eventStreamCodec = new EventStreamCodec(utf8Encoder, utf8Decoder);
|
|
6657
6688
|
this.utfEncoder = utf8Encoder;
|
|
6658
6689
|
}
|
|
6659
|
-
deserialize(
|
|
6660
|
-
const inputStream = getChunkedStream(
|
|
6690
|
+
deserialize(body2, deserializer) {
|
|
6691
|
+
const inputStream = getChunkedStream(body2);
|
|
6661
6692
|
return new SmithyMessageDecoderStream({
|
|
6662
6693
|
messageStream: new MessageDecoderStream({ inputStream, decoder: this.eventStreamCodec }),
|
|
6663
6694
|
deserializer: getMessageUnmarshaller(deserializer, this.utfEncoder)
|
|
@@ -6716,8 +6747,8 @@ var init_EventStreamMarshaller2 = __esm({
|
|
|
6716
6747
|
utf8Encoder
|
|
6717
6748
|
});
|
|
6718
6749
|
}
|
|
6719
|
-
deserialize(
|
|
6720
|
-
const bodyIterable = typeof
|
|
6750
|
+
deserialize(body2, deserializer) {
|
|
6751
|
+
const bodyIterable = typeof body2[Symbol.asyncIterator] === "function" ? body2 : readableToIterable(body2);
|
|
6721
6752
|
return this.universalMarshaller.deserialize(bodyIterable, deserializer);
|
|
6722
6753
|
}
|
|
6723
6754
|
serialize(input, serializer) {
|
|
@@ -6813,11 +6844,11 @@ var init_EventStreamSerde = __esm({
|
|
|
6813
6844
|
":content-type": { type: "string", value: defaultContentType2 }
|
|
6814
6845
|
};
|
|
6815
6846
|
serializer.write(requestSchema, initialRequest);
|
|
6816
|
-
const
|
|
6847
|
+
const body2 = serializer.flush();
|
|
6817
6848
|
yield {
|
|
6818
6849
|
[initialRequestMarker]: true,
|
|
6819
6850
|
headers,
|
|
6820
|
-
body
|
|
6851
|
+
body: body2
|
|
6821
6852
|
};
|
|
6822
6853
|
}
|
|
6823
6854
|
for await (const page of eventStream) {
|
|
@@ -6841,7 +6872,7 @@ var init_EventStreamSerde = __esm({
|
|
|
6841
6872
|
break;
|
|
6842
6873
|
}
|
|
6843
6874
|
}
|
|
6844
|
-
const { additionalHeaders, body, eventType, explicitPayloadContentType } = this.writeEventBody(unionMember, unionSchema, event);
|
|
6875
|
+
const { additionalHeaders, body: body2, eventType, explicitPayloadContentType } = this.writeEventBody(unionMember, unionSchema, event);
|
|
6845
6876
|
const headers = {
|
|
6846
6877
|
":event-type": { type: "string", value: eventType },
|
|
6847
6878
|
":message-type": { type: "string", value: "event" },
|
|
@@ -6850,7 +6881,7 @@ var init_EventStreamSerde = __esm({
|
|
|
6850
6881
|
};
|
|
6851
6882
|
return {
|
|
6852
6883
|
headers,
|
|
6853
|
-
body
|
|
6884
|
+
body: body2
|
|
6854
6885
|
};
|
|
6855
6886
|
});
|
|
6856
6887
|
}
|
|
@@ -6870,9 +6901,9 @@ var init_EventStreamSerde = __esm({
|
|
|
6870
6901
|
break;
|
|
6871
6902
|
}
|
|
6872
6903
|
}
|
|
6873
|
-
const
|
|
6904
|
+
const body2 = event[unionMember].body;
|
|
6874
6905
|
if (unionMember === (initialMessageType ?? "initial-response")) {
|
|
6875
|
-
const dataObject = await this.deserializer.read(responseSchema,
|
|
6906
|
+
const dataObject = await this.deserializer.read(responseSchema, body2);
|
|
6876
6907
|
delete dataObject[eventStreamMember];
|
|
6877
6908
|
return {
|
|
6878
6909
|
[initialResponseMarker]: true,
|
|
@@ -6888,11 +6919,11 @@ var init_EventStreamSerde = __esm({
|
|
|
6888
6919
|
hasBindings = hasBindings || Boolean(eventHeader || eventPayload);
|
|
6889
6920
|
if (eventPayload) {
|
|
6890
6921
|
if (member2.isBlobSchema()) {
|
|
6891
|
-
out[name] =
|
|
6922
|
+
out[name] = body2;
|
|
6892
6923
|
} else if (member2.isStringSchema()) {
|
|
6893
|
-
out[name] = (this.serdeContext?.utf8Encoder ?? toUtf8)(
|
|
6924
|
+
out[name] = (this.serdeContext?.utf8Encoder ?? toUtf8)(body2);
|
|
6894
6925
|
} else if (member2.isStructSchema()) {
|
|
6895
|
-
out[name] = await this.deserializer.read(member2,
|
|
6926
|
+
out[name] = await this.deserializer.read(member2, body2);
|
|
6896
6927
|
}
|
|
6897
6928
|
} else if (eventHeader) {
|
|
6898
6929
|
const value = event[unionMember].headers[name]?.value;
|
|
@@ -6910,11 +6941,11 @@ var init_EventStreamSerde = __esm({
|
|
|
6910
6941
|
}
|
|
6911
6942
|
}
|
|
6912
6943
|
return {
|
|
6913
|
-
[unionMember]: await this.readEventMember(eventStreamSchema,
|
|
6944
|
+
[unionMember]: await this.readEventMember(eventStreamSchema, body2, hasBindings, out)
|
|
6914
6945
|
};
|
|
6915
6946
|
}
|
|
6916
6947
|
return {
|
|
6917
|
-
[unionMember]: await this.deserializer.read(eventStreamSchema,
|
|
6948
|
+
[unionMember]: await this.deserializer.read(eventStreamSchema, body2)
|
|
6918
6949
|
};
|
|
6919
6950
|
} else {
|
|
6920
6951
|
return {
|
|
@@ -6952,7 +6983,7 @@ var init_EventStreamSerde = __esm({
|
|
|
6952
6983
|
}
|
|
6953
6984
|
};
|
|
6954
6985
|
}
|
|
6955
|
-
async readEventMember(eventStreamSchema,
|
|
6986
|
+
async readEventMember(eventStreamSchema, body2, hasBindings, out) {
|
|
6956
6987
|
let ErrCtor;
|
|
6957
6988
|
const staticStructuralSchema = eventStreamSchema.getSchema();
|
|
6958
6989
|
if (Array.isArray(staticStructuralSchema) && staticStructuralSchema[0] === -3) {
|
|
@@ -6961,7 +6992,7 @@ var init_EventStreamSerde = __esm({
|
|
|
6961
6992
|
this.compositeErrorRegistry?.copyFrom(nsRegistry);
|
|
6962
6993
|
ErrCtor = (this.compositeErrorRegistry ?? nsRegistry)?.getErrorCtor(staticStructuralSchema);
|
|
6963
6994
|
}
|
|
6964
|
-
const dataObject = hasBindings ? out :
|
|
6995
|
+
const dataObject = hasBindings ? out : body2.byteLength === 0 ? {} : await this.deserializer.read(eventStreamSchema, body2);
|
|
6965
6996
|
if (ErrCtor) {
|
|
6966
6997
|
const message2 = dataObject.message ?? dataObject.Message ?? "Unknown";
|
|
6967
6998
|
const metadata = {};
|
|
@@ -7039,9 +7070,9 @@ var init_EventStreamSerde = __esm({
|
|
|
7039
7070
|
}
|
|
7040
7071
|
}
|
|
7041
7072
|
const messageSerialization = serializer.flush() ?? new Uint8Array();
|
|
7042
|
-
const
|
|
7073
|
+
const body2 = typeof messageSerialization === "string" ? (this.serdeContext?.utf8Decoder ?? fromUtf8)(messageSerialization) : messageSerialization;
|
|
7043
7074
|
return {
|
|
7044
|
-
body,
|
|
7075
|
+
body: body2,
|
|
7045
7076
|
eventType,
|
|
7046
7077
|
explicitPayloadContentType,
|
|
7047
7078
|
additionalHeaders
|
|
@@ -7995,10 +8026,10 @@ function contentLengthMiddleware(bodyLengthChecker) {
|
|
|
7995
8026
|
return (next) => async (args) => {
|
|
7996
8027
|
const request = args.request;
|
|
7997
8028
|
if (HttpRequest.isInstance(request)) {
|
|
7998
|
-
const { body, headers } = request;
|
|
7999
|
-
if (
|
|
8029
|
+
const { body: body2, headers } = request;
|
|
8030
|
+
if (body2 && Object.keys(headers).map((str) => str.toLowerCase()).indexOf(CONTENT_LENGTH_HEADER) === -1) {
|
|
8000
8031
|
try {
|
|
8001
|
-
const length = bodyLengthChecker(
|
|
8032
|
+
const length = bodyLengthChecker(body2);
|
|
8002
8033
|
if (length != null) {
|
|
8003
8034
|
request.headers = {
|
|
8004
8035
|
...request.headers,
|
|
@@ -8123,17 +8154,17 @@ var init_constants3 = __esm({
|
|
|
8123
8154
|
});
|
|
8124
8155
|
|
|
8125
8156
|
// ../../node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/service-error-classification.js
|
|
8126
|
-
function isNodeJsHttp2TransientError(
|
|
8127
|
-
return
|
|
8157
|
+
function isNodeJsHttp2TransientError(error2) {
|
|
8158
|
+
return error2.code === "ERR_HTTP2_STREAM_ERROR" && error2.message.includes("NGHTTP2_REFUSED_STREAM");
|
|
8128
8159
|
}
|
|
8129
8160
|
var isRetryableByTrait, isClockSkewCorrectedError, isBrowserNetworkError, isThrottlingError, isTransientError, isServerError;
|
|
8130
8161
|
var init_service_error_classification = __esm({
|
|
8131
8162
|
"../../node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/service-error-classification.js"() {
|
|
8132
8163
|
"use strict";
|
|
8133
8164
|
init_constants3();
|
|
8134
|
-
isRetryableByTrait = (
|
|
8135
|
-
isClockSkewCorrectedError = (
|
|
8136
|
-
isBrowserNetworkError = (
|
|
8165
|
+
isRetryableByTrait = (error2) => error2?.$retryable !== void 0;
|
|
8166
|
+
isClockSkewCorrectedError = (error2) => error2.$metadata?.clockSkewCorrected;
|
|
8167
|
+
isBrowserNetworkError = (error2) => {
|
|
8137
8168
|
const errorMessages = /* @__PURE__ */ new Set([
|
|
8138
8169
|
"Failed to fetch",
|
|
8139
8170
|
"NetworkError when attempting to fetch resource",
|
|
@@ -8141,18 +8172,18 @@ var init_service_error_classification = __esm({
|
|
|
8141
8172
|
"Load failed",
|
|
8142
8173
|
"Network request failed"
|
|
8143
8174
|
]);
|
|
8144
|
-
const isValid =
|
|
8175
|
+
const isValid = error2 && error2 instanceof TypeError;
|
|
8145
8176
|
if (!isValid) {
|
|
8146
8177
|
return false;
|
|
8147
8178
|
}
|
|
8148
|
-
return errorMessages.has(
|
|
8179
|
+
return errorMessages.has(error2.message);
|
|
8149
8180
|
};
|
|
8150
|
-
isThrottlingError = (
|
|
8151
|
-
isTransientError = (
|
|
8152
|
-
isServerError = (
|
|
8153
|
-
if (
|
|
8154
|
-
const statusCode =
|
|
8155
|
-
if (500 <= statusCode && statusCode <= 599 && !isTransientError(
|
|
8181
|
+
isThrottlingError = (error2) => error2.$metadata?.httpStatusCode === 429 || THROTTLING_ERROR_CODES.includes(error2.name) || error2.$retryable?.throttling == true;
|
|
8182
|
+
isTransientError = (error2, depth = 0) => error2?.name !== "AbortError" && (isRetryableByTrait(error2) || isClockSkewCorrectedError(error2) || error2.name === "InvalidSignatureException" && error2.message?.includes("Signature expired") || TRANSIENT_ERROR_CODES.includes(error2.name) || NODEJS_TIMEOUT_ERROR_CODES.includes(error2?.code || "") || NODEJS_NETWORK_ERROR_CODES.includes(error2?.code || "") || TRANSIENT_ERROR_STATUS_CODES.includes(error2.$metadata?.httpStatusCode || 0) || isBrowserNetworkError(error2) || isNodeJsHttp2TransientError(error2) || error2.cause !== void 0 && depth <= 10 && isTransientError(error2.cause, depth + 1));
|
|
8183
|
+
isServerError = (error2) => {
|
|
8184
|
+
if (error2.$metadata?.httpStatusCode !== void 0) {
|
|
8185
|
+
const statusCode = error2.$metadata.httpStatusCode;
|
|
8186
|
+
if (500 <= statusCode && statusCode <= 599 && !isTransientError(error2)) {
|
|
8156
8187
|
return true;
|
|
8157
8188
|
}
|
|
8158
8189
|
return false;
|
|
@@ -8231,14 +8262,14 @@ var asSdkError;
|
|
|
8231
8262
|
var init_util = __esm({
|
|
8232
8263
|
"../../node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/util.js"() {
|
|
8233
8264
|
"use strict";
|
|
8234
|
-
asSdkError = (
|
|
8235
|
-
if (
|
|
8236
|
-
return
|
|
8237
|
-
if (
|
|
8238
|
-
return Object.assign(new Error(),
|
|
8239
|
-
if (typeof
|
|
8240
|
-
return new Error(
|
|
8241
|
-
return new Error(`AWS SDK error wrapper for ${
|
|
8265
|
+
asSdkError = (error2) => {
|
|
8266
|
+
if (error2 instanceof Error)
|
|
8267
|
+
return error2;
|
|
8268
|
+
if (error2 instanceof Object)
|
|
8269
|
+
return Object.assign(new Error(), error2);
|
|
8270
|
+
if (typeof error2 === "string")
|
|
8271
|
+
return new Error(error2);
|
|
8272
|
+
return new Error(`AWS SDK error wrapper for ${error2}`);
|
|
8242
8273
|
};
|
|
8243
8274
|
}
|
|
8244
8275
|
});
|
|
@@ -8324,23 +8355,23 @@ var init_retryMiddleware = __esm({
|
|
|
8324
8355
|
init_util();
|
|
8325
8356
|
cooldown = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8326
8357
|
isRetryStrategyV2 = (retryStrategy) => typeof retryStrategy.acquireInitialRetryToken !== "undefined" && typeof retryStrategy.refreshRetryTokenForRetry !== "undefined" && typeof retryStrategy.recordSuccess !== "undefined";
|
|
8327
|
-
getRetryErrorInfo = (
|
|
8358
|
+
getRetryErrorInfo = (error2, logger2) => {
|
|
8328
8359
|
const errorInfo = {
|
|
8329
|
-
error,
|
|
8330
|
-
errorType: getRetryErrorType(
|
|
8360
|
+
error: error2,
|
|
8361
|
+
errorType: getRetryErrorType(error2)
|
|
8331
8362
|
};
|
|
8332
|
-
const retryAfterHint = parseRetryAfterHeader(
|
|
8363
|
+
const retryAfterHint = parseRetryAfterHeader(error2.$response, logger2);
|
|
8333
8364
|
if (retryAfterHint) {
|
|
8334
8365
|
errorInfo.retryAfterHint = retryAfterHint;
|
|
8335
8366
|
}
|
|
8336
8367
|
return errorInfo;
|
|
8337
8368
|
};
|
|
8338
|
-
getRetryErrorType = (
|
|
8339
|
-
if (isThrottlingError(
|
|
8369
|
+
getRetryErrorType = (error2) => {
|
|
8370
|
+
if (isThrottlingError(error2))
|
|
8340
8371
|
return "THROTTLING";
|
|
8341
|
-
if (isTransientError(
|
|
8372
|
+
if (isTransientError(error2))
|
|
8342
8373
|
return "TRANSIENT";
|
|
8343
|
-
if (isServerError(
|
|
8374
|
+
if (isServerError(error2))
|
|
8344
8375
|
return "SERVER_ERROR";
|
|
8345
8376
|
return "CLIENT_ERROR";
|
|
8346
8377
|
};
|
|
@@ -8877,7 +8908,7 @@ var init_loggerMiddleware = __esm({
|
|
|
8877
8908
|
metadata: $metadata
|
|
8878
8909
|
});
|
|
8879
8910
|
return response;
|
|
8880
|
-
} catch (
|
|
8911
|
+
} catch (error2) {
|
|
8881
8912
|
const { clientName, commandName, logger: logger2, dynamoDbDocumentClientOptions = {} } = context;
|
|
8882
8913
|
const { overrideInputFilterSensitiveLog } = dynamoDbDocumentClientOptions;
|
|
8883
8914
|
const inputFilterSensitiveLog = overrideInputFilterSensitiveLog ?? context.inputFilterSensitiveLog;
|
|
@@ -8885,10 +8916,10 @@ var init_loggerMiddleware = __esm({
|
|
|
8885
8916
|
clientName,
|
|
8886
8917
|
commandName,
|
|
8887
8918
|
input: inputFilterSensitiveLog(args.input),
|
|
8888
|
-
error,
|
|
8889
|
-
metadata:
|
|
8919
|
+
error: error2,
|
|
8920
|
+
metadata: error2.$metadata
|
|
8890
8921
|
});
|
|
8891
|
-
throw
|
|
8922
|
+
throw error2;
|
|
8892
8923
|
}
|
|
8893
8924
|
};
|
|
8894
8925
|
loggerMiddlewareOptions = {
|
|
@@ -9246,8 +9277,8 @@ var init_httpSigningMiddleware = __esm({
|
|
|
9246
9277
|
"use strict";
|
|
9247
9278
|
init_protocols();
|
|
9248
9279
|
init_transport();
|
|
9249
|
-
defaultErrorHandler = (signingProperties) => (
|
|
9250
|
-
throw
|
|
9280
|
+
defaultErrorHandler = (signingProperties) => (error2) => {
|
|
9281
|
+
throw error2;
|
|
9251
9282
|
};
|
|
9252
9283
|
defaultSuccessHandler = (httpResponse, signingProperties) => {
|
|
9253
9284
|
};
|
|
@@ -11000,8 +11031,8 @@ var init_AwsSdkSigV4Signer = __esm({
|
|
|
11000
11031
|
return signedRequest;
|
|
11001
11032
|
}
|
|
11002
11033
|
errorHandler(signingProperties) {
|
|
11003
|
-
return (
|
|
11004
|
-
const errorException =
|
|
11034
|
+
return (error2) => {
|
|
11035
|
+
const errorException = error2;
|
|
11005
11036
|
if (!signingProperties._disableClockSkewCorrection) {
|
|
11006
11037
|
const serverTime = errorException.ServerTime ?? getDateHeader(errorException.$response);
|
|
11007
11038
|
if (serverTime) {
|
|
@@ -11019,7 +11050,7 @@ var init_AwsSdkSigV4Signer = __esm({
|
|
|
11019
11050
|
}
|
|
11020
11051
|
}
|
|
11021
11052
|
}
|
|
11022
|
-
throw
|
|
11053
|
+
throw error2;
|
|
11023
11054
|
};
|
|
11024
11055
|
}
|
|
11025
11056
|
successHandler(httpResponse, signingProperties) {
|
|
@@ -11536,7 +11567,7 @@ var init_getPayloadHash = __esm({
|
|
|
11536
11567
|
init_serde();
|
|
11537
11568
|
init_serde();
|
|
11538
11569
|
init_constants6();
|
|
11539
|
-
getPayloadHash = async ({ headers, body }, hashConstructor) => {
|
|
11570
|
+
getPayloadHash = async ({ headers, body: body2 }, hashConstructor) => {
|
|
11540
11571
|
for (const headerName in headers) {
|
|
11541
11572
|
if (!hasOwn(headers, headerName))
|
|
11542
11573
|
continue;
|
|
@@ -11544,11 +11575,11 @@ var init_getPayloadHash = __esm({
|
|
|
11544
11575
|
return headers[headerName];
|
|
11545
11576
|
}
|
|
11546
11577
|
}
|
|
11547
|
-
if (
|
|
11578
|
+
if (body2 == void 0) {
|
|
11548
11579
|
return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
11549
|
-
} else if (typeof
|
|
11580
|
+
} else if (typeof body2 === "string" || ArrayBuffer.isView(body2) || isArrayBuffer(body2)) {
|
|
11550
11581
|
const hashCtor = new hashConstructor();
|
|
11551
|
-
hashCtor.update(toUint8Array(
|
|
11582
|
+
hashCtor.update(toUint8Array(body2));
|
|
11552
11583
|
return toHex(await hashCtor.digest());
|
|
11553
11584
|
}
|
|
11554
11585
|
return UNSIGNED_PAYLOAD;
|
|
@@ -17182,12 +17213,12 @@ var init_fromInstanceMetadata = __esm({
|
|
|
17182
17213
|
let token;
|
|
17183
17214
|
try {
|
|
17184
17215
|
token = (await getMetadataToken({ ...endpoint, timeout })).toString();
|
|
17185
|
-
} catch (
|
|
17186
|
-
if (
|
|
17187
|
-
throw Object.assign(
|
|
17216
|
+
} catch (error2) {
|
|
17217
|
+
if (error2?.statusCode === 400) {
|
|
17218
|
+
throw Object.assign(error2, {
|
|
17188
17219
|
message: "EC2 Metadata token request returned error"
|
|
17189
17220
|
});
|
|
17190
|
-
} else if (
|
|
17221
|
+
} else if (error2.message === "TimeoutError" || [403, 404, 405].includes(error2.statusCode)) {
|
|
17191
17222
|
disableFetchToken = true;
|
|
17192
17223
|
}
|
|
17193
17224
|
logger2?.debug("AWS SDK Instance Metadata", "using v1 fallback (initial)");
|
|
@@ -17379,12 +17410,12 @@ var init_set_request_timeout = __esm({
|
|
|
17379
17410
|
return timing.setTimeout(() => {
|
|
17380
17411
|
let msg = `@smithy/node-http-handler - [${throwOnRequestTimeout ? "ERROR" : "WARN"}] a request has exceeded the configured ${timeoutInMs} ms requestTimeout.`;
|
|
17381
17412
|
if (throwOnRequestTimeout) {
|
|
17382
|
-
const
|
|
17413
|
+
const error2 = Object.assign(new Error(msg), {
|
|
17383
17414
|
name: "TimeoutError",
|
|
17384
17415
|
code: "ETIMEDOUT"
|
|
17385
17416
|
});
|
|
17386
|
-
req.destroy(
|
|
17387
|
-
reject(
|
|
17417
|
+
req.destroy(error2);
|
|
17418
|
+
reject(error2);
|
|
17388
17419
|
} else {
|
|
17389
17420
|
msg += ` Init client requestHandler with throwOnRequestTimeout=true to turn this into an error.`;
|
|
17390
17421
|
logger2?.warn?.(msg);
|
|
@@ -17487,28 +17518,28 @@ async function writeRequestBody(httpRequest2, request, maxContinueTimeoutMs = MI
|
|
|
17487
17518
|
writeBody(httpRequest2, request.body);
|
|
17488
17519
|
}
|
|
17489
17520
|
}
|
|
17490
|
-
function writeBody(httpRequest2,
|
|
17491
|
-
if (
|
|
17492
|
-
|
|
17521
|
+
function writeBody(httpRequest2, body2) {
|
|
17522
|
+
if (body2 instanceof Readable4) {
|
|
17523
|
+
body2.pipe(httpRequest2);
|
|
17493
17524
|
return;
|
|
17494
17525
|
}
|
|
17495
|
-
if (
|
|
17496
|
-
const isBuffer = Buffer.isBuffer(
|
|
17497
|
-
const isString = typeof
|
|
17526
|
+
if (body2) {
|
|
17527
|
+
const isBuffer = Buffer.isBuffer(body2);
|
|
17528
|
+
const isString = typeof body2 === "string";
|
|
17498
17529
|
if (isBuffer || isString) {
|
|
17499
|
-
if (isBuffer &&
|
|
17530
|
+
if (isBuffer && body2.byteLength === 0) {
|
|
17500
17531
|
httpRequest2.end();
|
|
17501
17532
|
} else {
|
|
17502
|
-
httpRequest2.end(
|
|
17533
|
+
httpRequest2.end(body2);
|
|
17503
17534
|
}
|
|
17504
17535
|
return;
|
|
17505
17536
|
}
|
|
17506
|
-
const uint8 =
|
|
17537
|
+
const uint8 = body2;
|
|
17507
17538
|
if (typeof uint8 === "object" && uint8.buffer && typeof uint8.byteOffset === "number" && typeof uint8.byteLength === "number") {
|
|
17508
17539
|
httpRequest2.end(Buffer.from(uint8.buffer, uint8.byteOffset, uint8.byteLength));
|
|
17509
17540
|
return;
|
|
17510
17541
|
}
|
|
17511
|
-
httpRequest2.end(Buffer.from(
|
|
17542
|
+
httpRequest2.end(Buffer.from(body2));
|
|
17512
17543
|
return;
|
|
17513
17544
|
}
|
|
17514
17545
|
httpRequest2.end();
|
|
@@ -18391,20 +18422,20 @@ var init_ProtocolLib = __esm({
|
|
|
18391
18422
|
decorateServiceException(exception, additions = {}) {
|
|
18392
18423
|
if (this.queryCompat) {
|
|
18393
18424
|
const msg = exception.Message ?? additions.Message;
|
|
18394
|
-
const
|
|
18425
|
+
const error2 = decorateServiceException(exception, additions);
|
|
18395
18426
|
if (msg) {
|
|
18396
|
-
|
|
18397
|
-
}
|
|
18398
|
-
const errorObj =
|
|
18399
|
-
errorObj.Type =
|
|
18400
|
-
errorObj.Code =
|
|
18401
|
-
errorObj.Message =
|
|
18402
|
-
|
|
18403
|
-
const reqId =
|
|
18427
|
+
error2.message = msg;
|
|
18428
|
+
}
|
|
18429
|
+
const errorObj = error2.Error ?? {};
|
|
18430
|
+
errorObj.Type = error2.Error?.Type;
|
|
18431
|
+
errorObj.Code = error2.Error?.Code;
|
|
18432
|
+
errorObj.Message = error2.Error?.message ?? error2.Error?.Message ?? msg;
|
|
18433
|
+
error2.Error = errorObj;
|
|
18434
|
+
const reqId = error2.$metadata.requestId;
|
|
18404
18435
|
if (reqId) {
|
|
18405
|
-
|
|
18436
|
+
error2.RequestId = reqId;
|
|
18406
18437
|
}
|
|
18407
|
-
return
|
|
18438
|
+
return error2;
|
|
18408
18439
|
}
|
|
18409
18440
|
return decorateServiceException(exception, additions);
|
|
18410
18441
|
}
|
|
@@ -18695,7 +18726,7 @@ var init_common = __esm({
|
|
|
18695
18726
|
"use strict";
|
|
18696
18727
|
init_protocols();
|
|
18697
18728
|
init_serde();
|
|
18698
|
-
collectBodyString = (streamBody, context) => collectBody(streamBody, context).then((
|
|
18729
|
+
collectBodyString = (streamBody, context) => collectBody(streamBody, context).then((body2) => (context?.utf8Encoder ?? toUtf8)(body2));
|
|
18699
18730
|
}
|
|
18700
18731
|
});
|
|
18701
18732
|
|
|
@@ -21614,13 +21645,13 @@ var init_fromSso = __esm({
|
|
|
21614
21645
|
expiresAt: newTokenExpiration.toISOString(),
|
|
21615
21646
|
refreshToken: newSsoOidcToken.refreshToken
|
|
21616
21647
|
});
|
|
21617
|
-
} catch (
|
|
21648
|
+
} catch (error2) {
|
|
21618
21649
|
}
|
|
21619
21650
|
return {
|
|
21620
21651
|
token: newSsoOidcToken.accessToken,
|
|
21621
21652
|
expiration: newTokenExpiration
|
|
21622
21653
|
};
|
|
21623
|
-
} catch (
|
|
21654
|
+
} catch (error2) {
|
|
21624
21655
|
validateTokenExpiry(existingToken);
|
|
21625
21656
|
return existingToken;
|
|
21626
21657
|
}
|
|
@@ -25091,9 +25122,9 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25091
25122
|
};
|
|
25092
25123
|
await this.saveToken(updatedToken);
|
|
25093
25124
|
return this.toCredentials(updatedToken.accessToken);
|
|
25094
|
-
} catch (
|
|
25095
|
-
if (
|
|
25096
|
-
const errorType =
|
|
25125
|
+
} catch (error2) {
|
|
25126
|
+
if (error2.name === "AccessDeniedException") {
|
|
25127
|
+
const errorType = error2.error;
|
|
25097
25128
|
let message2;
|
|
25098
25129
|
switch (errorType) {
|
|
25099
25130
|
case "TOKEN_EXPIRED":
|
|
@@ -25106,7 +25137,7 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25106
25137
|
message2 = "Unable to refresh credentials due to insufficient permissions. You may be missing permission for the 'CreateOAuth2Token' action.";
|
|
25107
25138
|
break;
|
|
25108
25139
|
default:
|
|
25109
|
-
message2 = `Failed to refresh token: ${String(
|
|
25140
|
+
message2 = `Failed to refresh token: ${String(error2)}. Please re-authenticate using \`aws login\``;
|
|
25110
25141
|
}
|
|
25111
25142
|
throw new CredentialsProviderError(message2, {
|
|
25112
25143
|
logger: this.logger,
|
|
@@ -25115,10 +25146,10 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25115
25146
|
}
|
|
25116
25147
|
const tokenExpiry2 = new Date(freshToken.accessToken.expiresAt).getTime();
|
|
25117
25148
|
if (tokenExpiry2 > Date.now()) {
|
|
25118
|
-
this.logger?.warn?.(`Failed to refresh token: ${String(
|
|
25149
|
+
this.logger?.warn?.(`Failed to refresh token: ${String(error2)}. Using existing token until expiry.`);
|
|
25119
25150
|
return this.toCredentials(freshToken.accessToken);
|
|
25120
25151
|
}
|
|
25121
|
-
throw new CredentialsProviderError(`Failed to refresh token: ${String(
|
|
25152
|
+
throw new CredentialsProviderError(`Failed to refresh token: ${String(error2)}. Please re-authenticate using aws login`, { logger: this.logger });
|
|
25122
25153
|
}
|
|
25123
25154
|
}
|
|
25124
25155
|
async loadToken() {
|
|
@@ -25137,8 +25168,8 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25137
25168
|
});
|
|
25138
25169
|
}
|
|
25139
25170
|
return token;
|
|
25140
|
-
} catch (
|
|
25141
|
-
throw new CredentialsProviderError(`Failed to load token from ${tokenFilePath}: ${String(
|
|
25171
|
+
} catch (error2) {
|
|
25172
|
+
throw new CredentialsProviderError(`Failed to load token from ${tokenFilePath}: ${String(error2)}`, {
|
|
25142
25173
|
logger: this.logger,
|
|
25143
25174
|
tryNextLink: false
|
|
25144
25175
|
});
|
|
@@ -25149,7 +25180,7 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25149
25180
|
const directory = dirname(tokenFilePath);
|
|
25150
25181
|
try {
|
|
25151
25182
|
await fs2.mkdir(directory, { recursive: true });
|
|
25152
|
-
} catch (
|
|
25183
|
+
} catch (error2) {
|
|
25153
25184
|
}
|
|
25154
25185
|
await fs2.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
|
|
25155
25186
|
}
|
|
@@ -25240,8 +25271,8 @@ var init_LoginCredentialsFetcher = __esm({
|
|
|
25240
25271
|
const rawSignature = this.derToRawSignature(asn1Signature);
|
|
25241
25272
|
const signatureB64 = rawSignature.toString("base64url");
|
|
25242
25273
|
return `${message2}.${signatureB64}`;
|
|
25243
|
-
} catch (
|
|
25244
|
-
throw new CredentialsProviderError(`Failed to generate Dpop proof: ${
|
|
25274
|
+
} catch (error2) {
|
|
25275
|
+
throw new CredentialsProviderError(`Failed to generate Dpop proof: ${error2 instanceof Error ? error2.message : String(error2)}`, { logger: this.logger, tryNextLink: false });
|
|
25245
25276
|
}
|
|
25246
25277
|
}
|
|
25247
25278
|
};
|
|
@@ -25370,8 +25401,8 @@ var init_resolveProcessCredentials = __esm({
|
|
|
25370
25401
|
throw Error(`Profile ${profileName} credential_process returned invalid JSON.`);
|
|
25371
25402
|
}
|
|
25372
25403
|
return getValidatedProcessCredentials(profileName, data, profiles);
|
|
25373
|
-
} catch (
|
|
25374
|
-
throw new CredentialsProviderError(
|
|
25404
|
+
} catch (error2) {
|
|
25405
|
+
throw new CredentialsProviderError(error2.message, { logger: logger2 });
|
|
25375
25406
|
}
|
|
25376
25407
|
} else {
|
|
25377
25408
|
throw new CredentialsProviderError(`Profile ${profileName} did not contain credential_process.`, { logger: logger2 });
|
|
@@ -28679,16 +28710,16 @@ function stillMatches(where) {
|
|
|
28679
28710
|
});
|
|
28680
28711
|
return { text: parts.join(" AND "), names, values };
|
|
28681
28712
|
}
|
|
28682
|
-
function failedWrite(
|
|
28683
|
-
const { name, CancellationReasons } =
|
|
28713
|
+
function failedWrite(error2) {
|
|
28714
|
+
const { name, CancellationReasons } = error2;
|
|
28684
28715
|
if (name === "ConditionalCheckFailedException") return "refused";
|
|
28685
|
-
if (name && RETRYABLE_ERRORS.includes(name)) return { retry:
|
|
28716
|
+
if (name && RETRYABLE_ERRORS.includes(name)) return { retry: error2 };
|
|
28686
28717
|
if (name === "TransactionCanceledException") {
|
|
28687
28718
|
const reasons = (CancellationReasons ?? []).map((r6) => r6.Code ?? "None").filter((code) => code !== "None");
|
|
28688
28719
|
if (reasons.includes("ConditionalCheckFailed")) return "refused";
|
|
28689
|
-
if (reasons.length && reasons.every((code) => RETRYABLE_REASONS.includes(code))) return { retry:
|
|
28720
|
+
if (reasons.length && reasons.every((code) => RETRYABLE_REASONS.includes(code))) return { retry: error2 };
|
|
28690
28721
|
}
|
|
28691
|
-
throw
|
|
28722
|
+
throw error2;
|
|
28692
28723
|
}
|
|
28693
28724
|
function createDynamoDbAdapter(options) {
|
|
28694
28725
|
const { tableName: tableName2 } = options;
|
|
@@ -29793,8 +29824,8 @@ async function importWithAlgCache(cache7, jwk, entry) {
|
|
|
29793
29824
|
if (key.type !== "public")
|
|
29794
29825
|
throw new JWKSInvalid("JSON Web Key Set members must be public keys");
|
|
29795
29826
|
return cached[alg] = key, key;
|
|
29796
|
-
}).catch((
|
|
29797
|
-
throw cached[alg] === pending && delete cached[alg],
|
|
29827
|
+
}).catch((error2) => {
|
|
29828
|
+
throw cached[alg] === pending && delete cached[alg], error2;
|
|
29798
29829
|
});
|
|
29799
29830
|
cached[alg] = pending;
|
|
29800
29831
|
}
|
|
@@ -29820,14 +29851,14 @@ function createLocalJWKSet(jwks) {
|
|
|
29820
29851
|
if (!length)
|
|
29821
29852
|
throw new JWKSNoMatchingKey();
|
|
29822
29853
|
if (length !== 1) {
|
|
29823
|
-
const
|
|
29824
|
-
throw
|
|
29854
|
+
const error2 = new JWKSMultipleMatchingKeys();
|
|
29855
|
+
throw error2[Symbol.asyncIterator] = async function* () {
|
|
29825
29856
|
for (const jwk2 of candidates)
|
|
29826
29857
|
try {
|
|
29827
29858
|
yield await importWithAlgCache(cached, jwk2, entry);
|
|
29828
29859
|
} catch {
|
|
29829
29860
|
}
|
|
29830
|
-
},
|
|
29861
|
+
}, error2;
|
|
29831
29862
|
}
|
|
29832
29863
|
return importWithAlgCache(cached, jwk, entry);
|
|
29833
29864
|
}, "jwks", {
|
|
@@ -30027,7 +30058,10 @@ var WEBHOOK_TYPES = [
|
|
|
30027
30058
|
"email.password_reset",
|
|
30028
30059
|
"user.signed_in",
|
|
30029
30060
|
"user.deleted",
|
|
30030
|
-
"user.claims_changed"
|
|
30061
|
+
"user.claims_changed",
|
|
30062
|
+
"totp.enabled",
|
|
30063
|
+
"totp.disabled",
|
|
30064
|
+
"totp.recovery_code_used"
|
|
30031
30065
|
];
|
|
30032
30066
|
var WEBHOOK_TIMEOUT_MS = 1e4;
|
|
30033
30067
|
var MAX_LOCALE_LENGTH = 35;
|
|
@@ -30051,8 +30085,8 @@ function hmac2(secret, content) {
|
|
|
30051
30085
|
const key = Buffer.from(secret.slice(SECRET_PREFIX.length), "base64");
|
|
30052
30086
|
return createHmac("sha256", key).update(content).digest("base64");
|
|
30053
30087
|
}
|
|
30054
|
-
function signWebhook(secret, id, timestamp,
|
|
30055
|
-
return `v1,${hmac2(secret, `${id}.${timestamp}.${
|
|
30088
|
+
function signWebhook(secret, id, timestamp, body2) {
|
|
30089
|
+
return `v1,${hmac2(secret, `${id}.${timestamp}.${body2}`)}`;
|
|
30056
30090
|
}
|
|
30057
30091
|
function createWebhookClient(settings, fetchImpl = fetch) {
|
|
30058
30092
|
const wants = (type) => settings.events.has(type);
|
|
@@ -30062,7 +30096,7 @@ function createWebhookClient(settings, fetchImpl = fetch) {
|
|
|
30062
30096
|
if (!wants(type)) return { ok: true, body: void 0 };
|
|
30063
30097
|
const id = `msg_${randomUUID()}`;
|
|
30064
30098
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
30065
|
-
const
|
|
30099
|
+
const body2 = JSON.stringify({ type, data });
|
|
30066
30100
|
let response;
|
|
30067
30101
|
try {
|
|
30068
30102
|
response = await fetchImpl(settings.url, {
|
|
@@ -30071,9 +30105,9 @@ function createWebhookClient(settings, fetchImpl = fetch) {
|
|
|
30071
30105
|
"Content-Type": "application/json",
|
|
30072
30106
|
"webhook-id": id,
|
|
30073
30107
|
"webhook-timestamp": String(timestamp),
|
|
30074
|
-
"webhook-signature": signWebhook(settings.secret, id, timestamp,
|
|
30108
|
+
"webhook-signature": signWebhook(settings.secret, id, timestamp, body2)
|
|
30075
30109
|
},
|
|
30076
|
-
body,
|
|
30110
|
+
body: body2,
|
|
30077
30111
|
signal: AbortSignal.timeout(timeoutMs)
|
|
30078
30112
|
});
|
|
30079
30113
|
} catch (e6) {
|
|
@@ -30094,6 +30128,12 @@ function createWebhookClient(settings, fetchImpl = fetch) {
|
|
|
30094
30128
|
}
|
|
30095
30129
|
|
|
30096
30130
|
// src/config.ts
|
|
30131
|
+
function sendsVerificationEmails(config) {
|
|
30132
|
+
return !!config.webhook?.events.has("email.verify");
|
|
30133
|
+
}
|
|
30134
|
+
function methodConfig(config) {
|
|
30135
|
+
return { google: config.google, password: config.password, emailVerification: sendsVerificationEmails(config) };
|
|
30136
|
+
}
|
|
30097
30137
|
async function resolveEnv(source = process.env) {
|
|
30098
30138
|
return typeof source === "function" ? source() : source;
|
|
30099
30139
|
}
|
|
@@ -30157,9 +30197,10 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
30157
30197
|
const webhook = webhookFromEnv(read("WEBHOOK_URL"), read("WEBHOOK_SECRET"), read("WEBHOOK_EVENTS"));
|
|
30158
30198
|
const sendsEmails = !!webhook && REQUIRED_EMAIL_TYPES.every((type) => webhook.events.has(type));
|
|
30159
30199
|
const password = sendsEmails ? { minLength: passwordMinLength(read("PASSWORD_MIN_LENGTH")) } : void 0;
|
|
30160
|
-
|
|
30200
|
+
const emailVerification = !!webhook && webhook.events.has("email.verify");
|
|
30201
|
+
if (!clientId && !password && !emailVerification) {
|
|
30161
30202
|
throw new ConfigError(
|
|
30162
|
-
`No sign-in method is configured. Set GOOGLE_CLIENT_ID for Google sign-in, and/or a webhook that sends the e-mails
|
|
30203
|
+
`No sign-in method is configured. Set GOOGLE_CLIENT_ID for Google sign-in, and/or a webhook that sends the e-mails: e-mail & password sign-in needs email.verify and email.reset, sign-up with the authenticator app needs email.verify. 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.`
|
|
30163
30204
|
);
|
|
30164
30205
|
}
|
|
30165
30206
|
const store = overrides.store ?? await storeFromDatabaseUrl(read("DATABASE_URL"));
|
|
@@ -30187,6 +30228,7 @@ MADAUTH_SIGNING_KEY='${JSON.stringify(key)}'
|
|
|
30187
30228
|
sessionTtlSeconds,
|
|
30188
30229
|
renewalTtlSeconds,
|
|
30189
30230
|
cookieDomain: read("COOKIE_DOMAIN"),
|
|
30231
|
+
totpIssuer: read("TOTP_ISSUER"),
|
|
30190
30232
|
google: clientId ? { clientId, clientSecret: read("GOOGLE_CLIENT_SECRET") } : void 0,
|
|
30191
30233
|
store,
|
|
30192
30234
|
password,
|
|
@@ -30585,8 +30627,8 @@ var newRequestFromIncoming = (method, url, headers, incoming, abortController) =
|
|
|
30585
30627
|
const { done, value } = await reader.read();
|
|
30586
30628
|
if (done) controller.close();
|
|
30587
30629
|
else controller.enqueue(value);
|
|
30588
|
-
} catch (
|
|
30589
|
-
controller.error(
|
|
30630
|
+
} catch (error2) {
|
|
30631
|
+
controller.error(error2);
|
|
30590
30632
|
}
|
|
30591
30633
|
} });
|
|
30592
30634
|
} else {
|
|
@@ -30714,13 +30756,13 @@ var readBodyDirect = (request) => {
|
|
|
30714
30756
|
cleanup();
|
|
30715
30757
|
callback();
|
|
30716
30758
|
};
|
|
30717
|
-
const recoverCompleteBodyAfterDisconnect = (
|
|
30718
|
-
const streamError = incoming.errored ??
|
|
30759
|
+
const recoverCompleteBodyAfterDisconnect = (error2) => {
|
|
30760
|
+
const streamError = incoming.errored ?? error2;
|
|
30719
30761
|
if (!isRecoverableDisconnectedIncoming(incoming) || streamError && streamError.code !== "ECONNRESET") return false;
|
|
30720
30762
|
finish(() => {
|
|
30721
30763
|
const recovered = readBodyBufferedBeforeDisconnect(incoming, chunks);
|
|
30722
30764
|
if (recovered instanceof Error) reject(recovered);
|
|
30723
|
-
else if (recovered === void 0) reject(
|
|
30765
|
+
else if (recovered === void 0) reject(error2 ?? normalizeAbortError(request, incoming));
|
|
30724
30766
|
else {
|
|
30725
30767
|
request[bodyBufferKey] = recovered;
|
|
30726
30768
|
resolve(recovered);
|
|
@@ -30738,10 +30780,10 @@ var readBodyDirect = (request) => {
|
|
|
30738
30780
|
resolve(buffer);
|
|
30739
30781
|
});
|
|
30740
30782
|
};
|
|
30741
|
-
const onError = (
|
|
30742
|
-
if (recoverCompleteBodyAfterDisconnect(
|
|
30783
|
+
const onError = (error2) => {
|
|
30784
|
+
if (recoverCompleteBodyAfterDisconnect(error2)) return;
|
|
30743
30785
|
finish(() => {
|
|
30744
|
-
reject(
|
|
30786
|
+
reject(error2);
|
|
30745
30787
|
});
|
|
30746
30788
|
};
|
|
30747
30789
|
const onClose = () => {
|
|
@@ -30935,9 +30977,9 @@ var Response$1 = class Response$12 {
|
|
|
30935
30977
|
headers: liveHeaders
|
|
30936
30978
|
} : this.#init);
|
|
30937
30979
|
}
|
|
30938
|
-
constructor(
|
|
30980
|
+
constructor(body2, init) {
|
|
30939
30981
|
let headers;
|
|
30940
|
-
this.#body =
|
|
30982
|
+
this.#body = body2;
|
|
30941
30983
|
if (init instanceof GlobalResponse) {
|
|
30942
30984
|
const cachedGlobalResponse = init[responseCache];
|
|
30943
30985
|
if (cachedGlobalResponse) {
|
|
@@ -30948,9 +30990,9 @@ var Response$1 = class Response$12 {
|
|
|
30948
30990
|
this.#init = init instanceof Response$12 ? init.#init : init;
|
|
30949
30991
|
headers = new Headers(init.headers);
|
|
30950
30992
|
} else this.#init = init;
|
|
30951
|
-
if (
|
|
30993
|
+
if (body2 == null || typeof body2 === "string" || typeof body2?.getReader !== "undefined" || body2 instanceof Blob || body2 instanceof Uint8Array) this[cacheKey] = [
|
|
30952
30994
|
init?.status || 200,
|
|
30953
|
-
|
|
30995
|
+
body2 ?? null,
|
|
30954
30996
|
headers || init?.headers
|
|
30955
30997
|
];
|
|
30956
30998
|
}
|
|
@@ -31034,15 +31076,15 @@ Object.defineProperty(Response$1, "redirect", {
|
|
|
31034
31076
|
});
|
|
31035
31077
|
Object.defineProperty(Response$1, "json", {
|
|
31036
31078
|
value: function json(data, init) {
|
|
31037
|
-
const
|
|
31038
|
-
if (
|
|
31079
|
+
const body2 = JSON.stringify(data);
|
|
31080
|
+
if (body2 === void 0) throw new TypeError("The data is not JSON serializable");
|
|
31039
31081
|
const initHeaders = init?.headers;
|
|
31040
31082
|
let headers;
|
|
31041
31083
|
if (initHeaders) {
|
|
31042
31084
|
headers = new Headers(initHeaders);
|
|
31043
31085
|
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
31044
31086
|
} else headers = { "content-type": "application/json" };
|
|
31045
|
-
return new Response$1(
|
|
31087
|
+
return new Response$1(body2, {
|
|
31046
31088
|
status: init?.status ?? 200,
|
|
31047
31089
|
statusText: init?.statusText,
|
|
31048
31090
|
headers
|
|
@@ -31055,8 +31097,8 @@ async function readWithoutBlocking(readPromise) {
|
|
|
31055
31097
|
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
31056
31098
|
}
|
|
31057
31099
|
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
31058
|
-
const cancel = (
|
|
31059
|
-
reader.cancel(
|
|
31100
|
+
const cancel = (error2) => {
|
|
31101
|
+
reader.cancel(error2).catch(() => {
|
|
31060
31102
|
});
|
|
31061
31103
|
};
|
|
31062
31104
|
writable.on("close", cancel);
|
|
@@ -31066,8 +31108,8 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
|
|
|
31066
31108
|
writable.off("close", cancel);
|
|
31067
31109
|
writable.off("error", cancel);
|
|
31068
31110
|
});
|
|
31069
|
-
function handleStreamError(
|
|
31070
|
-
if (
|
|
31111
|
+
function handleStreamError(error2) {
|
|
31112
|
+
if (error2) writable.destroy(error2);
|
|
31071
31113
|
}
|
|
31072
31114
|
function onDrain() {
|
|
31073
31115
|
reader.read().then(flow, handleStreamError);
|
|
@@ -31156,8 +31198,8 @@ var makeCloseHandler = (req, incoming, outgoing, needsBodyCleanup) => () => {
|
|
|
31156
31198
|
};
|
|
31157
31199
|
var isImmediateCacheableResponse = (res) => {
|
|
31158
31200
|
if (!(cacheKey in res)) return false;
|
|
31159
|
-
const
|
|
31160
|
-
return
|
|
31201
|
+
const body2 = res[cacheKey][1];
|
|
31202
|
+
return body2 === null || typeof body2 === "string" || body2 instanceof Uint8Array;
|
|
31161
31203
|
};
|
|
31162
31204
|
var handleRequestError = () => new Response(null, { status: 400 });
|
|
31163
31205
|
var handleFetchError = (e6) => new Response(null, { status: e6 instanceof Error && (e6.name === "TimeoutError" || e6.constructor.name === "TimeoutError") ? 504 : 500 });
|
|
@@ -31193,24 +31235,24 @@ var writeDefaultHeaders = (outgoing, status, length) => {
|
|
|
31193
31235
|
});
|
|
31194
31236
|
};
|
|
31195
31237
|
var responseViaCache = async (res, outgoing) => {
|
|
31196
|
-
let [status,
|
|
31238
|
+
let [status, body2, header] = res[cacheKey];
|
|
31197
31239
|
if (!header) {
|
|
31198
|
-
if (
|
|
31240
|
+
if (body2 === null) {
|
|
31199
31241
|
outgoing.writeHead(status);
|
|
31200
31242
|
outgoing.end();
|
|
31201
|
-
} else if (typeof
|
|
31202
|
-
writeDefaultHeaders(outgoing, status, Buffer.byteLength(
|
|
31203
|
-
outgoing.end(
|
|
31204
|
-
} else if (
|
|
31205
|
-
writeDefaultHeaders(outgoing, status,
|
|
31206
|
-
outgoing.end(
|
|
31207
|
-
} else if (
|
|
31208
|
-
writeDefaultHeaders(outgoing, status,
|
|
31209
|
-
outgoing.end(new Uint8Array(await
|
|
31243
|
+
} else if (typeof body2 === "string") {
|
|
31244
|
+
writeDefaultHeaders(outgoing, status, Buffer.byteLength(body2));
|
|
31245
|
+
outgoing.end(body2);
|
|
31246
|
+
} else if (body2 instanceof Uint8Array) {
|
|
31247
|
+
writeDefaultHeaders(outgoing, status, body2.byteLength);
|
|
31248
|
+
outgoing.end(body2);
|
|
31249
|
+
} else if (body2 instanceof Blob) {
|
|
31250
|
+
writeDefaultHeaders(outgoing, status, body2.size);
|
|
31251
|
+
outgoing.end(new Uint8Array(await body2.arrayBuffer()));
|
|
31210
31252
|
} else {
|
|
31211
31253
|
outgoing.writeHead(status, { "Content-Type": defaultContentType });
|
|
31212
31254
|
flushHeaders(outgoing);
|
|
31213
|
-
await writeFromReadableStream(
|
|
31255
|
+
await writeFromReadableStream(body2, outgoing)?.catch((e6) => handleResponseError(e6, outgoing));
|
|
31214
31256
|
}
|
|
31215
31257
|
outgoing[outgoingEnded]?.();
|
|
31216
31258
|
return;
|
|
@@ -31220,11 +31262,11 @@ var responseViaCache = async (res, outgoing) => {
|
|
|
31220
31262
|
let canAutoLength = true;
|
|
31221
31263
|
if (header instanceof Headers) {
|
|
31222
31264
|
hasContentLength = header.has("content-length");
|
|
31223
|
-
header = buildOutgoingHttpHeaders(header,
|
|
31265
|
+
header = buildOutgoingHttpHeaders(header, body2 === null ? void 0 : defaultContentType);
|
|
31224
31266
|
} else if (Array.isArray(header)) {
|
|
31225
31267
|
const headerObj = new Headers(header);
|
|
31226
31268
|
hasContentLength = headerObj.has("content-length");
|
|
31227
|
-
header = buildOutgoingHttpHeaders(headerObj,
|
|
31269
|
+
header = buildOutgoingHttpHeaders(headerObj, body2 === null ? void 0 : defaultContentType);
|
|
31228
31270
|
} else {
|
|
31229
31271
|
plainHeaders = true;
|
|
31230
31272
|
for (const key in header) {
|
|
@@ -31237,21 +31279,21 @@ var responseViaCache = async (res, outgoing) => {
|
|
|
31237
31279
|
}
|
|
31238
31280
|
if (!hasContentLength) {
|
|
31239
31281
|
let length;
|
|
31240
|
-
if (typeof
|
|
31241
|
-
else if (
|
|
31242
|
-
else if (
|
|
31282
|
+
if (typeof body2 === "string") length = Buffer.byteLength(body2);
|
|
31283
|
+
else if (body2 instanceof Uint8Array) length = body2.byteLength;
|
|
31284
|
+
else if (body2 instanceof Blob) length = body2.size;
|
|
31243
31285
|
if (length !== void 0 && (!plainHeaders || !canAutoLength || !trySetContentLength(outgoing, status, length))) {
|
|
31244
31286
|
if (plainHeaders) header = { ...header };
|
|
31245
31287
|
header["Content-Length"] = length;
|
|
31246
31288
|
}
|
|
31247
31289
|
}
|
|
31248
31290
|
outgoing.writeHead(status, header);
|
|
31249
|
-
if (
|
|
31250
|
-
else if (typeof
|
|
31251
|
-
else if (
|
|
31291
|
+
if (body2 == null) outgoing.end();
|
|
31292
|
+
else if (typeof body2 === "string" || body2 instanceof Uint8Array) outgoing.end(body2);
|
|
31293
|
+
else if (body2 instanceof Blob) outgoing.end(new Uint8Array(await body2.arrayBuffer()));
|
|
31252
31294
|
else {
|
|
31253
31295
|
flushHeaders(outgoing);
|
|
31254
|
-
await writeFromReadableStream(
|
|
31296
|
+
await writeFromReadableStream(body2, outgoing)?.catch((e6) => handleResponseError(e6, outgoing));
|
|
31255
31297
|
}
|
|
31256
31298
|
outgoing[outgoingEnded]?.();
|
|
31257
31299
|
};
|
|
@@ -31583,9 +31625,9 @@ var upgradeWebSocket = defineWebSocketHelper(async (c6, events, options) => {
|
|
|
31583
31625
|
(options?.onError ?? console.error)(e6);
|
|
31584
31626
|
}
|
|
31585
31627
|
});
|
|
31586
|
-
ws.on("error", (
|
|
31628
|
+
ws.on("error", (error2) => {
|
|
31587
31629
|
try {
|
|
31588
|
-
events?.onError?.(new ErrorEvent("error", { error }), ctx);
|
|
31630
|
+
events?.onError?.(new ErrorEvent("error", { error: error2 }), ctx);
|
|
31589
31631
|
} catch (e6) {
|
|
31590
31632
|
(options?.onError ?? console.error)(e6);
|
|
31591
31633
|
}
|
|
@@ -31943,10 +31985,10 @@ var HonoRequest = class {
|
|
|
31943
31985
|
const { bodyCache, raw: raw2 } = this;
|
|
31944
31986
|
const cachedBody = bodyCache[key];
|
|
31945
31987
|
if (cachedBody) return cachedBody;
|
|
31946
|
-
for (const anyCachedKey in bodyCache) return bodyCache[anyCachedKey].then((
|
|
31947
|
-
if (anyCachedKey === "json")
|
|
31988
|
+
for (const anyCachedKey in bodyCache) return bodyCache[anyCachedKey].then((body2) => {
|
|
31989
|
+
if (anyCachedKey === "json") body2 = JSON.stringify(body2);
|
|
31948
31990
|
const contentType2 = anyCachedKey === "formData" ? void 0 : raw2.headers.get("content-type");
|
|
31949
|
-
return new Response(
|
|
31991
|
+
return new Response(body2, { headers: contentType2 ? { "Content-Type": contentType2 } : void 0 })[key]();
|
|
31950
31992
|
});
|
|
31951
31993
|
return bodyCache[key] = raw2[key]();
|
|
31952
31994
|
};
|
|
@@ -32171,7 +32213,7 @@ var setDefaultContentType = (contentType2, headers) => {
|
|
|
32171
32213
|
...headers
|
|
32172
32214
|
};
|
|
32173
32215
|
};
|
|
32174
|
-
var createResponseInstance = (
|
|
32216
|
+
var createResponseInstance = (body2, init) => new Response(body2, init);
|
|
32175
32217
|
var Context = class {
|
|
32176
32218
|
#rawRequest;
|
|
32177
32219
|
#req;
|
|
@@ -33712,115 +33754,58 @@ function safeEqual(a6, b6) {
|
|
|
33712
33754
|
return x2.length === y2.length && timingSafeEqual2(x2, y2);
|
|
33713
33755
|
}
|
|
33714
33756
|
|
|
33715
|
-
// src/google.ts
|
|
33716
|
-
var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
|
|
33717
|
-
var googleJwks;
|
|
33718
|
-
function googleKeys() {
|
|
33719
|
-
googleJwks ??= createRemoteJWKSet(new URL("https://www.googleapis.com/oauth2/v3/certs"));
|
|
33720
|
-
return googleJwks;
|
|
33721
|
-
}
|
|
33722
|
-
async function verifyGoogleIdToken(idToken, opts) {
|
|
33723
|
-
let payload;
|
|
33724
|
-
try {
|
|
33725
|
-
({ payload } = await jwtVerify(idToken, opts.keys ?? googleKeys(), {
|
|
33726
|
-
issuer: GOOGLE_ISSUERS,
|
|
33727
|
-
audience: opts.clientId,
|
|
33728
|
-
algorithms: ["RS256", "ES256"]
|
|
33729
|
-
}));
|
|
33730
|
-
} catch (e6) {
|
|
33731
|
-
return { ok: false, error: "verification_failed", reason: e6.message };
|
|
33732
|
-
}
|
|
33733
|
-
if (typeof payload.sub !== "string" || !payload.sub) {
|
|
33734
|
-
return { ok: false, error: "verification_failed", reason: "missing sub" };
|
|
33735
|
-
}
|
|
33736
|
-
if (payload.nonce !== opts.nonce) {
|
|
33737
|
-
return { ok: false, error: "verification_failed", reason: "nonce mismatch" };
|
|
33738
|
-
}
|
|
33739
|
-
if (typeof payload.email !== "string" || !payload.email || payload.email_verified !== true) {
|
|
33740
|
-
return { ok: false, error: "email_unverified", reason: "e-mail address not verified" };
|
|
33741
|
-
}
|
|
33742
|
-
const profile = { sub: payload.sub, email: payload.email };
|
|
33743
|
-
if (typeof payload.name === "string") profile.name = payload.name;
|
|
33744
|
-
if (typeof payload.picture === "string") profile.picture = payload.picture;
|
|
33745
|
-
return { ok: true, profile };
|
|
33746
|
-
}
|
|
33747
|
-
|
|
33748
|
-
// src/user.ts
|
|
33749
|
-
var SESSION_COOKIE = "madauth_session";
|
|
33750
|
-
var SESSION_TYP = "madauth-session+jwt";
|
|
33751
|
-
var RENEWAL_COOKIE = "madauth_renewal";
|
|
33752
|
-
var RENEWAL_TYP = "madauth-renewal+jwt";
|
|
33753
|
-
var EXPIRY_COOKIE = "madauth_session_expires";
|
|
33754
|
-
|
|
33755
|
-
// src/tokens.ts
|
|
33756
|
-
function randomString(bytes = 32) {
|
|
33757
|
-
return base64url_exports.encode(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
33758
|
-
}
|
|
33759
|
-
async function signToken(keys, issuer, typ, claims, ttlSeconds) {
|
|
33760
|
-
return new SignJWT(claims).setProtectedHeader({ alg: SIGNING_ALG, kid: keys.kid, typ }).setIssuer(issuer).setIssuedAt().setExpirationTime(`${ttlSeconds}s`).sign(keys.privateKey);
|
|
33761
|
-
}
|
|
33762
|
-
async function readToken(keys, issuer, typ, token) {
|
|
33763
|
-
if (!token) return null;
|
|
33764
|
-
try {
|
|
33765
|
-
const { payload } = await jwtVerify(token, keys.publicKey, { issuer, typ, algorithms: [SIGNING_ALG] });
|
|
33766
|
-
return payload;
|
|
33767
|
-
} catch {
|
|
33768
|
-
return null;
|
|
33769
|
-
}
|
|
33770
|
-
}
|
|
33771
|
-
function signSession(keys, issuer, ttlSeconds, user, amr, extra = {}) {
|
|
33772
|
-
const claims = {
|
|
33773
|
-
...extra,
|
|
33774
|
-
sub: user.id,
|
|
33775
|
-
email: user.email,
|
|
33776
|
-
email_verified: user.email ? true : void 0,
|
|
33777
|
-
name: user.name,
|
|
33778
|
-
picture: user.picture,
|
|
33779
|
-
claims: user.claims && Object.keys(user.claims).length ? user.claims : void 0,
|
|
33780
|
-
amr
|
|
33781
|
-
};
|
|
33782
|
-
return signToken(keys, issuer, SESSION_TYP, claims, ttlSeconds);
|
|
33783
|
-
}
|
|
33784
|
-
function signRenewal(keys, issuer, ttlSeconds, user, amr, extra = {}) {
|
|
33785
|
-
const claims = {
|
|
33786
|
-
...extra,
|
|
33787
|
-
sub: user.id,
|
|
33788
|
-
email: user.email,
|
|
33789
|
-
email_verified: user.email ? true : void 0,
|
|
33790
|
-
name: user.name,
|
|
33791
|
-
picture: user.picture,
|
|
33792
|
-
amr
|
|
33793
|
-
};
|
|
33794
|
-
return signToken(keys, issuer, RENEWAL_TYP, claims, ttlSeconds);
|
|
33795
|
-
}
|
|
33796
|
-
function userFromClaims(claims) {
|
|
33797
|
-
const user = { id: claims.sub };
|
|
33798
|
-
if (claims.email) user.email = claims.email;
|
|
33799
|
-
if (claims.name) user.name = claims.name;
|
|
33800
|
-
if (claims.picture) user.picture = claims.picture;
|
|
33801
|
-
if (claims.claims && typeof claims.claims === "object" && Object.keys(claims.claims).length) user.claims = claims.claims;
|
|
33802
|
-
return user;
|
|
33803
|
-
}
|
|
33804
|
-
|
|
33805
33757
|
// src/users.ts
|
|
33806
33758
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
33807
33759
|
|
|
33808
33760
|
// src/settings.ts
|
|
33809
|
-
var SIGN_IN_METHODS = ["google", "password"];
|
|
33761
|
+
var SIGN_IN_METHODS = ["google", "password", "totp"];
|
|
33762
|
+
var METHODS_WITH_SECOND_FACTOR = ["google", "password"];
|
|
33763
|
+
var SECOND_FACTOR_POLICIES = ["none", "optional", "required"];
|
|
33810
33764
|
var METHODS_SETTING = "methods";
|
|
33811
|
-
|
|
33765
|
+
var isSignInMethod = (value) => SIGN_IN_METHODS.includes(value);
|
|
33766
|
+
var isPolicy = (value) => SECOND_FACTOR_POLICIES.includes(value);
|
|
33767
|
+
function parseMethodSettings(input, options = {}) {
|
|
33812
33768
|
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
33813
33769
|
const settings = {};
|
|
33814
|
-
for (const [
|
|
33815
|
-
if (!
|
|
33816
|
-
|
|
33770
|
+
for (const [method, value] of Object.entries(input)) {
|
|
33771
|
+
if (!isSignInMethod(method)) return null;
|
|
33772
|
+
if (typeof value === "boolean") {
|
|
33773
|
+
if (!options.booleans) return null;
|
|
33774
|
+
if (value) settings[method] = {};
|
|
33775
|
+
continue;
|
|
33776
|
+
}
|
|
33777
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
33778
|
+
const setting = {};
|
|
33779
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
33780
|
+
if (field !== "secondFactor" || method === "totp" || !isPolicy(fieldValue)) return null;
|
|
33781
|
+
setting.secondFactor = fieldValue;
|
|
33782
|
+
}
|
|
33783
|
+
settings[method] = setting;
|
|
33817
33784
|
}
|
|
33818
33785
|
return settings;
|
|
33819
33786
|
}
|
|
33787
|
+
function isConfigured(config, method) {
|
|
33788
|
+
if (method === "google") return !!config.google;
|
|
33789
|
+
if (method === "password") return !!config.password;
|
|
33790
|
+
return true;
|
|
33791
|
+
}
|
|
33792
|
+
function canSignUp(config, method) {
|
|
33793
|
+
return method === "totp" ? !!config.emailVerification : isConfigured(config, method);
|
|
33794
|
+
}
|
|
33795
|
+
function initialMethods(config) {
|
|
33796
|
+
const methods = {};
|
|
33797
|
+
if (config.google) methods.google = { secondFactor: "none" };
|
|
33798
|
+
if (config.password) methods.password = { secondFactor: "none" };
|
|
33799
|
+
if (!config.google && !config.password && config.emailVerification) methods.totp = {};
|
|
33800
|
+
return methods;
|
|
33801
|
+
}
|
|
33820
33802
|
var MAX_TRIES = 3;
|
|
33821
33803
|
var Settings = class {
|
|
33822
|
-
|
|
33804
|
+
/** `initial` is what counts until the methods are set, see {@link initialMethods}. */
|
|
33805
|
+
constructor(store, initial = {}) {
|
|
33823
33806
|
this.store = store;
|
|
33807
|
+
this.initial = initial;
|
|
33808
|
+
this.warned = false;
|
|
33824
33809
|
}
|
|
33825
33810
|
/** The setting's value, or undefined if it was never set. */
|
|
33826
33811
|
async get(id) {
|
|
@@ -33857,20 +33842,38 @@ var Settings = class {
|
|
|
33857
33842
|
async set(id, value, updatedBy) {
|
|
33858
33843
|
await this.change(id, () => value, updatedBy);
|
|
33859
33844
|
}
|
|
33860
|
-
/** Which sign-in methods are on;
|
|
33845
|
+
/** Which sign-in methods are on, with their settings; the initial ones until an admin set them. */
|
|
33861
33846
|
async methods() {
|
|
33862
|
-
return
|
|
33847
|
+
return this.current(await this.get(METHODS_SETTING));
|
|
33863
33848
|
}
|
|
33864
33849
|
/** Changes which methods are on, see {@link change}; `apply` gets the methods as they are now. */
|
|
33865
33850
|
async changeMethods(apply, updatedBy) {
|
|
33866
|
-
return this.change(METHODS_SETTING, (current) => apply(
|
|
33851
|
+
return this.change(METHODS_SETTING, (current) => apply(this.current(current)), updatedBy);
|
|
33852
|
+
}
|
|
33853
|
+
/** The stored methods, or the initial ones if nothing (usable) is stored. */
|
|
33854
|
+
current(stored) {
|
|
33855
|
+
if (stored === void 0) return this.initial;
|
|
33856
|
+
const parsed = parseMethodSettings(stored);
|
|
33857
|
+
if (parsed) return parsed;
|
|
33858
|
+
if (!this.warned) {
|
|
33859
|
+
this.warned = true;
|
|
33860
|
+
console.warn("[madauth] The stored sign-in methods are not in the form this version expects (they were set by an older one?). Set them again with set-methods; until then the initial methods count.");
|
|
33861
|
+
}
|
|
33862
|
+
return this.initial;
|
|
33867
33863
|
}
|
|
33868
33864
|
};
|
|
33869
|
-
function isOn(method,
|
|
33870
|
-
return
|
|
33865
|
+
function isOn(method, configured, methods) {
|
|
33866
|
+
return configured(method) && methods[method] !== void 0;
|
|
33867
|
+
}
|
|
33868
|
+
function policyOf(method, methods) {
|
|
33869
|
+
return methods[method]?.secondFactor ?? "none";
|
|
33870
|
+
}
|
|
33871
|
+
function anyOn(signsUp, methods) {
|
|
33872
|
+
return SIGN_IN_METHODS.some((method) => signsUp(method) && methods[method] !== void 0);
|
|
33871
33873
|
}
|
|
33872
|
-
function
|
|
33873
|
-
|
|
33874
|
+
function authenticatorOn(configured, methods) {
|
|
33875
|
+
if (isOn("totp", configured, methods)) return true;
|
|
33876
|
+
return METHODS_WITH_SECOND_FACTOR.some((method) => isOn(method, configured, methods) && policyOf(method, methods) !== "none");
|
|
33874
33877
|
}
|
|
33875
33878
|
|
|
33876
33879
|
// src/users.ts
|
|
@@ -33883,6 +33886,9 @@ function passwordAccountKey(userId) {
|
|
|
33883
33886
|
function googleAccountKey(sub) {
|
|
33884
33887
|
return `google:${sub}`;
|
|
33885
33888
|
}
|
|
33889
|
+
function totpAccountKey(userId) {
|
|
33890
|
+
return `totp:${userId}`;
|
|
33891
|
+
}
|
|
33886
33892
|
function toMadauthUser(user, profile = {}) {
|
|
33887
33893
|
const result = { id: user.id, email: user.email };
|
|
33888
33894
|
const name = user.name ?? profile.name;
|
|
@@ -33911,18 +33917,32 @@ var Users = class {
|
|
|
33911
33917
|
async passwordAccount(userId) {
|
|
33912
33918
|
return this.findAccountByKey(passwordAccountKey(userId));
|
|
33913
33919
|
}
|
|
33914
|
-
/**
|
|
33915
|
-
async
|
|
33920
|
+
/** The user's authenticator app, if they set one up. */
|
|
33921
|
+
async totpAccount(userId) {
|
|
33922
|
+
return this.findAccountByKey(totpAccountKey(userId));
|
|
33923
|
+
}
|
|
33924
|
+
/** Whether the user has any way to sign in; a sign-up with the authenticator app has none until it is set up. */
|
|
33925
|
+
async hasAccounts(userId) {
|
|
33926
|
+
return (await this.store.findMany("account", { userId }, { limit: 1 })).length > 0;
|
|
33927
|
+
}
|
|
33928
|
+
/**
|
|
33929
|
+
* The ways the user can sign in, from their accounts: e.g. `['google']` for a user without a password.
|
|
33930
|
+
* The authenticator app counts only when it signs in on its own (`methods` lists it); as a second
|
|
33931
|
+
* factor it is a step of another method, not a way in.
|
|
33932
|
+
*/
|
|
33933
|
+
async signInMethods(userId, methods) {
|
|
33916
33934
|
const accounts = await this.store.findMany("account", { userId });
|
|
33917
|
-
const
|
|
33918
|
-
return SIGN_IN_METHODS.filter((method) =>
|
|
33935
|
+
const keys = accounts.map((account) => String(account.key).split(":")[0]);
|
|
33936
|
+
return SIGN_IN_METHODS.filter((method) => keys.includes(method) && (method !== "totp" || methods.totp !== void 0));
|
|
33919
33937
|
}
|
|
33920
33938
|
async findAccountByKey(key) {
|
|
33921
33939
|
return await this.store.findOne("account", { key });
|
|
33922
33940
|
}
|
|
33923
33941
|
/**
|
|
33924
|
-
* Creates a user with their first account. Resolves to null if the e-mail address or the
|
|
33925
|
-
* taken. `account.key` is `password:<userId>` for a password (its hash as `secret`), or
|
|
33942
|
+
* Creates a user, with their first account if given. Resolves to null if the e-mail address or the
|
|
33943
|
+
* account key is taken. `account.key` is `password:<userId>` for a password (its hash as `secret`), or
|
|
33944
|
+
* e.g. `google:<sub>`. A sign-up with the authenticator app creates the user without an account: the app
|
|
33945
|
+
* is set up once the address is confirmed.
|
|
33926
33946
|
*/
|
|
33927
33947
|
async createUser(data) {
|
|
33928
33948
|
const user = {
|
|
@@ -33938,6 +33958,7 @@ var Users = class {
|
|
|
33938
33958
|
createdAt: Date.now()
|
|
33939
33959
|
};
|
|
33940
33960
|
if (!await this.store.create("user", user)) return null;
|
|
33961
|
+
if (!data.account) return user;
|
|
33941
33962
|
const account = typeof data.account === "function" ? data.account(user.id) : data.account;
|
|
33942
33963
|
if (!await this.linkAccount(user.id, account)) {
|
|
33943
33964
|
await this.store.delete("user", { id: user.id });
|
|
@@ -33959,6 +33980,16 @@ var Users = class {
|
|
|
33959
33980
|
async deleteAccount(id) {
|
|
33960
33981
|
await this.store.delete("account", { id });
|
|
33961
33982
|
}
|
|
33983
|
+
/** Removes a sign-in method by its key. Resolves to false if there was none. */
|
|
33984
|
+
async unlinkAccount(key) {
|
|
33985
|
+
return await this.store.delete("account", { key }) === 1;
|
|
33986
|
+
}
|
|
33987
|
+
/** Removes the user's authenticator app and recovery codes. Resolves to false if they had no app. */
|
|
33988
|
+
async removeTotp(userId) {
|
|
33989
|
+
const removed = await this.unlinkAccount(totpAccountKey(userId));
|
|
33990
|
+
await this.clearRecoveryCodes(userId);
|
|
33991
|
+
return removed;
|
|
33992
|
+
}
|
|
33962
33993
|
/** Adds a sign-in method to a user. Resolves to null if the account key is taken. */
|
|
33963
33994
|
async linkAccount(userId, account) {
|
|
33964
33995
|
const record = {
|
|
@@ -33969,10 +34000,70 @@ var Users = class {
|
|
|
33969
34000
|
failedAttempts: 0,
|
|
33970
34001
|
lockedUntil: 0,
|
|
33971
34002
|
email: account.email ?? null,
|
|
34003
|
+
lastUsedStep: account.lastUsedStep ?? 0,
|
|
34004
|
+
setupId: account.setupId ?? null,
|
|
33972
34005
|
createdAt: Date.now()
|
|
33973
34006
|
};
|
|
33974
34007
|
return await this.store.create("account", record) ? record : null;
|
|
33975
34008
|
}
|
|
34009
|
+
/**
|
|
34010
|
+
* Writes the secret of a confirmed setup over the authenticator account, only if the account still has the
|
|
34011
|
+
* setup it had when this setup started (`replaces`): the write count decides, so of two setups confirmed
|
|
34012
|
+
* at once exactly one wins, and the other is told to start again.
|
|
34013
|
+
*/
|
|
34014
|
+
async confirmTotpSetup(accountId, replaces, patch) {
|
|
34015
|
+
return await this.store.update("account", { id: accountId, setupId: replaces }, patch) === 1;
|
|
34016
|
+
}
|
|
34017
|
+
/**
|
|
34018
|
+
* Marks the time step of an accepted authenticator code as used and resets the failed attempts, only if
|
|
34019
|
+
* no other request accepted a code meanwhile: the write count decides, so the same code sent twice signs
|
|
34020
|
+
* in once.
|
|
34021
|
+
*/
|
|
34022
|
+
async useTotpStep(account, step) {
|
|
34023
|
+
const changed = await this.store.update(
|
|
34024
|
+
"account",
|
|
34025
|
+
{ id: account.id, lastUsedStep: account.lastUsedStep ?? null },
|
|
34026
|
+
{ lastUsedStep: step, failedAttempts: 0, lockedUntil: 0 }
|
|
34027
|
+
);
|
|
34028
|
+
return changed === 1;
|
|
34029
|
+
}
|
|
34030
|
+
/** Replaces the user's recovery codes with new ones, given as their hashes. */
|
|
34031
|
+
/** Stores the recovery codes of a setup (their hashes), next to whatever codes the user has. */
|
|
34032
|
+
async addRecoveryCodes(userId, setupId, hashes) {
|
|
34033
|
+
const createdAt = Date.now();
|
|
34034
|
+
for (const id of hashes) await this.store.create("recoveryCode", { id, userId, setupId, createdAt });
|
|
34035
|
+
}
|
|
34036
|
+
/** New recovery codes for the user's current app: the codes they had before stop working. */
|
|
34037
|
+
async replaceRecoveryCodes(account, hashes) {
|
|
34038
|
+
const before = await this.store.findMany("recoveryCode", { userId: account.userId });
|
|
34039
|
+
await this.addRecoveryCodes(account.userId, account.setupId ?? "", hashes);
|
|
34040
|
+
for (const old of before) await this.store.delete("recoveryCode", { id: old.id });
|
|
34041
|
+
}
|
|
34042
|
+
/**
|
|
34043
|
+
* Uses up the recovery code with this hash for the user's authenticator account. Resolves to false if it
|
|
34044
|
+
* is not theirs, belongs to another setup than the account's, or was used already.
|
|
34045
|
+
*/
|
|
34046
|
+
async consumeRecoveryCode(account, hash) {
|
|
34047
|
+
const record = await this.store.findOne("recoveryCode", { id: hash });
|
|
34048
|
+
if (!record || record.userId !== account.userId || record.setupId !== (account.setupId ?? "")) return false;
|
|
34049
|
+
return await this.store.delete("recoveryCode", { id: hash }) === 1;
|
|
34050
|
+
}
|
|
34051
|
+
/** How many recovery codes of the account's setup are left. */
|
|
34052
|
+
async countRecoveryCodes(account) {
|
|
34053
|
+
const codes = await this.store.findMany("recoveryCode", { userId: account.userId });
|
|
34054
|
+
return codes.filter((code) => code.setupId === (account.setupId ?? "")).length;
|
|
34055
|
+
}
|
|
34056
|
+
/** Deletes the user's recovery codes: all of them, or only those of one setup, or all but one setup's. */
|
|
34057
|
+
async clearRecoveryCodes(userId, which = {}) {
|
|
34058
|
+
if (which.only === void 0 && which.except === void 0) {
|
|
34059
|
+
await this.store.delete("recoveryCode", { userId });
|
|
34060
|
+
return;
|
|
34061
|
+
}
|
|
34062
|
+
for (const code of await this.store.findMany("recoveryCode", { userId })) {
|
|
34063
|
+
const match2 = which.only !== void 0 ? code.setupId === which.only : code.setupId !== which.except;
|
|
34064
|
+
if (match2) await this.store.delete("recoveryCode", { id: code.id });
|
|
34065
|
+
}
|
|
34066
|
+
}
|
|
33976
34067
|
/**
|
|
33977
34068
|
* Deletes the user and everything stored for them. Resolves to false if there is no such user (any more).
|
|
33978
34069
|
*/
|
|
@@ -33983,6 +34074,7 @@ var Users = class {
|
|
|
33983
34074
|
try {
|
|
33984
34075
|
await this.store.delete("account", { userId: user.id });
|
|
33985
34076
|
await this.store.delete("verification", { userId: user.id });
|
|
34077
|
+
await this.store.delete("recoveryCode", { userId: user.id });
|
|
33986
34078
|
} catch (e6) {
|
|
33987
34079
|
console.error(`[madauth] Deleted user ${user.id}, but not all of their records: ${e6.message}`);
|
|
33988
34080
|
}
|
|
@@ -34026,6 +34118,11 @@ var Users = class {
|
|
|
34026
34118
|
await this.store.create("verification", record);
|
|
34027
34119
|
return { token, code };
|
|
34028
34120
|
}
|
|
34121
|
+
/** The user ID a link token belongs to, without using the verification up; null if invalid, expired or used. */
|
|
34122
|
+
async peekLinkToken(token, purpose) {
|
|
34123
|
+
const record = await this.store.findOne("verification", { id: hashLinkToken(token), purpose });
|
|
34124
|
+
return record && record.expiresAt > Date.now() ? record.userId : null;
|
|
34125
|
+
}
|
|
34029
34126
|
/** Uses up the verification a link token belongs to. Resolves to its user ID, or null if invalid, expired or used. */
|
|
34030
34127
|
async consumeLinkToken(token, purpose) {
|
|
34031
34128
|
const id = hashLinkToken(token);
|
|
@@ -34072,6 +34169,215 @@ var Users = class {
|
|
|
34072
34169
|
}
|
|
34073
34170
|
};
|
|
34074
34171
|
|
|
34172
|
+
// src/routes/helpers.ts
|
|
34173
|
+
async function body(c6) {
|
|
34174
|
+
const data = await c6.req.json().catch(() => null);
|
|
34175
|
+
return data && typeof data === "object" ? data : {};
|
|
34176
|
+
}
|
|
34177
|
+
function error(c6, status, code, message2) {
|
|
34178
|
+
return c6.json({ error: code, message: message2 }, status);
|
|
34179
|
+
}
|
|
34180
|
+
var unavailable = (c6) => error(c6, 503, "temporarily_unavailable", "E-mails can not be sent right now. Please try again later.");
|
|
34181
|
+
var accepted = (c6) => c6.json({}, 202);
|
|
34182
|
+
var noSession = (c6) => error(c6, 401, "no_session", "Please sign in first.");
|
|
34183
|
+
function redirectTarget(ctx, c6, value) {
|
|
34184
|
+
const url = ctx.allowedUrl(value);
|
|
34185
|
+
return url ?? error(c6, 400, "invalid_options", "redirectTo must be an absolute URL whose origin is in ALLOWED_ORIGINS.");
|
|
34186
|
+
}
|
|
34187
|
+
var MAX_NAME_LENGTH = 100;
|
|
34188
|
+
function nameOf(value) {
|
|
34189
|
+
return typeof value === "string" && value.trim() ? value.trim().slice(0, MAX_NAME_LENGTH) : null;
|
|
34190
|
+
}
|
|
34191
|
+
function signInAnswer(c6, step) {
|
|
34192
|
+
if ("disabled" in step) return error(c6, 403, "method_disabled", "This way of signing in is switched off.");
|
|
34193
|
+
if ("next" in step) {
|
|
34194
|
+
return step.next === "totp" ? c6.json({ error: "totp_required", message: "Enter the code from your authenticator app to finish signing in.", method: step.method }, 401) : c6.json({ error: "totp_setup_required", message: "Set up an authenticator app to finish signing in.", method: step.method }, 401);
|
|
34195
|
+
}
|
|
34196
|
+
return c6.json({ user: step.user });
|
|
34197
|
+
}
|
|
34198
|
+
|
|
34199
|
+
// src/routes/emails.ts
|
|
34200
|
+
var MAIL_INTERVAL_MS = 60 * 1e3;
|
|
34201
|
+
var VERIFY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
34202
|
+
var RESET_TTL_MS = 30 * 60 * 1e3;
|
|
34203
|
+
var VERIFY_LINK_PARAM = "madauth_verify";
|
|
34204
|
+
var RESET_LINK_PARAM = "madauth_reset";
|
|
34205
|
+
function createEmails(ctx, users, webhook) {
|
|
34206
|
+
const sendEmail = async (user, type, data) => {
|
|
34207
|
+
if (Date.now() - user.lastMailAt < MAIL_INTERVAL_MS) return true;
|
|
34208
|
+
const result = await webhook.call(type, { to: user.email, user: { id: user.id, name: user.name ?? void 0 }, ...data }, WEBHOOK_TIMEOUT_MS);
|
|
34209
|
+
if (!result.ok) {
|
|
34210
|
+
console.error(`[madauth] Webhook "${type}" for ${user.email} failed: ${result.reason}`);
|
|
34211
|
+
return false;
|
|
34212
|
+
}
|
|
34213
|
+
await users.updateUser(user.id, { lastMailAt: Date.now() });
|
|
34214
|
+
return true;
|
|
34215
|
+
};
|
|
34216
|
+
return {
|
|
34217
|
+
sendEmail,
|
|
34218
|
+
async sendLinkEmail(user, purpose, target, locale) {
|
|
34219
|
+
if (Date.now() - user.lastMailAt < MAIL_INTERVAL_MS) return true;
|
|
34220
|
+
const ttl = purpose === "verify" ? VERIFY_TTL_MS : RESET_TTL_MS;
|
|
34221
|
+
const { token, code } = await users.issueVerification(user.id, purpose, ttl);
|
|
34222
|
+
const link = new URL(target);
|
|
34223
|
+
link.hash = `${purpose === "verify" ? VERIFY_LINK_PARAM : RESET_LINK_PARAM}=${token}`;
|
|
34224
|
+
return sendEmail(user, purpose === "verify" ? "email.verify" : "email.reset", {
|
|
34225
|
+
link: link.href,
|
|
34226
|
+
code,
|
|
34227
|
+
expiresAt: Date.now() + ttl,
|
|
34228
|
+
site: target.host,
|
|
34229
|
+
locale
|
|
34230
|
+
});
|
|
34231
|
+
},
|
|
34232
|
+
async sendAccountEmail(user, type, target, locale) {
|
|
34233
|
+
if (!webhook.wants(type)) return true;
|
|
34234
|
+
const methods = await users.signInMethods(user.id, await ctx.settings.methods());
|
|
34235
|
+
return sendEmail(user, type, { link: target.href, site: target.host, locale, methods });
|
|
34236
|
+
},
|
|
34237
|
+
async consume(data, purpose) {
|
|
34238
|
+
if (typeof data.token === "string" && data.token) {
|
|
34239
|
+
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
34240
|
+
}
|
|
34241
|
+
if (isValidEmail(data.email) && typeof data.code === "string") {
|
|
34242
|
+
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34243
|
+
if (!user) return { userId: null, via: "code" };
|
|
34244
|
+
return { ...await users.consumeCode(user, purpose, data.code.replace(/\s/g, "")), via: "code" };
|
|
34245
|
+
}
|
|
34246
|
+
return { userId: null, via: "code" };
|
|
34247
|
+
},
|
|
34248
|
+
invalidVerification(c6, { via, locked }) {
|
|
34249
|
+
if (locked) {
|
|
34250
|
+
return error(c6, 429, "codes_locked", "Too many wrong codes. Please ask for a new e-mail and use the link in it.");
|
|
34251
|
+
}
|
|
34252
|
+
return via === "link" ? error(c6, 400, "link_invalid", "The link is invalid, expired or was already used.") : error(c6, 400, "code_invalid", "The code is wrong or expired.");
|
|
34253
|
+
}
|
|
34254
|
+
};
|
|
34255
|
+
}
|
|
34256
|
+
|
|
34257
|
+
// src/routes/email.ts
|
|
34258
|
+
function emailRoutes(app, ctx, users, webhook) {
|
|
34259
|
+
const emails = createEmails(ctx, users, webhook);
|
|
34260
|
+
const signUpMethod = async (userId) => await users.passwordAccount(userId) ? "password" : "totp";
|
|
34261
|
+
app.post("/auth/email/send-verification", async (c6) => {
|
|
34262
|
+
const data = await body(c6);
|
|
34263
|
+
const target = redirectTarget(ctx, c6, data.redirectTo);
|
|
34264
|
+
if (target instanceof Response) return target;
|
|
34265
|
+
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
34266
|
+
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34267
|
+
if (user && !user.emailVerified && await ctx.enabled(c6, await signUpMethod(user.id))) {
|
|
34268
|
+
if (!await emails.sendLinkEmail(user, "verify", target, localeOf(data.locale))) return unavailable(c6);
|
|
34269
|
+
}
|
|
34270
|
+
return accepted(c6);
|
|
34271
|
+
});
|
|
34272
|
+
const disabled = (c6) => error(c6, 403, "method_disabled", "This way of signing in is switched off.");
|
|
34273
|
+
app.post("/auth/email/verify", async (c6) => {
|
|
34274
|
+
const data = await body(c6);
|
|
34275
|
+
const waiting = typeof data.token === "string" && data.token ? await users.peekLinkToken(data.token, "verify") : isValidEmail(data.email) ? (await users.findByEmail(normalizeEmail(data.email)))?.id ?? null : null;
|
|
34276
|
+
if (waiting && !await ctx.enabled(c6, await signUpMethod(waiting))) return disabled(c6);
|
|
34277
|
+
const consumed = await emails.consume(data, "verify");
|
|
34278
|
+
const { userId, via } = consumed;
|
|
34279
|
+
const user = userId ? await users.findById(userId) : null;
|
|
34280
|
+
if (!user) return emails.invalidVerification(c6, consumed);
|
|
34281
|
+
const method = await signUpMethod(user.id);
|
|
34282
|
+
if (!await ctx.enabled(c6, method)) return disabled(c6);
|
|
34283
|
+
await users.updateUser(user.id, { emailVerified: true, wrongCodes: 0 });
|
|
34284
|
+
const verified = { ...user, emailVerified: true, wrongCodes: 0 };
|
|
34285
|
+
await ctx.emit("email.verified", { user: toMadauthUser(verified), via });
|
|
34286
|
+
return signInAnswer(c6, await ctx.secondStep(c6, verified, method === "password" ? ["pwd"] : [], method));
|
|
34287
|
+
});
|
|
34288
|
+
}
|
|
34289
|
+
|
|
34290
|
+
// src/google.ts
|
|
34291
|
+
var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
|
|
34292
|
+
var googleJwks;
|
|
34293
|
+
function googleKeys() {
|
|
34294
|
+
googleJwks ??= createRemoteJWKSet(new URL("https://www.googleapis.com/oauth2/v3/certs"));
|
|
34295
|
+
return googleJwks;
|
|
34296
|
+
}
|
|
34297
|
+
async function verifyGoogleIdToken(idToken, opts) {
|
|
34298
|
+
let payload;
|
|
34299
|
+
try {
|
|
34300
|
+
({ payload } = await jwtVerify(idToken, opts.keys ?? googleKeys(), {
|
|
34301
|
+
issuer: GOOGLE_ISSUERS,
|
|
34302
|
+
audience: opts.clientId,
|
|
34303
|
+
algorithms: ["RS256", "ES256"]
|
|
34304
|
+
}));
|
|
34305
|
+
} catch (e6) {
|
|
34306
|
+
return { ok: false, error: "verification_failed", reason: e6.message };
|
|
34307
|
+
}
|
|
34308
|
+
if (typeof payload.sub !== "string" || !payload.sub) {
|
|
34309
|
+
return { ok: false, error: "verification_failed", reason: "missing sub" };
|
|
34310
|
+
}
|
|
34311
|
+
if (payload.nonce !== opts.nonce) {
|
|
34312
|
+
return { ok: false, error: "verification_failed", reason: "nonce mismatch" };
|
|
34313
|
+
}
|
|
34314
|
+
if (typeof payload.email !== "string" || !payload.email || payload.email_verified !== true) {
|
|
34315
|
+
return { ok: false, error: "email_unverified", reason: "e-mail address not verified" };
|
|
34316
|
+
}
|
|
34317
|
+
const profile = { sub: payload.sub, email: payload.email };
|
|
34318
|
+
if (typeof payload.name === "string") profile.name = payload.name;
|
|
34319
|
+
if (typeof payload.picture === "string") profile.picture = payload.picture;
|
|
34320
|
+
return { ok: true, profile };
|
|
34321
|
+
}
|
|
34322
|
+
|
|
34323
|
+
// src/user.ts
|
|
34324
|
+
var SESSION_COOKIE = "madauth_session";
|
|
34325
|
+
var SESSION_TYP = "madauth-session+jwt";
|
|
34326
|
+
var RENEWAL_COOKIE = "madauth_renewal";
|
|
34327
|
+
var RENEWAL_TYP = "madauth-renewal+jwt";
|
|
34328
|
+
var EXPIRY_COOKIE = "madauth_session_expires";
|
|
34329
|
+
|
|
34330
|
+
// src/tokens.ts
|
|
34331
|
+
function randomString(bytes = 32) {
|
|
34332
|
+
return base64url_exports.encode(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
34333
|
+
}
|
|
34334
|
+
async function signToken(keys, issuer, typ, claims, ttlSeconds) {
|
|
34335
|
+
return new SignJWT(claims).setProtectedHeader({ alg: SIGNING_ALG, kid: keys.kid, typ }).setIssuer(issuer).setIssuedAt().setExpirationTime(`${ttlSeconds}s`).sign(keys.privateKey);
|
|
34336
|
+
}
|
|
34337
|
+
async function readToken(keys, issuer, typ, token) {
|
|
34338
|
+
if (!token) return null;
|
|
34339
|
+
try {
|
|
34340
|
+
const { payload } = await jwtVerify(token, keys.publicKey, { issuer, typ, algorithms: [SIGNING_ALG] });
|
|
34341
|
+
return payload;
|
|
34342
|
+
} catch {
|
|
34343
|
+
return null;
|
|
34344
|
+
}
|
|
34345
|
+
}
|
|
34346
|
+
function signSession(keys, issuer, ttlSeconds, user, amr, extra = {}) {
|
|
34347
|
+
const claims = {
|
|
34348
|
+
...extra,
|
|
34349
|
+
sub: user.id,
|
|
34350
|
+
email: user.email,
|
|
34351
|
+
email_verified: user.email ? true : void 0,
|
|
34352
|
+
name: user.name,
|
|
34353
|
+
picture: user.picture,
|
|
34354
|
+
claims: user.claims && Object.keys(user.claims).length ? user.claims : void 0,
|
|
34355
|
+
amr
|
|
34356
|
+
};
|
|
34357
|
+
return signToken(keys, issuer, SESSION_TYP, claims, ttlSeconds);
|
|
34358
|
+
}
|
|
34359
|
+
function signRenewal(keys, issuer, ttlSeconds, user, amr, extra = {}) {
|
|
34360
|
+
const claims = {
|
|
34361
|
+
...extra,
|
|
34362
|
+
sub: user.id,
|
|
34363
|
+
email: user.email,
|
|
34364
|
+
email_verified: user.email ? true : void 0,
|
|
34365
|
+
name: user.name,
|
|
34366
|
+
picture: user.picture,
|
|
34367
|
+
amr
|
|
34368
|
+
};
|
|
34369
|
+
return signToken(keys, issuer, RENEWAL_TYP, claims, ttlSeconds);
|
|
34370
|
+
}
|
|
34371
|
+
function userFromClaims(claims) {
|
|
34372
|
+
const user = { id: claims.sub };
|
|
34373
|
+
if (claims.email) user.email = claims.email;
|
|
34374
|
+
if (claims.name) user.name = claims.name;
|
|
34375
|
+
if (claims.picture) user.picture = claims.picture;
|
|
34376
|
+
if (claims.claims && typeof claims.claims === "object" && Object.keys(claims.claims).length) user.claims = claims.claims;
|
|
34377
|
+
if (Array.isArray(claims.amr)) user.amr = claims.amr.filter((value) => typeof value === "string");
|
|
34378
|
+
return user;
|
|
34379
|
+
}
|
|
34380
|
+
|
|
34075
34381
|
// src/routes/google.ts
|
|
34076
34382
|
var NONCE_COOKIE = "madauth_nonce";
|
|
34077
34383
|
var NONCE_TYP = "madauth-nonce+jwt";
|
|
@@ -34082,6 +34388,7 @@ var OAUTH_TTL = 10 * 60;
|
|
|
34082
34388
|
var GOOGLE_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
34083
34389
|
var GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
34084
34390
|
var REDIRECT_ERROR_PARAM = "madauth_error";
|
|
34391
|
+
var REDIRECT_NEXT_PARAM = "madauth_next";
|
|
34085
34392
|
async function pkceChallenge(verifier) {
|
|
34086
34393
|
const digest2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
34087
34394
|
return base64url_exports.encode(new Uint8Array(digest2));
|
|
@@ -34093,7 +34400,7 @@ function googleRoutes(app, ctx, google) {
|
|
|
34093
34400
|
const resolveUser = async (profile, locale) => {
|
|
34094
34401
|
const key = googleAccountKey(profile.sub);
|
|
34095
34402
|
const emailNormalized = normalizeEmail(profile.email);
|
|
34096
|
-
const signedIn = (user) => ({ ok: true, user
|
|
34403
|
+
const signedIn = (user) => ({ ok: true, user });
|
|
34097
34404
|
const account = await users.findAccountByKey(key);
|
|
34098
34405
|
if (account) {
|
|
34099
34406
|
const user = await users.findById(account.userId);
|
|
@@ -34106,6 +34413,11 @@ function googleRoutes(app, ctx, google) {
|
|
|
34106
34413
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
34107
34414
|
const existing = await users.findByEmail(emailNormalized);
|
|
34108
34415
|
if (existing) {
|
|
34416
|
+
if (existing.emailVerified && await users.hasAccounts(existing.id)) {
|
|
34417
|
+
const methods = await users.signInMethods(existing.id, await ctx.settings.methods());
|
|
34418
|
+
const how = methods.map((method) => ({ password: "a password", totp: "an authenticator app", google: "Google" })[method]).join(" or ");
|
|
34419
|
+
return { ok: false, error: "other_method", message: `This e-mail address signs in with ${how || "another method"}. Use that instead of Google.`, methods };
|
|
34420
|
+
}
|
|
34109
34421
|
await users.verifyByProvider(existing);
|
|
34110
34422
|
if (!await users.linkAccount(existing.id, { key, email: profile.email })) {
|
|
34111
34423
|
const linked = await users.findAccountByKey(key);
|
|
@@ -34137,7 +34449,7 @@ function googleRoutes(app, ctx, google) {
|
|
|
34137
34449
|
};
|
|
34138
34450
|
const disabled = (c6) => c6.json({ error: "method_disabled", message: "Google sign-in is switched off." }, 403);
|
|
34139
34451
|
app.post("/auth/google/nonce", async (c6) => {
|
|
34140
|
-
if (!await ctx.enabled("google")) return disabled(c6);
|
|
34452
|
+
if (!await ctx.enabled(c6, "google")) return disabled(c6);
|
|
34141
34453
|
const nonce = randomString();
|
|
34142
34454
|
const token = await signToken(await keys, issuer, NONCE_TYP, { nonce }, NONCE_TTL);
|
|
34143
34455
|
setCookie(c6, NONCE_COOKIE, token, {
|
|
@@ -34150,16 +34462,16 @@ function googleRoutes(app, ctx, google) {
|
|
|
34150
34462
|
return c6.json({ nonce });
|
|
34151
34463
|
});
|
|
34152
34464
|
app.post("/auth/google/verify", async (c6) => {
|
|
34153
|
-
if (!await ctx.enabled("google")) return disabled(c6);
|
|
34154
|
-
const
|
|
34155
|
-
if (typeof
|
|
34465
|
+
if (!await ctx.enabled(c6, "google")) return disabled(c6);
|
|
34466
|
+
const body2 = await c6.req.json().catch(() => ({}));
|
|
34467
|
+
if (typeof body2.credential !== "string" || !body2.credential) {
|
|
34156
34468
|
return c6.json({ error: "verification_failed", message: "credential is missing" }, 400);
|
|
34157
34469
|
}
|
|
34158
34470
|
const nonceClaims = await readToken(await keys, issuer, NONCE_TYP, getCookie(c6, NONCE_COOKIE));
|
|
34159
34471
|
if (!nonceClaims) {
|
|
34160
34472
|
return c6.json({ error: "verification_failed", message: "nonce cookie missing or expired" }, 401);
|
|
34161
34473
|
}
|
|
34162
|
-
const result = await verifyGoogleIdToken(
|
|
34474
|
+
const result = await verifyGoogleIdToken(body2.credential, {
|
|
34163
34475
|
clientId: google.clientId,
|
|
34164
34476
|
nonce: nonceClaims.nonce,
|
|
34165
34477
|
keys: config.jwksResolver
|
|
@@ -34168,17 +34480,16 @@ function googleRoutes(app, ctx, google) {
|
|
|
34168
34480
|
return c6.json({ error: result.error, message: result.reason }, result.error === "email_unverified" ? 403 : 401);
|
|
34169
34481
|
}
|
|
34170
34482
|
deleteCookie(c6, NONCE_COOKIE, { path: "/auth/google", secure });
|
|
34171
|
-
const resolved = await resolveUser(result.profile, localeOf(
|
|
34483
|
+
const resolved = await resolveUser(result.profile, localeOf(body2.locale));
|
|
34172
34484
|
if (!resolved.ok) {
|
|
34485
|
+
if (resolved.error === "other_method") return c6.json({ error: resolved.error, message: resolved.message, methods: resolved.methods }, 403);
|
|
34173
34486
|
return c6.json({ error: resolved.error, message: resolved.message }, resolved.error === "signup_rejected" ? 403 : 503);
|
|
34174
34487
|
}
|
|
34175
|
-
|
|
34176
|
-
await ctx.emit("user.signed_in", { user, method: "google" });
|
|
34177
|
-
return c6.json({ user });
|
|
34488
|
+
return signInAnswer(c6, await ctx.secondStep(c6, resolved.user, ["google"], "google", result.profile));
|
|
34178
34489
|
});
|
|
34179
34490
|
const requireCodeFlow = async (c6, next) => {
|
|
34180
34491
|
if (!google.clientSecret) return c6.json({ error: "not_found" }, 404);
|
|
34181
|
-
if (!await ctx.enabled("google")) return disabled(c6);
|
|
34492
|
+
if (!await ctx.enabled(c6, "google")) return disabled(c6);
|
|
34182
34493
|
await next();
|
|
34183
34494
|
};
|
|
34184
34495
|
app.get("/auth/google/start", requireCodeFlow, async (c6) => {
|
|
@@ -34216,9 +34527,10 @@ function googleRoutes(app, ctx, google) {
|
|
|
34216
34527
|
const oauth = await readToken(await keys, issuer, OAUTH_TYP, getCookie(c6, OAUTH_COOKIE));
|
|
34217
34528
|
if (!oauth) return c6.text("Sign-in expired or was started in another browser. Please try again.", 400);
|
|
34218
34529
|
deleteCookie(c6, OAUTH_COOKIE, { path: "/auth/google", secure });
|
|
34219
|
-
const back = (
|
|
34530
|
+
const back = (error2, next) => {
|
|
34220
34531
|
const url = new URL(oauth.returnTo);
|
|
34221
|
-
if (
|
|
34532
|
+
if (error2) url.hash = `${REDIRECT_ERROR_PARAM}=${error2}`;
|
|
34533
|
+
if (next) url.hash = `${REDIRECT_NEXT_PARAM}=${next}`;
|
|
34222
34534
|
return c6.redirect(url.href, 302);
|
|
34223
34535
|
};
|
|
34224
34536
|
if (c6.req.query("error")) return back(c6.req.query("error") === "access_denied" ? "cancelled" : "verification_failed");
|
|
@@ -34252,85 +34564,38 @@ function googleRoutes(app, ctx, google) {
|
|
|
34252
34564
|
if (!result.ok) return back(result.error);
|
|
34253
34565
|
const resolved = await resolveUser(result.profile, typeof oauth.locale === "string" ? oauth.locale : void 0);
|
|
34254
34566
|
if (!resolved.ok) return back(resolved.error);
|
|
34255
|
-
const
|
|
34256
|
-
|
|
34567
|
+
const step = await ctx.secondStep(c6, resolved.user, ["google"], "google", result.profile);
|
|
34568
|
+
if ("disabled" in step) return back("method_disabled");
|
|
34569
|
+
if ("next" in step) return back(void 0, step.next);
|
|
34257
34570
|
return back();
|
|
34258
34571
|
});
|
|
34259
34572
|
}
|
|
34260
34573
|
|
|
34261
|
-
// src/routes/
|
|
34574
|
+
// src/routes/lockout.ts
|
|
34262
34575
|
var FREE_ATTEMPTS = 5;
|
|
34263
34576
|
var MAX_LOCK_MS = 15 * 60 * 1e3;
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34577
|
+
async function countSignInAttempt(c6, users, account, now = Date.now()) {
|
|
34578
|
+
const tooMany = (message2) => c6.json({ error: "too_many_attempts", message: message2 }, 429);
|
|
34579
|
+
if (account.lockedUntil > now) {
|
|
34580
|
+
const seconds = Math.ceil((account.lockedUntil - now) / 1e3);
|
|
34581
|
+
return tooMany(`Too many failed attempts. Try again in ${seconds} seconds.`);
|
|
34582
|
+
}
|
|
34583
|
+
const failed = account.failedAttempts + 1;
|
|
34584
|
+
const lockedUntil = failed >= FREE_ATTEMPTS ? now + Math.min(2 ** (failed - FREE_ATTEMPTS) * 1e3, MAX_LOCK_MS) : 0;
|
|
34585
|
+
if (!await users.countAttempt(account, { failedAttempts: failed, lockedUntil })) {
|
|
34586
|
+
return tooMany("Too many attempts at once. Please try again.");
|
|
34587
|
+
}
|
|
34588
|
+
return null;
|
|
34589
|
+
}
|
|
34590
|
+
|
|
34591
|
+
// src/routes/password.ts
|
|
34270
34592
|
function passwordRoutes(app, ctx, password, users, webhook) {
|
|
34271
34593
|
const { minLength } = password;
|
|
34272
|
-
const
|
|
34594
|
+
const emails = createEmails(ctx, users, webhook);
|
|
34273
34595
|
app.use("/auth/password/*", async (c6, next) => {
|
|
34274
|
-
if (!await ctx.enabled("password")) return
|
|
34596
|
+
if (!await ctx.enabled(c6, "password")) return error(c6, 403, "method_disabled", "E-mail & password sign-in is switched off.");
|
|
34275
34597
|
await next();
|
|
34276
34598
|
});
|
|
34277
|
-
const body = async (c6) => {
|
|
34278
|
-
const data = await c6.req.json().catch(() => null);
|
|
34279
|
-
return data && typeof data === "object" ? data : {};
|
|
34280
|
-
};
|
|
34281
|
-
const error = (c6, status, code, message2) => c6.json({ error: code, message: message2 }, status);
|
|
34282
|
-
const unavailable = (c6) => error(c6, 503, "temporarily_unavailable", "E-mails can not be sent right now. Please try again later.");
|
|
34283
|
-
const accepted = (c6) => c6.json({}, 202);
|
|
34284
|
-
const redirectTarget = (c6, value) => {
|
|
34285
|
-
const url = ctx.allowedUrl(value);
|
|
34286
|
-
return url ?? error(c6, 400, "invalid_options", "redirectTo must be an absolute URL whose origin is in ALLOWED_ORIGINS.");
|
|
34287
|
-
};
|
|
34288
|
-
const sendEmail = async (user, type, data) => {
|
|
34289
|
-
if (Date.now() - user.lastMailAt < MAIL_INTERVAL_MS) return true;
|
|
34290
|
-
const result = await webhook.call(type, { to: user.email, user: { id: user.id, name: user.name ?? void 0 }, ...data }, WEBHOOK_TIMEOUT_MS);
|
|
34291
|
-
if (!result.ok) {
|
|
34292
|
-
console.error(`[madauth] Webhook "${type}" for ${user.email} failed: ${result.reason}`);
|
|
34293
|
-
return false;
|
|
34294
|
-
}
|
|
34295
|
-
await users.updateUser(user.id, { lastMailAt: Date.now() });
|
|
34296
|
-
return true;
|
|
34297
|
-
};
|
|
34298
|
-
const sendLinkEmail = async (user, purpose, target, locale) => {
|
|
34299
|
-
if (Date.now() - user.lastMailAt < MAIL_INTERVAL_MS) return true;
|
|
34300
|
-
const ttl = purpose === "verify" ? VERIFY_TTL_MS : RESET_TTL_MS;
|
|
34301
|
-
const { token, code } = await users.issueVerification(user.id, purpose, ttl);
|
|
34302
|
-
const link = new URL(target);
|
|
34303
|
-
link.hash = `${purpose === "verify" ? VERIFY_LINK_PARAM : RESET_LINK_PARAM}=${token}`;
|
|
34304
|
-
return sendEmail(user, purpose === "verify" ? "email.verify" : "email.reset", {
|
|
34305
|
-
link: link.href,
|
|
34306
|
-
code,
|
|
34307
|
-
expiresAt: Date.now() + ttl,
|
|
34308
|
-
site: target.host,
|
|
34309
|
-
locale
|
|
34310
|
-
});
|
|
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
|
-
};
|
|
34317
|
-
const consume = async (data, purpose) => {
|
|
34318
|
-
if (typeof data.token === "string" && data.token) {
|
|
34319
|
-
return { userId: await users.consumeLinkToken(data.token, purpose), via: "link" };
|
|
34320
|
-
}
|
|
34321
|
-
if (isValidEmail(data.email) && typeof data.code === "string") {
|
|
34322
|
-
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34323
|
-
if (!user) return { userId: null, via: "code" };
|
|
34324
|
-
return { ...await users.consumeCode(user, purpose, data.code.replace(/\s/g, "")), via: "code" };
|
|
34325
|
-
}
|
|
34326
|
-
return { userId: null, via: "code" };
|
|
34327
|
-
};
|
|
34328
|
-
const invalidVerification = (c6, { via, locked }) => {
|
|
34329
|
-
if (locked) {
|
|
34330
|
-
return error(c6, 429, "codes_locked", "Too many wrong codes. Please ask for a new e-mail and use the link in it.");
|
|
34331
|
-
}
|
|
34332
|
-
return via === "link" ? error(c6, 400, "link_invalid", "The link is invalid, expired or was already used.") : error(c6, 400, "code_invalid", "The code is wrong or expired.");
|
|
34333
|
-
};
|
|
34334
34599
|
app.post("/auth/password/signin", async (c6) => {
|
|
34335
34600
|
const data = await body(c6);
|
|
34336
34601
|
const wrong = () => error(c6, 401, "invalid_credentials", "E-mail or password is wrong.");
|
|
@@ -34342,16 +34607,8 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34342
34607
|
await verifyAgainstDummy(pw);
|
|
34343
34608
|
return wrong();
|
|
34344
34609
|
}
|
|
34345
|
-
const
|
|
34346
|
-
if (
|
|
34347
|
-
const seconds = Math.ceil((account.lockedUntil - now) / 1e3);
|
|
34348
|
-
return error(c6, 429, "too_many_attempts", `Too many failed attempts. Try again in ${seconds} seconds.`);
|
|
34349
|
-
}
|
|
34350
|
-
const failed = account.failedAttempts + 1;
|
|
34351
|
-
const lockedUntil = failed >= FREE_ATTEMPTS ? now + Math.min(2 ** (failed - FREE_ATTEMPTS) * 1e3, MAX_LOCK_MS) : 0;
|
|
34352
|
-
if (!await users.countAttempt(account, { failedAttempts: failed, lockedUntil })) {
|
|
34353
|
-
return error(c6, 429, "too_many_attempts", "Too many attempts at once. Please try again.");
|
|
34354
|
-
}
|
|
34610
|
+
const refused = await countSignInAttempt(c6, users, account);
|
|
34611
|
+
if (refused) return refused;
|
|
34355
34612
|
const { ok, needsRehash } = await verifyPassword(pw, account.secret);
|
|
34356
34613
|
if (!ok) return wrong();
|
|
34357
34614
|
await users.updateAccount(account.id, {
|
|
@@ -34362,26 +34619,24 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34362
34619
|
if (!user.emailVerified) {
|
|
34363
34620
|
return error(c6, 403, "email_unverified", "Please confirm your e-mail address first. We can send the e-mail again.");
|
|
34364
34621
|
}
|
|
34365
|
-
|
|
34366
|
-
await ctx.emit("user.signed_in", { user: result, method: "password" });
|
|
34367
|
-
return c6.json({ user: result });
|
|
34622
|
+
return signInAnswer(c6, await ctx.secondStep(c6, user, ["pwd"], "password"));
|
|
34368
34623
|
});
|
|
34369
34624
|
app.post("/auth/password/signup", async (c6) => {
|
|
34370
34625
|
const data = await body(c6);
|
|
34371
|
-
const target = redirectTarget(c6, data.redirectTo);
|
|
34626
|
+
const target = redirectTarget(ctx, c6, data.redirectTo);
|
|
34372
34627
|
if (target instanceof Response) return target;
|
|
34373
34628
|
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
34374
34629
|
const policy = checkPasswordPolicy(data.password, minLength);
|
|
34375
34630
|
if (policy) return error(c6, 400, "weak_password", policy);
|
|
34376
|
-
const name =
|
|
34631
|
+
const name = nameOf(data.name);
|
|
34377
34632
|
const email = data.email.trim();
|
|
34378
34633
|
const locale = localeOf(data.locale);
|
|
34379
34634
|
const check2 = await ctx.checkSignup({ email, name: name ?? void 0, locale, method: "password" });
|
|
34380
34635
|
if (!check2.ok) return check2.reason === "rejected" ? error(c6, 403, "signup_rejected", check2.message) : unavailable(c6);
|
|
34381
34636
|
const passwordHash = await hashPassword(data.password);
|
|
34382
34637
|
const existing = await users.findByEmail(normalizeEmail(email));
|
|
34383
|
-
if (existing?.emailVerified) {
|
|
34384
|
-
return await sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34638
|
+
if (existing?.emailVerified && await users.hasAccounts(existing.id)) {
|
|
34639
|
+
return await emails.sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34385
34640
|
}
|
|
34386
34641
|
if (existing) {
|
|
34387
34642
|
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c6);
|
|
@@ -34389,7 +34644,7 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34389
34644
|
if (account) await users.updateAccount(account.id, { secret: passwordHash, failedAttempts: 0, lockedUntil: 0 });
|
|
34390
34645
|
else await users.linkAccount(existing.id, { key: passwordAccountKey(existing.id), secret: passwordHash });
|
|
34391
34646
|
await users.updateUser(existing.id, { name });
|
|
34392
|
-
return await sendLinkEmail({ ...existing, name }, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34647
|
+
return await emails.sendLinkEmail({ ...existing, name }, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34393
34648
|
}
|
|
34394
34649
|
const user = await users.createUser({
|
|
34395
34650
|
email,
|
|
@@ -34400,41 +34655,19 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34400
34655
|
});
|
|
34401
34656
|
if (!user) return accepted(c6);
|
|
34402
34657
|
await ctx.emit("user.created", { user: toMadauthUser(user), method: "password" });
|
|
34403
|
-
return await sendLinkEmail(user, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34404
|
-
});
|
|
34405
|
-
app.post("/auth/password/send-verification", async (c6) => {
|
|
34406
|
-
const data = await body(c6);
|
|
34407
|
-
const target = redirectTarget(c6, data.redirectTo);
|
|
34408
|
-
if (target instanceof Response) return target;
|
|
34409
|
-
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
34410
|
-
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34411
|
-
if (user && !user.emailVerified && await users.passwordAccount(user.id)) {
|
|
34412
|
-
if (!await sendLinkEmail(user, "verify", target, localeOf(data.locale))) return unavailable(c6);
|
|
34413
|
-
}
|
|
34414
|
-
return accepted(c6);
|
|
34415
|
-
});
|
|
34416
|
-
app.post("/auth/password/verify-email", async (c6) => {
|
|
34417
|
-
const consumed = await consume(await body(c6), "verify");
|
|
34418
|
-
const { userId, via } = consumed;
|
|
34419
|
-
const user = userId ? await users.findById(userId) : null;
|
|
34420
|
-
if (!user) return invalidVerification(c6, consumed);
|
|
34421
|
-
await users.updateUser(user.id, { emailVerified: true, wrongCodes: 0 });
|
|
34422
|
-
const result = await ctx.startSession(c6, toMadauthUser(user), ["pwd"], { sv: user.sessionVersion });
|
|
34423
|
-
await ctx.emit("email.verified", { user: result, via });
|
|
34424
|
-
await ctx.emit("user.signed_in", { user: result, method: "password" });
|
|
34425
|
-
return c6.json({ user: result });
|
|
34658
|
+
return await emails.sendLinkEmail(user, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
34426
34659
|
});
|
|
34427
34660
|
app.post("/auth/password/send-reset", async (c6) => {
|
|
34428
34661
|
const data = await body(c6);
|
|
34429
|
-
const target = redirectTarget(c6, data.redirectTo);
|
|
34662
|
+
const target = redirectTarget(ctx, c6, data.redirectTo);
|
|
34430
34663
|
if (target instanceof Response) return target;
|
|
34431
34664
|
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
34432
34665
|
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34433
34666
|
const locale = localeOf(data.locale);
|
|
34434
34667
|
if (user && await users.passwordAccount(user.id)) {
|
|
34435
|
-
if (!await sendLinkEmail(user, "reset", target, locale)) return unavailable(c6);
|
|
34668
|
+
if (!await emails.sendLinkEmail(user, "reset", target, locale)) return unavailable(c6);
|
|
34436
34669
|
} else if (user) {
|
|
34437
|
-
if (!await sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c6);
|
|
34670
|
+
if (!await emails.sendAccountEmail(user, "email.no_password", target, locale)) return unavailable(c6);
|
|
34438
34671
|
}
|
|
34439
34672
|
return accepted(c6);
|
|
34440
34673
|
});
|
|
@@ -34442,20 +34675,359 @@ function passwordRoutes(app, ctx, password, users, webhook) {
|
|
|
34442
34675
|
const data = await body(c6);
|
|
34443
34676
|
const policy = checkPasswordPolicy(data.password, minLength);
|
|
34444
34677
|
if (policy) return error(c6, 400, "weak_password", policy);
|
|
34445
|
-
const consumed = await consume(data, "reset");
|
|
34678
|
+
const consumed = await emails.consume(data, "reset");
|
|
34446
34679
|
const user = consumed.userId ? await users.findById(consumed.userId) : null;
|
|
34447
34680
|
const account = user ? await users.passwordAccount(user.id) : null;
|
|
34448
|
-
if (!user || !account) return invalidVerification(c6, consumed);
|
|
34681
|
+
if (!user || !account) return emails.invalidVerification(c6, consumed);
|
|
34449
34682
|
const secret = await hashPassword(data.password);
|
|
34450
|
-
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return invalidVerification(c6, consumed);
|
|
34683
|
+
if (!await users.updateAccount(account.id, { secret, failedAttempts: 0, lockedUntil: 0 })) return emails.invalidVerification(c6, consumed);
|
|
34451
34684
|
const sessionVersion = user.sessionVersion + 1;
|
|
34452
34685
|
await users.updateUser(user.id, { emailVerified: true, sessionVersion, wrongCodes: 0 });
|
|
34453
34686
|
await users.clearVerifications(user.id);
|
|
34454
|
-
const
|
|
34455
|
-
await ctx.emit("email.password_reset", { user:
|
|
34456
|
-
await ctx.
|
|
34687
|
+
const reset = { ...user, emailVerified: true, sessionVersion, wrongCodes: 0 };
|
|
34688
|
+
await ctx.emit("email.password_reset", { user: toMadauthUser(reset) });
|
|
34689
|
+
return signInAnswer(c6, await ctx.secondStep(c6, reset, ["pwd"], "password"));
|
|
34690
|
+
});
|
|
34691
|
+
}
|
|
34692
|
+
|
|
34693
|
+
// src/totp.ts
|
|
34694
|
+
import { createCipheriv, createDecipheriv, createHmac as createHmac4, hkdfSync as hkdfSync2, randomBytes as randomBytes4, randomInt as randomInt2 } from "crypto";
|
|
34695
|
+
var TOTP_STEP_SECONDS = 30;
|
|
34696
|
+
var TOTP_DIGITS = 6;
|
|
34697
|
+
var TOTP_WINDOW = 1;
|
|
34698
|
+
var RECOVERY_CODES = 10;
|
|
34699
|
+
var SECRET_BYTES = 20;
|
|
34700
|
+
var BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
34701
|
+
var RECOVERY_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
34702
|
+
var RECOVERY_CODE_LENGTH = 10;
|
|
34703
|
+
var ENCRYPTION_VERSION = "v1";
|
|
34704
|
+
var IV_BYTES = 12;
|
|
34705
|
+
var TAG_BYTES = 16;
|
|
34706
|
+
function base32Encode(bytes) {
|
|
34707
|
+
let bits = 0;
|
|
34708
|
+
let value = 0;
|
|
34709
|
+
let out = "";
|
|
34710
|
+
for (const byte of bytes) {
|
|
34711
|
+
value = (value << 8 | byte) & 65535;
|
|
34712
|
+
bits += 8;
|
|
34713
|
+
while (bits >= 5) {
|
|
34714
|
+
out += BASE32[value >>> bits - 5 & 31];
|
|
34715
|
+
bits -= 5;
|
|
34716
|
+
}
|
|
34717
|
+
}
|
|
34718
|
+
if (bits > 0) out += BASE32[value << 5 - bits & 31];
|
|
34719
|
+
return out;
|
|
34720
|
+
}
|
|
34721
|
+
function hotp(secret, counter, digits = TOTP_DIGITS) {
|
|
34722
|
+
const message2 = Buffer.alloc(8);
|
|
34723
|
+
message2.writeBigUInt64BE(BigInt(counter));
|
|
34724
|
+
const digest2 = createHmac4("sha1", secret).update(message2).digest();
|
|
34725
|
+
const offset = digest2[digest2.length - 1] & 15;
|
|
34726
|
+
const binary = (digest2[offset] & 127) << 24 | digest2[offset + 1] << 16 | digest2[offset + 2] << 8 | digest2[offset + 3];
|
|
34727
|
+
return (binary % 10 ** digits).toString().padStart(digits, "0");
|
|
34728
|
+
}
|
|
34729
|
+
function stepOf(nowMs) {
|
|
34730
|
+
return Math.floor(nowMs / 1e3 / TOTP_STEP_SECONDS);
|
|
34731
|
+
}
|
|
34732
|
+
function totp(secret, step, digits = TOTP_DIGITS) {
|
|
34733
|
+
return hotp(secret, step, digits);
|
|
34734
|
+
}
|
|
34735
|
+
function verifyTotp(secret, code, nowMs, window = TOTP_WINDOW) {
|
|
34736
|
+
const given = code.replace(/\s/g, "");
|
|
34737
|
+
if (!new RegExp(`^\\d{${TOTP_DIGITS}}$`).test(given)) return null;
|
|
34738
|
+
const now = stepOf(nowMs);
|
|
34739
|
+
let found = null;
|
|
34740
|
+
for (let step = now - window; step <= now + window; step++) {
|
|
34741
|
+
if (safeEqual(totp(secret, step), given) && found === null) found = step;
|
|
34742
|
+
}
|
|
34743
|
+
return found;
|
|
34744
|
+
}
|
|
34745
|
+
function generateTotpSecret() {
|
|
34746
|
+
return randomBytes4(SECRET_BYTES);
|
|
34747
|
+
}
|
|
34748
|
+
function otpauthUri(options) {
|
|
34749
|
+
const issuer = encodeURIComponent(options.issuer);
|
|
34750
|
+
const account = encodeURIComponent(options.account);
|
|
34751
|
+
const secret = base32Encode(options.secret);
|
|
34752
|
+
return `otpauth://totp/${issuer}:${account}?secret=${secret}&issuer=${issuer}&algorithm=SHA1&digits=${TOTP_DIGITS}&period=${TOTP_STEP_SECONDS}`;
|
|
34753
|
+
}
|
|
34754
|
+
function deriveTotpKey(signingKeyD) {
|
|
34755
|
+
return Buffer.from(hkdfSync2("sha256", Buffer.from(signingKeyD, "base64url"), "", "madauth totp secret", 32));
|
|
34756
|
+
}
|
|
34757
|
+
function encryptSecret(key, secret) {
|
|
34758
|
+
const iv = randomBytes4(IV_BYTES);
|
|
34759
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
34760
|
+
const encrypted = Buffer.concat([cipher.update(secret), cipher.final()]);
|
|
34761
|
+
const parts = [iv, encrypted, cipher.getAuthTag()].map((part) => part.toString("base64url"));
|
|
34762
|
+
return [ENCRYPTION_VERSION, ...parts].join(".");
|
|
34763
|
+
}
|
|
34764
|
+
function decryptSecret(key, stored) {
|
|
34765
|
+
const [version, iv, encrypted, tag2, ...rest] = stored.split(".");
|
|
34766
|
+
if (version !== ENCRYPTION_VERSION || !iv || !encrypted || !tag2 || rest.length) return null;
|
|
34767
|
+
const ivBytes = Buffer.from(iv, "base64url");
|
|
34768
|
+
const tagBytes = Buffer.from(tag2, "base64url");
|
|
34769
|
+
if (ivBytes.length !== IV_BYTES || tagBytes.length !== TAG_BYTES) return null;
|
|
34770
|
+
try {
|
|
34771
|
+
const decipher = createDecipheriv("aes-256-gcm", key, ivBytes);
|
|
34772
|
+
decipher.setAuthTag(tagBytes);
|
|
34773
|
+
return Buffer.concat([decipher.update(Buffer.from(encrypted, "base64url")), decipher.final()]);
|
|
34774
|
+
} catch {
|
|
34775
|
+
return null;
|
|
34776
|
+
}
|
|
34777
|
+
}
|
|
34778
|
+
function generateRecoveryCodes() {
|
|
34779
|
+
const codes = /* @__PURE__ */ new Set();
|
|
34780
|
+
while (codes.size < RECOVERY_CODES) {
|
|
34781
|
+
let code = "";
|
|
34782
|
+
for (let i6 = 0; i6 < RECOVERY_CODE_LENGTH; i6++) code += RECOVERY_ALPHABET[randomInt2(RECOVERY_ALPHABET.length)];
|
|
34783
|
+
codes.add(`${code.slice(0, 5)}-${code.slice(5)}`);
|
|
34784
|
+
}
|
|
34785
|
+
return [...codes];
|
|
34786
|
+
}
|
|
34787
|
+
function normalizeRecoveryCode(text) {
|
|
34788
|
+
if (typeof text !== "string") return null;
|
|
34789
|
+
const code = text.toLowerCase().replace(/[\s-]/g, "");
|
|
34790
|
+
return code.length === RECOVERY_CODE_LENGTH && [...code].every((char) => RECOVERY_ALPHABET.includes(char)) ? code : null;
|
|
34791
|
+
}
|
|
34792
|
+
function hashRecoveryCode(codeKey, code) {
|
|
34793
|
+
return hashCode(codeKey, `recovery:${code}`);
|
|
34794
|
+
}
|
|
34795
|
+
|
|
34796
|
+
// src/routes/totp.ts
|
|
34797
|
+
var CHALLENGE_COOKIE = "madauth_challenge";
|
|
34798
|
+
var CHALLENGE_TYP = "madauth-challenge+jwt";
|
|
34799
|
+
var CHALLENGE_TTL = 10 * 60;
|
|
34800
|
+
var SETUP_COOKIE = "madauth_totp_setup";
|
|
34801
|
+
var SETUP_TYP = "madauth-totp-setup+jwt";
|
|
34802
|
+
var SETUP_TTL = 10 * 60;
|
|
34803
|
+
var COOKIE_PATH = "/auth/totp";
|
|
34804
|
+
var cookieOptions = (secure) => ({ path: COOKIE_PATH, httpOnly: true, secure, sameSite: "Strict" });
|
|
34805
|
+
async function setChallenge(c6, ctx, challenge) {
|
|
34806
|
+
const token = await signToken(await ctx.keys, ctx.config.issuer, CHALLENGE_TYP, { ...challenge }, CHALLENGE_TTL);
|
|
34807
|
+
setCookie(c6, CHALLENGE_COOKIE, token, { ...cookieOptions(ctx.secure), maxAge: CHALLENGE_TTL });
|
|
34808
|
+
}
|
|
34809
|
+
function clearChallenge(c6, secure) {
|
|
34810
|
+
deleteCookie(c6, CHALLENGE_COOKIE, cookieOptions(secure));
|
|
34811
|
+
}
|
|
34812
|
+
async function readChallenge(c6, ctx, next) {
|
|
34813
|
+
const claims = await readToken(await ctx.keys, ctx.config.issuer, CHALLENGE_TYP, getCookie(c6, CHALLENGE_COOKIE));
|
|
34814
|
+
return claims && claims.next === next && typeof claims.sub === "string" && Array.isArray(claims.amr) ? claims : null;
|
|
34815
|
+
}
|
|
34816
|
+
async function checkCode(c6, ctx, keys, user, account, data, wrong = () => error(c6, 401, "code_invalid", "The code is wrong or was already used.")) {
|
|
34817
|
+
const { users } = ctx;
|
|
34818
|
+
const refused = await countSignInAttempt(c6, users, account);
|
|
34819
|
+
if (refused) return refused;
|
|
34820
|
+
if (typeof data.recoveryCode === "string") {
|
|
34821
|
+
const code = normalizeRecoveryCode(data.recoveryCode);
|
|
34822
|
+
if (!code || !await users.consumeRecoveryCode(account, hashRecoveryCode(keys.codeKey, code))) return wrong();
|
|
34823
|
+
await users.updateAccount(account.id, { failedAttempts: 0, lockedUntil: 0 });
|
|
34824
|
+
const remaining = await users.countRecoveryCodes(account);
|
|
34825
|
+
await ctx.emit("totp.recovery_code_used", { user: toMadauthUser(user), remaining });
|
|
34826
|
+
return "recovery_code";
|
|
34827
|
+
}
|
|
34828
|
+
if (typeof data.code !== "string" || !account.secret) return wrong();
|
|
34829
|
+
const secret = decryptSecret(keys.totpKey, account.secret);
|
|
34830
|
+
if (!secret) {
|
|
34831
|
+
console.error(`[madauth] The authenticator secret of user ${user.id} can't be read: was MADAUTH_SIGNING_KEY changed?`);
|
|
34832
|
+
return wrong();
|
|
34833
|
+
}
|
|
34834
|
+
const step = verifyTotp(secret, data.code, Date.now(), TOTP_WINDOW);
|
|
34835
|
+
if (step === null || step <= (account.lastUsedStep ?? 0) || !await users.useTotpStep(account, step)) return wrong();
|
|
34836
|
+
return "totp";
|
|
34837
|
+
}
|
|
34838
|
+
function totpRoutes(app, ctx, keys) {
|
|
34839
|
+
const { config, secure, users } = ctx;
|
|
34840
|
+
const { issuer } = config;
|
|
34841
|
+
const cookie = cookieOptions(secure);
|
|
34842
|
+
const methodsNow = (c6) => ctx.methods(c6);
|
|
34843
|
+
app.use(`${COOKIE_PATH}/*`, async (c6, next) => {
|
|
34844
|
+
if (!authenticatorOn(ctx.configured, await methodsNow(c6))) {
|
|
34845
|
+
return error(c6, 403, "method_disabled", "The authenticator app is switched off.");
|
|
34846
|
+
}
|
|
34847
|
+
await next();
|
|
34848
|
+
});
|
|
34849
|
+
const standalone = async (c6) => isOn("totp", ctx.configured, await methodsNow(c6));
|
|
34850
|
+
const notStandalone = (c6) => error(c6, 403, "method_disabled", "Signing in with the authenticator app alone is switched off.");
|
|
34851
|
+
const methodStillOn = async (c6, challenge) => isOn(challenge.method, ctx.configured, await methodsNow(c6));
|
|
34852
|
+
const methodOff = (c6) => error(c6, 403, "method_disabled", "This way of signing in is switched off.");
|
|
34853
|
+
const signedIn = async (c6) => {
|
|
34854
|
+
const user = await ctx.currentUser(c6);
|
|
34855
|
+
return user ? users.findById(user.id) : null;
|
|
34856
|
+
};
|
|
34857
|
+
const whoSetsUp = async (c6) => {
|
|
34858
|
+
const challenge = await readChallenge(c6, ctx, "totp-setup");
|
|
34859
|
+
const waiting = challenge ? await users.findById(challenge.sub) : null;
|
|
34860
|
+
if (challenge && waiting && waiting.sessionVersion === challenge.sv) return { user: waiting, challenge };
|
|
34861
|
+
const user = await signedIn(c6);
|
|
34862
|
+
return user ? { user, challenge: null } : null;
|
|
34863
|
+
};
|
|
34864
|
+
const newRecoveryCodes = () => {
|
|
34865
|
+
const recoveryCodes = generateRecoveryCodes();
|
|
34866
|
+
return { recoveryCodes, hashes: recoveryCodes.map((code) => hashRecoveryCode(keys.codeKey, normalizeRecoveryCode(code))) };
|
|
34867
|
+
};
|
|
34868
|
+
const issuerLabel = (c6) => {
|
|
34869
|
+
if (config.totpIssuer) return config.totpIssuer;
|
|
34870
|
+
const origin = c6.req.header("origin");
|
|
34871
|
+
return URL.canParse(origin ?? "") ? new URL(origin).hostname : new URL(issuer).hostname;
|
|
34872
|
+
};
|
|
34873
|
+
const finishSignIn = async (c6, user, challenge, via) => {
|
|
34874
|
+
const result = await ctx.startSession(c6, toMadauthUser(user, challenge.profile), [...challenge.amr, "otp"], { sv: user.sessionVersion });
|
|
34875
|
+
await ctx.emit("user.signed_in", { user: result, method: challenge.method, ...challenge.amr.length ? { secondFactor: via } : {} });
|
|
34876
|
+
return result;
|
|
34877
|
+
};
|
|
34878
|
+
app.post(`${COOKIE_PATH}/setup`, async (c6) => {
|
|
34879
|
+
const who = await whoSetsUp(c6);
|
|
34880
|
+
if (!who) return noSession(c6);
|
|
34881
|
+
if (who.challenge && !await methodStillOn(c6, who.challenge)) return methodOff(c6);
|
|
34882
|
+
const existing = await users.totpAccount(who.user.id);
|
|
34883
|
+
let approved;
|
|
34884
|
+
if (existing) {
|
|
34885
|
+
const previous = await readToken(await ctx.keys, issuer, SETUP_TYP, getCookie(c6, SETUP_COOKIE));
|
|
34886
|
+
const holds = previous?.sub === who.user.id && typeof previous.approved === "number" && Date.now() - previous.approved < SETUP_TTL * 1e3;
|
|
34887
|
+
if (holds) {
|
|
34888
|
+
approved = previous.approved;
|
|
34889
|
+
} else {
|
|
34890
|
+
const data = await body(c6);
|
|
34891
|
+
if (data.code === void 0 && data.recoveryCode === void 0) {
|
|
34892
|
+
return error(c6, 401, "code_required", "Enter a code from your current authenticator app (or a recovery code) to replace it.");
|
|
34893
|
+
}
|
|
34894
|
+
const via = await checkCode(c6, ctx, keys, who.user, existing, data);
|
|
34895
|
+
if (via instanceof Response) return via;
|
|
34896
|
+
approved = Date.now();
|
|
34897
|
+
}
|
|
34898
|
+
}
|
|
34899
|
+
const secret = generateTotpSecret();
|
|
34900
|
+
const claims = {
|
|
34901
|
+
sub: who.user.id,
|
|
34902
|
+
setup: newId("tsu"),
|
|
34903
|
+
secret: encryptSecret(keys.totpKey, secret),
|
|
34904
|
+
...existing ? { replaces: existing.setupId } : {},
|
|
34905
|
+
...approved ? { approved } : {}
|
|
34906
|
+
};
|
|
34907
|
+
const token = await signToken(await ctx.keys, issuer, SETUP_TYP, claims, SETUP_TTL);
|
|
34908
|
+
setCookie(c6, SETUP_COOKIE, token, { ...cookie, maxAge: SETUP_TTL });
|
|
34909
|
+
return c6.json({ secret: base32Encode(secret), uri: otpauthUri({ issuer: issuerLabel(c6), account: who.user.email, secret }) });
|
|
34910
|
+
});
|
|
34911
|
+
app.post(`${COOKIE_PATH}/confirm`, async (c6) => {
|
|
34912
|
+
const who = await whoSetsUp(c6);
|
|
34913
|
+
if (!who) return noSession(c6);
|
|
34914
|
+
const { user, challenge } = who;
|
|
34915
|
+
if (challenge && !await methodStillOn(c6, challenge)) return methodOff(c6);
|
|
34916
|
+
const data = await body(c6);
|
|
34917
|
+
const setup = await readToken(await ctx.keys, issuer, SETUP_TYP, getCookie(c6, SETUP_COOKIE));
|
|
34918
|
+
const secret = setup && setup.sub === user.id && typeof setup.secret === "string" && typeof setup.setup === "string" ? decryptSecret(keys.totpKey, setup.secret) : null;
|
|
34919
|
+
if (!setup || !secret) return error(c6, 400, "setup_expired", "The setup has expired or was started in another browser. Please start again.");
|
|
34920
|
+
const step = typeof data.code === "string" ? verifyTotp(secret, data.code, Date.now(), TOTP_WINDOW) : null;
|
|
34921
|
+
if (step === null) return error(c6, 400, "code_invalid", "The code is wrong. Enter the current code from the app.");
|
|
34922
|
+
const { recoveryCodes, hashes } = newRecoveryCodes();
|
|
34923
|
+
await users.addRecoveryCodes(user.id, setup.setup, hashes);
|
|
34924
|
+
const patch = { secret: setup.secret, setupId: setup.setup, failedAttempts: 0, lockedUntil: 0, lastUsedStep: step };
|
|
34925
|
+
const current = setup.replaces === void 0 ? null : await users.totpAccount(user.id);
|
|
34926
|
+
const written = setup.replaces === void 0 ? !!await users.linkAccount(user.id, { key: totpAccountKey(user.id), ...patch }) : !!current && await users.confirmTotpSetup(current.id, setup.replaces, patch);
|
|
34927
|
+
if (!written) {
|
|
34928
|
+
await users.clearRecoveryCodes(user.id, { only: setup.setup });
|
|
34929
|
+
deleteCookie(c6, SETUP_COOKIE, cookie);
|
|
34930
|
+
return error(c6, 400, "setup_expired", "Your authenticator app was set up or removed elsewhere meanwhile. Please start again.");
|
|
34931
|
+
}
|
|
34932
|
+
await users.clearRecoveryCodes(user.id, { except: setup.setup });
|
|
34933
|
+
deleteCookie(c6, SETUP_COOKIE, cookie);
|
|
34934
|
+
await ctx.emit("totp.enabled", { user: toMadauthUser(user) });
|
|
34935
|
+
if (!challenge) return c6.json({ recoveryCodes });
|
|
34936
|
+
const result = await finishSignIn(c6, user, challenge, "totp");
|
|
34937
|
+
return c6.json({ user: result, recoveryCodes });
|
|
34938
|
+
});
|
|
34939
|
+
app.post(`${COOKIE_PATH}/verify`, async (c6) => {
|
|
34940
|
+
const expired = () => error(c6, 401, "challenge_expired", "The sign-in has expired. Please sign in again.");
|
|
34941
|
+
const challenge = await readChallenge(c6, ctx, "totp");
|
|
34942
|
+
if (!challenge) return expired();
|
|
34943
|
+
if (!await methodStillOn(c6, challenge)) return methodOff(c6);
|
|
34944
|
+
const user = await users.findById(challenge.sub);
|
|
34945
|
+
const account = user && user.sessionVersion === challenge.sv ? await users.totpAccount(user.id) : null;
|
|
34946
|
+
if (!user || !account) return expired();
|
|
34947
|
+
const via = await checkCode(c6, ctx, keys, user, account, await body(c6));
|
|
34948
|
+
if (via instanceof Response) return via;
|
|
34949
|
+
return c6.json({ user: await finishSignIn(c6, user, challenge, via) });
|
|
34950
|
+
});
|
|
34951
|
+
app.post(`${COOKIE_PATH}/signin`, async (c6) => {
|
|
34952
|
+
if (!await standalone(c6)) return notStandalone(c6);
|
|
34953
|
+
const data = await body(c6);
|
|
34954
|
+
const wrong = () => error(c6, 401, "invalid_credentials", "E-mail or code is wrong.");
|
|
34955
|
+
if (!isValidEmail(data.email)) return wrong();
|
|
34956
|
+
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
34957
|
+
const account = user ? await users.totpAccount(user.id) : null;
|
|
34958
|
+
if (!user || !user.emailVerified || !account?.secret) return wrong();
|
|
34959
|
+
const via = await checkCode(c6, ctx, keys, user, account, data, wrong);
|
|
34960
|
+
if (via instanceof Response) return via;
|
|
34961
|
+
const result = await ctx.startSession(c6, toMadauthUser(user), ["otp"], { sv: user.sessionVersion });
|
|
34962
|
+
await ctx.emit("user.signed_in", { user: result, method: "totp" });
|
|
34457
34963
|
return c6.json({ user: result });
|
|
34458
34964
|
});
|
|
34965
|
+
app.post(`${COOKIE_PATH}/remove`, async (c6) => {
|
|
34966
|
+
const user = await signedIn(c6);
|
|
34967
|
+
if (!user) return noSession(c6);
|
|
34968
|
+
const methods = await methodsNow(c6);
|
|
34969
|
+
const required = METHODS_WITH_SECOND_FACTOR.filter((method) => isOn(method, ctx.configured, methods) && policyOf(method, methods) === "required");
|
|
34970
|
+
if (required.length) {
|
|
34971
|
+
const names = required.map((method) => method === "google" ? "Google" : "e-mail & password");
|
|
34972
|
+
return error(c6, 403, "required_by_policy", `Signing in with ${names.join(" and ")} requires the authenticator app, so it can't be removed.`);
|
|
34973
|
+
}
|
|
34974
|
+
const account = await users.totpAccount(user.id);
|
|
34975
|
+
if (!account) return c6.json({});
|
|
34976
|
+
if (!(await users.signInMethods(user.id, methods)).some((method) => method !== "totp" && isOn(method, ctx.configured, methods))) {
|
|
34977
|
+
return error(c6, 403, "only_method", "The authenticator app is the only way you sign in, so it can't be removed. Delete your account instead.");
|
|
34978
|
+
}
|
|
34979
|
+
const via = await checkCode(c6, ctx, keys, user, account, await body(c6));
|
|
34980
|
+
if (via instanceof Response) return via;
|
|
34981
|
+
await users.removeTotp(user.id);
|
|
34982
|
+
await ctx.emit("totp.disabled", { user: toMadauthUser(user) });
|
|
34983
|
+
return c6.json({});
|
|
34984
|
+
});
|
|
34985
|
+
app.post(`${COOKIE_PATH}/recovery-codes`, async (c6) => {
|
|
34986
|
+
const user = await signedIn(c6);
|
|
34987
|
+
if (!user) return noSession(c6);
|
|
34988
|
+
const account = await users.totpAccount(user.id);
|
|
34989
|
+
if (!account) return error(c6, 400, "no_authenticator", "No authenticator app is set up.");
|
|
34990
|
+
const via = await checkCode(c6, ctx, keys, user, account, await body(c6));
|
|
34991
|
+
if (via instanceof Response) return via;
|
|
34992
|
+
const { recoveryCodes, hashes } = newRecoveryCodes();
|
|
34993
|
+
await users.replaceRecoveryCodes(account, hashes);
|
|
34994
|
+
return c6.json({ recoveryCodes });
|
|
34995
|
+
});
|
|
34996
|
+
app.get(`${COOKIE_PATH}/status`, async (c6) => {
|
|
34997
|
+
const user = await signedIn(c6);
|
|
34998
|
+
if (!user) return noSession(c6);
|
|
34999
|
+
const account = await users.totpAccount(user.id);
|
|
35000
|
+
return c6.json({ enabled: !!account, recoveryCodesLeft: account ? await users.countRecoveryCodes(account) : 0 });
|
|
35001
|
+
});
|
|
35002
|
+
if (ctx.webhook && ctx.emailVerification) {
|
|
35003
|
+
const emails = createEmails(ctx, users, ctx.webhook);
|
|
35004
|
+
app.post(`${COOKIE_PATH}/signup`, async (c6) => {
|
|
35005
|
+
if (!await standalone(c6)) return notStandalone(c6);
|
|
35006
|
+
const data = await body(c6);
|
|
35007
|
+
const target = redirectTarget(ctx, c6, data.redirectTo);
|
|
35008
|
+
if (target instanceof Response) return target;
|
|
35009
|
+
if (!isValidEmail(data.email)) return error(c6, 400, "invalid_email", "This is not a valid e-mail address.");
|
|
35010
|
+
const name = nameOf(data.name);
|
|
35011
|
+
const email = data.email.trim();
|
|
35012
|
+
const locale = localeOf(data.locale);
|
|
35013
|
+
const check2 = await ctx.checkSignup({ email, name: name ?? void 0, locale, method: "totp" });
|
|
35014
|
+
if (!check2.ok) return check2.reason === "rejected" ? error(c6, 403, "signup_rejected", check2.message) : unavailable(c6);
|
|
35015
|
+
const existing = await users.findByEmail(normalizeEmail(email));
|
|
35016
|
+
if (existing?.emailVerified && await users.hasAccounts(existing.id)) {
|
|
35017
|
+
return await emails.sendAccountEmail(existing, "email.already_registered", target, locale) ? accepted(c6) : unavailable(c6);
|
|
35018
|
+
}
|
|
35019
|
+
if (existing) {
|
|
35020
|
+
if (Date.now() - existing.lastMailAt < MAIL_INTERVAL_MS) return accepted(c6);
|
|
35021
|
+
if (!existing.emailVerified) await users.unlinkAccount(passwordAccountKey(existing.id));
|
|
35022
|
+
await users.updateUser(existing.id, { name });
|
|
35023
|
+
return await emails.sendLinkEmail({ ...existing, name }, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
35024
|
+
}
|
|
35025
|
+
const user = await users.createUser({ email, emailNormalized: normalizeEmail(email), name, emailVerified: false });
|
|
35026
|
+
if (!user) return accepted(c6);
|
|
35027
|
+
await ctx.emit("user.created", { user: toMadauthUser(user), method: "totp" });
|
|
35028
|
+
return await emails.sendLinkEmail(user, "verify", target, locale) ? accepted(c6) : unavailable(c6);
|
|
35029
|
+
});
|
|
35030
|
+
}
|
|
34459
35031
|
}
|
|
34460
35032
|
|
|
34461
35033
|
// src/app.ts
|
|
@@ -34465,9 +35037,14 @@ function createApp(config) {
|
|
|
34465
35037
|
const keys = importSigningKeys(config.signingKey);
|
|
34466
35038
|
const secure = issuer.startsWith("https:");
|
|
34467
35039
|
const isAllowedOrigin = (origin) => !!origin && allowedOrigins.includes(origin);
|
|
34468
|
-
const
|
|
34469
|
-
const
|
|
34470
|
-
const
|
|
35040
|
+
const codeKey = deriveCodeKey(config.signingKey.d);
|
|
35041
|
+
const users = new Users(config.store, codeKey);
|
|
35042
|
+
const methods = methodConfig(config);
|
|
35043
|
+
const settings = new Settings(config.store, initialMethods(methods));
|
|
35044
|
+
const configured = (method) => isConfigured(methods, method);
|
|
35045
|
+
const signsUp = (method) => canSignUp(methods, method);
|
|
35046
|
+
const emailVerification = sendsVerificationEmails(config);
|
|
35047
|
+
const totpKeys = { totpKey: deriveTotpKey(config.signingKey.d), codeKey };
|
|
34471
35048
|
const issueSession = async (c6, user, amr, extra) => {
|
|
34472
35049
|
const signingKeys = await keys;
|
|
34473
35050
|
const [sessionToken, renewalToken] = await Promise.all([
|
|
@@ -34502,6 +35079,29 @@ function createApp(config) {
|
|
|
34502
35079
|
deleteCookie(c6, RENEWAL_COOKIE, { path: "/auth", secure });
|
|
34503
35080
|
deleteCookie(c6, EXPIRY_COOKIE, { path: "/", domain: cookieDomain, secure });
|
|
34504
35081
|
};
|
|
35082
|
+
const tokenClaims = async (c6, cookie, typ) => readToken(await keys, issuer, typ, getCookie(c6, cookie));
|
|
35083
|
+
const checked = async (claims) => {
|
|
35084
|
+
const stored = await users.findById(claims.sub);
|
|
35085
|
+
if (!stored || stored.sessionVersion !== claims.sv) return null;
|
|
35086
|
+
const { claims: _, ...user } = userFromClaims(claims);
|
|
35087
|
+
const current = claimsFromJson(stored.claims);
|
|
35088
|
+
return Object.keys(current).length ? { ...user, claims: current } : user;
|
|
35089
|
+
};
|
|
35090
|
+
const currentSession = async (c6) => {
|
|
35091
|
+
const session = await tokenClaims(c6, SESSION_COOKIE, SESSION_TYP);
|
|
35092
|
+
const user = session ? await checked(session) : null;
|
|
35093
|
+
const upToDate = session && user && sameClaims(user.claims, session.claims) && Date.now() / 1e3 - session.iat <= sessionTtlSeconds / 2;
|
|
35094
|
+
if (upToDate) return user;
|
|
35095
|
+
const renewal = await tokenClaims(c6, RENEWAL_COOKIE, RENEWAL_TYP);
|
|
35096
|
+
const sameSignIn = session && user && renewal && renewal.sub === session.sub && renewal.sv === session.sv && renewal.email === session.email;
|
|
35097
|
+
const renewed = sameSignIn ? user : renewal ? await checked(renewal) : null;
|
|
35098
|
+
if (renewal && renewed) {
|
|
35099
|
+
await issueSession(c6, renewed, renewal.amr, { sv: renewal.sv });
|
|
35100
|
+
return renewed;
|
|
35101
|
+
}
|
|
35102
|
+
return user;
|
|
35103
|
+
};
|
|
35104
|
+
const methodsOf = /* @__PURE__ */ new WeakMap();
|
|
34505
35105
|
const ctx = {
|
|
34506
35106
|
config,
|
|
34507
35107
|
keys,
|
|
@@ -34519,14 +35119,42 @@ function createApp(config) {
|
|
|
34519
35119
|
return url;
|
|
34520
35120
|
},
|
|
34521
35121
|
async startSession(c6, user, amr, extra) {
|
|
35122
|
+
clearChallenge(c6, secure);
|
|
34522
35123
|
await issueSession(c6, user, amr, extra);
|
|
34523
|
-
return user;
|
|
35124
|
+
return { ...user, amr };
|
|
34524
35125
|
},
|
|
35126
|
+
async secondStep(c6, user, amr, method, profile) {
|
|
35127
|
+
const methods2 = await this.methods(c6);
|
|
35128
|
+
if (!isOn(method, configured, methods2)) return { disabled: true };
|
|
35129
|
+
const enrolled = await users.totpAccount(user.id);
|
|
35130
|
+
const policy = method === "totp" ? "required" : policyOf(method, methods2);
|
|
35131
|
+
const next = policy === "none" ? null : enrolled ? "totp" : policy === "required" ? "totp-setup" : null;
|
|
35132
|
+
if (next) {
|
|
35133
|
+
clearSession(c6);
|
|
35134
|
+
const known = { ...profile?.name ? { name: profile.name } : {}, ...profile?.picture ? { picture: profile.picture } : {} };
|
|
35135
|
+
await setChallenge(c6, this, { sub: user.id, sv: user.sessionVersion, amr, method, next, ...Object.keys(known).length ? { profile: known } : {} });
|
|
35136
|
+
return { next, method };
|
|
35137
|
+
}
|
|
35138
|
+
const result = await this.startSession(c6, toMadauthUser(user, profile), amr, { sv: user.sessionVersion });
|
|
35139
|
+
await this.emit("user.signed_in", { user: result, method });
|
|
35140
|
+
return { user: result };
|
|
35141
|
+
},
|
|
35142
|
+
currentUser: currentSession,
|
|
34525
35143
|
users,
|
|
34526
35144
|
settings,
|
|
34527
35145
|
webhook: config.webhook ? createWebhookClient(config.webhook, config.webhookFetch) : void 0,
|
|
34528
|
-
|
|
34529
|
-
|
|
35146
|
+
configured,
|
|
35147
|
+
emailVerification,
|
|
35148
|
+
methods(c6) {
|
|
35149
|
+
let reading = methodsOf.get(c6.req.raw);
|
|
35150
|
+
if (!reading) {
|
|
35151
|
+
reading = settings.methods();
|
|
35152
|
+
methodsOf.set(c6.req.raw, reading);
|
|
35153
|
+
}
|
|
35154
|
+
return reading;
|
|
35155
|
+
},
|
|
35156
|
+
async enabled(c6, method) {
|
|
35157
|
+
return isOn(method, configured, await this.methods(c6));
|
|
34530
35158
|
},
|
|
34531
35159
|
async checkSignup(data) {
|
|
34532
35160
|
if (!this.webhook?.wants("signup.before")) return { ok: true };
|
|
@@ -34544,7 +35172,9 @@ function createApp(config) {
|
|
|
34544
35172
|
},
|
|
34545
35173
|
async emit(type, data) {
|
|
34546
35174
|
if (!this.webhook?.wants(type)) return;
|
|
34547
|
-
const
|
|
35175
|
+
const user = data.user;
|
|
35176
|
+
const payload = user && typeof user === "object" && "amr" in user ? { ...data, user: { ...user, amr: void 0 } } : data;
|
|
35177
|
+
const result = await this.webhook.call(type, payload, EVENT_TIMEOUT_MS);
|
|
34548
35178
|
if (!result.ok) console.error(`[madauth] Webhook "${type}" failed: ${result.reason}`);
|
|
34549
35179
|
}
|
|
34550
35180
|
};
|
|
@@ -34572,37 +35202,20 @@ function createApp(config) {
|
|
|
34572
35202
|
return c6.json({ keys: [publicJwk] });
|
|
34573
35203
|
});
|
|
34574
35204
|
app.get("/auth/config", async (c6) => {
|
|
34575
|
-
const
|
|
34576
|
-
const on = (method) => isOn(method,
|
|
35205
|
+
const current = await ctx.methods(c6);
|
|
35206
|
+
const on = (method) => isOn(method, configured, current);
|
|
34577
35207
|
return c6.json({
|
|
34578
|
-
google: on("google") ? { clientId: config.google.clientId, codeFlow: !!config.google.clientSecret } : null,
|
|
34579
|
-
password: on("password") ? { minLength: config.password.minLength } : null
|
|
35208
|
+
google: on("google") ? { clientId: config.google.clientId, codeFlow: !!config.google.clientSecret, secondFactor: policyOf("google", current) } : null,
|
|
35209
|
+
password: on("password") ? { minLength: config.password.minLength, secondFactor: policyOf("password", current) } : null,
|
|
35210
|
+
// The app can be set up when it is in use at all; `signIn` says whether it signs in on its own.
|
|
35211
|
+
totp: authenticatorOn(configured, current) ? { signIn: on("totp"), signUp: on("totp") && emailVerification } : null,
|
|
35212
|
+
email: { verification: emailVerification }
|
|
34580
35213
|
});
|
|
34581
35214
|
});
|
|
35215
|
+
if (emailVerification && ctx.webhook) emailRoutes(app, ctx, users, ctx.webhook);
|
|
34582
35216
|
if (config.google) googleRoutes(app, ctx, config.google);
|
|
34583
35217
|
if (config.password && ctx.webhook) passwordRoutes(app, ctx, config.password, users, ctx.webhook);
|
|
34584
|
-
|
|
34585
|
-
const checked = async (claims) => {
|
|
34586
|
-
const stored = await users.findById(claims.sub);
|
|
34587
|
-
if (!stored || stored.sessionVersion !== claims.sv) return null;
|
|
34588
|
-
const { claims: _, ...user } = userFromClaims(claims);
|
|
34589
|
-
const current = claimsFromJson(stored.claims);
|
|
34590
|
-
return Object.keys(current).length ? { ...user, claims: current } : user;
|
|
34591
|
-
};
|
|
34592
|
-
const currentSession = async (c6) => {
|
|
34593
|
-
const session = await tokenClaims(c6, SESSION_COOKIE, SESSION_TYP);
|
|
34594
|
-
const user = session ? await checked(session) : null;
|
|
34595
|
-
const upToDate = session && user && sameClaims(user.claims, session.claims) && Date.now() / 1e3 - session.iat <= sessionTtlSeconds / 2;
|
|
34596
|
-
if (upToDate) return user;
|
|
34597
|
-
const renewal = await tokenClaims(c6, RENEWAL_COOKIE, RENEWAL_TYP);
|
|
34598
|
-
const sameSignIn = session && user && renewal && renewal.sub === session.sub && renewal.sv === session.sv && renewal.email === session.email;
|
|
34599
|
-
const renewed = sameSignIn ? user : renewal ? await checked(renewal) : null;
|
|
34600
|
-
if (renewal && renewed) {
|
|
34601
|
-
await issueSession(c6, renewed, renewal.amr, { sv: renewal.sv });
|
|
34602
|
-
return renewed;
|
|
34603
|
-
}
|
|
34604
|
-
return user;
|
|
34605
|
-
};
|
|
35218
|
+
totpRoutes(app, ctx, totpKeys);
|
|
34606
35219
|
app.get("/auth/session", async (c6) => {
|
|
34607
35220
|
const user = await currentSession(c6);
|
|
34608
35221
|
if (!user) {
|
|
@@ -34613,11 +35226,23 @@ function createApp(config) {
|
|
|
34613
35226
|
});
|
|
34614
35227
|
app.post("/auth/logout", (c6) => {
|
|
34615
35228
|
clearSession(c6);
|
|
35229
|
+
clearChallenge(c6, secure);
|
|
34616
35230
|
return c6.body(null, 204);
|
|
34617
35231
|
});
|
|
34618
35232
|
app.post("/auth/account/delete", async (c6) => {
|
|
34619
35233
|
const user = await currentSession(c6);
|
|
34620
35234
|
if (!user) return c6.json({ error: "no_session" }, 401);
|
|
35235
|
+
const stored = await users.findById(user.id);
|
|
35236
|
+
const protects = stored && authenticatorOn(configured, await ctx.methods(c6));
|
|
35237
|
+
const authenticator = protects ? await users.totpAccount(user.id) : null;
|
|
35238
|
+
if (stored && authenticator) {
|
|
35239
|
+
const data = await body(c6);
|
|
35240
|
+
if (data.code === void 0 && data.recoveryCode === void 0) {
|
|
35241
|
+
return error(c6, 401, "code_required", "Enter a code from your authenticator app (or a recovery code) to delete your account.");
|
|
35242
|
+
}
|
|
35243
|
+
const via = await checkCode(c6, ctx, totpKeys, stored, authenticator, data);
|
|
35244
|
+
if (via instanceof Response) return via;
|
|
35245
|
+
}
|
|
34621
35246
|
await users.deleteById(user.id);
|
|
34622
35247
|
clearSession(c6);
|
|
34623
35248
|
await ctx.emit("user.deleted", { user });
|
|
@@ -34633,10 +35258,6 @@ function createApp(config) {
|
|
|
34633
35258
|
}
|
|
34634
35259
|
return { email };
|
|
34635
35260
|
};
|
|
34636
|
-
const body = async (c6) => {
|
|
34637
|
-
const data = await c6.req.json().catch(() => null);
|
|
34638
|
-
return data && typeof data === "object" ? data : {};
|
|
34639
|
-
};
|
|
34640
35261
|
const invalidEmail = (c6) => c6.json({ error: "invalid_email", message: "This is not a valid e-mail address." }, 400);
|
|
34641
35262
|
const noUser = (c6) => c6.json({ error: "user_not_found", message: "No user has this e-mail address." }, 404);
|
|
34642
35263
|
app.post("/auth/admin/claims/get", async (c6) => {
|
|
@@ -34670,9 +35291,16 @@ function createApp(config) {
|
|
|
34670
35291
|
await ctx.emit("user.claims_changed", { userId: user.id, email, claims, by: caller.email });
|
|
34671
35292
|
return c6.json({ email, userId: user.id, claims });
|
|
34672
35293
|
});
|
|
34673
|
-
const settingsAnswer = (
|
|
35294
|
+
const settingsAnswer = (methods2) => ({
|
|
34674
35295
|
methods: Object.fromEntries(
|
|
34675
|
-
SIGN_IN_METHODS.map((method) => [
|
|
35296
|
+
SIGN_IN_METHODS.map((method) => [
|
|
35297
|
+
method,
|
|
35298
|
+
{
|
|
35299
|
+
configured: configured(method),
|
|
35300
|
+
enabled: isOn(method, configured, methods2),
|
|
35301
|
+
...method === "totp" ? {} : { secondFactor: policyOf(method, methods2) }
|
|
35302
|
+
}
|
|
35303
|
+
])
|
|
34676
35304
|
)
|
|
34677
35305
|
});
|
|
34678
35306
|
app.post("/auth/admin/settings/get", async (c6) => {
|
|
@@ -34685,14 +35313,26 @@ function createApp(config) {
|
|
|
34685
35313
|
if ("answer" in caller) return caller.answer;
|
|
34686
35314
|
const data = await body(c6);
|
|
34687
35315
|
const invalid2 = (message2) => c6.json({ error: "invalid_settings", message: message2 }, 400);
|
|
34688
|
-
const
|
|
34689
|
-
if (!
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
}
|
|
34694
|
-
|
|
34695
|
-
return
|
|
35316
|
+
const next = parseMethodSettings(data.methods, { booleans: true });
|
|
35317
|
+
if (!next) {
|
|
35318
|
+
return invalid2(
|
|
35319
|
+
`methods must list the sign-in methods that are on (${SIGN_IN_METHODS.join(", ")}), each as true, {} or { "secondFactor": "none" | "optional" | "required" } (google and password), e.g. { "google": {}, "password": { "secondFactor": "optional" } }. A method that is not listed is off.`
|
|
35320
|
+
);
|
|
35321
|
+
}
|
|
35322
|
+
const methods2 = await settings.changeMethods(() => anyOn(signsUp, next) ? next : null, caller.email);
|
|
35323
|
+
if (!methods2) return invalid2("At least one sign-in method that creates users must stay on: Google, e-mail & password, or the authenticator app with sign-up by e-mail.");
|
|
35324
|
+
return c6.json(settingsAnswer(methods2));
|
|
35325
|
+
});
|
|
35326
|
+
app.post("/auth/admin/totp/remove", async (c6) => {
|
|
35327
|
+
const caller = await admin(c6);
|
|
35328
|
+
if ("answer" in caller) return caller.answer;
|
|
35329
|
+
const data = await body(c6);
|
|
35330
|
+
if (!isValidEmail(data.email)) return invalidEmail(c6);
|
|
35331
|
+
const user = await users.findByEmail(normalizeEmail(data.email));
|
|
35332
|
+
if (!user) return noUser(c6);
|
|
35333
|
+
const removed = await users.removeTotp(user.id);
|
|
35334
|
+
if (removed) await ctx.emit("totp.disabled", { user: toMadauthUser(user), by: caller.email });
|
|
35335
|
+
return c6.json({ email: user.emailNormalized, userId: user.id, enabled: false });
|
|
34696
35336
|
});
|
|
34697
35337
|
return app;
|
|
34698
35338
|
}
|