@vsaas/loopback 12.1.1 → 12.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/common/models/rate-limit-state.json +121 -0
- package/common/models/two-factor-challenge.json +112 -0
- package/common/models/two-factor-credential.json +117 -0
- package/common/models/two-factor-recovery-code.json +63 -0
- package/common/models/user.json +30 -0
- package/dist/_virtual/_rolldown/runtime.cjs +9 -2
- package/dist/common/models/access-token.d.cts +3 -0
- package/dist/common/models/acl.cjs +1 -3
- package/dist/common/models/acl.d.cts +1 -0
- package/dist/common/models/application.d.cts +3 -0
- package/dist/common/models/change.cjs +21 -18
- package/dist/common/models/change.d.cts +1 -0
- package/dist/common/models/checkpoint.d.cts +3 -0
- package/dist/common/models/email.d.cts +3 -0
- package/dist/common/models/key-value-model.d.cts +3 -0
- package/dist/common/models/rate-limit-state.cjs +7 -0
- package/dist/common/models/rate-limit-state.d.cts +3 -0
- package/dist/common/models/rate-limit-state2.cjs +109 -0
- package/dist/common/models/role-mapping.d.cts +3 -0
- package/dist/common/models/role.cjs +12 -8
- package/dist/common/models/role.d.cts +1 -0
- package/dist/common/models/scope.d.cts +3 -0
- package/dist/common/models/two-factor-challenge.cjs +7 -0
- package/dist/common/models/two-factor-challenge.d.cts +3 -0
- package/dist/common/models/two-factor-challenge2.cjs +108 -0
- package/dist/common/models/two-factor-credential.cjs +7 -0
- package/dist/common/models/two-factor-credential.d.cts +3 -0
- package/dist/common/models/two-factor-credential2.cjs +103 -0
- package/dist/common/models/two-factor-recovery-code.cjs +7 -0
- package/dist/common/models/two-factor-recovery-code.d.cts +3 -0
- package/dist/common/models/two-factor-recovery-code2.cjs +57 -0
- package/dist/common/models/user.cjs +261 -21
- package/dist/common/models/user.d.cts +3 -0
- package/dist/common/models/user2.cjs +30 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -0
- package/dist/lib/access-context.d.cts +33 -0
- package/dist/lib/application.d.cts +1 -0
- package/dist/lib/authentication-security-initializer.cjs +351 -0
- package/dist/lib/authentication-security-initializer.d.cts +4 -0
- package/dist/lib/builtin-models.cjs +12 -0
- package/dist/lib/builtin-models.d.cts +3 -0
- package/dist/lib/configure-shared-methods.cjs +7 -4
- package/dist/lib/configure-shared-methods.d.cts +3 -0
- package/dist/lib/connectors/base-connector.d.cts +6 -0
- package/dist/lib/connectors/mail-direct-transport.cjs +26 -23
- package/dist/lib/connectors/mail-direct-transport.d.cts +22 -0
- package/dist/lib/connectors/mail-stub-transport.d.cts +14 -0
- package/dist/lib/connectors/mail.d.cts +7 -0
- package/dist/lib/connectors/memory.d.cts +6 -0
- package/dist/lib/current-context.d.cts +3 -0
- package/dist/lib/globalize.d.cts +10 -0
- package/dist/lib/loopback.cjs +6 -0
- package/dist/lib/loopback.d.cts +1 -0
- package/dist/lib/model.d.cts +1 -0
- package/dist/lib/persisted-model.cjs +7 -12
- package/dist/lib/persisted-model.d.cts +1 -0
- package/dist/lib/rate-limit/algorithms/common.cjs +37 -0
- package/dist/lib/rate-limit/algorithms/failure-lockout.cjs +70 -0
- package/dist/lib/rate-limit/algorithms/fixed-window.cjs +61 -0
- package/dist/lib/rate-limit/algorithms/index.cjs +55 -0
- package/dist/lib/rate-limit/config.cjs +107 -0
- package/dist/lib/rate-limit/errors.cjs +58 -0
- package/dist/lib/rate-limit/index.cjs +15 -0
- package/dist/lib/rate-limit/index.d.cts +1 -0
- package/dist/lib/rate-limit/initializer.cjs +92 -0
- package/dist/lib/rate-limit/initializer.d.cts +4 -0
- package/dist/lib/rate-limit/limiter.cjs +368 -0
- package/dist/lib/rate-limit/registry.cjs +92 -0
- package/dist/lib/rate-limit/remote.cjs +180 -0
- package/dist/lib/rate-limit/remote.d.cts +6 -0
- package/dist/lib/rate-limit/stores/persisted-model.cjs +353 -0
- package/dist/lib/rate-limit/subject.cjs +127 -0
- package/dist/lib/rate-limit/types.cjs +1 -0
- package/dist/lib/rate-limit/types.d.cts +167 -0
- package/dist/lib/registry.d.cts +1 -0
- package/dist/lib/runtime.d.cts +10 -0
- package/dist/lib/server-app.d.cts +3 -0
- package/dist/lib/two-factor/config.cjs +199 -0
- package/dist/lib/two-factor/config.d.cts +55 -0
- package/dist/lib/two-factor/crypto.cjs +68 -0
- package/dist/lib/two-factor/crypto.d.cts +16 -0
- package/dist/lib/two-factor/errors.cjs +58 -0
- package/dist/lib/two-factor/errors.d.cts +18 -0
- package/dist/lib/two-factor/rate-limit.cjs +153 -0
- package/dist/lib/two-factor/rate-limit.d.cts +22 -0
- package/dist/lib/two-factor/recovery-codes.cjs +49 -0
- package/dist/lib/two-factor/recovery-codes.d.cts +16 -0
- package/dist/lib/two-factor/service.cjs +1108 -0
- package/dist/lib/two-factor/service.d.cts +78 -0
- package/dist/lib/two-factor/totp.cjs +50 -0
- package/dist/lib/two-factor/totp.d.cts +14 -0
- package/dist/lib/utils.d.cts +36 -0
- package/dist/package.cjs +46 -10
- package/dist/server/middleware/context.d.cts +3 -0
- package/dist/server/middleware/error-handler.d.cts +3 -0
- package/dist/server/middleware/favicon.d.cts +7 -0
- package/dist/server/middleware/rest.d.cts +6 -0
- package/dist/server/middleware/static.d.cts +3 -0
- package/dist/server/middleware/status.d.cts +19 -0
- package/dist/server/middleware/token.d.cts +3 -0
- package/dist/server/middleware/url-not-found.d.cts +13 -0
- package/docs/api-explorer-details.md +110 -0
- package/docs/application-guide.md +1110 -0
- package/docs/assets/explorer-api.png +0 -0
- package/docs/assets/explorer-endpoint.png +0 -0
- package/docs/assets/explorer-listing.png +0 -0
- package/docs/assets/explorer-req-res.png +0 -0
- package/docs/assets/lb-modules.png +0 -0
- package/docs/assets/loopback-architecture.png +0 -0
- package/docs/assets/loopback-concepts.png +0 -0
- package/docs/assets/loopback_ov.png +0 -0
- package/docs/rate-limiting.md +879 -0
- package/docs/two-factor-authentication.md +611 -0
- package/docs/two-factor-authenticator-loss-runbook.md +117 -0
- package/package.json +56 -20
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_persisted_model = require("./rate-limit/stores/persisted-model.cjs");
|
|
4
|
+
const require_lib_two_factor_config = require("./two-factor/config.cjs");
|
|
5
|
+
const require_lib_two_factor_errors = require("./two-factor/errors.cjs");
|
|
6
|
+
const require_lib_two_factor_rate_limit = require("./two-factor/rate-limit.cjs");
|
|
7
|
+
const require_lib_two_factor_service = require("./two-factor/service.cjs");
|
|
8
|
+
//#region src/lib/authentication-security-initializer.ts
|
|
9
|
+
var authentication_security_initializer_exports = /* @__PURE__ */ require_runtime.__exportAll({ installAuthenticationSecurity: () => installAuthenticationSecurity });
|
|
10
|
+
function isUserModel(model, baseUser) {
|
|
11
|
+
return model === baseUser || model?.prototype && baseUser?.prototype && model.prototype instanceof baseUser;
|
|
12
|
+
}
|
|
13
|
+
function requirePrivateModel(app, name) {
|
|
14
|
+
const model = app.models[name];
|
|
15
|
+
if (!model || !model.dataSource) throw new Error(`Authentication security model ${name} is not connected`);
|
|
16
|
+
if ((model.config || {}).public === true) throw new Error(`Authentication security model ${name} must be private`);
|
|
17
|
+
return model;
|
|
18
|
+
}
|
|
19
|
+
function resultCount(result) {
|
|
20
|
+
return typeof result === "number" ? result : Number(result?.count || 0);
|
|
21
|
+
}
|
|
22
|
+
function requireUniquePrincipalProviderIndex(model) {
|
|
23
|
+
const index = (model.settings?.indexes || model.definition?.settings?.indexes || {}).two_factor_principal_provider_unique;
|
|
24
|
+
if (!index || index.options?.unique !== true || Number(index.keys?.principalType) !== 1 || Number(index.keys?.principalId) !== 1 || Number(index.keys?.provider) !== 1) throw new Error("TwoFactorCredential requires a unique principalType/principalId/provider index");
|
|
25
|
+
}
|
|
26
|
+
function installAuthenticationSecurity(app) {
|
|
27
|
+
let initialization = null;
|
|
28
|
+
app.authenticationSecurityReadiness = {
|
|
29
|
+
ready: false,
|
|
30
|
+
error: null
|
|
31
|
+
};
|
|
32
|
+
const twoFactorMethods = [
|
|
33
|
+
"verifyTwoFactor",
|
|
34
|
+
"beginTwoFactorEnrollment",
|
|
35
|
+
"confirmTwoFactorEnrollment",
|
|
36
|
+
"regenerateTwoFactorRecoveryCodes",
|
|
37
|
+
"disableTwoFactor"
|
|
38
|
+
];
|
|
39
|
+
function disableUnconfiguredRemoteMethods() {
|
|
40
|
+
const BaseUser = app.registry.findModel("User");
|
|
41
|
+
if (!BaseUser) return;
|
|
42
|
+
for (const model of app.models().filter((candidate) => isUserModel(candidate, BaseUser))) if (model.settings.twoFactor == null) for (const method of twoFactorMethods) model.disableRemoteMethodByName(method);
|
|
43
|
+
}
|
|
44
|
+
app.on("modelRemoted", disableUnconfiguredRemoteMethods);
|
|
45
|
+
app.initializeAuthenticationSecurity = function(callback) {
|
|
46
|
+
if (!initialization) initialization = Promise.resolve().then(async () => {
|
|
47
|
+
const BaseUser = app.registry.findModel("User");
|
|
48
|
+
const userModels = BaseUser ? app.models().filter((model) => isUserModel(model, BaseUser)) : [];
|
|
49
|
+
disableUnconfiguredRemoteMethods();
|
|
50
|
+
const models = userModels.filter((model) => model.settings.twoFactor != null || model.settings.authenticationRateLimit?.enabled === true);
|
|
51
|
+
if (models.some((model) => model.settings.twoFactor != null)) {
|
|
52
|
+
requireUniquePrincipalProviderIndex(requirePrivateModel(app, "TwoFactorCredential"));
|
|
53
|
+
requirePrivateModel(app, "TwoFactorChallenge");
|
|
54
|
+
requirePrivateModel(app, "TwoFactorRecoveryCode");
|
|
55
|
+
}
|
|
56
|
+
const normalized = models.map((model) => ({
|
|
57
|
+
model,
|
|
58
|
+
twoFactor: require_lib_two_factor_config.normalizeTwoFactorConfig(model.settings.twoFactor),
|
|
59
|
+
rateLimit: require_lib_two_factor_config.normalizeAuthenticationRateLimitConfig(model.settings.authenticationRateLimit, model.modelName, app.get("rateLimit") || {})
|
|
60
|
+
}));
|
|
61
|
+
for (const item of normalized) {
|
|
62
|
+
if (item.twoFactor?.mode === "disabled") {
|
|
63
|
+
if (await app.models.TwoFactorCredential.count({
|
|
64
|
+
principalType: item.model.modelName,
|
|
65
|
+
status: "enabled"
|
|
66
|
+
}) !== 0) throw new Error(`Cannot disable two-factor authentication for ${item.model.modelName} with active credentials`);
|
|
67
|
+
}
|
|
68
|
+
if (!item.rateLimit) continue;
|
|
69
|
+
app.registerRateLimitSubjectKeyring(item.rateLimit.keyringName, item.model.settings.authenticationRateLimit.subjectHash);
|
|
70
|
+
if (item.rateLimit.usesApplicationStore) continue;
|
|
71
|
+
if (item.rateLimit.store.provider !== "persisted-model") throw new Error(`Rate limit provider ${item.rateLimit.store.provider} is not available`);
|
|
72
|
+
const stateModel = app.models[item.rateLimit.store.model || "RateLimitState"];
|
|
73
|
+
if (!stateModel || !stateModel.dataSource) throw new Error(`Rate limit state model ${item.rateLimit.store.model || "RateLimitState"} is not connected`);
|
|
74
|
+
const connectorName = stateModel.dataSource.connector?.name || stateModel.dataSource.settings?.connector;
|
|
75
|
+
const allowMemory = item.rateLimit.store.allowMemory === true;
|
|
76
|
+
if (connectorName === "memory" && !allowMemory) throw new Error("Memory rate limit store is allowed only when explicitly enabled");
|
|
77
|
+
app.registerRateLimitStore(item.rateLimit.storeName, new require_persisted_model.PersistedModelRateLimitStore({
|
|
78
|
+
model: stateModel,
|
|
79
|
+
allowMemory,
|
|
80
|
+
maxRetries: item.rateLimit.store.maxRetries
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
await app.initializeRateLimiting();
|
|
84
|
+
for (const item of normalized) {
|
|
85
|
+
const limiter = item.rateLimit ? new require_lib_two_factor_rate_limit.AuthenticationRateLimiter(item.model.modelName, item.rateLimit, app.rateLimit.limiter) : null;
|
|
86
|
+
item.model._authenticationSecurity = new require_lib_two_factor_service.TwoFactorService(item.model, item.twoFactor, limiter, app);
|
|
87
|
+
item.model._authenticationSecurityReady = true;
|
|
88
|
+
}
|
|
89
|
+
app.authenticationSecurityReadiness.ready = true;
|
|
90
|
+
}).catch((error) => {
|
|
91
|
+
app.authenticationSecurityReadiness.error = error;
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
if (typeof callback === "function") {
|
|
95
|
+
initialization.then(() => callback(null), callback);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return initialization;
|
|
99
|
+
};
|
|
100
|
+
app.assertAuthenticationSecurityReady = function(UserModel) {
|
|
101
|
+
if ((UserModel.settings.twoFactor != null || UserModel.settings.authenticationRateLimit?.enabled === true) && !UserModel._authenticationSecurityReady) throw require_lib_two_factor_errors.authError("AUTHENTICATION_SECURITY_UNAVAILABLE", 503);
|
|
102
|
+
};
|
|
103
|
+
app.cleanupAuthenticationSecurity = function(options = {}, callback) {
|
|
104
|
+
const promise = Promise.resolve().then(async () => {
|
|
105
|
+
const now = options.now || /* @__PURE__ */ new Date();
|
|
106
|
+
const nowMs = now.getTime();
|
|
107
|
+
const terminalRetention = options.terminalRetentionMs ?? 6048e5;
|
|
108
|
+
const credentialRetention = options.disabledCredentialRetentionMs ?? 2592e6;
|
|
109
|
+
const consumingTimeout = options.consumingTimeoutMs ?? 6e5;
|
|
110
|
+
const Challenge = requirePrivateModel(app, "TwoFactorChallenge");
|
|
111
|
+
const Credential = requirePrivateModel(app, "TwoFactorCredential");
|
|
112
|
+
const RecoveryCode = requirePrivateModel(app, "TwoFactorRecoveryCode");
|
|
113
|
+
const result = {
|
|
114
|
+
expiredChallenges: 0,
|
|
115
|
+
recoveredVerifications: 0,
|
|
116
|
+
quarantinedConsumptions: 0,
|
|
117
|
+
reconciledTokens: 0,
|
|
118
|
+
releasedReservations: 0,
|
|
119
|
+
finalizedReservations: 0,
|
|
120
|
+
removedChallenges: 0,
|
|
121
|
+
expiredEnrollments: 0,
|
|
122
|
+
removedCredentials: 0,
|
|
123
|
+
removedRecoveryCodes: 0
|
|
124
|
+
};
|
|
125
|
+
const releaseReservations = async (ownerId) => {
|
|
126
|
+
const released = await RecoveryCode.updateAll({
|
|
127
|
+
status: "reserved",
|
|
128
|
+
reservationOwnerId: ownerId
|
|
129
|
+
}, {
|
|
130
|
+
status: "unused",
|
|
131
|
+
reservationId: null,
|
|
132
|
+
reservationExpiresAt: null,
|
|
133
|
+
reservationOwnerType: null,
|
|
134
|
+
reservationOwnerId: null
|
|
135
|
+
});
|
|
136
|
+
result.releasedReservations += resultCount(released);
|
|
137
|
+
};
|
|
138
|
+
const expiredChallenges = await Challenge.updateAll({
|
|
139
|
+
status: "pending",
|
|
140
|
+
expiresAt: { lte: now }
|
|
141
|
+
}, { status: "expired" });
|
|
142
|
+
result.expiredChallenges += resultCount(expiredChallenges);
|
|
143
|
+
const expiredLeases = await Challenge.find({ where: {
|
|
144
|
+
status: "verifying",
|
|
145
|
+
verificationLeaseExpiresAt: { lte: now }
|
|
146
|
+
} });
|
|
147
|
+
for (const challenge of expiredLeases) {
|
|
148
|
+
if (resultCount(await Challenge.updateAll({
|
|
149
|
+
id: challenge.id,
|
|
150
|
+
status: "verifying",
|
|
151
|
+
verificationLeaseId: challenge.verificationLeaseId
|
|
152
|
+
}, {
|
|
153
|
+
status: "reconciling_no_token",
|
|
154
|
+
verificationLeaseId: null,
|
|
155
|
+
verificationLeaseExpiresAt: null
|
|
156
|
+
})) !== 1) continue;
|
|
157
|
+
await releaseReservations(`challenge:${challenge.id}`);
|
|
158
|
+
await Challenge.updateAll({
|
|
159
|
+
id: challenge.id,
|
|
160
|
+
status: "reconciling_no_token"
|
|
161
|
+
}, { status: new Date(challenge.expiresAt).getTime() <= nowMs ? "expired" : "pending" });
|
|
162
|
+
result.recoveredVerifications++;
|
|
163
|
+
}
|
|
164
|
+
const stuckConsumptions = await Challenge.find({ where: {
|
|
165
|
+
status: "consuming",
|
|
166
|
+
claimedAt: { lte: new Date(nowMs - consumingTimeout) }
|
|
167
|
+
} });
|
|
168
|
+
for (const challenge of stuckConsumptions) {
|
|
169
|
+
const quarantined = await Challenge.updateAll({
|
|
170
|
+
id: challenge.id,
|
|
171
|
+
status: "consuming"
|
|
172
|
+
}, { status: "token_revocation_pending" });
|
|
173
|
+
result.quarantinedConsumptions += resultCount(quarantined);
|
|
174
|
+
}
|
|
175
|
+
const pendingRevocations = await Challenge.find({ where: {
|
|
176
|
+
status: "token_revocation_pending",
|
|
177
|
+
issuedTokenId: { neq: null }
|
|
178
|
+
} });
|
|
179
|
+
for (const challenge of pendingRevocations) {
|
|
180
|
+
const AccessToken = app.models[challenge.principalType]?.relations?.accessTokens?.modelTo;
|
|
181
|
+
if (!AccessToken || !challenge.issuedTokenId) continue;
|
|
182
|
+
try {
|
|
183
|
+
await AccessToken.destroyById(challenge.issuedTokenId);
|
|
184
|
+
if (await AccessToken.findById(challenge.issuedTokenId)) continue;
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (resultCount(await Challenge.updateAll({
|
|
189
|
+
id: challenge.id,
|
|
190
|
+
status: "token_revocation_pending",
|
|
191
|
+
issuedTokenId: challenge.issuedTokenId
|
|
192
|
+
}, {
|
|
193
|
+
status: "token_failed",
|
|
194
|
+
issuedTokenId: null
|
|
195
|
+
})) !== 1) continue;
|
|
196
|
+
await releaseReservations(`challenge:${challenge.id}`);
|
|
197
|
+
result.reconciledTokens++;
|
|
198
|
+
}
|
|
199
|
+
const expiredReservations = await RecoveryCode.find({ where: {
|
|
200
|
+
status: "reserved",
|
|
201
|
+
reservationExpiresAt: { lte: now }
|
|
202
|
+
} });
|
|
203
|
+
for (const recoveryCode of expiredReservations) {
|
|
204
|
+
const ownerId = String(recoveryCode.reservationOwnerId || "");
|
|
205
|
+
if (!ownerId.startsWith("challenge:")) continue;
|
|
206
|
+
const challenge = await Challenge.findById(ownerId.slice(10));
|
|
207
|
+
if (!challenge) continue;
|
|
208
|
+
if (challenge.status === "consumed") {
|
|
209
|
+
const finalized = await RecoveryCode.updateAll({
|
|
210
|
+
id: recoveryCode.id,
|
|
211
|
+
status: "reserved",
|
|
212
|
+
reservationId: recoveryCode.reservationId,
|
|
213
|
+
reservationOwnerId: ownerId
|
|
214
|
+
}, {
|
|
215
|
+
status: "used",
|
|
216
|
+
usedAt: challenge.consumedAt || now,
|
|
217
|
+
reservationId: null,
|
|
218
|
+
reservationExpiresAt: null,
|
|
219
|
+
reservationOwnerType: null,
|
|
220
|
+
reservationOwnerId: null
|
|
221
|
+
});
|
|
222
|
+
result.finalizedReservations += resultCount(finalized);
|
|
223
|
+
} else if ([
|
|
224
|
+
"expired",
|
|
225
|
+
"locked",
|
|
226
|
+
"token_failed"
|
|
227
|
+
].includes(challenge.status)) await releaseReservations(ownerId);
|
|
228
|
+
}
|
|
229
|
+
const expiredEnrollments = await Credential.updateAll({
|
|
230
|
+
status: "pending",
|
|
231
|
+
pendingExpiresAt: { lte: now }
|
|
232
|
+
}, {
|
|
233
|
+
status: "disabled",
|
|
234
|
+
encryptedSecret: null,
|
|
235
|
+
secretKeyId: null,
|
|
236
|
+
enrollmentTokenDigest: null,
|
|
237
|
+
disabledAt: now,
|
|
238
|
+
updatedAt: now
|
|
239
|
+
});
|
|
240
|
+
result.expiredEnrollments += resultCount(expiredEnrollments);
|
|
241
|
+
const credentials = await Credential.find({});
|
|
242
|
+
const retainedCredentialIds = new Set(credentials.map((credential) => String(credential.id)));
|
|
243
|
+
for (const credential of credentials) {
|
|
244
|
+
const UserModel = app.models[credential.principalType];
|
|
245
|
+
const principalExists = UserModel && await UserModel.findById(credential.principalId, { fields: { id: true } });
|
|
246
|
+
const disabledExpired = credential.status === "disabled" && credential.disabledAt && new Date(credential.disabledAt).getTime() <= nowMs - credentialRetention;
|
|
247
|
+
if (!principalExists || disabledExpired) {
|
|
248
|
+
const removedCodes = await RecoveryCode.destroyAll({ credentialId: String(credential.id) });
|
|
249
|
+
const removedChallenges = await Challenge.destroyAll({ credentialId: String(credential.id) });
|
|
250
|
+
const removedCredential = await Credential.destroyAll({ id: credential.id });
|
|
251
|
+
result.removedRecoveryCodes += resultCount(removedCodes);
|
|
252
|
+
result.removedChallenges += resultCount(removedChallenges);
|
|
253
|
+
result.removedCredentials += resultCount(removedCredential);
|
|
254
|
+
retainedCredentialIds.delete(String(credential.id));
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (credential.activeRecoveryCodeSetId) {
|
|
258
|
+
const removedCodes = await RecoveryCode.destroyAll({
|
|
259
|
+
credentialId: String(credential.id),
|
|
260
|
+
setId: { neq: credential.activeRecoveryCodeSetId },
|
|
261
|
+
status: { neq: "reserved" }
|
|
262
|
+
});
|
|
263
|
+
result.removedRecoveryCodes += resultCount(removedCodes);
|
|
264
|
+
} else {
|
|
265
|
+
const removedCodes = await RecoveryCode.destroyAll({
|
|
266
|
+
credentialId: String(credential.id),
|
|
267
|
+
status: { neq: "reserved" }
|
|
268
|
+
});
|
|
269
|
+
result.removedRecoveryCodes += resultCount(removedCodes);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const recoveryCredentialIds = new Set((await RecoveryCode.find({ fields: { credentialId: true } })).map((recoveryCode) => String(recoveryCode.credentialId)));
|
|
273
|
+
const orphanedCredentialIds = Array.from(recoveryCredentialIds).filter((credentialId) => !retainedCredentialIds.has(credentialId));
|
|
274
|
+
if (orphanedCredentialIds.length) {
|
|
275
|
+
const removedCodes = await RecoveryCode.destroyAll({
|
|
276
|
+
credentialId: { inq: orphanedCredentialIds },
|
|
277
|
+
status: { neq: "reserved" }
|
|
278
|
+
});
|
|
279
|
+
result.removedRecoveryCodes += resultCount(removedCodes);
|
|
280
|
+
}
|
|
281
|
+
const terminalChallenges = await Challenge.find({
|
|
282
|
+
where: {
|
|
283
|
+
status: { inq: [
|
|
284
|
+
"expired",
|
|
285
|
+
"consumed",
|
|
286
|
+
"locked",
|
|
287
|
+
"token_failed"
|
|
288
|
+
] },
|
|
289
|
+
expiresAt: { lt: new Date(nowMs - terminalRetention) }
|
|
290
|
+
},
|
|
291
|
+
fields: { id: true }
|
|
292
|
+
});
|
|
293
|
+
for (const challenge of terminalChallenges) {
|
|
294
|
+
if (await RecoveryCode.count({
|
|
295
|
+
status: "reserved",
|
|
296
|
+
reservationOwnerId: `challenge:${challenge.id}`
|
|
297
|
+
}) !== 0) continue;
|
|
298
|
+
const removed = await Challenge.destroyAll({ id: challenge.id });
|
|
299
|
+
result.removedChallenges += resultCount(removed);
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
});
|
|
303
|
+
if (typeof callback === "function") {
|
|
304
|
+
promise.then((result) => callback(null, result), callback);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
return promise;
|
|
308
|
+
};
|
|
309
|
+
app.rotateAuthenticationSecurityEncryption = function(options = {}, callback) {
|
|
310
|
+
const promise = Promise.resolve().then(async () => {
|
|
311
|
+
if (!app.authenticationSecurityReadiness.ready) throw require_lib_two_factor_errors.authError("AUTHENTICATION_SECURITY_UNAVAILABLE", 503);
|
|
312
|
+
const BaseUser = app.registry.findModel("User");
|
|
313
|
+
const models = BaseUser ? app.models().filter((model) => isUserModel(model, BaseUser) && model.settings.twoFactor != null && model._authenticationSecurity) : [];
|
|
314
|
+
const perModel = {};
|
|
315
|
+
for (const model of models) perModel[model.modelName] = await model._authenticationSecurity.rotateEncryption(options);
|
|
316
|
+
return perModel;
|
|
317
|
+
});
|
|
318
|
+
if (typeof callback === "function") {
|
|
319
|
+
promise.then((result) => callback(null, result), callback);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
return promise;
|
|
323
|
+
};
|
|
324
|
+
app.once("booted", function() {
|
|
325
|
+
app.initializeAuthenticationSecurity().catch((error) => {
|
|
326
|
+
app.emit("authenticationSecurityError", error);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
var init_authentication_security_initializer = require_runtime.__esmMin((() => {
|
|
331
|
+
require_persisted_model.init_persisted_model();
|
|
332
|
+
require_lib_two_factor_config.init_config();
|
|
333
|
+
require_lib_two_factor_rate_limit.init_rate_limit();
|
|
334
|
+
require_lib_two_factor_service.init_service();
|
|
335
|
+
require_lib_two_factor_errors.init_errors();
|
|
336
|
+
}));
|
|
337
|
+
//#endregion
|
|
338
|
+
init_authentication_security_initializer();
|
|
339
|
+
Object.defineProperty(exports, "authentication_security_initializer_exports", {
|
|
340
|
+
enumerable: true,
|
|
341
|
+
get: function() {
|
|
342
|
+
return authentication_security_initializer_exports;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
Object.defineProperty(exports, "init_authentication_security_initializer", {
|
|
346
|
+
enumerable: true,
|
|
347
|
+
get: function() {
|
|
348
|
+
return init_authentication_security_initializer;
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
exports.installAuthenticationSecurity = installAuthenticationSecurity;
|
|
@@ -10,6 +10,14 @@ const require_access_token$2 = require("../common/models/access-token2.cjs");
|
|
|
10
10
|
const require_common_models_access_token = require("../common/models/access-token.cjs");
|
|
11
11
|
const require_user$2 = require("../common/models/user2.cjs");
|
|
12
12
|
const require_common_models_user = require("../common/models/user.cjs");
|
|
13
|
+
const require_rate_limit_state$2 = require("../common/models/rate-limit-state2.cjs");
|
|
14
|
+
const require_common_models_rate_limit_state = require("../common/models/rate-limit-state.cjs");
|
|
15
|
+
const require_two_factor_credential$2 = require("../common/models/two-factor-credential2.cjs");
|
|
16
|
+
const require_common_models_two_factor_credential = require("../common/models/two-factor-credential.cjs");
|
|
17
|
+
const require_two_factor_challenge$2 = require("../common/models/two-factor-challenge2.cjs");
|
|
18
|
+
const require_common_models_two_factor_challenge = require("../common/models/two-factor-challenge.cjs");
|
|
19
|
+
const require_two_factor_recovery_code$2 = require("../common/models/two-factor-recovery-code2.cjs");
|
|
20
|
+
const require_common_models_two_factor_recovery_code = require("../common/models/two-factor-recovery-code.cjs");
|
|
13
21
|
const require_role_mapping$2 = require("../common/models/role-mapping2.cjs");
|
|
14
22
|
const require_common_models_role_mapping = require("../common/models/role-mapping.cjs");
|
|
15
23
|
const require_role$2 = require("../common/models/role2.cjs");
|
|
@@ -31,6 +39,10 @@ var require_builtin_models = /* @__PURE__ */ require_runtime.__commonJSMin(((exp
|
|
|
31
39
|
registry.Application = createModel(registry, require_application$2.default, require_common_models_application);
|
|
32
40
|
registry.AccessToken = createModel(registry, require_access_token$2.default, require_common_models_access_token);
|
|
33
41
|
registry.User = createModel(registry, require_user$2.default, require_common_models_user);
|
|
42
|
+
registry.RateLimitState = createModel(registry, require_rate_limit_state$2.default, require_common_models_rate_limit_state);
|
|
43
|
+
registry.TwoFactorCredential = createModel(registry, require_two_factor_credential$2.default, require_common_models_two_factor_credential);
|
|
44
|
+
registry.TwoFactorChallenge = createModel(registry, require_two_factor_challenge$2.default, require_common_models_two_factor_challenge);
|
|
45
|
+
registry.TwoFactorRecoveryCode = createModel(registry, require_two_factor_recovery_code$2.default, require_common_models_two_factor_recovery_code);
|
|
34
46
|
registry.RoleMapping = createModel(registry, require_role_mapping$2.default, require_common_models_role_mapping);
|
|
35
47
|
registry.Role = createModel(registry, require_role$2.default, require_common_models_role);
|
|
36
48
|
registry.ACL = createModel(registry, require_acl$2.default, require_common_models_acl);
|
|
@@ -24,11 +24,14 @@ var require_configure_shared_methods = /* @__PURE__ */ require_runtime.__commonJ
|
|
|
24
24
|
}) || [];
|
|
25
25
|
sharedMethods.forEach(function(sharedMethod) {
|
|
26
26
|
const methodName = sharedMethod.isStatic ? sharedMethod.name : "prototype." + sharedMethod.name;
|
|
27
|
-
if (Object.prototype.hasOwnProperty.call(settings, methodName))
|
|
28
|
-
|
|
29
|
-
else tests.forEach(function(glob) {
|
|
30
|
-
if (glob.regex.test(methodName)) if (glob.setting === false) sharedMethod.sharedClass.disableMethodByName(methodName);
|
|
27
|
+
if (Object.prototype.hasOwnProperty.call(settings, methodName)) {
|
|
28
|
+
if (settings[methodName] === false) sharedMethod.sharedClass.disableMethodByName(methodName);
|
|
31
29
|
else sharedMethod.shared = true;
|
|
30
|
+
} else tests.forEach(function(glob) {
|
|
31
|
+
if (glob.regex.test(methodName)) {
|
|
32
|
+
if (glob.setting === false) sharedMethod.sharedClass.disableMethodByName(methodName);
|
|
33
|
+
else sharedMethod.shared = true;
|
|
34
|
+
}
|
|
32
35
|
});
|
|
33
36
|
});
|
|
34
37
|
}
|
|
@@ -160,11 +160,13 @@ var require_mail_direct_transport = /* @__PURE__ */ require("../../_virtual/_rol
|
|
|
160
160
|
combinedInfo.pending = combinedInfo.pending.concat(info.pending || []);
|
|
161
161
|
combinedInfo.messageId = info.messageId;
|
|
162
162
|
}
|
|
163
|
-
if (returned >= domains.length)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
if (returned >= domains.length) {
|
|
164
|
+
if (combinedInfo.errors.length === domains.length) {
|
|
165
|
+
const sendError = /* @__PURE__ */ new Error("Sending failed");
|
|
166
|
+
sendError.errors = combinedInfo.errors;
|
|
167
|
+
callback(sendError);
|
|
168
|
+
} else callback(null, combinedInfo);
|
|
169
|
+
}
|
|
168
170
|
}
|
|
169
171
|
};
|
|
170
172
|
this._queue.insert(item);
|
|
@@ -191,29 +193,30 @@ var require_mail_direct_transport = /* @__PURE__ */ require("../../_virtual/_rol
|
|
|
191
193
|
return item.exchange;
|
|
192
194
|
});
|
|
193
195
|
this._process([].concat(exchanges), data, (processErr, response) => {
|
|
194
|
-
if (processErr)
|
|
195
|
-
processErr.
|
|
196
|
-
processErr.exchange = exchanges[0];
|
|
197
|
-
processErr.recipients = data.envelope.to;
|
|
198
|
-
data.callback(processErr);
|
|
199
|
-
} else {
|
|
200
|
-
data.replies = (data.replies || 0) + 1;
|
|
201
|
-
if (data.replies <= 5) {
|
|
202
|
-
this._queue.insert(data, this.options.retryDelay || data.replies * 15 * 60 * 1e3);
|
|
203
|
-
data.callback(null, { pending: {
|
|
204
|
-
domain: data.domain,
|
|
205
|
-
exchange: exchanges[0],
|
|
206
|
-
recipients: data.envelope.to,
|
|
207
|
-
response: processErr.response
|
|
208
|
-
} });
|
|
209
|
-
} else {
|
|
196
|
+
if (processErr) {
|
|
197
|
+
if (processErr.responseCode && processErr.responseCode >= 500) {
|
|
210
198
|
processErr.domain = data.domain;
|
|
211
199
|
processErr.exchange = exchanges[0];
|
|
212
200
|
processErr.recipients = data.envelope.to;
|
|
213
201
|
data.callback(processErr);
|
|
202
|
+
} else {
|
|
203
|
+
data.replies = (data.replies || 0) + 1;
|
|
204
|
+
if (data.replies <= 5) {
|
|
205
|
+
this._queue.insert(data, this.options.retryDelay || data.replies * 15 * 60 * 1e3);
|
|
206
|
+
data.callback(null, { pending: {
|
|
207
|
+
domain: data.domain,
|
|
208
|
+
exchange: exchanges[0],
|
|
209
|
+
recipients: data.envelope.to,
|
|
210
|
+
response: processErr.response
|
|
211
|
+
} });
|
|
212
|
+
} else {
|
|
213
|
+
processErr.domain = data.domain;
|
|
214
|
+
processErr.exchange = exchanges[0];
|
|
215
|
+
processErr.recipients = data.envelope.to;
|
|
216
|
+
data.callback(processErr);
|
|
217
|
+
}
|
|
214
218
|
}
|
|
215
|
-
}
|
|
216
|
-
else data.callback(null, response);
|
|
219
|
+
} else data.callback(null, response);
|
|
217
220
|
setImmediate(this._loop.bind(this));
|
|
218
221
|
});
|
|
219
222
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/lib/connectors/mail-direct-transport.d.ts
|
|
2
|
+
declare const EventEmitter: any;
|
|
3
|
+
declare function createDirectTransport(options: any): DirectTransport;
|
|
4
|
+
declare class DirectTransport extends EventEmitter {
|
|
5
|
+
options: any;
|
|
6
|
+
_queue: any;
|
|
7
|
+
_started: any;
|
|
8
|
+
_lastId: any;
|
|
9
|
+
name: any;
|
|
10
|
+
version: any;
|
|
11
|
+
constructor(options: any);
|
|
12
|
+
get length(): any;
|
|
13
|
+
getSocket(options: any, callback: any): void;
|
|
14
|
+
send(mail: any, callback: any): void;
|
|
15
|
+
_loop(): void;
|
|
16
|
+
_process(exchanges: any, data: any, callback: any): void;
|
|
17
|
+
_formatMessage(message: any): void;
|
|
18
|
+
_clearStreams(mail: any, callback: any): void;
|
|
19
|
+
_resolveMx(domain: any, callback: any): void;
|
|
20
|
+
_resolveHostname(name: any): any;
|
|
21
|
+
}
|
|
22
|
+
export = createDirectTransport;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/lib/connectors/mail-stub-transport.d.ts
|
|
2
|
+
declare const EventEmitter: any;
|
|
3
|
+
declare function createStubTransport(options: any): StubTransport;
|
|
4
|
+
declare class StubTransport extends EventEmitter {
|
|
5
|
+
options: any;
|
|
6
|
+
name: any;
|
|
7
|
+
version: any;
|
|
8
|
+
constructor(options: any);
|
|
9
|
+
isIdle(): boolean;
|
|
10
|
+
verify(callback: any): void;
|
|
11
|
+
send(mail: any, callback: any): void;
|
|
12
|
+
_log(level: any, type: any, ...args: any[]): void;
|
|
13
|
+
}
|
|
14
|
+
export = createStubTransport;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/lib/globalize.d.ts
|
|
2
|
+
declare function f(message: any): any;
|
|
3
|
+
declare function log(message: any): void;
|
|
4
|
+
declare function warn(message: any): void;
|
|
5
|
+
declare const globalize: {
|
|
6
|
+
f: typeof f;
|
|
7
|
+
log: typeof log;
|
|
8
|
+
warn: typeof warn;
|
|
9
|
+
};
|
|
10
|
+
export = globalize;
|
package/dist/lib/loopback.cjs
CHANGED
|
@@ -5,6 +5,8 @@ const require_lib_runtime = require("./runtime.cjs");
|
|
|
5
5
|
const require_lib_registry = require("./registry.cjs");
|
|
6
6
|
const require_lib_application = require("./application.cjs");
|
|
7
7
|
const require_lib_configure_shared_methods = require("./configure-shared-methods.cjs");
|
|
8
|
+
const require_lib_rate_limit_initializer = require("./rate-limit/initializer.cjs");
|
|
9
|
+
const require_lib_authentication_security_initializer = require("./authentication-security-initializer.cjs");
|
|
8
10
|
const require_package$1 = require("../package.cjs");
|
|
9
11
|
const require_lib_builtin_models = require("./builtin-models.cjs");
|
|
10
12
|
const require_lib_current_context = require("./current-context.cjs");
|
|
@@ -24,6 +26,8 @@ var require_loopback = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports
|
|
|
24
26
|
const Registry = require_lib_registry;
|
|
25
27
|
const juggler = require("@vsaas/loopback-datasource-juggler");
|
|
26
28
|
const configureSharedMethods = require_lib_configure_shared_methods;
|
|
29
|
+
const installRateLimiting = (require_lib_rate_limit_initializer.init_initializer(), require_runtime$1.__toCommonJS(require_lib_rate_limit_initializer.initializer_exports)).installRateLimiting;
|
|
30
|
+
const installAuthenticationSecurity = (require_lib_authentication_security_initializer.init_authentication_security_initializer(), require_runtime$1.__toCommonJS(require_lib_authentication_security_initializer.authentication_security_initializer_exports)).installAuthenticationSecurity;
|
|
27
31
|
/**
|
|
28
32
|
* LoopBack core module. It provides static properties and
|
|
29
33
|
* methods to create models and data sources. The module itself is a function
|
|
@@ -73,6 +77,8 @@ var require_loopback = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports
|
|
|
73
77
|
function createApplication(options) {
|
|
74
78
|
const app = loopbackExpress();
|
|
75
79
|
Object.assign(app, proto);
|
|
80
|
+
installRateLimiting(app);
|
|
81
|
+
installAuthenticationSecurity(app);
|
|
76
82
|
app.loopback = loopback;
|
|
77
83
|
app.on("modelRemoted", function() {
|
|
78
84
|
app.models().forEach(function(Model) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -1389,9 +1389,7 @@ var require_persisted_model = /* @__PURE__ */ require_runtime$1.__commonJSMin(((
|
|
|
1389
1389
|
});
|
|
1390
1390
|
});
|
|
1391
1391
|
break;
|
|
1392
|
-
case Change.DELETE:
|
|
1393
|
-
updates.push(update);
|
|
1394
|
-
break;
|
|
1392
|
+
case Change.DELETE: updates.push(update);
|
|
1395
1393
|
}
|
|
1396
1394
|
});
|
|
1397
1395
|
Promise.all(tasks.map(function(task) {
|
|
@@ -1434,11 +1432,9 @@ var require_persisted_model = /* @__PURE__ */ require_runtime$1.__commonJSMin(((
|
|
|
1434
1432
|
applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb);
|
|
1435
1433
|
});
|
|
1436
1434
|
break;
|
|
1437
|
-
case Change.DELETE:
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
});
|
|
1441
|
-
break;
|
|
1435
|
+
case Change.DELETE: tasks.push(function(cb) {
|
|
1436
|
+
applyDelete(Model, id, current, update.change, conflicts, options, cb);
|
|
1437
|
+
});
|
|
1442
1438
|
}
|
|
1443
1439
|
});
|
|
1444
1440
|
Promise.all(tasks.map(function(task) {
|
|
@@ -1554,7 +1550,8 @@ var require_persisted_model = /* @__PURE__ */ require_runtime$1.__commonJSMin(((
|
|
|
1554
1550
|
*/
|
|
1555
1551
|
PersistedModel.getChangeModel = function() {
|
|
1556
1552
|
const changeModel = this.Change;
|
|
1557
|
-
|
|
1553
|
+
const isSetup = changeModel && changeModel.dataSource;
|
|
1554
|
+
assert(isSetup, "Cannot get a setup Change model for " + this.modelName);
|
|
1558
1555
|
return changeModel;
|
|
1559
1556
|
};
|
|
1560
1557
|
/**
|
|
@@ -1750,9 +1747,7 @@ var require_persisted_model = /* @__PURE__ */ require_runtime$1.__commonJSMin(((
|
|
|
1750
1747
|
if (ctx.isNewInstance === void 0) change.type = hasTarget ? "update" : "create";
|
|
1751
1748
|
else change.type = ctx.isNewInstance ? "create" : "update";
|
|
1752
1749
|
break;
|
|
1753
|
-
case "delete":
|
|
1754
|
-
change.type = "remove";
|
|
1755
|
-
break;
|
|
1750
|
+
case "delete": change.type = "remove";
|
|
1756
1751
|
}
|
|
1757
1752
|
return change;
|
|
1758
1753
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|