@nxgt/janus 0.6.0 → 0.8.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 +107 -17
- package/dist/auth/config.d.ts +8 -0
- package/dist/auth/config.d.ts.map +1 -1
- package/dist/auth/context.d.ts +10 -1
- package/dist/auth/context.d.ts.map +1 -1
- package/dist/auth/email-flows.d.ts +11 -0
- package/dist/auth/email-flows.d.ts.map +1 -0
- package/dist/auth/events.d.ts +52 -0
- package/dist/auth/events.d.ts.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/janus.d.ts.map +1 -1
- package/dist/auth/one-time.d.ts +7 -0
- package/dist/auth/one-time.d.ts.map +1 -1
- package/dist/auth/password-written.d.ts +24 -0
- package/dist/auth/password-written.d.ts.map +1 -0
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/types.d.ts +23 -1
- package/dist/auth/port/types.d.ts.map +1 -1
- package/dist/auth/second-factor/challenge.d.ts.map +1 -1
- package/dist/auth/sign-in-code.d.ts +6 -3
- package/dist/auth/sign-in-code.d.ts.map +1 -1
- package/dist/auth/types.d.ts +6 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/users.d.ts.map +1 -1
- package/dist/chunks/{index-gbwn2tts.js → index-c0jajeda.js} +12 -2
- package/dist/chunks/{index-gbwn2tts.js.map → index-c0jajeda.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/tokens.d.ts.map +1 -1
- package/dist/conformance/index.js +73 -2
- package/dist/conformance/index.js.map +4 -4
- package/dist/index.js +207 -95
- package/dist/index.js.map +14 -11
- package/docs/README.md +2 -1
- package/docs/guide/adapters.md +56 -3
- package/docs/guide/email-flows.md +11 -1
- package/docs/guide/events.md +199 -0
- package/docs/guide/second-factor.md +27 -2
- package/docs/guide/sign-in-code.md +28 -9
- package/docs/guide/users.md +8 -4
- package/docs/guide/vocabulary.md +3 -0
- package/docs/roadmap.md +25 -13
- package/docs/troubleshooting.md +89 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isId2,
|
|
12
12
|
mintedAt2,
|
|
13
13
|
createMemoryStores2
|
|
14
|
-
} from "./chunks/index-
|
|
14
|
+
} from "./chunks/index-c0jajeda.js";
|
|
15
15
|
import {
|
|
16
16
|
JanusError2,
|
|
17
17
|
StoreFailure2,
|
|
@@ -389,7 +389,7 @@ async function unlessVersionConflict(write) {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
// src/auth/context.ts
|
|
392
|
-
function createContext(config, store, relations, capabilities, clock, hasher, verifiers) {
|
|
392
|
+
function createContext(config, store, relations, capabilities, clock, hasher, verifiers, events = null) {
|
|
393
393
|
let dummy = null;
|
|
394
394
|
return {
|
|
395
395
|
config,
|
|
@@ -399,6 +399,7 @@ function createContext(config, store, relations, capabilities, clock, hasher, ve
|
|
|
399
399
|
clock,
|
|
400
400
|
hasher,
|
|
401
401
|
verifiers,
|
|
402
|
+
events,
|
|
402
403
|
dummyHash: () => {
|
|
403
404
|
if (hasher === null) {
|
|
404
405
|
throw new TypeError("janus: no hasher to compare a dummy hash with");
|
|
@@ -514,6 +515,12 @@ function loginsOf(type, fields, where) {
|
|
|
514
515
|
logins.add(normalizeEmail(email));
|
|
515
516
|
return [...logins];
|
|
516
517
|
}
|
|
518
|
+
function passwordRule(type, where) {
|
|
519
|
+
if (type.password === null) {
|
|
520
|
+
throw new TypeError(`${where}: the ${type.name} type does not sign in with a password — add password: { login } to it`);
|
|
521
|
+
}
|
|
522
|
+
return type.password;
|
|
523
|
+
}
|
|
517
524
|
function checkPassword(type, password, where) {
|
|
518
525
|
const minLength = type.password?.minLength ?? 8;
|
|
519
526
|
if (typeof password !== "string" || password.length < minLength) {
|
|
@@ -589,6 +596,33 @@ async function holderOfEmail(context, type, email) {
|
|
|
589
596
|
return held !== null && normalizeEmail(held) === wanted ? record : null;
|
|
590
597
|
}
|
|
591
598
|
|
|
599
|
+
// src/auth/events.ts
|
|
600
|
+
function resolveEvents(events, where) {
|
|
601
|
+
if (events === undefined)
|
|
602
|
+
return null;
|
|
603
|
+
if (typeof events !== "function") {
|
|
604
|
+
throw new TypeError(`${where}: events must be a function that takes a user event — webhooks({ … }) from @nxgt/janus-webhooks, or your own`);
|
|
605
|
+
}
|
|
606
|
+
return events;
|
|
607
|
+
}
|
|
608
|
+
async function emit(context, type, user, occurredAt) {
|
|
609
|
+
const listener = context.events;
|
|
610
|
+
if (listener === null)
|
|
611
|
+
return;
|
|
612
|
+
const event = Object.freeze({
|
|
613
|
+
id: mintId2(occurredAt.getTime()),
|
|
614
|
+
type,
|
|
615
|
+
occurredAt: new Date(occurredAt.getTime()),
|
|
616
|
+
userId: user.id,
|
|
617
|
+
userType: user.type
|
|
618
|
+
});
|
|
619
|
+
try {
|
|
620
|
+
await listener(event);
|
|
621
|
+
} catch (failure) {
|
|
622
|
+
process.emitWarning(`janus: the events listener failed on ${type} ${event.id} for user ${user.id}: ${failure instanceof Error ? failure.name : typeof failure}`, { code: "JANUS_EVENT_FAILED" });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
592
626
|
// src/auth/port/assert-stores.ts
|
|
593
627
|
var REQUIRED = {
|
|
594
628
|
users: [
|
|
@@ -607,7 +641,13 @@ var REQUIRED = {
|
|
|
607
641
|
"revokeUserSessions",
|
|
608
642
|
"deleteUserSessions"
|
|
609
643
|
],
|
|
610
|
-
tokens: [
|
|
644
|
+
tokens: [
|
|
645
|
+
"insertToken",
|
|
646
|
+
"consumeToken",
|
|
647
|
+
"countAttempt",
|
|
648
|
+
"spendUserTokens",
|
|
649
|
+
"deleteUserTokens"
|
|
650
|
+
]
|
|
611
651
|
};
|
|
612
652
|
function assertStores(stores, where) {
|
|
613
653
|
if (typeof stores !== "object" || stores === null) {
|
|
@@ -833,6 +873,12 @@ function refuseUnusable(token, now, where, noun) {
|
|
|
833
873
|
var unknownOneTime = (where, noun) => new TokenError2("TOKEN_UNKNOWN", `${where}: no such ${noun}`, {
|
|
834
874
|
operation: where
|
|
835
875
|
});
|
|
876
|
+
async function unknownChallenge(context, token, secret, where) {
|
|
877
|
+
if (token.attempts === CODE_ATTEMPTS) {
|
|
878
|
+
await burnOneTime(context, secret, token.kind);
|
|
879
|
+
}
|
|
880
|
+
return unknownOneTime(where, "challenge");
|
|
881
|
+
}
|
|
836
882
|
async function spendOneTime(context, secret, kind, where, noun) {
|
|
837
883
|
const now = context.clock.now();
|
|
838
884
|
const token = await context.store.tokens.consumeToken(hashSecret(secret), kind, now);
|
|
@@ -875,6 +921,113 @@ async function insertOneTime(context, token, code) {
|
|
|
875
921
|
return { secret, expiresAt };
|
|
876
922
|
}
|
|
877
923
|
|
|
924
|
+
// src/auth/password-written.ts
|
|
925
|
+
async function endSignInsWaiting(context, userId) {
|
|
926
|
+
await context.store.tokens.spendUserTokens(userId, "secondFactor", context.clock.now());
|
|
927
|
+
}
|
|
928
|
+
async function heldByPassword(context, type, verified, password, result, where) {
|
|
929
|
+
const now = await findRecord(context, verified.id, type.name);
|
|
930
|
+
const kept = now !== null && now.password !== null && (now.password.hash === verified.password?.hash || await passwordMatches(context, now, password, where));
|
|
931
|
+
if (kept)
|
|
932
|
+
return true;
|
|
933
|
+
if (result.status === "signedIn") {
|
|
934
|
+
await context.store.sessions.revokeSession(result.session.id, context.clock.now());
|
|
935
|
+
} else {
|
|
936
|
+
await burnOneTime(context, result.challenge, "secondFactor");
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/auth/email-flows.ts
|
|
942
|
+
function emailFlows(context, type, at) {
|
|
943
|
+
const { store, clock } = context;
|
|
944
|
+
const redeem = async (kind, secret, where) => {
|
|
945
|
+
const token = await spendOneTime(context, secret, kind, where, "token");
|
|
946
|
+
const user = await findRecord(context, token.userId, type.name);
|
|
947
|
+
if (user === null)
|
|
948
|
+
throw unknownOneTime(where, "token");
|
|
949
|
+
refuseStale(type, user, token, where, "token");
|
|
950
|
+
return { token, user };
|
|
951
|
+
};
|
|
952
|
+
const issue = async (kind, user, where) => {
|
|
953
|
+
const email = emailOf(type, user.fields);
|
|
954
|
+
if (email === null) {
|
|
955
|
+
throw new NotFoundError2(`${where}: the user has no e-mail`, {
|
|
956
|
+
userId: user.id,
|
|
957
|
+
userType: type.name,
|
|
958
|
+
operation: where
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const { secret, expiresAt } = await issueOneTime(context, {
|
|
962
|
+
kind,
|
|
963
|
+
userId: user.id,
|
|
964
|
+
address: email,
|
|
965
|
+
ttlMs: context.config.tokenTtlMs[kind]
|
|
966
|
+
});
|
|
967
|
+
return { token: secret, email, expiresAt };
|
|
968
|
+
};
|
|
969
|
+
return {
|
|
970
|
+
verifyEmail: {
|
|
971
|
+
async send(user) {
|
|
972
|
+
const where = at("verifyEmail.send");
|
|
973
|
+
const record = await getRecord(context, idOf(user), type.name, where);
|
|
974
|
+
return issue("verifyEmail", record, where);
|
|
975
|
+
},
|
|
976
|
+
async confirm(secret) {
|
|
977
|
+
const where = at("verifyEmail.confirm");
|
|
978
|
+
const { token, user } = await redeem("verifyEmail", secret, where);
|
|
979
|
+
let newlyVerified = false;
|
|
980
|
+
const written = await writeUser(context, user.id, type, undefined, where, (record, now) => {
|
|
981
|
+
refuseStale(type, record, token, where, "token");
|
|
982
|
+
newlyVerified = record.emailVerifiedAt === null;
|
|
983
|
+
return { emailVerifiedAt: now };
|
|
984
|
+
});
|
|
985
|
+
if (newlyVerified) {
|
|
986
|
+
await emit(context, "user.emailVerified", written, written.updatedAt);
|
|
987
|
+
}
|
|
988
|
+
return toUser(written);
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
resetPassword: {
|
|
992
|
+
async request(email) {
|
|
993
|
+
const where = at("resetPassword.request");
|
|
994
|
+
passwordRule(type, where);
|
|
995
|
+
const record = await holderOfEmail(context, type, String(email));
|
|
996
|
+
if (record === null)
|
|
997
|
+
return null;
|
|
998
|
+
const issued = await issue("resetPassword", record, where);
|
|
999
|
+
return { ...issued, user: toUser(record) };
|
|
1000
|
+
},
|
|
1001
|
+
async confirm(secret, password) {
|
|
1002
|
+
const where = at("resetPassword.confirm");
|
|
1003
|
+
passwordRule(type, where);
|
|
1004
|
+
checkPassword(type, password, where);
|
|
1005
|
+
const hash = await requireHasher(context, where).hash(password);
|
|
1006
|
+
const { token, user } = await redeem("resetPassword", secret, where);
|
|
1007
|
+
let newlyVerified = false;
|
|
1008
|
+
const written = await writeUser(context, user.id, type, undefined, where, (record, now) => {
|
|
1009
|
+
refuseStale(type, record, token, where, "token");
|
|
1010
|
+
newlyVerified = record.emailVerifiedAt === null;
|
|
1011
|
+
return {
|
|
1012
|
+
password: { hash, updatedAt: now },
|
|
1013
|
+
...record.emailVerifiedAt === null ? { emailVerifiedAt: now } : {}
|
|
1014
|
+
};
|
|
1015
|
+
});
|
|
1016
|
+
try {
|
|
1017
|
+
await store.sessions.revokeUserSessions(written.id, clock.now());
|
|
1018
|
+
await endSignInsWaiting(context, written.id);
|
|
1019
|
+
} finally {
|
|
1020
|
+
await emit(context, "user.passwordReset", written, written.updatedAt);
|
|
1021
|
+
if (newlyVerified) {
|
|
1022
|
+
await emit(context, "user.emailVerified", written, written.updatedAt);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return toUser(written);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
878
1031
|
// src/auth/totp.ts
|
|
879
1032
|
import { createHmac, randomBytes as randomBytes4 } from "node:crypto";
|
|
880
1033
|
var TOTP = { digits: 6, stepMs: 30000, drift: 1 };
|
|
@@ -990,7 +1143,12 @@ function challengeFlows(context, type, at) {
|
|
|
990
1143
|
address: "",
|
|
991
1144
|
ttlMs: configured.challengeTtlMs
|
|
992
1145
|
});
|
|
993
|
-
return {
|
|
1146
|
+
return {
|
|
1147
|
+
status: "secondFactor",
|
|
1148
|
+
challenge: secret,
|
|
1149
|
+
expiresAt,
|
|
1150
|
+
userId: record.id
|
|
1151
|
+
};
|
|
994
1152
|
},
|
|
995
1153
|
async confirm(challenge, code) {
|
|
996
1154
|
const where = at("secondFactor.confirm");
|
|
@@ -998,8 +1156,9 @@ function challengeFlows(context, type, at) {
|
|
|
998
1156
|
const secret = String(challenge);
|
|
999
1157
|
const { token, attemptsLeft } = await countCodeAttempt(context, secret, "secondFactor", where, type.name);
|
|
1000
1158
|
const record = await findRecord(context, token.userId, type.name);
|
|
1001
|
-
if (record === null)
|
|
1002
|
-
throw
|
|
1159
|
+
if (record === null) {
|
|
1160
|
+
throw await unknownChallenge(context, token, secret, where);
|
|
1161
|
+
}
|
|
1003
1162
|
if (!record.active) {
|
|
1004
1163
|
await spend(secret, where);
|
|
1005
1164
|
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
@@ -1116,6 +1275,7 @@ function signInCodeFlows(context, type, at, finish) {
|
|
|
1116
1275
|
address: String(record.fields[type.email]),
|
|
1117
1276
|
ttlMs: context.config.tokenTtlMs.signInCode
|
|
1118
1277
|
});
|
|
1278
|
+
await context.store.tokens.spendUserTokens(record.id, "signInCode", context.clock.now(), hashSecret(secret));
|
|
1119
1279
|
return {
|
|
1120
1280
|
code,
|
|
1121
1281
|
challenge: secret,
|
|
@@ -1129,8 +1289,9 @@ function signInCodeFlows(context, type, at, finish) {
|
|
|
1129
1289
|
const secret = String(challenge);
|
|
1130
1290
|
const { token, attemptsLeft } = await countCodeAttempt(context, secret, "signInCode", where, type.name);
|
|
1131
1291
|
const user = await findRecord(context, token.userId, type.name);
|
|
1132
|
-
if (user === null)
|
|
1133
|
-
throw
|
|
1292
|
+
if (user === null) {
|
|
1293
|
+
throw await unknownChallenge(context, token, secret, where);
|
|
1294
|
+
}
|
|
1134
1295
|
if (!codeMatches(token, secret, String(code))) {
|
|
1135
1296
|
if (token.attempts === CODE_ATTEMPTS) {
|
|
1136
1297
|
await burnOneTime(context, secret, "signInCode");
|
|
@@ -1145,7 +1306,10 @@ function signInCodeFlows(context, type, at, finish) {
|
|
|
1145
1306
|
userType: type.name
|
|
1146
1307
|
});
|
|
1147
1308
|
}
|
|
1148
|
-
|
|
1309
|
+
if (user.emailVerifiedAt !== null)
|
|
1310
|
+
return finish(user, where);
|
|
1311
|
+
const proved = await writeUser(context, user.id, type, { ifVersion: user.version }, where, (_, now) => ({ emailVerifiedAt: now }));
|
|
1312
|
+
await emit(context, "user.emailVerified", proved, proved.updatedAt);
|
|
1149
1313
|
return finish(proved, where);
|
|
1150
1314
|
}
|
|
1151
1315
|
};
|
|
@@ -1170,7 +1334,7 @@ function typeApi(context, type) {
|
|
|
1170
1334
|
updatedAt: now
|
|
1171
1335
|
};
|
|
1172
1336
|
}
|
|
1173
|
-
|
|
1337
|
+
const inserted = await store.users.insertUser({
|
|
1174
1338
|
id: mintId2(now.getTime()),
|
|
1175
1339
|
type: type.name,
|
|
1176
1340
|
schemaVersion: type.schemaVersion,
|
|
@@ -1184,37 +1348,8 @@ function typeApi(context, type) {
|
|
|
1184
1348
|
createdAt: now,
|
|
1185
1349
|
updatedAt: now
|
|
1186
1350
|
});
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const token = await spendOneTime(context, secret, kind, where, "token");
|
|
1190
|
-
const user = await findRecord(context, token.userId, type.name);
|
|
1191
|
-
if (user === null)
|
|
1192
|
-
throw unknownOneTime(where, "token");
|
|
1193
|
-
refuseStale(type, user, token, where, "token");
|
|
1194
|
-
return { token, user };
|
|
1195
|
-
};
|
|
1196
|
-
const issue = async (kind, user, where) => {
|
|
1197
|
-
const email = emailOf(type, user.fields);
|
|
1198
|
-
if (email === null) {
|
|
1199
|
-
throw new NotFoundError2(`${where}: the user has no e-mail`, {
|
|
1200
|
-
userId: user.id,
|
|
1201
|
-
userType: type.name,
|
|
1202
|
-
operation: where
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
const { secret, expiresAt } = await issueOneTime(context, {
|
|
1206
|
-
kind,
|
|
1207
|
-
userId: user.id,
|
|
1208
|
-
address: email,
|
|
1209
|
-
ttlMs: context.config.tokenTtlMs[kind]
|
|
1210
|
-
});
|
|
1211
|
-
return { token: secret, email, expiresAt };
|
|
1212
|
-
};
|
|
1213
|
-
const passwordRule = (where) => {
|
|
1214
|
-
if (type.password === null) {
|
|
1215
|
-
throw new TypeError(`${where}: the ${type.name} type does not sign in with a password — add password: { login } to it`);
|
|
1216
|
-
}
|
|
1217
|
-
return type.password;
|
|
1351
|
+
await emit(context, "user.created", inserted, inserted.createdAt);
|
|
1352
|
+
return inserted;
|
|
1218
1353
|
};
|
|
1219
1354
|
return {
|
|
1220
1355
|
async create(input) {
|
|
@@ -1267,15 +1402,22 @@ function typeApi(context, type) {
|
|
|
1267
1402
|
const record = await store.users.findUser(id);
|
|
1268
1403
|
if (record !== null && record.type !== type.name)
|
|
1269
1404
|
return false;
|
|
1405
|
+
const deletedAt = clock.now();
|
|
1270
1406
|
const deleted = record !== null && await store.users.deleteUser(id);
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1407
|
+
try {
|
|
1408
|
+
await store.sessions.deleteUserSessions(id);
|
|
1409
|
+
await store.tokens.deleteUserTokens(id);
|
|
1410
|
+
await context.relations?.deleteEntity({ type: type.name, id });
|
|
1411
|
+
} finally {
|
|
1412
|
+
if (deleted) {
|
|
1413
|
+
await emit(context, "user.deleted", { id, type: type.name }, deletedAt);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1274
1416
|
return deleted;
|
|
1275
1417
|
},
|
|
1276
1418
|
async signUp(input) {
|
|
1277
1419
|
const where = at("signUp");
|
|
1278
|
-
passwordRule(where);
|
|
1420
|
+
passwordRule(type, where);
|
|
1279
1421
|
if (input?.password === undefined) {
|
|
1280
1422
|
checkPassword(type, undefined, where);
|
|
1281
1423
|
}
|
|
@@ -1284,7 +1426,7 @@ function typeApi(context, type) {
|
|
|
1284
1426
|
},
|
|
1285
1427
|
async signIn(input) {
|
|
1286
1428
|
const where = at("signIn");
|
|
1287
|
-
const rule = passwordRule(where);
|
|
1429
|
+
const rule = passwordRule(type, where);
|
|
1288
1430
|
const hasher = requireHasher(context, where);
|
|
1289
1431
|
const login = input?.[rule.login];
|
|
1290
1432
|
const password = input?.password;
|
|
@@ -1303,28 +1445,33 @@ function typeApi(context, type) {
|
|
|
1303
1445
|
userType: type.name
|
|
1304
1446
|
});
|
|
1305
1447
|
}
|
|
1306
|
-
|
|
1448
|
+
const verified = await rehashed(context, record, String(password));
|
|
1449
|
+
const result = await secondFactor.finish(verified, where);
|
|
1450
|
+
if (!await heldByPassword(context, type, verified, String(password), result, where)) {
|
|
1451
|
+
throw refuse("wrongPassword");
|
|
1452
|
+
}
|
|
1453
|
+
return result;
|
|
1307
1454
|
},
|
|
1308
1455
|
async findByLogin(login) {
|
|
1309
|
-
const rule = passwordRule(at("findByLogin"));
|
|
1456
|
+
const rule = passwordRule(type, at("findByLogin"));
|
|
1310
1457
|
const record = await byLogin(rule.normalize(login));
|
|
1311
1458
|
return record === null ? null : toUser(record);
|
|
1312
1459
|
},
|
|
1313
1460
|
async setPassword(user, password, options) {
|
|
1314
1461
|
const where = at("setPassword");
|
|
1315
|
-
passwordRule(where);
|
|
1462
|
+
passwordRule(type, where);
|
|
1316
1463
|
checkPassword(type, password, where);
|
|
1317
1464
|
const hash = await requireHasher(context, where).hash(password);
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1465
|
+
const written = await writeUser(context, user, type, options, where, (_, now) => ({ password: { hash, updatedAt: now } }));
|
|
1466
|
+
await endSignInsWaiting(context, written.id);
|
|
1467
|
+
return toUser(written);
|
|
1321
1468
|
},
|
|
1322
1469
|
async changePassword(user, change, options) {
|
|
1323
1470
|
const where = at("changePassword");
|
|
1324
|
-
passwordRule(where);
|
|
1471
|
+
passwordRule(type, where);
|
|
1325
1472
|
checkPassword(type, change?.next, where);
|
|
1326
1473
|
const hasher = requireHasher(context, where);
|
|
1327
|
-
|
|
1474
|
+
const written = await writeUser(context, user, type, options, where, async (record, now) => {
|
|
1328
1475
|
if (record.password === null || !await passwordMatches(context, record, change.current, where)) {
|
|
1329
1476
|
throw new CredentialError2("CREDENTIALS_INVALID", `${where}: the current password does not match`, {
|
|
1330
1477
|
reason: record.password === null ? "noPassword" : "wrongPassword",
|
|
@@ -1338,48 +1485,13 @@ function typeApi(context, type) {
|
|
|
1338
1485
|
updatedAt: now
|
|
1339
1486
|
}
|
|
1340
1487
|
};
|
|
1341
|
-
})
|
|
1488
|
+
});
|
|
1489
|
+
await endSignInsWaiting(context, written.id);
|
|
1490
|
+
return toUser(written);
|
|
1342
1491
|
},
|
|
1343
1492
|
secondFactor: secondFactor.api,
|
|
1344
1493
|
signInCode,
|
|
1345
|
-
|
|
1346
|
-
async send(user) {
|
|
1347
|
-
const where = at("verifyEmail.send");
|
|
1348
|
-
const record = await getRecord(context, idOf(user), type.name, where);
|
|
1349
|
-
return issue("verifyEmail", record, where);
|
|
1350
|
-
},
|
|
1351
|
-
async confirm(secret) {
|
|
1352
|
-
const where = at("verifyEmail.confirm");
|
|
1353
|
-
const { user } = await redeem("verifyEmail", secret, where);
|
|
1354
|
-
return toUser(await writeUser(context, user.id, type, undefined, where, (_, now) => ({
|
|
1355
|
-
emailVerifiedAt: now
|
|
1356
|
-
})));
|
|
1357
|
-
}
|
|
1358
|
-
},
|
|
1359
|
-
resetPassword: {
|
|
1360
|
-
async request(email) {
|
|
1361
|
-
const where = at("resetPassword.request");
|
|
1362
|
-
passwordRule(where);
|
|
1363
|
-
const record = await holderOfEmail(context, type, String(email));
|
|
1364
|
-
if (record === null)
|
|
1365
|
-
return null;
|
|
1366
|
-
const issued = await issue("resetPassword", record, where);
|
|
1367
|
-
return { ...issued, user: toUser(record) };
|
|
1368
|
-
},
|
|
1369
|
-
async confirm(secret, password) {
|
|
1370
|
-
const where = at("resetPassword.confirm");
|
|
1371
|
-
passwordRule(where);
|
|
1372
|
-
checkPassword(type, password, where);
|
|
1373
|
-
const hash = await requireHasher(context, where).hash(password);
|
|
1374
|
-
const { user } = await redeem("resetPassword", secret, where);
|
|
1375
|
-
const written = await writeUser(context, user.id, type, undefined, where, (record, now) => ({
|
|
1376
|
-
password: { hash, updatedAt: now },
|
|
1377
|
-
...record.emailVerifiedAt === null ? { emailVerifiedAt: now } : {}
|
|
1378
|
-
}));
|
|
1379
|
-
await store.sessions.revokeUserSessions(written.id, clock.now());
|
|
1380
|
-
return toUser(written);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1494
|
+
...emailFlows(context, type, at)
|
|
1383
1495
|
};
|
|
1384
1496
|
}
|
|
1385
1497
|
|
|
@@ -1407,7 +1519,7 @@ function janus(config) {
|
|
|
1407
1519
|
if (relations !== null && typeof relations.deleteEntity !== "function") {
|
|
1408
1520
|
throw new TypeError(`${where}: relations must be a relation store — relations.deleteEntity is missing`);
|
|
1409
1521
|
}
|
|
1410
|
-
const context = createContext(resolved, guardStores(config.store), relations === null ? null : guardRelations(relations), capabilities, config.clock ?? systemClock, hasher, verifiers);
|
|
1522
|
+
const context = createContext(resolved, guardStores(config.store), relations === null ? null : guardRelations(relations), capabilities, config.clock ?? systemClock, hasher, verifiers, resolveEvents(config.events, where));
|
|
1411
1523
|
const shared = sharedApi(context);
|
|
1412
1524
|
const types = Object.fromEntries([...resolved.types.values()].map((type) => [
|
|
1413
1525
|
type.name,
|
|
@@ -1455,5 +1567,5 @@ export {
|
|
|
1455
1567
|
systemClock
|
|
1456
1568
|
};
|
|
1457
1569
|
|
|
1458
|
-
//# debugId=
|
|
1570
|
+
//# debugId=52492AAB2CF93D3A64756E2164756E21
|
|
1459
1571
|
//# sourceMappingURL=index.js.map
|