@nxgt/janus 0.18.0 → 0.20.0
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 +148 -12
- package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
- package/dist/auth/events.d.ts +3 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/magic-link.d.ts +3 -0
- package/dist/auth/magic-link.d.ts.map +1 -1
- package/dist/auth/one-time/issue.d.ts +6 -0
- package/dist/auth/one-time/issue.d.ts.map +1 -1
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/memory/users.d.ts.map +1 -1
- package/dist/auth/port/types/stores.d.ts +5 -0
- package/dist/auth/port/types/stores.d.ts.map +1 -1
- package/dist/auth/port/types/users.d.ts +17 -0
- package/dist/auth/port/types/users.d.ts.map +1 -1
- package/dist/auth/prepared.d.ts +21 -0
- package/dist/auth/prepared.d.ts.map +1 -0
- package/dist/auth/proven-sign-in.d.ts +22 -0
- package/dist/auth/proven-sign-in.d.ts.map +1 -0
- package/dist/auth/sign-in-code.d.ts +4 -0
- package/dist/auth/sign-in-code.d.ts.map +1 -1
- package/dist/auth/types/email-flows.d.ts +18 -0
- package/dist/auth/types/email-flows.d.ts.map +1 -1
- package/dist/auth/types/index.d.ts +2 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +7 -5
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/magic-link.d.ts +11 -0
- package/dist/auth/types/magic-link.d.ts.map +1 -1
- package/dist/auth/types/prepared.d.ts +41 -0
- package/dist/auth/types/prepared.d.ts.map +1 -0
- package/dist/auth/types/proven-sign-in.d.ts +43 -0
- package/dist/auth/types/proven-sign-in.d.ts.map +1 -0
- package/dist/auth/types/sign-in.d.ts +2 -2
- package/dist/auth/types/user-type.d.ts +15 -0
- package/dist/auth/types/user-type.d.ts.map +1 -1
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/find-many.d.ts +29 -0
- package/dist/auth/users/find-many.d.ts.map +1 -0
- package/dist/auth/users/record-flows.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +2 -2
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/chunks/{index-kvjj5xkp.js → index-6bjfv4xv.js} +8 -2
- package/dist/chunks/{index-kvjj5xkp.js.map → index-6bjfv4xv.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/users/batch.d.ts +8 -0
- package/dist/conformance/cases/users/batch.d.ts.map +1 -0
- package/dist/conformance/cases/users/index.d.ts.map +1 -1
- package/dist/conformance/describe.d.ts +2 -1
- package/dist/conformance/describe.d.ts.map +1 -1
- package/dist/conformance/index.d.ts +1 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +104 -44
- package/dist/conformance/index.js.map +7 -6
- package/dist/conformance/types.d.ts +10 -2
- package/dist/conformance/types.d.ts.map +1 -1
- package/dist/index.js +150 -23
- package/dist/index.js.map +14 -11
- package/docs/README.md +1 -1
- package/docs/guide/adapters.md +44 -4
- package/docs/guide/devices.md +4 -0
- package/docs/guide/email-flows.md +105 -0
- package/docs/guide/errors.md +2 -2
- package/docs/guide/events.md +2 -2
- package/docs/guide/magic-link.md +49 -2
- package/docs/guide/passwords.md +5 -2
- package/docs/guide/second-factor.md +1 -1
- package/docs/guide/sessions.md +94 -1
- package/docs/guide/sign-in-code.md +54 -2
- package/docs/guide/users.md +49 -0
- package/docs/roadmap.md +28 -39
- package/docs/troubleshooting.md +120 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
isId2,
|
|
14
14
|
mintedAt2,
|
|
15
15
|
createMemoryStores2
|
|
16
|
-
} from "./chunks/index-
|
|
16
|
+
} from "./chunks/index-6bjfv4xv.js";
|
|
17
17
|
import {
|
|
18
18
|
JanusError2,
|
|
19
19
|
UserInvalidError2,
|
|
@@ -774,12 +774,18 @@ function assertStores(stores, where) {
|
|
|
774
774
|
}
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
777
|
+
const slots = stores;
|
|
778
|
+
return {
|
|
779
|
+
collectExpired: optional(slots, "sessions", "deleteExpiredSessions", where),
|
|
780
|
+
findUsers: optional(slots, "users", "findUsers", where)
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function optional(stores, slot, method, where) {
|
|
784
|
+
const found = stores[slot][method];
|
|
785
|
+
if (found !== undefined && typeof found !== "function") {
|
|
786
|
+
throw new TypeError(`${where}: store.${slot}.${method} must be a function or absent`);
|
|
781
787
|
}
|
|
782
|
-
return
|
|
788
|
+
return found !== undefined;
|
|
783
789
|
}
|
|
784
790
|
|
|
785
791
|
// src/auth/devices/token.ts
|
|
@@ -1147,7 +1153,7 @@ async function issueCode(context, token) {
|
|
|
1147
1153
|
}
|
|
1148
1154
|
async function insertOneTime(context, token, code) {
|
|
1149
1155
|
const now = context.clock.now();
|
|
1150
|
-
const secret = mintSecret();
|
|
1156
|
+
const secret = token.secret ?? mintSecret();
|
|
1151
1157
|
const expiresAt = new Date(now.getTime() + token.ttlMs);
|
|
1152
1158
|
await context.store.tokens.insertToken({
|
|
1153
1159
|
tokenHash: hashSecret(secret),
|
|
@@ -1289,6 +1295,18 @@ async function heldByPassword(context, type, verified, password, result, where)
|
|
|
1289
1295
|
return false;
|
|
1290
1296
|
}
|
|
1291
1297
|
|
|
1298
|
+
// src/auth/prepared.ts
|
|
1299
|
+
function sendOnce(where, issue) {
|
|
1300
|
+
let sent = false;
|
|
1301
|
+
return async () => {
|
|
1302
|
+
if (sent) {
|
|
1303
|
+
throw new TypeError(`${where}(…).send: already called — a prepared request sends once; call ${where} again for another`);
|
|
1304
|
+
}
|
|
1305
|
+
sent = true;
|
|
1306
|
+
return issue();
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1292
1310
|
// src/auth/email-flows/email-token.ts
|
|
1293
1311
|
async function redeemEmailToken(context, type, kind, secret, where) {
|
|
1294
1312
|
const token = await spendOneTime(context, secret, kind, where, "token");
|
|
@@ -1321,20 +1339,32 @@ function resetPasswordFlows(context, type, at) {
|
|
|
1321
1339
|
return {
|
|
1322
1340
|
async request(email) {
|
|
1323
1341
|
const where = at("resetPassword.request");
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const issued = await issueEmailToken(context, type, "resetPassword", record, where);
|
|
1330
|
-
await context.store.tokens.spendUserTokens(record.id, "resetPassword", context.clock.now(), hashSecret(issued.token));
|
|
1331
|
-
return { ...issued, user: toUser(record) };
|
|
1342
|
+
return (await prepareReset(context, type, String(email), where)).send();
|
|
1343
|
+
},
|
|
1344
|
+
async prepare(email) {
|
|
1345
|
+
const where = at("resetPassword.prepare");
|
|
1346
|
+
return prepareReset(context, type, String(email), where);
|
|
1332
1347
|
},
|
|
1333
1348
|
async confirm(secret, password) {
|
|
1334
1349
|
return confirmReset(context, type, secret, password, at("resetPassword.confirm"));
|
|
1335
1350
|
}
|
|
1336
1351
|
};
|
|
1337
1352
|
}
|
|
1353
|
+
async function prepareReset(context, type, email, where) {
|
|
1354
|
+
passwordRule(type, where);
|
|
1355
|
+
await countMailByAddress(context, type, "resetPassword", email, where);
|
|
1356
|
+
return Object.freeze({
|
|
1357
|
+
send: sendOnce(where, () => issueReset(context, type, email, where))
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
async function issueReset(context, type, email, where) {
|
|
1361
|
+
const record = await holderOfEmail(context, type, email);
|
|
1362
|
+
if (record === null)
|
|
1363
|
+
return null;
|
|
1364
|
+
const issued = await issueEmailToken(context, type, "resetPassword", record, where);
|
|
1365
|
+
await context.store.tokens.spendUserTokens(record.id, "resetPassword", context.clock.now(), hashSecret(issued.token));
|
|
1366
|
+
return { ...issued, user: toUser(record) };
|
|
1367
|
+
}
|
|
1338
1368
|
async function confirmReset(context, type, secret, password, where) {
|
|
1339
1369
|
passwordRule(type, where);
|
|
1340
1370
|
checkPassword(type, password, where);
|
|
@@ -1555,7 +1585,12 @@ async function finishEmailSignIn(context, type, finish, signIn) {
|
|
|
1555
1585
|
function magicLinkFlows(context, type, at, finish) {
|
|
1556
1586
|
return {
|
|
1557
1587
|
async request(email) {
|
|
1558
|
-
|
|
1588
|
+
const where = at("magicLink.request");
|
|
1589
|
+
return (await prepareLink(context, type, String(email), where)).send();
|
|
1590
|
+
},
|
|
1591
|
+
async prepare(email) {
|
|
1592
|
+
const where = at("magicLink.prepare");
|
|
1593
|
+
return prepareLink(context, type, String(email), where);
|
|
1559
1594
|
},
|
|
1560
1595
|
async confirm(token, options) {
|
|
1561
1596
|
const where = at("magicLink.confirm");
|
|
@@ -1564,8 +1599,13 @@ function magicLinkFlows(context, type, at, finish) {
|
|
|
1564
1599
|
}
|
|
1565
1600
|
};
|
|
1566
1601
|
}
|
|
1567
|
-
async function
|
|
1602
|
+
async function prepareLink(context, type, email, where) {
|
|
1568
1603
|
await countMailByAddress(context, type, "magicLink", email, where);
|
|
1604
|
+
return Object.freeze({
|
|
1605
|
+
send: sendOnce(where, () => issueLink(context, type, email))
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
async function issueLink(context, type, email) {
|
|
1569
1609
|
const record = await signInHolder(context, type, email);
|
|
1570
1610
|
if (record === null)
|
|
1571
1611
|
return null;
|
|
@@ -1590,6 +1630,32 @@ async function confirmLink(context, type, finish, secret, where, device) {
|
|
|
1590
1630
|
});
|
|
1591
1631
|
}
|
|
1592
1632
|
|
|
1633
|
+
// src/auth/proven-sign-in.ts
|
|
1634
|
+
function provenSignInFlows(context, type, at, finish) {
|
|
1635
|
+
return {
|
|
1636
|
+
async signInProven(user, options) {
|
|
1637
|
+
const where = at("signInProven");
|
|
1638
|
+
const device = deviceHint(context, options, where);
|
|
1639
|
+
const record = await getRecord(context, idOf2(user), type.name, where);
|
|
1640
|
+
if (!record.active) {
|
|
1641
|
+
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
1642
|
+
userId: record.id,
|
|
1643
|
+
userType: type.name
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
const result = await finish(record, where, device);
|
|
1647
|
+
await reportNewDevice(context, result);
|
|
1648
|
+
return result;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
function idOf2(user) {
|
|
1653
|
+
if (typeof user === "string")
|
|
1654
|
+
return user;
|
|
1655
|
+
const id = typeof user === "object" && user !== null ? user.id : undefined;
|
|
1656
|
+
return typeof id === "string" ? id : "";
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1593
1659
|
// src/auth/sign-in-attempts.ts
|
|
1594
1660
|
var PURPOSE3 = "janus/sign-in/attempts/v1";
|
|
1595
1661
|
async function countLogin(context, type, login, where) {
|
|
@@ -1968,29 +2034,43 @@ function secondFactorFlows(context, type, at) {
|
|
|
1968
2034
|
function signInCodeFlows(context, type, at, finish) {
|
|
1969
2035
|
return {
|
|
1970
2036
|
async request(email) {
|
|
1971
|
-
|
|
2037
|
+
const where = at("signInCode.request");
|
|
2038
|
+
return (await prepareCode(context, type, String(email), where)).send();
|
|
2039
|
+
},
|
|
2040
|
+
async prepare(email) {
|
|
2041
|
+
const where = at("signInCode.prepare");
|
|
2042
|
+
return prepareCode(context, type, String(email), where);
|
|
1972
2043
|
},
|
|
1973
2044
|
async confirm(challenge, code, options) {
|
|
1974
2045
|
return confirmCode(context, type, finish, { challenge, code, options }, at);
|
|
1975
2046
|
}
|
|
1976
2047
|
};
|
|
1977
2048
|
}
|
|
1978
|
-
async function
|
|
2049
|
+
async function prepareCode(context, type, email, where) {
|
|
1979
2050
|
await countMailByAddress(context, type, "signInCode", email, where);
|
|
2051
|
+
const challenge = mintSecret();
|
|
2052
|
+
return Object.freeze({
|
|
2053
|
+
challenge,
|
|
2054
|
+
send: sendOnce(where, () => issueSignInCode(context, type, email, challenge))
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
async function issueSignInCode(context, type, email, challenge) {
|
|
1980
2058
|
const record = await signInHolder(context, type, email);
|
|
1981
2059
|
if (record === null)
|
|
1982
2060
|
return null;
|
|
2061
|
+
const address = String(record.fields[type.email]);
|
|
1983
2062
|
const { secret, code, expiresAt } = await issueCode(context, {
|
|
1984
2063
|
kind: "signInCode",
|
|
1985
2064
|
userId: record.id,
|
|
1986
|
-
address
|
|
1987
|
-
ttlMs: context.config.tokenTtlMs.signInCode
|
|
2065
|
+
address,
|
|
2066
|
+
ttlMs: context.config.tokenTtlMs.signInCode,
|
|
2067
|
+
secret: challenge
|
|
1988
2068
|
});
|
|
1989
2069
|
await keepOnlyLatest(context, record.id, "signInCode", secret);
|
|
1990
2070
|
return {
|
|
1991
2071
|
code,
|
|
1992
2072
|
challenge: secret,
|
|
1993
|
-
email:
|
|
2073
|
+
email: address,
|
|
1994
2074
|
expiresAt,
|
|
1995
2075
|
user: toUser(record)
|
|
1996
2076
|
};
|
|
@@ -2317,6 +2397,48 @@ async function deleteUser(context, type, user) {
|
|
|
2317
2397
|
return deleted;
|
|
2318
2398
|
}
|
|
2319
2399
|
|
|
2400
|
+
// src/auth/users/find-many.ts
|
|
2401
|
+
var FIND_MANY_BATCH = 100;
|
|
2402
|
+
var FIND_MANY_CONCURRENCY = 10;
|
|
2403
|
+
async function findManyRecords(context, ids, type, where) {
|
|
2404
|
+
if (!Array.isArray(ids)) {
|
|
2405
|
+
throw new TypeError(`${where}: ids must be an array of user ids`);
|
|
2406
|
+
}
|
|
2407
|
+
const wanted = [
|
|
2408
|
+
...new Set(ids.filter((id) => typeof id === "string" && isId2(id)))
|
|
2409
|
+
];
|
|
2410
|
+
const found = new Map;
|
|
2411
|
+
for (let start = 0;start < wanted.length; start += FIND_MANY_BATCH) {
|
|
2412
|
+
const batch = wanted.slice(start, start + FIND_MANY_BATCH);
|
|
2413
|
+
for (const record of await readBatch(context, batch)) {
|
|
2414
|
+
if (record.type === type)
|
|
2415
|
+
found.set(record.id, record);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return wanted.flatMap((id) => {
|
|
2419
|
+
const record = found.get(id);
|
|
2420
|
+
return record === undefined ? [] : [record];
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
async function readBatch(context, ids) {
|
|
2424
|
+
const { users } = context.store;
|
|
2425
|
+
if (context.capabilities.findUsers && users.findUsers !== undefined) {
|
|
2426
|
+
const answer = await users.findUsers(ids);
|
|
2427
|
+
if (!Array.isArray(answer)) {
|
|
2428
|
+
throw new StoreFailure2("users.findUsers answered no list: an absence is a shorter list, so this store forgot to answer", { slot: "users", operation: "findUsers" });
|
|
2429
|
+
}
|
|
2430
|
+
return answer;
|
|
2431
|
+
}
|
|
2432
|
+
const records = [];
|
|
2433
|
+
for (let start = 0;start < ids.length; start += FIND_MANY_CONCURRENCY) {
|
|
2434
|
+
const answers = await Promise.all(ids.slice(start, start + FIND_MANY_CONCURRENCY).map((id) => users.findUser(id)));
|
|
2435
|
+
for (const record of answers)
|
|
2436
|
+
if (record !== null)
|
|
2437
|
+
records.push(record);
|
|
2438
|
+
}
|
|
2439
|
+
return records;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2320
2442
|
// src/auth/users/fields-patch.ts
|
|
2321
2443
|
var normalized = (email) => email === null ? null : normalizeEmail(email);
|
|
2322
2444
|
function emailChanged(type, before, after) {
|
|
@@ -2359,6 +2481,10 @@ function recordFlows(context, type, at) {
|
|
|
2359
2481
|
const record = await findRecord(context, id, type.name);
|
|
2360
2482
|
return record === null ? null : toUser(record);
|
|
2361
2483
|
},
|
|
2484
|
+
async findMany(ids) {
|
|
2485
|
+
const records = await findManyRecords(context, ids, type.name, at("findMany"));
|
|
2486
|
+
return records.map(toUser);
|
|
2487
|
+
},
|
|
2362
2488
|
async get(id) {
|
|
2363
2489
|
return toUser(await getRecord(context, id, type.name, at("get")));
|
|
2364
2490
|
},
|
|
@@ -2396,6 +2522,7 @@ function typeApi(context, type) {
|
|
|
2396
2522
|
return {
|
|
2397
2523
|
...recordFlows(context, type, at),
|
|
2398
2524
|
...passwordFlows(context, type, at, secondFactor.finish),
|
|
2525
|
+
...provenSignInFlows(context, type, at, secondFactor.finish),
|
|
2399
2526
|
secondFactor: secondFactor.api,
|
|
2400
2527
|
signInCode,
|
|
2401
2528
|
magicLink: magicLinkFlows(context, type, at, secondFactor.finish),
|
|
@@ -2488,5 +2615,5 @@ export {
|
|
|
2488
2615
|
systemClock
|
|
2489
2616
|
};
|
|
2490
2617
|
|
|
2491
|
-
//# debugId=
|
|
2618
|
+
//# debugId=58DD1D230B6CE48B64756E2164756E21
|
|
2492
2619
|
//# sourceMappingURL=index.js.map
|