@vsaas/loopback 12.4.0 → 12.5.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/common/models/user.json
CHANGED
|
@@ -52,6 +52,12 @@
|
|
|
52
52
|
"permission": "ALLOW",
|
|
53
53
|
"property": "verifyTwoFactor"
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
"principalType": "ROLE",
|
|
57
|
+
"principalId": "$authenticated",
|
|
58
|
+
"permission": "ALLOW",
|
|
59
|
+
"property": "getTwoFactorStatus"
|
|
60
|
+
},
|
|
55
61
|
{
|
|
56
62
|
"principalType": "ROLE",
|
|
57
63
|
"principalId": "$authenticated",
|
|
@@ -3,6 +3,7 @@ const require_runtime$1 = require("../../_virtual/_rolldown/runtime.cjs");
|
|
|
3
3
|
const require_lib_globalize = require("../../lib/globalize.cjs");
|
|
4
4
|
const require_lib_runtime = require("../../lib/runtime.cjs");
|
|
5
5
|
const require_lib_utils = require("../../lib/utils.cjs");
|
|
6
|
+
const require_lib_two_factor_service = require("../../lib/two-factor/service.cjs");
|
|
6
7
|
//#region src/common/models/user.ts
|
|
7
8
|
var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, module) => {
|
|
8
9
|
const path = require("path");
|
|
@@ -12,6 +13,7 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
12
13
|
require("fs");
|
|
13
14
|
const isEmail = require("isemail");
|
|
14
15
|
const utils = require_lib_utils;
|
|
16
|
+
const { TWO_FACTOR_ENROLLMENT_SCOPE } = (require_lib_two_factor_service.init_service(), require_runtime$1.__toCommonJS(require_lib_two_factor_service.service_exports));
|
|
15
17
|
const qs = require("querystring");
|
|
16
18
|
const crypto = require("crypto");
|
|
17
19
|
const assert = require("assert");
|
|
@@ -209,6 +211,17 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
209
211
|
process.nextTick(fn, null, true);
|
|
210
212
|
return fn.promise;
|
|
211
213
|
};
|
|
214
|
+
/**
|
|
215
|
+
* Whether the application requires `user` to use a second factor. Override
|
|
216
|
+
* it to enforce a policy: a user that has not enrolled yet logs in with a
|
|
217
|
+
* session restricted to the enrollment, and cannot disable the factor.
|
|
218
|
+
*/
|
|
219
|
+
User.isTwoFactorRequired = function(user, options, fn) {
|
|
220
|
+
if (typeof options === "function") fn = options;
|
|
221
|
+
fn = fn || utils.createPromiseCallback();
|
|
222
|
+
process.nextTick(fn, null, false);
|
|
223
|
+
return fn.promise;
|
|
224
|
+
};
|
|
212
225
|
User.buildTwoFactorLoginContext = function(credentials, options, fn) {
|
|
213
226
|
if (typeof options === "function") fn = options;
|
|
214
227
|
fn = fn || utils.createPromiseCallback();
|
|
@@ -235,6 +248,61 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
235
248
|
}
|
|
236
249
|
return this._authenticationSecurity.verifyTwoFactor(verification, options || {}, fn);
|
|
237
250
|
};
|
|
251
|
+
function twoFactorUnavailable(Model, fn) {
|
|
252
|
+
fn = fn || utils.createPromiseCallback();
|
|
253
|
+
const error = /* @__PURE__ */ new Error("Two-factor authentication is not configured");
|
|
254
|
+
error.statusCode = Model.settings.twoFactor == null ? 404 : 503;
|
|
255
|
+
error.code = Model.settings.twoFactor == null ? "TWO_FACTOR_NOT_CONFIGURED" : "AUTHENTICATION_SECURITY_UNAVAILABLE";
|
|
256
|
+
process.nextTick(fn, error);
|
|
257
|
+
return fn.promise;
|
|
258
|
+
}
|
|
259
|
+
User.getTwoFactorStatus = function(userId, options, fn) {
|
|
260
|
+
if (typeof options === "function") {
|
|
261
|
+
fn = options;
|
|
262
|
+
options = void 0;
|
|
263
|
+
}
|
|
264
|
+
if (!this._authenticationSecurityReady || !this._authenticationSecurity) return twoFactorUnavailable(this, fn);
|
|
265
|
+
return this._authenticationSecurity.status(userId, options || {}, fn);
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* DANGER: removes the second factor of user `userId` and closes their
|
|
269
|
+
* sessions, so their account is protected by the password alone until they
|
|
270
|
+
* enroll again. Whoever can call this can strip 2FA from that user.
|
|
271
|
+
*
|
|
272
|
+
* It is deliberately not a remote method: an application that exposes it
|
|
273
|
+
* must declare its own endpoint and grant it to the narrowest roles. As a
|
|
274
|
+
* floor, this method requires an authenticated actor other than the user,
|
|
275
|
+
* who must prove themselves with `request.currentPassword` and, when they
|
|
276
|
+
* use 2FA, `request.totpCode`. It emits `twoFactorReset` with the actor.
|
|
277
|
+
*/
|
|
278
|
+
User.resetTwoFactor = function(userId, request, options, fn) {
|
|
279
|
+
if (typeof options === "function") {
|
|
280
|
+
fn = options;
|
|
281
|
+
options = void 0;
|
|
282
|
+
}
|
|
283
|
+
fn = fn || utils.createPromiseCallback();
|
|
284
|
+
const Model = this;
|
|
285
|
+
const token = options && options.accessToken;
|
|
286
|
+
if (!Model._authenticationSecurityReady || !Model._authenticationSecurity) return twoFactorUnavailable(Model, fn);
|
|
287
|
+
const ActorModel = token && token.principalType && token.principalType !== Model.modelName ? Model.app && Model.app.models[token.principalType] : Model;
|
|
288
|
+
if (!token || token.userId == null || !ActorModel) {
|
|
289
|
+
const error = /* @__PURE__ */ new Error("Authorization required");
|
|
290
|
+
error.statusCode = 401;
|
|
291
|
+
error.code = "AUTHORIZATION_REQUIRED";
|
|
292
|
+
process.nextTick(fn, error);
|
|
293
|
+
return fn.promise;
|
|
294
|
+
}
|
|
295
|
+
if (ActorModel === Model && String(token.userId) === String(userId)) {
|
|
296
|
+
const error = /* @__PURE__ */ new Error("Use disable to change your own factor");
|
|
297
|
+
error.statusCode = 403;
|
|
298
|
+
error.code = "TWO_FACTOR_RESET_SELF";
|
|
299
|
+
process.nextTick(fn, error);
|
|
300
|
+
return fn.promise;
|
|
301
|
+
}
|
|
302
|
+
if (!ActorModel._authenticationSecurityReady || !ActorModel._authenticationSecurity) return twoFactorUnavailable(ActorModel, fn);
|
|
303
|
+
ActorModel._authenticationSecurity.proveActor(token.userId, request || {}, options).then(() => Model._authenticationSecurity.reset(userId, options)).then((result) => fn(null, result), fn);
|
|
304
|
+
return fn.promise;
|
|
305
|
+
};
|
|
238
306
|
for (const method of [
|
|
239
307
|
"beginTwoFactorEnrollment",
|
|
240
308
|
"confirmTwoFactorEnrollment",
|
|
@@ -245,14 +313,7 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
245
313
|
fn = options;
|
|
246
314
|
options = void 0;
|
|
247
315
|
}
|
|
248
|
-
if (!this._authenticationSecurityReady || !this._authenticationSecurity)
|
|
249
|
-
fn = fn || utils.createPromiseCallback();
|
|
250
|
-
const error = /* @__PURE__ */ new Error("Two-factor authentication is not configured");
|
|
251
|
-
error.statusCode = this.settings.twoFactor == null ? 404 : 503;
|
|
252
|
-
error.code = this.settings.twoFactor == null ? "TWO_FACTOR_NOT_CONFIGURED" : "AUTHENTICATION_SECURITY_UNAVAILABLE";
|
|
253
|
-
process.nextTick(fn, error);
|
|
254
|
-
return fn.promise;
|
|
255
|
-
}
|
|
316
|
+
if (!this._authenticationSecurityReady || !this._authenticationSecurity) return twoFactorUnavailable(this, fn);
|
|
256
317
|
const serviceMethod = {
|
|
257
318
|
beginTwoFactorEnrollment: "beginEnrollment",
|
|
258
319
|
confirmTwoFactorEnrollment: "confirmEnrollment",
|
|
@@ -690,9 +751,33 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
690
751
|
path: "/two-factor/verify"
|
|
691
752
|
}
|
|
692
753
|
});
|
|
693
|
-
|
|
754
|
+
const enrollmentScopes = ["DEFAULT", TWO_FACTOR_ENROLLMENT_SCOPE];
|
|
755
|
+
UserModel.remoteMethod("getTwoFactorStatus", {
|
|
756
|
+
description: "Two-factor state of the current user.",
|
|
757
|
+
accepts: [{
|
|
758
|
+
arg: "userId",
|
|
759
|
+
type: "any",
|
|
760
|
+
http: getUserIdFromRequestContext
|
|
761
|
+
}, {
|
|
762
|
+
arg: "options",
|
|
763
|
+
type: "object",
|
|
764
|
+
http: buildAuthenticationOptions
|
|
765
|
+
}],
|
|
766
|
+
returns: {
|
|
767
|
+
arg: "status",
|
|
768
|
+
type: "object",
|
|
769
|
+
root: true
|
|
770
|
+
},
|
|
771
|
+
accessScopes: enrollmentScopes,
|
|
772
|
+
http: {
|
|
773
|
+
verb: "get",
|
|
774
|
+
path: "/two-factor/status"
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
function registerTwoFactorManagementMethod(name, path, description, accessScopes) {
|
|
694
778
|
UserModel.remoteMethod(name, {
|
|
695
779
|
description,
|
|
780
|
+
...accessScopes ? { accessScopes } : {},
|
|
696
781
|
accepts: [
|
|
697
782
|
{
|
|
698
783
|
arg: "userId",
|
|
@@ -722,13 +807,14 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
722
807
|
}
|
|
723
808
|
});
|
|
724
809
|
}
|
|
725
|
-
registerTwoFactorManagementMethod("beginTwoFactorEnrollment", "/two-factor/setup", "Begin two-factor enrollment.");
|
|
726
|
-
registerTwoFactorManagementMethod("confirmTwoFactorEnrollment", "/two-factor/confirm", "Confirm two-factor enrollment.");
|
|
810
|
+
registerTwoFactorManagementMethod("beginTwoFactorEnrollment", "/two-factor/setup", "Begin two-factor enrollment.", enrollmentScopes);
|
|
811
|
+
registerTwoFactorManagementMethod("confirmTwoFactorEnrollment", "/two-factor/confirm", "Confirm two-factor enrollment.", enrollmentScopes);
|
|
727
812
|
registerTwoFactorManagementMethod("regenerateTwoFactorRecoveryCodes", "/two-factor/recovery-codes", "Regenerate two-factor recovery codes.");
|
|
728
813
|
registerTwoFactorManagementMethod("disableTwoFactor", "/two-factor/disable", "Disable two-factor authentication.");
|
|
729
814
|
for (const method of [
|
|
730
815
|
"login",
|
|
731
816
|
"verifyTwoFactor",
|
|
817
|
+
"getTwoFactorStatus",
|
|
732
818
|
"beginTwoFactorEnrollment",
|
|
733
819
|
"confirmTwoFactorEnrollment",
|
|
734
820
|
"regenerateTwoFactorRecoveryCodes",
|
|
@@ -741,6 +827,7 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
741
827
|
for (const method of [
|
|
742
828
|
"login",
|
|
743
829
|
"verifyTwoFactor",
|
|
830
|
+
"getTwoFactorStatus",
|
|
744
831
|
"beginTwoFactorEnrollment",
|
|
745
832
|
"confirmTwoFactorEnrollment",
|
|
746
833
|
"regenerateTwoFactorRecoveryCodes",
|
|
@@ -765,6 +852,7 @@ var require_user = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, mo
|
|
|
765
852
|
},
|
|
766
853
|
description: "Do not supply this argument, it is automatically extracted from request headers."
|
|
767
854
|
}],
|
|
855
|
+
accessScopes: enrollmentScopes,
|
|
768
856
|
http: { verb: "all" }
|
|
769
857
|
});
|
|
770
858
|
UserModel.remoteMethod("prototype.verify", {
|
|
@@ -48,6 +48,12 @@ var require_user = /* @__PURE__ */ require("../../_virtual/_rolldown/runtime.cjs
|
|
|
48
48
|
"permission": "ALLOW",
|
|
49
49
|
"property": "verifyTwoFactor"
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
"principalType": "ROLE",
|
|
53
|
+
"principalId": "$authenticated",
|
|
54
|
+
"permission": "ALLOW",
|
|
55
|
+
"property": "getTwoFactorStatus"
|
|
56
|
+
},
|
|
51
57
|
{
|
|
52
58
|
"principalType": "ROLE",
|
|
53
59
|
"principalId": "$authenticated",
|
|
@@ -5,6 +5,10 @@ const require_lib_two_factor_crypto = require("./crypto.cjs");
|
|
|
5
5
|
const require_lib_two_factor_totp = require("./totp.cjs");
|
|
6
6
|
const require_lib_two_factor_recovery_codes = require("./recovery-codes.cjs");
|
|
7
7
|
//#region src/lib/two-factor/service.ts
|
|
8
|
+
var service_exports = /* @__PURE__ */ require_runtime.__exportAll({
|
|
9
|
+
TWO_FACTOR_ENROLLMENT_SCOPE: () => TWO_FACTOR_ENROLLMENT_SCOPE,
|
|
10
|
+
TwoFactorService: () => TwoFactorService
|
|
11
|
+
});
|
|
8
12
|
function count(result) {
|
|
9
13
|
return typeof result === "number" ? result : Number(result?.count || 0);
|
|
10
14
|
}
|
|
@@ -30,6 +34,9 @@ function invoke(target, name, args) {
|
|
|
30
34
|
if (result && typeof result.then === "function") result.then((value) => done(null, value), done);
|
|
31
35
|
});
|
|
32
36
|
}
|
|
37
|
+
function hasEnrollmentScope(token) {
|
|
38
|
+
return Array.isArray(token?.scopes) && token.scopes.includes("two-factor-enrollment");
|
|
39
|
+
}
|
|
33
40
|
function includeUser(include) {
|
|
34
41
|
if (Array.isArray(include)) return include.map(String).map((v) => v.toLowerCase()).includes("user");
|
|
35
42
|
return String(include || "").toLowerCase() === "user";
|
|
@@ -40,12 +47,13 @@ function loginError() {
|
|
|
40
47
|
error.code = "LOGIN_FAILED";
|
|
41
48
|
return error;
|
|
42
49
|
}
|
|
43
|
-
var TwoFactorService;
|
|
50
|
+
var TWO_FACTOR_ENROLLMENT_SCOPE, TwoFactorService;
|
|
44
51
|
var init_service = require_runtime.__esmMin((() => {
|
|
45
52
|
require_lib_two_factor_errors.init_errors();
|
|
46
53
|
require_lib_two_factor_crypto.init_crypto();
|
|
47
54
|
require_lib_two_factor_totp.init_totp();
|
|
48
55
|
require_lib_two_factor_recovery_codes.init_recovery_codes();
|
|
56
|
+
TWO_FACTOR_ENROLLMENT_SCOPE = "two-factor-enrollment";
|
|
49
57
|
TwoFactorService = class {
|
|
50
58
|
User;
|
|
51
59
|
config;
|
|
@@ -84,12 +92,31 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
84
92
|
confirmEnrollment(userId, request, options, callback) {
|
|
85
93
|
return optionalCallback(this.confirmEnrollmentInternal(userId, request || {}, options || {}), callback);
|
|
86
94
|
}
|
|
95
|
+
status(userId, options, callback) {
|
|
96
|
+
return optionalCallback(this.statusInternal(userId, options || {}), callback);
|
|
97
|
+
}
|
|
87
98
|
regenerateRecoveryCodes(userId, request, options, callback) {
|
|
88
99
|
return optionalCallback(this.regenerateRecoveryCodesInternal(userId, request || {}, options || {}), callback);
|
|
89
100
|
}
|
|
90
101
|
disable(userId, request, options, callback) {
|
|
91
102
|
return optionalCallback(this.disableInternal(userId, request || {}, options || {}), callback);
|
|
92
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Removes the second factor of another user, e.g. after the device and the
|
|
106
|
+
* recovery codes were lost. Called through `User.resetTwoFactor` once the
|
|
107
|
+
* actor proved itself (`proveActor`); see its warning.
|
|
108
|
+
*/
|
|
109
|
+
reset(principalId, options, callback) {
|
|
110
|
+
return optionalCallback(this.resetInternal(String(principalId), options || {}), callback);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Step-up for an operation on another user: the actor of `options` signs
|
|
114
|
+
* it with their password, and with their authenticator code when they use
|
|
115
|
+
* a second factor. Recovery codes are not accepted here.
|
|
116
|
+
*/
|
|
117
|
+
proveActor(actorId, request, options, callback) {
|
|
118
|
+
return optionalCallback(this.proveActorInternal(String(actorId), request || {}, options || {}), callback);
|
|
119
|
+
}
|
|
93
120
|
deletePrincipals(principalIds, callback) {
|
|
94
121
|
return optionalCallback(this.deletePrincipalsInternal(principalIds.map(String)), callback);
|
|
95
122
|
}
|
|
@@ -147,6 +174,7 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
147
174
|
} }) : null;
|
|
148
175
|
if (!credential) {
|
|
149
176
|
if (this.rateLimiter) await this.rateLimiter.reset(observed);
|
|
177
|
+
if (await this.isRequired(user, options)) return this.issueEnrollmentToken(user, normalizedInclude);
|
|
150
178
|
return this.issueAccessToken(user, credentials, normalizedInclude, false);
|
|
151
179
|
}
|
|
152
180
|
const secondFactorObserved = [];
|
|
@@ -268,6 +296,32 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
268
296
|
if (includeUser(include)) token.__data.user = user;
|
|
269
297
|
return token;
|
|
270
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Issues the session of a user that must enroll before using the API. The
|
|
301
|
+
* token has to come back restricted: an application `createAccessToken`
|
|
302
|
+
* override may swap the scope (e.g. a forced password change), but an
|
|
303
|
+
* unrestricted token would bypass the requirement, so it is revoked.
|
|
304
|
+
*/
|
|
305
|
+
async issueEnrollmentToken(user, include) {
|
|
306
|
+
const config = this.requireConfig(true);
|
|
307
|
+
const token = await invoke(user, "createAccessToken", [{
|
|
308
|
+
ttl: Math.ceil(config.enrollmentTtlMs / 1e3),
|
|
309
|
+
scopes: [TWO_FACTOR_ENROLLMENT_SCOPE]
|
|
310
|
+
}, {}]);
|
|
311
|
+
if (!token?.id) throw require_lib_two_factor_errors.authError("AUTHENTICATION_SECURITY_UNAVAILABLE", 503);
|
|
312
|
+
const scopes = token.scopes;
|
|
313
|
+
if (!Array.isArray(scopes) || !scopes.length || scopes.includes("DEFAULT")) {
|
|
314
|
+
await this.revokeToken(token);
|
|
315
|
+
throw require_lib_two_factor_errors.authError("AUTHENTICATION_SECURITY_UNAVAILABLE", 503);
|
|
316
|
+
}
|
|
317
|
+
if (includeUser(include)) token.__data.user = user;
|
|
318
|
+
return token;
|
|
319
|
+
}
|
|
320
|
+
/** Enrollment can only be demanded while users are able to enroll. */
|
|
321
|
+
async isRequired(user, options) {
|
|
322
|
+
if (this.config?.mode !== "enroll-and-verify") return false;
|
|
323
|
+
return await invoke(this.User, "isTwoFactorRequired", [user, options]) === true;
|
|
324
|
+
}
|
|
271
325
|
async buildTwoFactorTokenOptions(credentials, options) {
|
|
272
326
|
return invoke(this.User, "buildTwoFactorLoginContext", [credentials, options]);
|
|
273
327
|
}
|
|
@@ -607,6 +661,21 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
607
661
|
throw require_lib_two_factor_errors.authError("AUTHENTICATION_SECURITY_UNAVAILABLE", 503, "Authentication security reconciliation required");
|
|
608
662
|
}
|
|
609
663
|
}
|
|
664
|
+
async statusInternal(userId, options) {
|
|
665
|
+
const config = this.requireConfig();
|
|
666
|
+
const user = await this.authenticatedUser(userId, options);
|
|
667
|
+
const enabled = config.mode !== "disabled" && await this.Credential.count({
|
|
668
|
+
principalType: this.User.modelName,
|
|
669
|
+
principalId: String(userId),
|
|
670
|
+
provider: "totp",
|
|
671
|
+
status: "enabled"
|
|
672
|
+
}) > 0;
|
|
673
|
+
return {
|
|
674
|
+
mode: config.mode,
|
|
675
|
+
enabled,
|
|
676
|
+
required: await this.isRequired(user, options)
|
|
677
|
+
};
|
|
678
|
+
}
|
|
610
679
|
async beginEnrollmentInternal(userId, request, options) {
|
|
611
680
|
const config = this.requireConfig(true);
|
|
612
681
|
const user = await this.authenticatedUser(userId, options);
|
|
@@ -707,7 +776,7 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
707
776
|
const generated = require_lib_two_factor_recovery_codes.generateRecoveryCodes(config.recoveryCodeCount, config.recoveryCodeBytes);
|
|
708
777
|
await this.persistRecoverySet(credential, generated);
|
|
709
778
|
try {
|
|
710
|
-
await this.revokeSessions(principalId, options, config.sessions.onEnable);
|
|
779
|
+
await this.revokeSessions(principalId, options, hasEnrollmentScope(options.accessToken) ? "revoke-all" : config.sessions.onEnable);
|
|
711
780
|
if (count(await this.Credential.updateAll({
|
|
712
781
|
id: credential.id,
|
|
713
782
|
status: "pending",
|
|
@@ -941,8 +1010,44 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
941
1010
|
status: "unused"
|
|
942
1011
|
}) !== rows.length) throw new Error("Incomplete recovery code set");
|
|
943
1012
|
}
|
|
1013
|
+
async proveActorInternal(actorId, request, options) {
|
|
1014
|
+
const config = this.requireConfig();
|
|
1015
|
+
const actor = await this.authenticatedUser(actorId, options);
|
|
1016
|
+
const observed = await this.reauthenticateManagement(actor, request.currentPassword);
|
|
1017
|
+
const credential = config.mode === "disabled" ? null : await this.Credential.findOne({ where: {
|
|
1018
|
+
principalType: this.User.modelName,
|
|
1019
|
+
principalId: actorId,
|
|
1020
|
+
provider: "totp",
|
|
1021
|
+
status: "enabled"
|
|
1022
|
+
} });
|
|
1023
|
+
if (credential) {
|
|
1024
|
+
if (!(await this.verifyCredentialFactor(credential, "totp", request.totpCode, `management:${require_lib_two_factor_crypto.randomToken(16)}`)).valid) {
|
|
1025
|
+
await this.consumeManagementFailure(actorId);
|
|
1026
|
+
throw require_lib_two_factor_errors.invalidFactor();
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (this.rateLimiter && observed) await this.rateLimiter.reset([observed]);
|
|
1030
|
+
}
|
|
1031
|
+
async resetInternal(principalId, options) {
|
|
1032
|
+
this.requireConfig();
|
|
1033
|
+
const token = options.accessToken;
|
|
1034
|
+
if (!token || token.userId == null) throw require_lib_two_factor_errors.authError("AUTHORIZATION_REQUIRED", 401, "Authorization required");
|
|
1035
|
+
if (String(token.userId) === principalId && (!("principalType" in token) || token.principalType === this.User.modelName)) throw require_lib_two_factor_errors.authError("TWO_FACTOR_RESET_SELF", 403, "Use disable to change your own factor");
|
|
1036
|
+
if (!await this.User.findById(principalId)) throw require_lib_two_factor_errors.authError("NOT_FOUND", 404, "User not found");
|
|
1037
|
+
await this.revokeSessions(principalId, {}, "revoke-all");
|
|
1038
|
+
await this.destroyFactors([principalId]);
|
|
1039
|
+
this.audit("twoFactorReset", {
|
|
1040
|
+
principalId,
|
|
1041
|
+
actorPrincipalType: token.principalType ?? this.User.modelName,
|
|
1042
|
+
actorPrincipalId: String(token.userId)
|
|
1043
|
+
});
|
|
1044
|
+
return { enabled: false };
|
|
1045
|
+
}
|
|
944
1046
|
async deletePrincipalsInternal(principalIds) {
|
|
945
1047
|
if (!principalIds.length) return;
|
|
1048
|
+
await Promise.all([this.destroyFactors(principalIds), this.rateLimiter ? Promise.all(principalIds.map((principalId) => this.rateLimiter.deletePrincipal(principalId))) : Promise.resolve()]);
|
|
1049
|
+
}
|
|
1050
|
+
async destroyFactors(principalIds) {
|
|
946
1051
|
const credentialIds = (await this.Credential.find({
|
|
947
1052
|
where: {
|
|
948
1053
|
principalType: this.User.modelName,
|
|
@@ -959,8 +1064,7 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
959
1064
|
this.Credential.destroyAll({
|
|
960
1065
|
principalType: this.User.modelName,
|
|
961
1066
|
principalId: { inq: principalIds }
|
|
962
|
-
})
|
|
963
|
-
this.rateLimiter ? Promise.all(principalIds.map((principalId) => this.rateLimiter.deletePrincipal(principalId))) : Promise.resolve()
|
|
1067
|
+
})
|
|
964
1068
|
]);
|
|
965
1069
|
}
|
|
966
1070
|
async rotateEncryptionInternal(options) {
|
|
@@ -1099,6 +1203,7 @@ var init_service = require_runtime.__esmMin((() => {
|
|
|
1099
1203
|
}));
|
|
1100
1204
|
//#endregion
|
|
1101
1205
|
init_service();
|
|
1206
|
+
exports.TWO_FACTOR_ENROLLMENT_SCOPE = TWO_FACTOR_ENROLLMENT_SCOPE;
|
|
1102
1207
|
exports.TwoFactorService = TwoFactorService;
|
|
1103
1208
|
Object.defineProperty(exports, "init_service", {
|
|
1104
1209
|
enumerable: true,
|
|
@@ -1106,3 +1211,9 @@ Object.defineProperty(exports, "init_service", {
|
|
|
1106
1211
|
return init_service;
|
|
1107
1212
|
}
|
|
1108
1213
|
});
|
|
1214
|
+
Object.defineProperty(exports, "service_exports", {
|
|
1215
|
+
enumerable: true,
|
|
1216
|
+
get: function() {
|
|
1217
|
+
return service_exports;
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { NormalizedTwoFactorConfig } from "./config.cjs";
|
|
2
2
|
import { AuthenticationRateLimiter } from "./rate-limit.cjs";
|
|
3
3
|
//#region src/lib/two-factor/service.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Scope of the access token issued at login when the application requires a
|
|
6
|
+
* second factor (`User.isTwoFactorRequired`) and the user has not enrolled yet.
|
|
7
|
+
* It only reaches the methods that complete the enrollment.
|
|
8
|
+
*/
|
|
9
|
+
declare const TWO_FACTOR_ENROLLMENT_SCOPE = "two-factor-enrollment";
|
|
4
10
|
declare class TwoFactorService {
|
|
5
11
|
private readonly User;
|
|
6
12
|
private readonly config;
|
|
@@ -23,12 +29,31 @@ declare class TwoFactorService {
|
|
|
23
29
|
enabled: boolean;
|
|
24
30
|
recoveryCodes: string[];
|
|
25
31
|
}>;
|
|
32
|
+
status(userId: any, options: any, callback?: Function): Promise<{
|
|
33
|
+
mode: "disabled" | "verify-only" | "enroll-and-verify";
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
required: boolean;
|
|
36
|
+
}>;
|
|
26
37
|
regenerateRecoveryCodes(userId: any, request: any, options: any, callback?: Function): Promise<{
|
|
27
38
|
recoveryCodes: any;
|
|
28
39
|
}>;
|
|
29
40
|
disable(userId: any, request: any, options: any, callback?: Function): Promise<{
|
|
30
41
|
enabled: boolean;
|
|
31
42
|
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Removes the second factor of another user, e.g. after the device and the
|
|
45
|
+
* recovery codes were lost. Called through `User.resetTwoFactor` once the
|
|
46
|
+
* actor proved itself (`proveActor`); see its warning.
|
|
47
|
+
*/
|
|
48
|
+
reset(principalId: any, options: any, callback?: Function): Promise<{
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Step-up for an operation on another user: the actor of `options` signs
|
|
53
|
+
* it with their password, and with their authenticator code when they use
|
|
54
|
+
* a second factor. Recovery codes are not accepted here.
|
|
55
|
+
*/
|
|
56
|
+
proveActor(actorId: any, request: any, options: any, callback?: Function): Promise<void>;
|
|
32
57
|
deletePrincipals(principalIds: ReadonlyArray<any>, callback?: Function): Promise<void>;
|
|
33
58
|
rotateEncryption(options?: any, callback?: Function): Promise<{
|
|
34
59
|
credentialsReencrypted: number;
|
|
@@ -45,6 +70,15 @@ declare class TwoFactorService {
|
|
|
45
70
|
private resolveSource;
|
|
46
71
|
private hasPassword;
|
|
47
72
|
private issueAccessToken;
|
|
73
|
+
/**
|
|
74
|
+
* Issues the session of a user that must enroll before using the API. The
|
|
75
|
+
* token has to come back restricted: an application `createAccessToken`
|
|
76
|
+
* override may swap the scope (e.g. a forced password change), but an
|
|
77
|
+
* unrestricted token would bypass the requirement, so it is revoked.
|
|
78
|
+
*/
|
|
79
|
+
private issueEnrollmentToken;
|
|
80
|
+
/** Enrollment can only be demanded while users are able to enroll. */
|
|
81
|
+
private isRequired;
|
|
48
82
|
private buildTwoFactorTokenOptions;
|
|
49
83
|
private secretAad;
|
|
50
84
|
private challengeAad;
|
|
@@ -58,6 +92,7 @@ declare class TwoFactorService {
|
|
|
58
92
|
private confirmRecoveryReservation;
|
|
59
93
|
private releaseRecoveryReservation;
|
|
60
94
|
private compensateChallengeFailure;
|
|
95
|
+
private statusInternal;
|
|
61
96
|
private beginEnrollmentInternal;
|
|
62
97
|
private confirmEnrollmentInternal;
|
|
63
98
|
private regenerateRecoveryCodesInternal;
|
|
@@ -67,7 +102,10 @@ declare class TwoFactorService {
|
|
|
67
102
|
private consumeManagementFailure;
|
|
68
103
|
private enabledCredential;
|
|
69
104
|
private persistRecoverySet;
|
|
105
|
+
private proveActorInternal;
|
|
106
|
+
private resetInternal;
|
|
70
107
|
private deletePrincipalsInternal;
|
|
108
|
+
private destroyFactors;
|
|
71
109
|
private rotateEncryptionInternal;
|
|
72
110
|
private revokeSessions;
|
|
73
111
|
private revokeToken;
|
|
@@ -75,4 +113,4 @@ declare class TwoFactorService {
|
|
|
75
113
|
private audit;
|
|
76
114
|
}
|
|
77
115
|
//#endregion
|
|
78
|
-
export { TwoFactorService };
|
|
116
|
+
export { TWO_FACTOR_ENROLLMENT_SCOPE, TwoFactorService };
|
package/dist/package.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var require_package = /* @__PURE__ */ require("./_virtual/_rolldown/runtime.cjs").__commonJSMin(((exports, module) => {
|
|
3
3
|
module.exports = {
|
|
4
4
|
"name": "@vsaas/loopback",
|
|
5
|
-
"version": "12.
|
|
5
|
+
"version": "12.5.0",
|
|
6
6
|
"description": "Fork of LoopBack 3 framework for building APIs and microservices in Node.js",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"StrongLoop",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vsaas/loopback",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.5.0",
|
|
4
4
|
"description": "Fork of LoopBack 3 framework for building APIs and microservices in Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"StrongLoop",
|
|
@@ -124,8 +124,8 @@
|
|
|
124
124
|
"tsdown": "0.22.14",
|
|
125
125
|
"typescript": "6.0.2",
|
|
126
126
|
"vitest": "4.1.11",
|
|
127
|
-
"@vsaas/
|
|
128
|
-
"@vsaas/
|
|
127
|
+
"@vsaas/loopback-boot": "^12.0.2",
|
|
128
|
+
"@vsaas/error-handler": "^12.0.1"
|
|
129
129
|
},
|
|
130
130
|
"engines": {
|
|
131
131
|
"node": ">=24"
|