@camstack/core 0.1.33 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/api-key-manager.d.ts +2 -2
- package/dist/auth/api-key-manager.d.ts.map +1 -1
- package/dist/auth/auth-manager.d.ts +70 -3
- package/dist/auth/auth-manager.d.ts.map +1 -1
- package/dist/auth/totp-manager.d.ts +53 -0
- package/dist/auth/totp-manager.d.ts.map +1 -0
- package/dist/auth/user-manager.d.ts +3 -3
- package/dist/auth/user-manager.d.ts.map +1 -1
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts.map +1 -1
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js +29 -9
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js.map +1 -1
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs +29 -9
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs.map +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +14 -0
- package/dist/builtins/local-auth/auth-schema.d.ts.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.d.ts +1 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1014 -22
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1025 -33
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -1
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +14 -0
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts.map +1 -0
- package/dist/builtins/platform-probe/index.d.ts +2 -0
- package/dist/builtins/platform-probe/index.d.ts.map +1 -1
- package/dist/builtins/platform-probe/index.js +198 -5
- package/dist/builtins/platform-probe/index.js.map +1 -1
- package/dist/builtins/platform-probe/index.mjs +198 -6
- package/dist/builtins/platform-probe/index.mjs.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +8 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +27 -21
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +27 -21
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +558 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +558 -94
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, r as __require, t as __commonJSMin } from "../../chunk-hT5z_Zn9.mjs";
|
|
2
|
-
import * as crypto from "node:crypto";
|
|
2
|
+
import * as crypto$1 from "node:crypto";
|
|
3
3
|
import { ApiKeyRecordSchema, BaseAddon, ScopedTokenSchema, UserRecordSchema, authProviderCapability, userManagementCapability } from "@camstack/types";
|
|
4
4
|
//#region ../../node_modules/safe-buffer/index.js
|
|
5
5
|
var require_safe_buffer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -224,14 +224,14 @@ var require_buffer_equal_constant_time = /* @__PURE__ */ __commonJSMin(((exports
|
|
|
224
224
|
//#region ../../node_modules/jwa/index.js
|
|
225
225
|
var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
226
226
|
var Buffer = require_safe_buffer().Buffer;
|
|
227
|
-
var crypto$
|
|
227
|
+
var crypto$2 = __require("crypto");
|
|
228
228
|
var formatEcdsa = require_ecdsa_sig_formatter();
|
|
229
229
|
var util$2 = __require("util");
|
|
230
230
|
var MSG_INVALID_ALGORITHM = "\"%s\" is not a valid algorithm.\n Supported algorithms are:\n \"HS256\", \"HS384\", \"HS512\", \"RS256\", \"RS384\", \"RS512\", \"PS256\", \"PS384\", \"PS512\", \"ES256\", \"ES384\", \"ES512\" and \"none\".";
|
|
231
231
|
var MSG_INVALID_SECRET = "secret must be a string or buffer";
|
|
232
232
|
var MSG_INVALID_VERIFIER_KEY = "key must be a string or a buffer";
|
|
233
233
|
var MSG_INVALID_SIGNER_KEY = "key must be a string, a buffer or an object";
|
|
234
|
-
var supportsKeyObjects = typeof crypto$
|
|
234
|
+
var supportsKeyObjects = typeof crypto$2.createPublicKey === "function";
|
|
235
235
|
if (supportsKeyObjects) {
|
|
236
236
|
MSG_INVALID_VERIFIER_KEY += " or a KeyObject";
|
|
237
237
|
MSG_INVALID_SECRET += "or a KeyObject";
|
|
@@ -284,14 +284,14 @@ var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
284
284
|
return function sign(thing, secret) {
|
|
285
285
|
checkIsSecretKey(secret);
|
|
286
286
|
thing = normalizeInput(thing);
|
|
287
|
-
var hmac = crypto$
|
|
287
|
+
var hmac = crypto$2.createHmac("sha" + bits, secret);
|
|
288
288
|
return fromBase64((hmac.update(thing), hmac.digest("base64")));
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
291
|
var bufferEqual;
|
|
292
|
-
var timingSafeEqual = "timingSafeEqual" in crypto$
|
|
292
|
+
var timingSafeEqual = "timingSafeEqual" in crypto$2 ? function timingSafeEqual(a, b) {
|
|
293
293
|
if (a.byteLength !== b.byteLength) return false;
|
|
294
|
-
return crypto$
|
|
294
|
+
return crypto$2.timingSafeEqual(a, b);
|
|
295
295
|
} : function timingSafeEqual(a, b) {
|
|
296
296
|
if (!bufferEqual) bufferEqual = require_buffer_equal_constant_time();
|
|
297
297
|
return bufferEqual(a, b);
|
|
@@ -306,7 +306,7 @@ var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
306
306
|
return function sign(thing, privateKey) {
|
|
307
307
|
checkIsPrivateKey(privateKey);
|
|
308
308
|
thing = normalizeInput(thing);
|
|
309
|
-
var signer = crypto$
|
|
309
|
+
var signer = crypto$2.createSign("RSA-SHA" + bits);
|
|
310
310
|
return fromBase64((signer.update(thing), signer.sign(privateKey, "base64")));
|
|
311
311
|
};
|
|
312
312
|
}
|
|
@@ -315,7 +315,7 @@ var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
315
315
|
checkIsPublicKey(publicKey);
|
|
316
316
|
thing = normalizeInput(thing);
|
|
317
317
|
signature = toBase64(signature);
|
|
318
|
-
var verifier = crypto$
|
|
318
|
+
var verifier = crypto$2.createVerify("RSA-SHA" + bits);
|
|
319
319
|
verifier.update(thing);
|
|
320
320
|
return verifier.verify(publicKey, signature, "base64");
|
|
321
321
|
};
|
|
@@ -324,11 +324,11 @@ var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
324
324
|
return function sign(thing, privateKey) {
|
|
325
325
|
checkIsPrivateKey(privateKey);
|
|
326
326
|
thing = normalizeInput(thing);
|
|
327
|
-
var signer = crypto$
|
|
327
|
+
var signer = crypto$2.createSign("RSA-SHA" + bits);
|
|
328
328
|
return fromBase64((signer.update(thing), signer.sign({
|
|
329
329
|
key: privateKey,
|
|
330
|
-
padding: crypto$
|
|
331
|
-
saltLength: crypto$
|
|
330
|
+
padding: crypto$2.constants.RSA_PKCS1_PSS_PADDING,
|
|
331
|
+
saltLength: crypto$2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
332
332
|
}, "base64")));
|
|
333
333
|
};
|
|
334
334
|
}
|
|
@@ -337,12 +337,12 @@ var require_jwa = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
337
337
|
checkIsPublicKey(publicKey);
|
|
338
338
|
thing = normalizeInput(thing);
|
|
339
339
|
signature = toBase64(signature);
|
|
340
|
-
var verifier = crypto$
|
|
340
|
+
var verifier = crypto$2.createVerify("RSA-SHA" + bits);
|
|
341
341
|
verifier.update(thing);
|
|
342
342
|
return verifier.verify({
|
|
343
343
|
key: publicKey,
|
|
344
|
-
padding: crypto$
|
|
345
|
-
saltLength: crypto$
|
|
344
|
+
padding: crypto$2.constants.RSA_PKCS1_PSS_PADDING,
|
|
345
|
+
saltLength: crypto$2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
346
346
|
}, signature, "base64");
|
|
347
347
|
};
|
|
348
348
|
}
|
|
@@ -6168,7 +6168,7 @@ var AuthManager = class {
|
|
|
6168
6168
|
const configured = this.config.get("auth.jwtSecret");
|
|
6169
6169
|
if (configured) this.jwtSecret = configured;
|
|
6170
6170
|
else {
|
|
6171
|
-
const secret = crypto.randomBytes(32).toString("hex");
|
|
6171
|
+
const secret = crypto$1.randomBytes(32).toString("hex");
|
|
6172
6172
|
this.config.update("auth", { jwtSecret: secret });
|
|
6173
6173
|
this.logger.info("Generated JWT secret and saved to config.yaml (auth.jwtSecret)");
|
|
6174
6174
|
this.jwtSecret = secret;
|
|
@@ -6187,15 +6187,15 @@ var AuthManager = class {
|
|
|
6187
6187
|
return import_bcryptjs.compare(password, hash);
|
|
6188
6188
|
}
|
|
6189
6189
|
generateApiKey() {
|
|
6190
|
-
const token = crypto.randomBytes(32).toString("hex");
|
|
6190
|
+
const token = crypto$1.randomBytes(32).toString("hex");
|
|
6191
6191
|
return {
|
|
6192
6192
|
token,
|
|
6193
|
-
hash: crypto.createHash("sha256").update(token).digest("hex"),
|
|
6193
|
+
hash: crypto$1.createHash("sha256").update(token).digest("hex"),
|
|
6194
6194
|
prefix: token.slice(0, 8)
|
|
6195
6195
|
};
|
|
6196
6196
|
}
|
|
6197
6197
|
validateApiKey(token, storedHash) {
|
|
6198
|
-
return crypto.createHash("sha256").update(token).digest("hex") === storedHash;
|
|
6198
|
+
return crypto$1.createHash("sha256").update(token).digest("hex") === storedHash;
|
|
6199
6199
|
}
|
|
6200
6200
|
/**
|
|
6201
6201
|
* Create a service token for agent/worker authentication.
|
|
@@ -6205,7 +6205,7 @@ var AuthManager = class {
|
|
|
6205
6205
|
const payload = {
|
|
6206
6206
|
userId: opts.agentId,
|
|
6207
6207
|
username: opts.agentId,
|
|
6208
|
-
|
|
6208
|
+
isAdmin: true,
|
|
6209
6209
|
type: "service",
|
|
6210
6210
|
agentId: opts.agentId,
|
|
6211
6211
|
allowedProviders: "*",
|
|
@@ -6214,6 +6214,112 @@ var AuthManager = class {
|
|
|
6214
6214
|
const expiresIn = opts.expiresIn ?? "24h";
|
|
6215
6215
|
return import_jsonwebtoken.sign(payload, this.jwtSecret, { expiresIn });
|
|
6216
6216
|
}
|
|
6217
|
+
/**
|
|
6218
|
+
* Mint a short-lived HMAC-signed bridge token used by SSO-style auth
|
|
6219
|
+
* providers (OIDC, SAML, magic-link, …) to hand the post-callback
|
|
6220
|
+
* claims to `/api/auth/sso/finish` without trusting unsigned query
|
|
6221
|
+
* parameters. The endpoint verifies the token signature with the same
|
|
6222
|
+
* `jwtSecret` and only then mints the user session JWT. This closes
|
|
6223
|
+
* the privilege-escalation gap where any client could craft a
|
|
6224
|
+
* `?isAdmin=1` link to `/sso/finish` and become admin.
|
|
6225
|
+
*
|
|
6226
|
+
* The payload carries `kind: 'sso-bridge'` so `verifySsoBridgeToken`
|
|
6227
|
+
* can reject session tokens reused as bridge tokens (and vice versa).
|
|
6228
|
+
* TTL defaults to 5 minutes — long enough to survive the IdP
|
|
6229
|
+
* redirect bounce, short enough that a leaked URL stops being useful
|
|
6230
|
+
* before the operator notices.
|
|
6231
|
+
*/
|
|
6232
|
+
signSsoBridgeToken(payload, ttlSec = 300) {
|
|
6233
|
+
const body = {
|
|
6234
|
+
kind: "sso-bridge",
|
|
6235
|
+
userId: payload.userId,
|
|
6236
|
+
username: payload.username,
|
|
6237
|
+
isAdmin: payload.isAdmin,
|
|
6238
|
+
provider: payload.provider,
|
|
6239
|
+
...payload.email !== void 0 ? { email: payload.email } : {},
|
|
6240
|
+
...payload.displayName !== void 0 ? { displayName: payload.displayName } : {},
|
|
6241
|
+
...payload.hubUrl !== void 0 ? { hubUrl: payload.hubUrl } : {}
|
|
6242
|
+
};
|
|
6243
|
+
return import_jsonwebtoken.sign(body, this.jwtSecret, { expiresIn: ttlSec });
|
|
6244
|
+
}
|
|
6245
|
+
/**
|
|
6246
|
+
* Verify + decode a bridge token minted by `signSsoBridgeToken`.
|
|
6247
|
+
* Returns `null` for invalid signature, expired token, or wrong
|
|
6248
|
+
* `kind` claim. Never throws — the consumer ( `/sso/finish`) treats
|
|
6249
|
+
* `null` as 400 Bad Request.
|
|
6250
|
+
*/
|
|
6251
|
+
verifySsoBridgeToken(token) {
|
|
6252
|
+
try {
|
|
6253
|
+
const decoded = import_jsonwebtoken.verify(token, this.jwtSecret);
|
|
6254
|
+
if (decoded["kind"] !== "sso-bridge") return null;
|
|
6255
|
+
const userId = decoded["userId"];
|
|
6256
|
+
const username = decoded["username"];
|
|
6257
|
+
const isAdmin = decoded["isAdmin"];
|
|
6258
|
+
const provider = decoded["provider"];
|
|
6259
|
+
if (typeof userId !== "string" || typeof username !== "string" || typeof isAdmin !== "boolean" || typeof provider !== "string") return null;
|
|
6260
|
+
const email = typeof decoded["email"] === "string" ? decoded["email"] : void 0;
|
|
6261
|
+
const displayName = typeof decoded["displayName"] === "string" ? decoded["displayName"] : void 0;
|
|
6262
|
+
const hubUrl = typeof decoded["hubUrl"] === "string" ? decoded["hubUrl"] : void 0;
|
|
6263
|
+
return {
|
|
6264
|
+
userId,
|
|
6265
|
+
username,
|
|
6266
|
+
isAdmin,
|
|
6267
|
+
provider,
|
|
6268
|
+
...email !== void 0 ? { email } : {},
|
|
6269
|
+
...displayName !== void 0 ? { displayName } : {},
|
|
6270
|
+
...hubUrl !== void 0 ? { hubUrl } : {}
|
|
6271
|
+
};
|
|
6272
|
+
} catch {
|
|
6273
|
+
return null;
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
/**
|
|
6277
|
+
* Mint a TOTP challenge token bridging the two-step login flow:
|
|
6278
|
+
*
|
|
6279
|
+
* 1. POST /login → password OK + user has 2FA → returns
|
|
6280
|
+
* `{requiresTotp: true, challengeToken: '...'}` (this token).
|
|
6281
|
+
* 2. POST /login/totp-verify → operator types 6-digit code →
|
|
6282
|
+
* server verifies challenge token + code → mints the real
|
|
6283
|
+
* session JWT.
|
|
6284
|
+
*
|
|
6285
|
+
* The token carries `kind: 'totp-challenge'` so it can't be confused
|
|
6286
|
+
* with a regular session token, plus the `userId` + `username` +
|
|
6287
|
+
* `isAdmin` claims that will end up in the final session if the code
|
|
6288
|
+
* verifies. TTL defaults to 5 minutes — long enough to type the code,
|
|
6289
|
+
* short enough that an abandoned login can't be picked up later.
|
|
6290
|
+
*/
|
|
6291
|
+
signTotpChallengeToken(payload, ttlSec = 300) {
|
|
6292
|
+
return import_jsonwebtoken.sign({
|
|
6293
|
+
kind: "totp-challenge",
|
|
6294
|
+
userId: payload.userId,
|
|
6295
|
+
username: payload.username,
|
|
6296
|
+
isAdmin: payload.isAdmin
|
|
6297
|
+
}, this.jwtSecret, { expiresIn: ttlSec });
|
|
6298
|
+
}
|
|
6299
|
+
/**
|
|
6300
|
+
* Verify + decode a TOTP challenge token. Returns `null` for any
|
|
6301
|
+
* failure (invalid signature, expired, wrong `kind`, missing
|
|
6302
|
+
* claims). The caller MUST also verify the 6-digit code against
|
|
6303
|
+
* `totpManager.verify(userId, code)` before minting the real
|
|
6304
|
+
* session — this method validates the bridge transport only.
|
|
6305
|
+
*/
|
|
6306
|
+
verifyTotpChallengeToken(token) {
|
|
6307
|
+
try {
|
|
6308
|
+
const decoded = import_jsonwebtoken.verify(token, this.jwtSecret);
|
|
6309
|
+
if (decoded["kind"] !== "totp-challenge") return null;
|
|
6310
|
+
const userId = decoded["userId"];
|
|
6311
|
+
const username = decoded["username"];
|
|
6312
|
+
const isAdmin = decoded["isAdmin"];
|
|
6313
|
+
if (typeof userId !== "string" || typeof username !== "string" || typeof isAdmin !== "boolean") return null;
|
|
6314
|
+
return {
|
|
6315
|
+
userId,
|
|
6316
|
+
username,
|
|
6317
|
+
isAdmin
|
|
6318
|
+
};
|
|
6319
|
+
} catch {
|
|
6320
|
+
return null;
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6217
6323
|
};
|
|
6218
6324
|
//#endregion
|
|
6219
6325
|
//#region src/auth/parse-record.ts
|
|
@@ -6247,10 +6353,10 @@ var UserManager = class {
|
|
|
6247
6353
|
const passwordHash = await this.auth.hashPassword(input.password);
|
|
6248
6354
|
const now = Date.now();
|
|
6249
6355
|
const record = {
|
|
6250
|
-
id: crypto.randomUUID(),
|
|
6356
|
+
id: crypto$1.randomUUID(),
|
|
6251
6357
|
username: input.username,
|
|
6252
6358
|
passwordHash,
|
|
6253
|
-
|
|
6359
|
+
isAdmin: input.isAdmin ?? false,
|
|
6254
6360
|
allowedProviders: input.allowedProviders ?? "*",
|
|
6255
6361
|
allowedDevices: input.allowedDevices ?? {},
|
|
6256
6362
|
scopes: input.scopes ?? [],
|
|
@@ -6338,7 +6444,7 @@ var UserManager = class {
|
|
|
6338
6444
|
await this.create({
|
|
6339
6445
|
username: adminUsername,
|
|
6340
6446
|
password: adminPassword,
|
|
6341
|
-
|
|
6447
|
+
isAdmin: true,
|
|
6342
6448
|
allowedProviders: "*",
|
|
6343
6449
|
allowedDevices: {}
|
|
6344
6450
|
});
|
|
@@ -6362,9 +6468,9 @@ var ApiKeyManager = class {
|
|
|
6362
6468
|
const { token: rawToken, hash, prefix } = this.auth.generateApiKey();
|
|
6363
6469
|
const now = Date.now();
|
|
6364
6470
|
const record = {
|
|
6365
|
-
id: crypto.randomUUID(),
|
|
6471
|
+
id: crypto$1.randomUUID(),
|
|
6366
6472
|
label: input.label,
|
|
6367
|
-
|
|
6473
|
+
isAdmin: input.isAdmin ?? false,
|
|
6368
6474
|
allowedProviders: input.allowedProviders ?? "*",
|
|
6369
6475
|
allowedDevices: input.allowedDevices ?? {},
|
|
6370
6476
|
tokenHash: hash,
|
|
@@ -6438,11 +6544,11 @@ var ScopedTokenManager = class {
|
|
|
6438
6544
|
this.store = store;
|
|
6439
6545
|
}
|
|
6440
6546
|
async create(userId, name, scopes, expiresAt) {
|
|
6441
|
-
const rawToken = `${TOKEN_PREFIX}${crypto.randomBytes(32).toString("hex")}`;
|
|
6442
|
-
const tokenHash = crypto.createHash("sha256").update(rawToken).digest("hex");
|
|
6547
|
+
const rawToken = `${TOKEN_PREFIX}${crypto$1.randomBytes(32).toString("hex")}`;
|
|
6548
|
+
const tokenHash = crypto$1.createHash("sha256").update(rawToken).digest("hex");
|
|
6443
6549
|
const tokenPrefix = rawToken.slice(0, 12);
|
|
6444
6550
|
const record = {
|
|
6445
|
-
id: crypto.randomUUID(),
|
|
6551
|
+
id: crypto$1.randomUUID(),
|
|
6446
6552
|
userId,
|
|
6447
6553
|
name,
|
|
6448
6554
|
tokenHash,
|
|
@@ -6466,7 +6572,7 @@ var ScopedTokenManager = class {
|
|
|
6466
6572
|
}
|
|
6467
6573
|
async validate(rawToken) {
|
|
6468
6574
|
if (!rawToken.startsWith(TOKEN_PREFIX)) return null;
|
|
6469
|
-
const tokenHash = crypto.createHash("sha256").update(rawToken).digest("hex");
|
|
6575
|
+
const tokenHash = crypto$1.createHash("sha256").update(rawToken).digest("hex");
|
|
6470
6576
|
const results = await this.store.query.query({
|
|
6471
6577
|
collection: TOKENS_COLLECTION,
|
|
6472
6578
|
filter: { where: { tokenHash } }
|
|
@@ -6558,10 +6664,841 @@ var ScopedTokenManager = class {
|
|
|
6558
6664
|
}
|
|
6559
6665
|
};
|
|
6560
6666
|
//#endregion
|
|
6667
|
+
//#region ../../node_modules/@otplib/plugin-crypto/index.js
|
|
6668
|
+
/**
|
|
6669
|
+
* @otplib/plugin-crypto
|
|
6670
|
+
*
|
|
6671
|
+
* @author Gerald Yeo <contact@fusedthought.com>
|
|
6672
|
+
* @version: 12.0.1
|
|
6673
|
+
* @license: MIT
|
|
6674
|
+
**/
|
|
6675
|
+
var require_plugin_crypto = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6676
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6677
|
+
function _interopDefault(ex) {
|
|
6678
|
+
return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex;
|
|
6679
|
+
}
|
|
6680
|
+
var crypto = _interopDefault(__require("crypto"));
|
|
6681
|
+
var createDigest = (algorithm, hmacKey, counter) => {
|
|
6682
|
+
return crypto.createHmac(algorithm, Buffer.from(hmacKey, "hex")).update(Buffer.from(counter, "hex")).digest().toString("hex");
|
|
6683
|
+
};
|
|
6684
|
+
var createRandomBytes = (size, encoding) => {
|
|
6685
|
+
return crypto.randomBytes(size).toString(encoding);
|
|
6686
|
+
};
|
|
6687
|
+
exports.createDigest = createDigest;
|
|
6688
|
+
exports.createRandomBytes = createRandomBytes;
|
|
6689
|
+
}));
|
|
6690
|
+
//#endregion
|
|
6691
|
+
//#region ../../node_modules/thirty-two/lib/thirty-two/thirty-two.js
|
|
6692
|
+
var require_thirty_two$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6693
|
+
var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
6694
|
+
var byteTable = [
|
|
6695
|
+
255,
|
|
6696
|
+
255,
|
|
6697
|
+
26,
|
|
6698
|
+
27,
|
|
6699
|
+
28,
|
|
6700
|
+
29,
|
|
6701
|
+
30,
|
|
6702
|
+
31,
|
|
6703
|
+
255,
|
|
6704
|
+
255,
|
|
6705
|
+
255,
|
|
6706
|
+
255,
|
|
6707
|
+
255,
|
|
6708
|
+
255,
|
|
6709
|
+
255,
|
|
6710
|
+
255,
|
|
6711
|
+
255,
|
|
6712
|
+
0,
|
|
6713
|
+
1,
|
|
6714
|
+
2,
|
|
6715
|
+
3,
|
|
6716
|
+
4,
|
|
6717
|
+
5,
|
|
6718
|
+
6,
|
|
6719
|
+
7,
|
|
6720
|
+
8,
|
|
6721
|
+
9,
|
|
6722
|
+
10,
|
|
6723
|
+
11,
|
|
6724
|
+
12,
|
|
6725
|
+
13,
|
|
6726
|
+
14,
|
|
6727
|
+
15,
|
|
6728
|
+
16,
|
|
6729
|
+
17,
|
|
6730
|
+
18,
|
|
6731
|
+
19,
|
|
6732
|
+
20,
|
|
6733
|
+
21,
|
|
6734
|
+
22,
|
|
6735
|
+
23,
|
|
6736
|
+
24,
|
|
6737
|
+
25,
|
|
6738
|
+
255,
|
|
6739
|
+
255,
|
|
6740
|
+
255,
|
|
6741
|
+
255,
|
|
6742
|
+
255,
|
|
6743
|
+
255,
|
|
6744
|
+
0,
|
|
6745
|
+
1,
|
|
6746
|
+
2,
|
|
6747
|
+
3,
|
|
6748
|
+
4,
|
|
6749
|
+
5,
|
|
6750
|
+
6,
|
|
6751
|
+
7,
|
|
6752
|
+
8,
|
|
6753
|
+
9,
|
|
6754
|
+
10,
|
|
6755
|
+
11,
|
|
6756
|
+
12,
|
|
6757
|
+
13,
|
|
6758
|
+
14,
|
|
6759
|
+
15,
|
|
6760
|
+
16,
|
|
6761
|
+
17,
|
|
6762
|
+
18,
|
|
6763
|
+
19,
|
|
6764
|
+
20,
|
|
6765
|
+
21,
|
|
6766
|
+
22,
|
|
6767
|
+
23,
|
|
6768
|
+
24,
|
|
6769
|
+
25,
|
|
6770
|
+
255,
|
|
6771
|
+
255,
|
|
6772
|
+
255,
|
|
6773
|
+
255,
|
|
6774
|
+
255
|
|
6775
|
+
];
|
|
6776
|
+
function quintetCount(buff) {
|
|
6777
|
+
var quintets = Math.floor(buff.length / 5);
|
|
6778
|
+
return buff.length % 5 === 0 ? quintets : quintets + 1;
|
|
6779
|
+
}
|
|
6780
|
+
exports.encode = function(plain) {
|
|
6781
|
+
if (!Buffer.isBuffer(plain)) plain = new Buffer(plain);
|
|
6782
|
+
var i = 0;
|
|
6783
|
+
var j = 0;
|
|
6784
|
+
var shiftIndex = 0;
|
|
6785
|
+
var digit = 0;
|
|
6786
|
+
var encoded = new Buffer(quintetCount(plain) * 8);
|
|
6787
|
+
while (i < plain.length) {
|
|
6788
|
+
var current = plain[i];
|
|
6789
|
+
if (shiftIndex > 3) {
|
|
6790
|
+
digit = current & 255 >> shiftIndex;
|
|
6791
|
+
shiftIndex = (shiftIndex + 5) % 8;
|
|
6792
|
+
digit = digit << shiftIndex | (i + 1 < plain.length ? plain[i + 1] : 0) >> 8 - shiftIndex;
|
|
6793
|
+
i++;
|
|
6794
|
+
} else {
|
|
6795
|
+
digit = current >> 8 - (shiftIndex + 5) & 31;
|
|
6796
|
+
shiftIndex = (shiftIndex + 5) % 8;
|
|
6797
|
+
if (shiftIndex === 0) i++;
|
|
6798
|
+
}
|
|
6799
|
+
encoded[j] = charTable.charCodeAt(digit);
|
|
6800
|
+
j++;
|
|
6801
|
+
}
|
|
6802
|
+
for (i = j; i < encoded.length; i++) encoded[i] = 61;
|
|
6803
|
+
return encoded;
|
|
6804
|
+
};
|
|
6805
|
+
exports.decode = function(encoded) {
|
|
6806
|
+
var shiftIndex = 0;
|
|
6807
|
+
var plainDigit = 0;
|
|
6808
|
+
var plainChar;
|
|
6809
|
+
var plainPos = 0;
|
|
6810
|
+
if (!Buffer.isBuffer(encoded)) encoded = new Buffer(encoded);
|
|
6811
|
+
var decoded = new Buffer(Math.ceil(encoded.length * 5 / 8));
|
|
6812
|
+
for (var i = 0; i < encoded.length; i++) {
|
|
6813
|
+
if (encoded[i] === 61) break;
|
|
6814
|
+
var encodedByte = encoded[i] - 48;
|
|
6815
|
+
if (encodedByte < byteTable.length) {
|
|
6816
|
+
plainDigit = byteTable[encodedByte];
|
|
6817
|
+
if (shiftIndex <= 3) {
|
|
6818
|
+
shiftIndex = (shiftIndex + 5) % 8;
|
|
6819
|
+
if (shiftIndex === 0) {
|
|
6820
|
+
plainChar |= plainDigit;
|
|
6821
|
+
decoded[plainPos] = plainChar;
|
|
6822
|
+
plainPos++;
|
|
6823
|
+
plainChar = 0;
|
|
6824
|
+
} else plainChar |= 255 & plainDigit << 8 - shiftIndex;
|
|
6825
|
+
} else {
|
|
6826
|
+
shiftIndex = (shiftIndex + 5) % 8;
|
|
6827
|
+
plainChar |= 255 & plainDigit >>> shiftIndex;
|
|
6828
|
+
decoded[plainPos] = plainChar;
|
|
6829
|
+
plainPos++;
|
|
6830
|
+
plainChar = 255 & plainDigit << 8 - shiftIndex;
|
|
6831
|
+
}
|
|
6832
|
+
} else throw new Error("Invalid input - it is not base32 encoded string");
|
|
6833
|
+
}
|
|
6834
|
+
return decoded.slice(0, plainPos);
|
|
6835
|
+
};
|
|
6836
|
+
}));
|
|
6837
|
+
//#endregion
|
|
6838
|
+
//#region ../../node_modules/thirty-two/lib/thirty-two/index.js
|
|
6839
|
+
var require_thirty_two = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6840
|
+
var base32 = require_thirty_two$1();
|
|
6841
|
+
exports.encode = base32.encode;
|
|
6842
|
+
exports.decode = base32.decode;
|
|
6843
|
+
}));
|
|
6844
|
+
//#endregion
|
|
6845
|
+
//#region ../../node_modules/@otplib/plugin-thirty-two/index.js
|
|
6846
|
+
/**
|
|
6847
|
+
* @otplib/plugin-thirty-two
|
|
6848
|
+
*
|
|
6849
|
+
* @author Gerald Yeo <contact@fusedthought.com>
|
|
6850
|
+
* @version: 12.0.1
|
|
6851
|
+
* @license: MIT
|
|
6852
|
+
**/
|
|
6853
|
+
var require_plugin_thirty_two = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6854
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6855
|
+
function _interopDefault(ex) {
|
|
6856
|
+
return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex;
|
|
6857
|
+
}
|
|
6858
|
+
var thirtyTwo = _interopDefault(require_thirty_two());
|
|
6859
|
+
var keyDecoder = (encodedSecret, encoding) => {
|
|
6860
|
+
return thirtyTwo.decode(encodedSecret).toString(encoding);
|
|
6861
|
+
};
|
|
6862
|
+
var keyEncoder = (secret, encoding) => {
|
|
6863
|
+
return thirtyTwo.encode(Buffer.from(secret, encoding).toString("ascii")).toString().replace(/=/g, "");
|
|
6864
|
+
};
|
|
6865
|
+
exports.keyDecoder = keyDecoder;
|
|
6866
|
+
exports.keyEncoder = keyEncoder;
|
|
6867
|
+
}));
|
|
6868
|
+
//#endregion
|
|
6869
|
+
//#region ../../node_modules/@otplib/core/index.js
|
|
6870
|
+
/**
|
|
6871
|
+
* @otplib/core
|
|
6872
|
+
*
|
|
6873
|
+
* @author Gerald Yeo <contact@fusedthought.com>
|
|
6874
|
+
* @version: 12.0.1
|
|
6875
|
+
* @license: MIT
|
|
6876
|
+
**/
|
|
6877
|
+
var require_core = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6878
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6879
|
+
function objectValues(value) {
|
|
6880
|
+
return Object.keys(value).map((key) => value[key]);
|
|
6881
|
+
}
|
|
6882
|
+
(function(HashAlgorithms) {
|
|
6883
|
+
HashAlgorithms["SHA1"] = "sha1";
|
|
6884
|
+
HashAlgorithms["SHA256"] = "sha256";
|
|
6885
|
+
HashAlgorithms["SHA512"] = "sha512";
|
|
6886
|
+
})(exports.HashAlgorithms || (exports.HashAlgorithms = {}));
|
|
6887
|
+
var HASH_ALGORITHMS = objectValues(exports.HashAlgorithms);
|
|
6888
|
+
(function(KeyEncodings) {
|
|
6889
|
+
KeyEncodings["ASCII"] = "ascii";
|
|
6890
|
+
KeyEncodings["BASE64"] = "base64";
|
|
6891
|
+
KeyEncodings["HEX"] = "hex";
|
|
6892
|
+
KeyEncodings["LATIN1"] = "latin1";
|
|
6893
|
+
KeyEncodings["UTF8"] = "utf8";
|
|
6894
|
+
})(exports.KeyEncodings || (exports.KeyEncodings = {}));
|
|
6895
|
+
var KEY_ENCODINGS = objectValues(exports.KeyEncodings);
|
|
6896
|
+
(function(Strategy) {
|
|
6897
|
+
Strategy["HOTP"] = "hotp";
|
|
6898
|
+
Strategy["TOTP"] = "totp";
|
|
6899
|
+
})(exports.Strategy || (exports.Strategy = {}));
|
|
6900
|
+
var STRATEGY = objectValues(exports.Strategy);
|
|
6901
|
+
var createDigestPlaceholder = () => {
|
|
6902
|
+
throw new Error("Please provide an options.createDigest implementation.");
|
|
6903
|
+
};
|
|
6904
|
+
function isTokenValid(value) {
|
|
6905
|
+
return /^(\d+)$/.test(value);
|
|
6906
|
+
}
|
|
6907
|
+
function padStart(value, maxLength, fillString) {
|
|
6908
|
+
if (value.length >= maxLength) return value;
|
|
6909
|
+
return `${Array(maxLength + 1).join(fillString)}${value}`.slice(-1 * maxLength);
|
|
6910
|
+
}
|
|
6911
|
+
function keyuri(options) {
|
|
6912
|
+
const tmpl = `otpauth://${options.type}/{labelPrefix}:{accountName}?secret={secret}{query}`;
|
|
6913
|
+
const params = [];
|
|
6914
|
+
if (STRATEGY.indexOf(options.type) < 0) throw new Error(`Expecting options.type to be one of ${STRATEGY.join(", ")}. Received ${options.type}.`);
|
|
6915
|
+
if (options.type === "hotp") {
|
|
6916
|
+
if (options.counter == null || typeof options.counter !== "number") throw new Error("Expecting options.counter to be a number when options.type is \"hotp\".");
|
|
6917
|
+
params.push(`&counter=${options.counter}`);
|
|
6918
|
+
}
|
|
6919
|
+
if (options.type === "totp" && options.step) params.push(`&period=${options.step}`);
|
|
6920
|
+
if (options.digits) params.push(`&digits=${options.digits}`);
|
|
6921
|
+
if (options.algorithm) params.push(`&algorithm=${options.algorithm.toUpperCase()}`);
|
|
6922
|
+
if (options.issuer) params.push(`&issuer=${encodeURIComponent(options.issuer)}`);
|
|
6923
|
+
return tmpl.replace("{labelPrefix}", encodeURIComponent(options.issuer || options.accountName)).replace("{accountName}", encodeURIComponent(options.accountName)).replace("{secret}", options.secret).replace("{query}", params.join(""));
|
|
6924
|
+
}
|
|
6925
|
+
var OTP = class OTP {
|
|
6926
|
+
constructor(defaultOptions = {}) {
|
|
6927
|
+
this._defaultOptions = Object.freeze({ ...defaultOptions });
|
|
6928
|
+
this._options = Object.freeze({});
|
|
6929
|
+
}
|
|
6930
|
+
create(defaultOptions = {}) {
|
|
6931
|
+
return new OTP(defaultOptions);
|
|
6932
|
+
}
|
|
6933
|
+
clone(defaultOptions = {}) {
|
|
6934
|
+
const instance = this.create({
|
|
6935
|
+
...this._defaultOptions,
|
|
6936
|
+
...defaultOptions
|
|
6937
|
+
});
|
|
6938
|
+
instance.options = this._options;
|
|
6939
|
+
return instance;
|
|
6940
|
+
}
|
|
6941
|
+
get options() {
|
|
6942
|
+
return Object.freeze({
|
|
6943
|
+
...this._defaultOptions,
|
|
6944
|
+
...this._options
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6947
|
+
set options(options) {
|
|
6948
|
+
this._options = Object.freeze({
|
|
6949
|
+
...this._options,
|
|
6950
|
+
...options
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
allOptions() {
|
|
6954
|
+
return this.options;
|
|
6955
|
+
}
|
|
6956
|
+
resetOptions() {
|
|
6957
|
+
this._options = Object.freeze({});
|
|
6958
|
+
}
|
|
6959
|
+
};
|
|
6960
|
+
function hotpOptionsValidator(options) {
|
|
6961
|
+
if (typeof options.createDigest !== "function") throw new Error("Expecting options.createDigest to be a function.");
|
|
6962
|
+
if (typeof options.createHmacKey !== "function") throw new Error("Expecting options.createHmacKey to be a function.");
|
|
6963
|
+
if (typeof options.digits !== "number") throw new Error("Expecting options.digits to be a number.");
|
|
6964
|
+
if (!options.algorithm || HASH_ALGORITHMS.indexOf(options.algorithm) < 0) throw new Error(`Expecting options.algorithm to be one of ${HASH_ALGORITHMS.join(", ")}. Received ${options.algorithm}.`);
|
|
6965
|
+
if (!options.encoding || KEY_ENCODINGS.indexOf(options.encoding) < 0) throw new Error(`Expecting options.encoding to be one of ${KEY_ENCODINGS.join(", ")}. Received ${options.encoding}.`);
|
|
6966
|
+
}
|
|
6967
|
+
var hotpCreateHmacKey = (algorithm, secret, encoding) => {
|
|
6968
|
+
return Buffer.from(secret, encoding).toString("hex");
|
|
6969
|
+
};
|
|
6970
|
+
function hotpDefaultOptions() {
|
|
6971
|
+
return {
|
|
6972
|
+
algorithm: exports.HashAlgorithms.SHA1,
|
|
6973
|
+
createHmacKey: hotpCreateHmacKey,
|
|
6974
|
+
createDigest: createDigestPlaceholder,
|
|
6975
|
+
digits: 6,
|
|
6976
|
+
encoding: exports.KeyEncodings.ASCII
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
function hotpOptions(opt) {
|
|
6980
|
+
const options = {
|
|
6981
|
+
...hotpDefaultOptions(),
|
|
6982
|
+
...opt
|
|
6983
|
+
};
|
|
6984
|
+
hotpOptionsValidator(options);
|
|
6985
|
+
return Object.freeze(options);
|
|
6986
|
+
}
|
|
6987
|
+
function hotpCounter(counter) {
|
|
6988
|
+
return padStart(counter.toString(16), 16, "0");
|
|
6989
|
+
}
|
|
6990
|
+
function hotpDigestToToken(hexDigest, digits) {
|
|
6991
|
+
const digest = Buffer.from(hexDigest, "hex");
|
|
6992
|
+
const offset = digest[digest.length - 1] & 15;
|
|
6993
|
+
const token = ((digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255) % Math.pow(10, digits);
|
|
6994
|
+
return padStart(String(token), digits, "0");
|
|
6995
|
+
}
|
|
6996
|
+
function hotpDigest(secret, counter, options) {
|
|
6997
|
+
const hexCounter = hotpCounter(counter);
|
|
6998
|
+
const hmacKey = options.createHmacKey(options.algorithm, secret, options.encoding);
|
|
6999
|
+
return options.createDigest(options.algorithm, hmacKey, hexCounter);
|
|
7000
|
+
}
|
|
7001
|
+
function hotpToken(secret, counter, options) {
|
|
7002
|
+
return hotpDigestToToken(options.digest || hotpDigest(secret, counter, options), options.digits);
|
|
7003
|
+
}
|
|
7004
|
+
function hotpCheck(token, secret, counter, options) {
|
|
7005
|
+
if (!isTokenValid(token)) return false;
|
|
7006
|
+
return token === hotpToken(secret, counter, options);
|
|
7007
|
+
}
|
|
7008
|
+
function hotpKeyuri(accountName, issuer, secret, counter, options) {
|
|
7009
|
+
return keyuri({
|
|
7010
|
+
algorithm: options.algorithm,
|
|
7011
|
+
digits: options.digits,
|
|
7012
|
+
type: exports.Strategy.HOTP,
|
|
7013
|
+
accountName,
|
|
7014
|
+
counter,
|
|
7015
|
+
issuer,
|
|
7016
|
+
secret
|
|
7017
|
+
});
|
|
7018
|
+
}
|
|
7019
|
+
var HOTP = class HOTP extends OTP {
|
|
7020
|
+
create(defaultOptions = {}) {
|
|
7021
|
+
return new HOTP(defaultOptions);
|
|
7022
|
+
}
|
|
7023
|
+
allOptions() {
|
|
7024
|
+
return hotpOptions(this.options);
|
|
7025
|
+
}
|
|
7026
|
+
generate(secret, counter) {
|
|
7027
|
+
return hotpToken(secret, counter, this.allOptions());
|
|
7028
|
+
}
|
|
7029
|
+
check(token, secret, counter) {
|
|
7030
|
+
return hotpCheck(token, secret, counter, this.allOptions());
|
|
7031
|
+
}
|
|
7032
|
+
verify(opts) {
|
|
7033
|
+
if (typeof opts !== "object") throw new Error("Expecting argument 0 of verify to be an object");
|
|
7034
|
+
return this.check(opts.token, opts.secret, opts.counter);
|
|
7035
|
+
}
|
|
7036
|
+
keyuri(accountName, issuer, secret, counter) {
|
|
7037
|
+
return hotpKeyuri(accountName, issuer, secret, counter, this.allOptions());
|
|
7038
|
+
}
|
|
7039
|
+
};
|
|
7040
|
+
function parseWindowBounds(win) {
|
|
7041
|
+
if (typeof win === "number") return [Math.abs(win), Math.abs(win)];
|
|
7042
|
+
if (Array.isArray(win)) {
|
|
7043
|
+
const [past, future] = win;
|
|
7044
|
+
if (typeof past === "number" && typeof future === "number") return [Math.abs(past), Math.abs(future)];
|
|
7045
|
+
}
|
|
7046
|
+
throw new Error("Expecting options.window to be an number or [number, number].");
|
|
7047
|
+
}
|
|
7048
|
+
function totpOptionsValidator(options) {
|
|
7049
|
+
hotpOptionsValidator(options);
|
|
7050
|
+
parseWindowBounds(options.window);
|
|
7051
|
+
if (typeof options.epoch !== "number") throw new Error("Expecting options.epoch to be a number.");
|
|
7052
|
+
if (typeof options.step !== "number") throw new Error("Expecting options.step to be a number.");
|
|
7053
|
+
}
|
|
7054
|
+
var totpPadSecret = (secret, encoding, minLength) => {
|
|
7055
|
+
const currentLength = secret.length;
|
|
7056
|
+
const hexSecret = Buffer.from(secret, encoding).toString("hex");
|
|
7057
|
+
if (currentLength < minLength) {
|
|
7058
|
+
const newSecret = new Array(minLength - currentLength + 1).join(hexSecret);
|
|
7059
|
+
return Buffer.from(newSecret, "hex").slice(0, minLength).toString("hex");
|
|
7060
|
+
}
|
|
7061
|
+
return hexSecret;
|
|
7062
|
+
};
|
|
7063
|
+
var totpCreateHmacKey = (algorithm, secret, encoding) => {
|
|
7064
|
+
switch (algorithm) {
|
|
7065
|
+
case exports.HashAlgorithms.SHA1: return totpPadSecret(secret, encoding, 20);
|
|
7066
|
+
case exports.HashAlgorithms.SHA256: return totpPadSecret(secret, encoding, 32);
|
|
7067
|
+
case exports.HashAlgorithms.SHA512: return totpPadSecret(secret, encoding, 64);
|
|
7068
|
+
default: throw new Error(`Expecting algorithm to be one of ${HASH_ALGORITHMS.join(", ")}. Received ${algorithm}.`);
|
|
7069
|
+
}
|
|
7070
|
+
};
|
|
7071
|
+
function totpDefaultOptions() {
|
|
7072
|
+
return {
|
|
7073
|
+
algorithm: exports.HashAlgorithms.SHA1,
|
|
7074
|
+
createDigest: createDigestPlaceholder,
|
|
7075
|
+
createHmacKey: totpCreateHmacKey,
|
|
7076
|
+
digits: 6,
|
|
7077
|
+
encoding: exports.KeyEncodings.ASCII,
|
|
7078
|
+
epoch: Date.now(),
|
|
7079
|
+
step: 30,
|
|
7080
|
+
window: 0
|
|
7081
|
+
};
|
|
7082
|
+
}
|
|
7083
|
+
function totpOptions(opt) {
|
|
7084
|
+
const options = {
|
|
7085
|
+
...totpDefaultOptions(),
|
|
7086
|
+
...opt
|
|
7087
|
+
};
|
|
7088
|
+
totpOptionsValidator(options);
|
|
7089
|
+
return Object.freeze(options);
|
|
7090
|
+
}
|
|
7091
|
+
function totpCounter(epoch, step) {
|
|
7092
|
+
return Math.floor(epoch / step / 1e3);
|
|
7093
|
+
}
|
|
7094
|
+
function totpToken(secret, options) {
|
|
7095
|
+
return hotpToken(secret, totpCounter(options.epoch, options.step), options);
|
|
7096
|
+
}
|
|
7097
|
+
function totpEpochsInWindow(epoch, direction, deltaPerEpoch, numOfEpoches) {
|
|
7098
|
+
const result = [];
|
|
7099
|
+
if (numOfEpoches === 0) return result;
|
|
7100
|
+
for (let i = 1; i <= numOfEpoches; i++) {
|
|
7101
|
+
const delta = direction * i * deltaPerEpoch;
|
|
7102
|
+
result.push(epoch + delta);
|
|
7103
|
+
}
|
|
7104
|
+
return result;
|
|
7105
|
+
}
|
|
7106
|
+
function totpEpochAvailable(epoch, step, win) {
|
|
7107
|
+
const bounds = parseWindowBounds(win);
|
|
7108
|
+
const delta = step * 1e3;
|
|
7109
|
+
return {
|
|
7110
|
+
current: epoch,
|
|
7111
|
+
past: totpEpochsInWindow(epoch, -1, delta, bounds[0]),
|
|
7112
|
+
future: totpEpochsInWindow(epoch, 1, delta, bounds[1])
|
|
7113
|
+
};
|
|
7114
|
+
}
|
|
7115
|
+
function totpCheck(token, secret, options) {
|
|
7116
|
+
if (!isTokenValid(token)) return false;
|
|
7117
|
+
return token === totpToken(secret, options);
|
|
7118
|
+
}
|
|
7119
|
+
function totpCheckByEpoch(epochs, token, secret, options) {
|
|
7120
|
+
let position = null;
|
|
7121
|
+
epochs.some((epoch, idx) => {
|
|
7122
|
+
if (totpCheck(token, secret, {
|
|
7123
|
+
...options,
|
|
7124
|
+
epoch
|
|
7125
|
+
})) {
|
|
7126
|
+
position = idx + 1;
|
|
7127
|
+
return true;
|
|
7128
|
+
}
|
|
7129
|
+
return false;
|
|
7130
|
+
});
|
|
7131
|
+
return position;
|
|
7132
|
+
}
|
|
7133
|
+
function totpCheckWithWindow(token, secret, options) {
|
|
7134
|
+
if (totpCheck(token, secret, options)) return 0;
|
|
7135
|
+
const epochs = totpEpochAvailable(options.epoch, options.step, options.window);
|
|
7136
|
+
const backward = totpCheckByEpoch(epochs.past, token, secret, options);
|
|
7137
|
+
if (backward !== null) return backward * -1;
|
|
7138
|
+
return totpCheckByEpoch(epochs.future, token, secret, options);
|
|
7139
|
+
}
|
|
7140
|
+
function totpTimeUsed(epoch, step) {
|
|
7141
|
+
return Math.floor(epoch / 1e3) % step;
|
|
7142
|
+
}
|
|
7143
|
+
function totpTimeRemaining(epoch, step) {
|
|
7144
|
+
return step - totpTimeUsed(epoch, step);
|
|
7145
|
+
}
|
|
7146
|
+
function totpKeyuri(accountName, issuer, secret, options) {
|
|
7147
|
+
return keyuri({
|
|
7148
|
+
algorithm: options.algorithm,
|
|
7149
|
+
digits: options.digits,
|
|
7150
|
+
step: options.step,
|
|
7151
|
+
type: exports.Strategy.TOTP,
|
|
7152
|
+
accountName,
|
|
7153
|
+
issuer,
|
|
7154
|
+
secret
|
|
7155
|
+
});
|
|
7156
|
+
}
|
|
7157
|
+
var TOTP = class TOTP extends HOTP {
|
|
7158
|
+
create(defaultOptions = {}) {
|
|
7159
|
+
return new TOTP(defaultOptions);
|
|
7160
|
+
}
|
|
7161
|
+
allOptions() {
|
|
7162
|
+
return totpOptions(this.options);
|
|
7163
|
+
}
|
|
7164
|
+
generate(secret) {
|
|
7165
|
+
return totpToken(secret, this.allOptions());
|
|
7166
|
+
}
|
|
7167
|
+
checkDelta(token, secret) {
|
|
7168
|
+
return totpCheckWithWindow(token, secret, this.allOptions());
|
|
7169
|
+
}
|
|
7170
|
+
check(token, secret) {
|
|
7171
|
+
return typeof this.checkDelta(token, secret) === "number";
|
|
7172
|
+
}
|
|
7173
|
+
verify(opts) {
|
|
7174
|
+
if (typeof opts !== "object") throw new Error("Expecting argument 0 of verify to be an object");
|
|
7175
|
+
return this.check(opts.token, opts.secret);
|
|
7176
|
+
}
|
|
7177
|
+
timeRemaining() {
|
|
7178
|
+
const options = this.allOptions();
|
|
7179
|
+
return totpTimeRemaining(options.epoch, options.step);
|
|
7180
|
+
}
|
|
7181
|
+
timeUsed() {
|
|
7182
|
+
const options = this.allOptions();
|
|
7183
|
+
return totpTimeUsed(options.epoch, options.step);
|
|
7184
|
+
}
|
|
7185
|
+
keyuri(accountName, issuer, secret) {
|
|
7186
|
+
return totpKeyuri(accountName, issuer, secret, this.allOptions());
|
|
7187
|
+
}
|
|
7188
|
+
};
|
|
7189
|
+
function authenticatorOptionValidator(options) {
|
|
7190
|
+
totpOptionsValidator(options);
|
|
7191
|
+
if (typeof options.keyDecoder !== "function") throw new Error("Expecting options.keyDecoder to be a function.");
|
|
7192
|
+
if (options.keyEncoder && typeof options.keyEncoder !== "function") throw new Error("Expecting options.keyEncoder to be a function.");
|
|
7193
|
+
}
|
|
7194
|
+
function authenticatorDefaultOptions() {
|
|
7195
|
+
return {
|
|
7196
|
+
algorithm: exports.HashAlgorithms.SHA1,
|
|
7197
|
+
createDigest: createDigestPlaceholder,
|
|
7198
|
+
createHmacKey: totpCreateHmacKey,
|
|
7199
|
+
digits: 6,
|
|
7200
|
+
encoding: exports.KeyEncodings.HEX,
|
|
7201
|
+
epoch: Date.now(),
|
|
7202
|
+
step: 30,
|
|
7203
|
+
window: 0
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
7206
|
+
function authenticatorOptions(opt) {
|
|
7207
|
+
const options = {
|
|
7208
|
+
...authenticatorDefaultOptions(),
|
|
7209
|
+
...opt
|
|
7210
|
+
};
|
|
7211
|
+
authenticatorOptionValidator(options);
|
|
7212
|
+
return Object.freeze(options);
|
|
7213
|
+
}
|
|
7214
|
+
function authenticatorEncoder(secret, options) {
|
|
7215
|
+
return options.keyEncoder(secret, options.encoding);
|
|
7216
|
+
}
|
|
7217
|
+
function authenticatorDecoder(secret, options) {
|
|
7218
|
+
return options.keyDecoder(secret, options.encoding);
|
|
7219
|
+
}
|
|
7220
|
+
function authenticatorGenerateSecret(numberOfBytes, options) {
|
|
7221
|
+
return authenticatorEncoder(options.createRandomBytes(numberOfBytes, options.encoding), options);
|
|
7222
|
+
}
|
|
7223
|
+
function authenticatorToken(secret, options) {
|
|
7224
|
+
return totpToken(authenticatorDecoder(secret, options), options);
|
|
7225
|
+
}
|
|
7226
|
+
function authenticatorCheckWithWindow(token, secret, options) {
|
|
7227
|
+
return totpCheckWithWindow(token, authenticatorDecoder(secret, options), options);
|
|
7228
|
+
}
|
|
7229
|
+
exports.Authenticator = class Authenticator extends TOTP {
|
|
7230
|
+
create(defaultOptions = {}) {
|
|
7231
|
+
return new Authenticator(defaultOptions);
|
|
7232
|
+
}
|
|
7233
|
+
allOptions() {
|
|
7234
|
+
return authenticatorOptions(this.options);
|
|
7235
|
+
}
|
|
7236
|
+
generate(secret) {
|
|
7237
|
+
return authenticatorToken(secret, this.allOptions());
|
|
7238
|
+
}
|
|
7239
|
+
checkDelta(token, secret) {
|
|
7240
|
+
return authenticatorCheckWithWindow(token, secret, this.allOptions());
|
|
7241
|
+
}
|
|
7242
|
+
encode(secret) {
|
|
7243
|
+
return authenticatorEncoder(secret, this.allOptions());
|
|
7244
|
+
}
|
|
7245
|
+
decode(secret) {
|
|
7246
|
+
return authenticatorDecoder(secret, this.allOptions());
|
|
7247
|
+
}
|
|
7248
|
+
generateSecret(numberOfBytes = 10) {
|
|
7249
|
+
return authenticatorGenerateSecret(numberOfBytes, this.allOptions());
|
|
7250
|
+
}
|
|
7251
|
+
};
|
|
7252
|
+
exports.HASH_ALGORITHMS = HASH_ALGORITHMS;
|
|
7253
|
+
exports.HOTP = HOTP;
|
|
7254
|
+
exports.KEY_ENCODINGS = KEY_ENCODINGS;
|
|
7255
|
+
exports.OTP = OTP;
|
|
7256
|
+
exports.STRATEGY = STRATEGY;
|
|
7257
|
+
exports.TOTP = TOTP;
|
|
7258
|
+
exports.authenticatorCheckWithWindow = authenticatorCheckWithWindow;
|
|
7259
|
+
exports.authenticatorDecoder = authenticatorDecoder;
|
|
7260
|
+
exports.authenticatorDefaultOptions = authenticatorDefaultOptions;
|
|
7261
|
+
exports.authenticatorEncoder = authenticatorEncoder;
|
|
7262
|
+
exports.authenticatorGenerateSecret = authenticatorGenerateSecret;
|
|
7263
|
+
exports.authenticatorOptionValidator = authenticatorOptionValidator;
|
|
7264
|
+
exports.authenticatorOptions = authenticatorOptions;
|
|
7265
|
+
exports.authenticatorToken = authenticatorToken;
|
|
7266
|
+
exports.createDigestPlaceholder = createDigestPlaceholder;
|
|
7267
|
+
exports.hotpCheck = hotpCheck;
|
|
7268
|
+
exports.hotpCounter = hotpCounter;
|
|
7269
|
+
exports.hotpCreateHmacKey = hotpCreateHmacKey;
|
|
7270
|
+
exports.hotpDefaultOptions = hotpDefaultOptions;
|
|
7271
|
+
exports.hotpDigestToToken = hotpDigestToToken;
|
|
7272
|
+
exports.hotpKeyuri = hotpKeyuri;
|
|
7273
|
+
exports.hotpOptions = hotpOptions;
|
|
7274
|
+
exports.hotpOptionsValidator = hotpOptionsValidator;
|
|
7275
|
+
exports.hotpToken = hotpToken;
|
|
7276
|
+
exports.isTokenValid = isTokenValid;
|
|
7277
|
+
exports.keyuri = keyuri;
|
|
7278
|
+
exports.objectValues = objectValues;
|
|
7279
|
+
exports.padStart = padStart;
|
|
7280
|
+
exports.totpCheck = totpCheck;
|
|
7281
|
+
exports.totpCheckByEpoch = totpCheckByEpoch;
|
|
7282
|
+
exports.totpCheckWithWindow = totpCheckWithWindow;
|
|
7283
|
+
exports.totpCounter = totpCounter;
|
|
7284
|
+
exports.totpCreateHmacKey = totpCreateHmacKey;
|
|
7285
|
+
exports.totpDefaultOptions = totpDefaultOptions;
|
|
7286
|
+
exports.totpEpochAvailable = totpEpochAvailable;
|
|
7287
|
+
exports.totpKeyuri = totpKeyuri;
|
|
7288
|
+
exports.totpOptions = totpOptions;
|
|
7289
|
+
exports.totpOptionsValidator = totpOptionsValidator;
|
|
7290
|
+
exports.totpPadSecret = totpPadSecret;
|
|
7291
|
+
exports.totpTimeRemaining = totpTimeRemaining;
|
|
7292
|
+
exports.totpTimeUsed = totpTimeUsed;
|
|
7293
|
+
exports.totpToken = totpToken;
|
|
7294
|
+
}));
|
|
7295
|
+
//#endregion
|
|
7296
|
+
//#region ../../node_modules/@otplib/preset-default/index.js
|
|
7297
|
+
/**
|
|
7298
|
+
* @otplib/preset-default
|
|
7299
|
+
*
|
|
7300
|
+
* @author Gerald Yeo <contact@fusedthought.com>
|
|
7301
|
+
* @version: 12.0.1
|
|
7302
|
+
* @license: MIT
|
|
7303
|
+
**/
|
|
7304
|
+
var require_preset_default = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
7305
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7306
|
+
var pluginCrypto = require_plugin_crypto();
|
|
7307
|
+
var pluginThirtyTwo = require_plugin_thirty_two();
|
|
7308
|
+
var core = require_core();
|
|
7309
|
+
var hotp = new core.HOTP({ createDigest: pluginCrypto.createDigest });
|
|
7310
|
+
var totp = new core.TOTP({ createDigest: pluginCrypto.createDigest });
|
|
7311
|
+
exports.authenticator = new core.Authenticator({
|
|
7312
|
+
createDigest: pluginCrypto.createDigest,
|
|
7313
|
+
createRandomBytes: pluginCrypto.createRandomBytes,
|
|
7314
|
+
keyDecoder: pluginThirtyTwo.keyDecoder,
|
|
7315
|
+
keyEncoder: pluginThirtyTwo.keyEncoder
|
|
7316
|
+
});
|
|
7317
|
+
exports.hotp = hotp;
|
|
7318
|
+
exports.totp = totp;
|
|
7319
|
+
}));
|
|
7320
|
+
//#endregion
|
|
7321
|
+
//#region src/auth/totp-manager.ts
|
|
7322
|
+
var import_otplib = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
7323
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7324
|
+
var presetDefault = require_preset_default();
|
|
7325
|
+
Object.keys(presetDefault).forEach(function(k) {
|
|
7326
|
+
if (k !== "default") Object.defineProperty(exports, k, {
|
|
7327
|
+
enumerable: true,
|
|
7328
|
+
get: function() {
|
|
7329
|
+
return presetDefault[k];
|
|
7330
|
+
}
|
|
7331
|
+
});
|
|
7332
|
+
});
|
|
7333
|
+
})))();
|
|
7334
|
+
/**
|
|
7335
|
+
* TOTP / 2FA enrollment manager.
|
|
7336
|
+
*
|
|
7337
|
+
* Three-state lifecycle keyed by `userId` (PK of `user_totp` collection):
|
|
7338
|
+
*
|
|
7339
|
+
* - No row → not set up. `setup()` inserts a row with a
|
|
7340
|
+
* fresh secret and `confirmedAt = null`.
|
|
7341
|
+
* - `confirmedAt = null` → setup in progress; user has the secret but
|
|
7342
|
+
* hasn't proved possession of the
|
|
7343
|
+
* authenticator yet. `verify()` returns
|
|
7344
|
+
* false even with a valid code — the row is
|
|
7345
|
+
* inert until confirmed.
|
|
7346
|
+
* - `confirmedAt != null` → enrolled; `verify()` accepts codes.
|
|
7347
|
+
*
|
|
7348
|
+
* Disable removes the row entirely. The next `setup()` starts fresh —
|
|
7349
|
+
* we do NOT reuse the old secret on re-enroll (avoids the case where a
|
|
7350
|
+
* leaked secret stays valid after a "rotation").
|
|
7351
|
+
*
|
|
7352
|
+
* The shared `authenticator` from `otplib` uses RFC 6238 TOTP with the
|
|
7353
|
+
* default 30-second window. We bump the validation window from 1 step
|
|
7354
|
+
* to 1 step in either direction (±30s) — operator clock skew is the
|
|
7355
|
+
* usual culprit for "valid code rejected" complaints.
|
|
7356
|
+
*/
|
|
7357
|
+
var TOTP_COLLECTION = "user_totp";
|
|
7358
|
+
var ISSUER = "CamStack";
|
|
7359
|
+
function parseRow(data) {
|
|
7360
|
+
const userId = data["userId"];
|
|
7361
|
+
const secret = data["secret"];
|
|
7362
|
+
const confirmedAt = data["confirmedAt"];
|
|
7363
|
+
const createdAt = data["createdAt"];
|
|
7364
|
+
if (typeof userId !== "string") throw new Error("user_totp row: missing userId");
|
|
7365
|
+
if (typeof secret !== "string") throw new Error("user_totp row: missing secret");
|
|
7366
|
+
if (typeof createdAt !== "number") throw new Error("user_totp row: missing createdAt");
|
|
7367
|
+
return {
|
|
7368
|
+
userId,
|
|
7369
|
+
secret,
|
|
7370
|
+
confirmedAt: typeof confirmedAt === "number" ? confirmedAt : null,
|
|
7371
|
+
createdAt
|
|
7372
|
+
};
|
|
7373
|
+
}
|
|
7374
|
+
var TotpManager = class {
|
|
7375
|
+
constructor(store, opts = {}) {
|
|
7376
|
+
this.store = store;
|
|
7377
|
+
this.opts = opts;
|
|
7378
|
+
import_otplib.authenticator.options = { window: opts.window ?? 1 };
|
|
7379
|
+
}
|
|
7380
|
+
/**
|
|
7381
|
+
* Begin enrollment. Always generates a fresh secret — calling `setup`
|
|
7382
|
+
* a second time on the same user replaces any pending half-enrollment
|
|
7383
|
+
* (the user must rescan the QR / re-enter the secret).
|
|
7384
|
+
*
|
|
7385
|
+
* The user identity (`label`) is what an authenticator displays as
|
|
7386
|
+
* the account name; we use the supplied `username` for human-readable
|
|
7387
|
+
* recognition. `ISSUER` is the CamStack brand.
|
|
7388
|
+
*/
|
|
7389
|
+
async setup(userId, username) {
|
|
7390
|
+
const secret = import_otplib.authenticator.generateSecret();
|
|
7391
|
+
const otpauthUrl = import_otplib.authenticator.keyuri(username, ISSUER, secret);
|
|
7392
|
+
const row = {
|
|
7393
|
+
userId,
|
|
7394
|
+
secret,
|
|
7395
|
+
confirmedAt: null,
|
|
7396
|
+
createdAt: Date.now()
|
|
7397
|
+
};
|
|
7398
|
+
await this.store.delete.mutate({
|
|
7399
|
+
collection: TOTP_COLLECTION,
|
|
7400
|
+
key: userId
|
|
7401
|
+
});
|
|
7402
|
+
await this.store.insert.mutate({
|
|
7403
|
+
collection: TOTP_COLLECTION,
|
|
7404
|
+
record: {
|
|
7405
|
+
id: userId,
|
|
7406
|
+
data: { ...row }
|
|
7407
|
+
}
|
|
7408
|
+
});
|
|
7409
|
+
return {
|
|
7410
|
+
secret,
|
|
7411
|
+
otpauthUrl
|
|
7412
|
+
};
|
|
7413
|
+
}
|
|
7414
|
+
/**
|
|
7415
|
+
* Confirm enrollment by checking a code from the authenticator
|
|
7416
|
+
* against the pending secret. On success, flips `confirmedAt` so
|
|
7417
|
+
* `verify()` starts accepting codes. Idempotent on already-confirmed
|
|
7418
|
+
* rows: re-confirming a valid code just succeeds with no state
|
|
7419
|
+
* change.
|
|
7420
|
+
*/
|
|
7421
|
+
async confirm(userId, code) {
|
|
7422
|
+
const row = await this.find(userId);
|
|
7423
|
+
if (!row) return false;
|
|
7424
|
+
if (!import_otplib.authenticator.check(code, row.secret)) return false;
|
|
7425
|
+
if (row.confirmedAt === null) {
|
|
7426
|
+
const next = {
|
|
7427
|
+
...row,
|
|
7428
|
+
confirmedAt: Date.now()
|
|
7429
|
+
};
|
|
7430
|
+
await this.store.update.mutate({
|
|
7431
|
+
collection: TOTP_COLLECTION,
|
|
7432
|
+
id: userId,
|
|
7433
|
+
data: { ...next }
|
|
7434
|
+
});
|
|
7435
|
+
}
|
|
7436
|
+
return true;
|
|
7437
|
+
}
|
|
7438
|
+
/**
|
|
7439
|
+
* Remove enrollment. Idempotent — no error if the user had no row.
|
|
7440
|
+
*/
|
|
7441
|
+
async disable(userId) {
|
|
7442
|
+
await this.store.delete.mutate({
|
|
7443
|
+
collection: TOTP_COLLECTION,
|
|
7444
|
+
key: userId
|
|
7445
|
+
});
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Verify a code against the confirmed secret. Returns `false` when:
|
|
7449
|
+
* - The user has no row (not set up).
|
|
7450
|
+
* - The row is pending (`confirmedAt === null`).
|
|
7451
|
+
* - The code doesn't match within the configured window.
|
|
7452
|
+
*/
|
|
7453
|
+
async verify(userId, code) {
|
|
7454
|
+
const row = await this.find(userId);
|
|
7455
|
+
if (!row) return false;
|
|
7456
|
+
if (row.confirmedAt === null) return false;
|
|
7457
|
+
return import_otplib.authenticator.check(code, row.secret);
|
|
7458
|
+
}
|
|
7459
|
+
/**
|
|
7460
|
+
* Read the user's enrollment status. Pending half-enrollments are
|
|
7461
|
+
* reported as `enabled: false` — only a confirmed row counts.
|
|
7462
|
+
*/
|
|
7463
|
+
async getStatus(userId) {
|
|
7464
|
+
const row = await this.find(userId);
|
|
7465
|
+
if (!row) return {
|
|
7466
|
+
enabled: false,
|
|
7467
|
+
confirmedAt: null
|
|
7468
|
+
};
|
|
7469
|
+
return {
|
|
7470
|
+
enabled: row.confirmedAt !== null,
|
|
7471
|
+
confirmedAt: row.confirmedAt
|
|
7472
|
+
};
|
|
7473
|
+
}
|
|
7474
|
+
async find(userId) {
|
|
7475
|
+
const results = await this.store.query.query({
|
|
7476
|
+
collection: TOTP_COLLECTION,
|
|
7477
|
+
filter: { where: { userId } }
|
|
7478
|
+
});
|
|
7479
|
+
if (results.length === 0) return null;
|
|
7480
|
+
return parseRow(results[0].data);
|
|
7481
|
+
}
|
|
7482
|
+
};
|
|
7483
|
+
//#endregion
|
|
6561
7484
|
//#region src/builtins/local-auth/auth-schema.ts
|
|
6562
7485
|
var USERS_COLLECTION = "users";
|
|
6563
7486
|
var API_KEYS_COLLECTION = "api_keys";
|
|
6564
7487
|
var SCOPED_TOKENS_COLLECTION = "scoped_tokens";
|
|
7488
|
+
/**
|
|
7489
|
+
* TOTP enrollment table — split off from `users` to avoid the
|
|
7490
|
+
* destructive drop-+-recreate migration `ensureTable` performs when a
|
|
7491
|
+
* new column is added to a typed schema (see `sqlite-settings-backend.ts`).
|
|
7492
|
+
*
|
|
7493
|
+
* Lifecycle:
|
|
7494
|
+
* - Setup: row inserted with `confirmedAt = null` and a fresh secret.
|
|
7495
|
+
* - Confirm: a valid code from the secret flips `confirmedAt` to now().
|
|
7496
|
+
* - Disable: the row is deleted (next setup starts fresh).
|
|
7497
|
+
*
|
|
7498
|
+
* "Enabled" means `confirmedAt != null`. A half-enrolled row (`confirmedAt
|
|
7499
|
+
* = null`) is INACTIVE — `verifyTotp` returns false until confirmation.
|
|
7500
|
+
*/
|
|
7501
|
+
var USER_TOTP_COLLECTION = "user_totp";
|
|
6565
7502
|
var USERS_COLUMNS = [
|
|
6566
7503
|
{
|
|
6567
7504
|
name: "id",
|
|
@@ -6581,8 +7518,8 @@ var USERS_COLUMNS = [
|
|
|
6581
7518
|
notNull: true
|
|
6582
7519
|
},
|
|
6583
7520
|
{
|
|
6584
|
-
name: "
|
|
6585
|
-
type: "
|
|
7521
|
+
name: "isAdmin",
|
|
7522
|
+
type: "BOOLEAN",
|
|
6586
7523
|
notNull: true
|
|
6587
7524
|
},
|
|
6588
7525
|
{
|
|
@@ -6621,8 +7558,8 @@ var API_KEYS_COLUMNS = [
|
|
|
6621
7558
|
notNull: true
|
|
6622
7559
|
},
|
|
6623
7560
|
{
|
|
6624
|
-
name: "
|
|
6625
|
-
type: "
|
|
7561
|
+
name: "isAdmin",
|
|
7562
|
+
type: "BOOLEAN",
|
|
6626
7563
|
notNull: true
|
|
6627
7564
|
},
|
|
6628
7565
|
{
|
|
@@ -6654,6 +7591,28 @@ var API_KEYS_COLUMNS = [
|
|
|
6654
7591
|
type: "INTEGER"
|
|
6655
7592
|
}
|
|
6656
7593
|
];
|
|
7594
|
+
var USER_TOTP_COLUMNS = [
|
|
7595
|
+
{
|
|
7596
|
+
name: "userId",
|
|
7597
|
+
type: "TEXT",
|
|
7598
|
+
primaryKey: true,
|
|
7599
|
+
notNull: true
|
|
7600
|
+
},
|
|
7601
|
+
{
|
|
7602
|
+
name: "secret",
|
|
7603
|
+
type: "TEXT",
|
|
7604
|
+
notNull: true
|
|
7605
|
+
},
|
|
7606
|
+
{
|
|
7607
|
+
name: "confirmedAt",
|
|
7608
|
+
type: "INTEGER"
|
|
7609
|
+
},
|
|
7610
|
+
{
|
|
7611
|
+
name: "createdAt",
|
|
7612
|
+
type: "INTEGER",
|
|
7613
|
+
notNull: true
|
|
7614
|
+
}
|
|
7615
|
+
];
|
|
6657
7616
|
var SCOPED_TOKENS_COLUMNS = [
|
|
6658
7617
|
{
|
|
6659
7618
|
name: "id",
|
|
@@ -6727,6 +7686,10 @@ async function declareAuthSchema(store) {
|
|
|
6727
7686
|
columns: ["userId"]
|
|
6728
7687
|
}]
|
|
6729
7688
|
});
|
|
7689
|
+
await store.declareCollection.mutate({
|
|
7690
|
+
collection: USER_TOTP_COLLECTION,
|
|
7691
|
+
columns: USER_TOTP_COLUMNS
|
|
7692
|
+
});
|
|
6730
7693
|
}
|
|
6731
7694
|
//#endregion
|
|
6732
7695
|
//#region src/builtins/local-auth/local-auth.addon.ts
|
|
@@ -6735,7 +7698,7 @@ function toAuthResult(user) {
|
|
|
6735
7698
|
userId: user.id,
|
|
6736
7699
|
username: user.username,
|
|
6737
7700
|
displayName: user.username,
|
|
6738
|
-
|
|
7701
|
+
isAdmin: user.isAdmin
|
|
6739
7702
|
};
|
|
6740
7703
|
}
|
|
6741
7704
|
var LocalAuthAddon = class extends BaseAddon {
|
|
@@ -6743,6 +7706,7 @@ var LocalAuthAddon = class extends BaseAddon {
|
|
|
6743
7706
|
userManager = null;
|
|
6744
7707
|
apiKeyManager = null;
|
|
6745
7708
|
scopedTokenManager = null;
|
|
7709
|
+
totpManager = null;
|
|
6746
7710
|
constructor() {
|
|
6747
7711
|
super({
|
|
6748
7712
|
jwtSecret: "",
|
|
@@ -6775,6 +7739,7 @@ var LocalAuthAddon = class extends BaseAddon {
|
|
|
6775
7739
|
this.userManager = new UserManager(storageAccess, this.authManager, reader);
|
|
6776
7740
|
this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager);
|
|
6777
7741
|
this.scopedTokenManager = new ScopedTokenManager(store);
|
|
7742
|
+
this.totpManager = new TotpManager(store);
|
|
6778
7743
|
try {
|
|
6779
7744
|
await this.userManager.ensureAdminExists();
|
|
6780
7745
|
const liveUsers = await this.userManager.listAll();
|
|
@@ -6890,6 +7855,33 @@ var LocalAuthAddon = class extends BaseAddon {
|
|
|
6890
7855
|
listScopedTokens: async (input) => {
|
|
6891
7856
|
if (!this.scopedTokenManager) return [];
|
|
6892
7857
|
return this.scopedTokenManager.listForUser(input.userId);
|
|
7858
|
+
},
|
|
7859
|
+
setupTotp: async (input) => {
|
|
7860
|
+
if (!this.totpManager || !this.userManager) throw new Error("TOTP management not available");
|
|
7861
|
+
const user = await this.userManager.findById(input.userId);
|
|
7862
|
+
if (!user) throw new Error(`User not found: ${input.userId}`);
|
|
7863
|
+
return this.totpManager.setup(user.id, user.username);
|
|
7864
|
+
},
|
|
7865
|
+
confirmTotp: async (input) => {
|
|
7866
|
+
if (!this.totpManager) throw new Error("TOTP management not available");
|
|
7867
|
+
if (!await this.totpManager.confirm(input.userId, input.code)) throw new Error("TOTP confirmation failed — code did not match");
|
|
7868
|
+
return { success: true };
|
|
7869
|
+
},
|
|
7870
|
+
disableTotp: async (input) => {
|
|
7871
|
+
if (!this.totpManager) throw new Error("TOTP management not available");
|
|
7872
|
+
await this.totpManager.disable(input.userId);
|
|
7873
|
+
return { success: true };
|
|
7874
|
+
},
|
|
7875
|
+
getTotpStatus: async (input) => {
|
|
7876
|
+
if (!this.totpManager) return {
|
|
7877
|
+
enabled: false,
|
|
7878
|
+
confirmedAt: null
|
|
7879
|
+
};
|
|
7880
|
+
return this.totpManager.getStatus(input.userId);
|
|
7881
|
+
},
|
|
7882
|
+
verifyTotp: async (input) => {
|
|
7883
|
+
if (!this.totpManager) return { valid: false };
|
|
7884
|
+
return { valid: await this.totpManager.verify(input.userId, input.code) };
|
|
6893
7885
|
}
|
|
6894
7886
|
};
|
|
6895
7887
|
this.ctx.logger.info("registered auth-provider + user-management capabilities");
|