@bitblit/ratchet-warden-server 5.1.118 → 5.1.122-alpha
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/lib/server/provider/warden-default-user-decoration-provider.js +3 -1
- package/lib/server/provider/warden-default-user-decoration-provider.js.map +1 -1
- package/lib/server/provider/warden-dynamo-storage-provider-options.d.ts +7 -0
- package/lib/server/provider/warden-dynamo-storage-provider-options.js +7 -0
- package/lib/server/provider/warden-dynamo-storage-provider-options.js.map +1 -0
- package/lib/server/provider/warden-dynamo-storage-provider.d.ts +50 -0
- package/lib/server/provider/warden-dynamo-storage-provider.js +214 -0
- package/lib/server/provider/warden-dynamo-storage-provider.js.map +1 -0
- package/lib/server/provider/warden-mailer-and-expiring-code-ratchet-single-use-code-provider.d.ts +1 -1
- package/lib/server/provider/warden-mailer-and-expiring-code-ratchet-single-use-code-provider.js +5 -2
- package/lib/server/provider/warden-mailer-and-expiring-code-ratchet-single-use-code-provider.js.map +1 -1
- package/lib/server/provider/warden-s3-single-file-storage-provider.d.ts +1 -0
- package/lib/server/provider/warden-s3-single-file-storage-provider.js +8 -0
- package/lib/server/provider/warden-s3-single-file-storage-provider.js.map +1 -1
- package/lib/server/provider/warden-single-use-code-provider.d.ts +1 -1
- package/lib/server/provider/warden-storage-provider.d.ts +1 -0
- package/lib/server/provider/warden-third-party-authentication-provider.d.ts +7 -0
- package/lib/server/provider/warden-third-party-authentication-provider.js +2 -0
- package/lib/server/provider/warden-third-party-authentication-provider.js.map +1 -0
- package/lib/server/provider/warden-twilio-verify-single-use-code-provider.d.ts +1 -1
- package/lib/server/provider/warden-twilio-verify-single-use-code-provider.js +1 -1
- package/lib/server/provider/warden-twilio-verify-single-use-code-provider.js.map +1 -1
- package/lib/server/warden-authorizer.d.ts +35 -0
- package/lib/server/warden-authorizer.js +88 -0
- package/lib/server/warden-authorizer.js.map +1 -0
- package/lib/server/warden-entry-builder.d.ts +13 -0
- package/lib/server/warden-entry-builder.js +42 -0
- package/lib/server/warden-entry-builder.js.map +1 -0
- package/lib/server/warden-service-options.d.ts +2 -0
- package/lib/server/warden-service.d.ts +21 -13
- package/lib/server/warden-service.js +239 -140
- package/lib/server/warden-service.js.map +1 -1
- package/lib/server/warden-web-authn-export-token.d.ts +5 -0
- package/lib/server/warden-web-authn-export-token.js +2 -0
- package/lib/server/warden-web-authn-export-token.js.map +1 -0
- package/package.json +16 -16
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse
|
|
2
|
-
import { WardenUtils } from
|
|
3
|
-
import { WardenStoreRegistrationResponseType } from
|
|
4
|
-
import { RequireRatchet } from
|
|
5
|
-
import { Logger } from
|
|
6
|
-
import { ErrorRatchet } from
|
|
7
|
-
import { StringRatchet } from
|
|
8
|
-
import { Base64Ratchet } from
|
|
9
|
-
import { WardenDefaultUserDecorationProvider } from
|
|
10
|
-
import { WardenNoOpEventProcessingProvider } from
|
|
11
|
-
import { WardenDefaultSendMagicLinkCommandValidator } from
|
|
1
|
+
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
|
|
2
|
+
import { WardenUtils } from "@bitblit/ratchet-warden-common/common/util/warden-utils";
|
|
3
|
+
import { WardenStoreRegistrationResponseType } from "@bitblit/ratchet-warden-common/common/model/warden-store-registration-response-type";
|
|
4
|
+
import { RequireRatchet } from "@bitblit/ratchet-common/lang/require-ratchet";
|
|
5
|
+
import { Logger } from "@bitblit/ratchet-common/logger/logger";
|
|
6
|
+
import { ErrorRatchet } from "@bitblit/ratchet-common/lang/error-ratchet";
|
|
7
|
+
import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
|
|
8
|
+
import { Base64Ratchet } from "@bitblit/ratchet-common/lang/base64-ratchet";
|
|
9
|
+
import { WardenDefaultUserDecorationProvider } from "./provider/warden-default-user-decoration-provider.js";
|
|
10
|
+
import { WardenNoOpEventProcessingProvider } from "./provider/warden-no-op-event-processing-provider.js";
|
|
11
|
+
import { WardenDefaultSendMagicLinkCommandValidator } from "./provider/warden-default-send-magic-link-command-validator.js";
|
|
12
|
+
import { WardenLoginRequestType } from "@bitblit/ratchet-warden-common/common/model/warden-login-request-type";
|
|
13
|
+
import { WardenEntryBuilder } from "./warden-entry-builder.js";
|
|
14
|
+
import { WardenAuthorizer } from "./warden-authorizer.js";
|
|
12
15
|
export class WardenService {
|
|
13
16
|
inOptions;
|
|
14
17
|
opts;
|
|
18
|
+
cacheAuthorizer;
|
|
15
19
|
constructor(inOptions) {
|
|
16
20
|
this.inOptions = inOptions;
|
|
17
|
-
RequireRatchet.notNullOrUndefined(inOptions,
|
|
18
|
-
RequireRatchet.notNullOrUndefined(inOptions.relyingPartyName,
|
|
19
|
-
RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.allowedOrigins,
|
|
20
|
-
RequireRatchet.notNullOrUndefined(inOptions.storageProvider,
|
|
21
|
-
RequireRatchet.notNullOrUndefined(inOptions.jwtRatchet,
|
|
22
|
-
RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.singleUseCodeProviders,
|
|
21
|
+
RequireRatchet.notNullOrUndefined(inOptions, "options");
|
|
22
|
+
RequireRatchet.notNullOrUndefined(inOptions.relyingPartyName, "options.relyingPartyName");
|
|
23
|
+
RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.allowedOrigins, "options.allowedOrigins");
|
|
24
|
+
RequireRatchet.notNullOrUndefined(inOptions.storageProvider, "options.storageProvider");
|
|
25
|
+
RequireRatchet.notNullOrUndefined(inOptions.jwtRatchet, "options.jwtRatchet");
|
|
26
|
+
RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.singleUseCodeProviders, "options.singleUseCodeProviders");
|
|
23
27
|
this.opts = Object.assign({
|
|
24
28
|
userTokenDataProvider: new WardenDefaultUserDecorationProvider(),
|
|
25
29
|
eventProcessor: new WardenNoOpEventProcessingProvider(),
|
|
26
|
-
sendMagicLinkCommandValidator: new WardenDefaultSendMagicLinkCommandValidator()
|
|
30
|
+
sendMagicLinkCommandValidator: new WardenDefaultSendMagicLinkCommandValidator()
|
|
27
31
|
}, inOptions);
|
|
32
|
+
this.cacheAuthorizer = new WardenAuthorizer(inOptions);
|
|
33
|
+
}
|
|
34
|
+
get authorizer() {
|
|
35
|
+
return this.cacheAuthorizer;
|
|
28
36
|
}
|
|
29
37
|
get options() {
|
|
30
38
|
return Object.assign({}, this.opts);
|
|
@@ -41,7 +49,7 @@ export class WardenService {
|
|
|
41
49
|
const cmd = JSON.parse(cmdString);
|
|
42
50
|
const resp = await this.processCommandToResponse(cmd, origin, loggedInUserId);
|
|
43
51
|
if (resp === null) {
|
|
44
|
-
Logger.warn(
|
|
52
|
+
Logger.warn("Response was null for %s %s %s", cmdString, origin, loggedInUserId);
|
|
45
53
|
}
|
|
46
54
|
else {
|
|
47
55
|
rval = JSON.stringify(resp);
|
|
@@ -49,7 +57,7 @@ export class WardenService {
|
|
|
49
57
|
}
|
|
50
58
|
catch (err) {
|
|
51
59
|
const errString = ErrorRatchet.safeStringifyErr(err);
|
|
52
|
-
Logger.error(
|
|
60
|
+
Logger.error("Failed %s : %j", errString, cmdString, err);
|
|
53
61
|
rval = JSON.stringify({ error: errString });
|
|
54
62
|
}
|
|
55
63
|
return rval;
|
|
@@ -57,25 +65,26 @@ export class WardenService {
|
|
|
57
65
|
async processCommandToResponse(cmd, origin, loggedInUserId) {
|
|
58
66
|
let rval = null;
|
|
59
67
|
if (cmd) {
|
|
60
|
-
Logger.info(
|
|
68
|
+
Logger.info("Processing command : UserID: %s Origin: %s Command: %j", loggedInUserId, origin, cmd);
|
|
61
69
|
if (cmd.sendExpiringValidationToken) {
|
|
62
|
-
rval = { sendExpiringValidationToken: await this.sendExpiringValidationToken(cmd.sendExpiringValidationToken) };
|
|
70
|
+
rval = { sendExpiringValidationToken: await this.sendExpiringValidationToken(cmd.sendExpiringValidationToken, origin) };
|
|
63
71
|
}
|
|
64
72
|
else if (cmd.generateWebAuthnAuthenticationChallengeForUserId) {
|
|
65
73
|
const tmp = await this.generateWebAuthnAuthenticationChallengeForUserId(cmd.generateWebAuthnAuthenticationChallengeForUserId, origin);
|
|
66
74
|
rval = { generateWebAuthnAuthenticationChallengeForUserId: { dataAsJson: JSON.stringify(tmp) } };
|
|
67
75
|
}
|
|
68
76
|
else if (cmd.createAccount) {
|
|
77
|
+
const newEntry = await this.createAccount(cmd.createAccount.contact, origin, cmd.createAccount.sendCode, cmd.createAccount.label, cmd.createAccount.tags);
|
|
69
78
|
rval = {
|
|
70
|
-
createAccount:
|
|
79
|
+
createAccount: newEntry.userId
|
|
71
80
|
};
|
|
72
81
|
}
|
|
73
82
|
else if (cmd.sendMagicLink) {
|
|
74
83
|
if (cmd?.sendMagicLink?.contactLookup && cmd?.sendMagicLink?.contact) {
|
|
75
|
-
throw ErrorRatchet.fErr(
|
|
84
|
+
throw ErrorRatchet.fErr("You may not specify both contact and contactLookup");
|
|
76
85
|
}
|
|
77
86
|
if (!cmd?.sendMagicLink?.contactLookup && !cmd?.sendMagicLink?.contact) {
|
|
78
|
-
throw ErrorRatchet.fErr(
|
|
87
|
+
throw ErrorRatchet.fErr("You must not specify either contact and contactLookup");
|
|
79
88
|
}
|
|
80
89
|
if (cmd.sendMagicLink.contactLookup) {
|
|
81
90
|
const entry = await this.findEntryById(cmd.sendMagicLink.contactLookup.userId);
|
|
@@ -90,7 +99,7 @@ export class WardenService {
|
|
|
90
99
|
cmd.sendMagicLink.contactLookup = null;
|
|
91
100
|
}
|
|
92
101
|
if (!cmd.sendMagicLink.contact) {
|
|
93
|
-
throw ErrorRatchet.fErr(
|
|
102
|
+
throw ErrorRatchet.fErr("Could not find contract entry either directly or by lookup");
|
|
94
103
|
}
|
|
95
104
|
const loggedInUser = StringRatchet.trimToNull(loggedInUserId)
|
|
96
105
|
? await this.opts.storageProvider.findEntryById(loggedInUserId)
|
|
@@ -98,19 +107,19 @@ export class WardenService {
|
|
|
98
107
|
await this.opts.sendMagicLinkCommandValidator.allowMagicLinkCommand(cmd.sendMagicLink, origin, loggedInUser);
|
|
99
108
|
const ttlSeconds = cmd?.sendMagicLink?.ttlSeconds || 300;
|
|
100
109
|
rval = {
|
|
101
|
-
sendMagicLink: await this.sendMagicLink(cmd.sendMagicLink.contact, cmd.sendMagicLink.overrideDestinationContact, this.opts.relyingPartyName, cmd.sendMagicLink.landingUrl, cmd.sendMagicLink.meta, ttlSeconds, cmd.sendMagicLink.customTemplate)
|
|
110
|
+
sendMagicLink: await this.sendMagicLink(cmd.sendMagicLink.contact, cmd.sendMagicLink.overrideDestinationContact, this.opts.relyingPartyName, cmd.sendMagicLink.landingUrl, cmd.sendMagicLink.meta, ttlSeconds, cmd.sendMagicLink.customTemplate)
|
|
102
111
|
};
|
|
103
112
|
}
|
|
104
113
|
else if (cmd.generateWebAuthnRegistrationChallengeForLoggedInUser) {
|
|
105
114
|
if (!StringRatchet.trimToNull(loggedInUserId)) {
|
|
106
|
-
ErrorRatchet.throwFormattedErr(
|
|
115
|
+
ErrorRatchet.throwFormattedErr("This requires a logged in user");
|
|
107
116
|
}
|
|
108
117
|
const tmp = await this.generateWebAuthnRegistrationChallengeForLoggedInUser(loggedInUserId, origin);
|
|
109
118
|
rval = { generateWebAuthnRegistrationChallengeForLoggedInUser: { dataAsJson: JSON.stringify(tmp) } };
|
|
110
119
|
}
|
|
111
120
|
else if (cmd.addContactToLoggedInUser) {
|
|
112
121
|
if (!WardenUtils.validContact(cmd.addContactToLoggedInUser)) {
|
|
113
|
-
ErrorRatchet.throwFormattedErr(
|
|
122
|
+
ErrorRatchet.throwFormattedErr("Cannot add, invalid contact %j", cmd.addContactToLoggedInUser);
|
|
114
123
|
}
|
|
115
124
|
else {
|
|
116
125
|
const out = await this.addContactMethodToUser(loggedInUserId, cmd.addContactToLoggedInUser);
|
|
@@ -119,7 +128,7 @@ export class WardenService {
|
|
|
119
128
|
}
|
|
120
129
|
else if (cmd.addWebAuthnRegistrationToLoggedInUser) {
|
|
121
130
|
if (!StringRatchet.trimToNull(loggedInUserId)) {
|
|
122
|
-
ErrorRatchet.throwFormattedErr(
|
|
131
|
+
ErrorRatchet.throwFormattedErr("This requires a logged in user");
|
|
123
132
|
}
|
|
124
133
|
const data = JSON.parse(cmd.addWebAuthnRegistrationToLoggedInUser.webAuthn.dataAsJson);
|
|
125
134
|
const out = await this.storeAuthnRegistration(loggedInUserId, origin, cmd.addWebAuthnRegistrationToLoggedInUser.applicationName, cmd.addWebAuthnRegistrationToLoggedInUser.deviceLabel, data);
|
|
@@ -130,72 +139,130 @@ export class WardenService {
|
|
|
130
139
|
rval = { error: out.error };
|
|
131
140
|
}
|
|
132
141
|
else {
|
|
133
|
-
rval = { error:
|
|
142
|
+
rval = { error: "Cannot happen - neither user nor error set" };
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
else if (cmd.removeWebAuthnRegistration) {
|
|
137
146
|
const modified = await this.removeSingleWebAuthnRegistration(cmd.removeWebAuthnRegistration.userId, cmd.removeWebAuthnRegistration.credentialId);
|
|
138
147
|
rval = {
|
|
139
|
-
removeWebAuthnRegistration: WardenUtils.stripWardenEntryToSummary(modified)
|
|
148
|
+
removeWebAuthnRegistration: WardenUtils.stripWardenEntryToSummary(modified)
|
|
140
149
|
};
|
|
141
150
|
}
|
|
142
151
|
else if (cmd.removeWebAuthnRegistrationFromLoggedInUser) {
|
|
143
152
|
const modified = await this.removeSingleWebAuthnRegistration(loggedInUserId, cmd.removeWebAuthnRegistrationFromLoggedInUser);
|
|
144
153
|
rval = {
|
|
145
|
-
removeWebAuthnRegistrationFromLoggedInUser: WardenUtils.stripWardenEntryToSummary(modified)
|
|
154
|
+
removeWebAuthnRegistrationFromLoggedInUser: WardenUtils.stripWardenEntryToSummary(modified)
|
|
146
155
|
};
|
|
147
156
|
}
|
|
148
157
|
else if (cmd.removeContactFromLoggedInUser) {
|
|
149
158
|
const output = await this.removeContactMethodFromUser(loggedInUserId, cmd.removeContactFromLoggedInUser);
|
|
150
159
|
rval = {
|
|
151
|
-
removeContactFromLoggedInUser: WardenUtils.stripWardenEntryToSummary(output)
|
|
160
|
+
removeContactFromLoggedInUser: WardenUtils.stripWardenEntryToSummary(output)
|
|
152
161
|
};
|
|
153
162
|
}
|
|
154
163
|
else if (cmd.performLogin) {
|
|
155
164
|
const loginData = cmd.performLogin;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
if (loginOk) {
|
|
159
|
-
const user = StringRatchet.trimToNull(loginData.userId)
|
|
160
|
-
? await this.opts.storageProvider.findEntryById(loginData.userId)
|
|
161
|
-
: await this.opts.storageProvider.findEntryByContact(loginData.contact);
|
|
165
|
+
const user = await this.processLogin(loginData, origin);
|
|
166
|
+
if (user) {
|
|
162
167
|
const decoration = await this.opts.userDecorationProvider.fetchDecoration(user);
|
|
163
168
|
const wardenToken = {
|
|
164
|
-
|
|
169
|
+
wardenData: WardenUtils.stripWardenEntryToSummary(user),
|
|
165
170
|
user: decoration.userTokenData,
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
proxy: decoration.proxyUserTokenData,
|
|
172
|
+
globalRoleIds: decoration.globalRoleIds,
|
|
173
|
+
teamRoleMappings: decoration.teamRoleMappings
|
|
168
174
|
};
|
|
169
175
|
const jwtToken = await this.opts.jwtRatchet.createTokenString(wardenToken, decoration.userTokenExpirationSeconds);
|
|
170
176
|
const output = {
|
|
171
177
|
request: loginData,
|
|
172
178
|
userId: user.userId,
|
|
173
|
-
jwtToken: jwtToken
|
|
179
|
+
jwtToken: jwtToken
|
|
174
180
|
};
|
|
175
181
|
rval = { performLogin: output };
|
|
176
182
|
}
|
|
177
183
|
else {
|
|
178
|
-
rval = { error:
|
|
184
|
+
rval = { error: "Login failed" };
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
else if (cmd.refreshJwtToken) {
|
|
182
188
|
const parsed = await this.opts.jwtRatchet.decodeToken(cmd.refreshJwtToken, 1);
|
|
183
|
-
const user = await this.opts.storageProvider.findEntryById(parsed.
|
|
189
|
+
const user = await this.opts.storageProvider.findEntryById(parsed.wardenData.userId);
|
|
184
190
|
const decoration = await this.opts.userDecorationProvider.fetchDecoration(user);
|
|
185
191
|
const wardenToken = {
|
|
186
|
-
|
|
192
|
+
wardenData: WardenUtils.stripWardenEntryToSummary(user),
|
|
187
193
|
user: decoration.userTokenData,
|
|
188
|
-
|
|
189
|
-
|
|
194
|
+
proxy: decoration.proxyUserTokenData,
|
|
195
|
+
globalRoleIds: decoration.globalRoleIds,
|
|
196
|
+
teamRoleMappings: decoration.teamRoleMappings
|
|
190
197
|
};
|
|
191
198
|
const newToken = await this.opts.jwtRatchet.createTokenString(wardenToken, decoration.userTokenExpirationSeconds);
|
|
192
199
|
rval = {
|
|
193
|
-
refreshJwtToken: newToken
|
|
200
|
+
refreshJwtToken: newToken
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
else if (cmd.exportWebAuthnRegistrationEntryForLoggedInUser) {
|
|
204
|
+
rval = {
|
|
205
|
+
exportWebAuthnRegistrationEntryForLoggedInUser: await this.exportWebAuthnRegistrationEntry(cmd.exportWebAuthnRegistrationEntryForLoggedInUser, loggedInUserId)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
else if (cmd.importWebAuthnRegistrationEntryForLoggedInUser) {
|
|
209
|
+
rval = {
|
|
210
|
+
importWebAuthnRegistrationEntryForLoggedInUser: await this.importWebAuthnRegistrationEntry(cmd.importWebAuthnRegistrationEntryForLoggedInUser, loggedInUserId)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
rval = { error: "No command sent" };
|
|
216
|
+
}
|
|
217
|
+
return rval;
|
|
218
|
+
}
|
|
219
|
+
async exportWebAuthnRegistrationEntry(origin, userId) {
|
|
220
|
+
const ent = await this.opts.storageProvider.findEntryById(userId);
|
|
221
|
+
let rval = null;
|
|
222
|
+
if (ent) {
|
|
223
|
+
const webAuth = ent.webAuthnAuthenticators.find(w => w.origin === origin);
|
|
224
|
+
if (webAuth) {
|
|
225
|
+
const oUrl = new URL(origin);
|
|
226
|
+
const rpId = oUrl.hostname;
|
|
227
|
+
Logger.debug('Finding challenge for origin %s / RPID %s', origin, rpId);
|
|
228
|
+
const expectedChallenge = await this.opts.storageProvider.fetchCurrentUserChallenge(userId, rpId);
|
|
229
|
+
const token = {
|
|
230
|
+
entry: webAuth,
|
|
231
|
+
challenge: expectedChallenge
|
|
194
232
|
};
|
|
233
|
+
const s1 = JSON.stringify(token);
|
|
234
|
+
rval = Base64Ratchet.encodeStringToBase64String(s1);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
Logger.warn("Could not export webauthn - no such origin");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
Logger.warn("Could not export webauthn - no such user id");
|
|
242
|
+
}
|
|
243
|
+
return rval;
|
|
244
|
+
}
|
|
245
|
+
async importWebAuthnRegistrationEntry(token, userId) {
|
|
246
|
+
const ent = await this.opts.storageProvider.findEntryById(userId);
|
|
247
|
+
let rval = false;
|
|
248
|
+
if (ent) {
|
|
249
|
+
const s1 = Base64Ratchet.base64StringToString(token);
|
|
250
|
+
const newEntry = JSON.parse(s1);
|
|
251
|
+
const old = ent.webAuthnAuthenticators.find(w => w.origin === newEntry.entry.origin);
|
|
252
|
+
if (old) {
|
|
253
|
+
Logger.warn("Removing existing entry %j", old);
|
|
254
|
+
ent.webAuthnAuthenticators = ent.webAuthnAuthenticators.filter(w => w.origin !== newEntry.entry.origin);
|
|
255
|
+
}
|
|
256
|
+
ent.webAuthnAuthenticators.push(newEntry.entry);
|
|
257
|
+
await this.opts.storageProvider.saveEntry(ent);
|
|
258
|
+
if (newEntry.challenge) {
|
|
259
|
+
await this.opts.storageProvider.updateUserChallenge(userId, newEntry.entry.origin, newEntry.challenge);
|
|
195
260
|
}
|
|
261
|
+
Logger.info("Wrote ok");
|
|
262
|
+
rval = true;
|
|
196
263
|
}
|
|
197
264
|
else {
|
|
198
|
-
|
|
265
|
+
Logger.warn("Could not export webauthn - no such user id");
|
|
199
266
|
}
|
|
200
267
|
return rval;
|
|
201
268
|
}
|
|
@@ -213,74 +280,81 @@ export class WardenService {
|
|
|
213
280
|
singleUseCodeProvider(contact, requireMagicLinkSupport, returnNullIfNoProviders) {
|
|
214
281
|
const rval = this.opts.singleUseCodeProviders.find((s) => s.handlesContactType(contact.type) && (!requireMagicLinkSupport || s.createCodeAndSendMagicLink));
|
|
215
282
|
if (!rval && !returnNullIfNoProviders) {
|
|
216
|
-
throw ErrorRatchet.fErr(
|
|
283
|
+
throw ErrorRatchet.fErr("Cannot find a single use code provider for contact type : %s", contact.type);
|
|
217
284
|
}
|
|
218
285
|
return rval;
|
|
219
286
|
}
|
|
220
287
|
async sendMagicLink(contact, overrideDestinationContact, relyingPartyName, landingUrl, metaIn, ttlSeconds, customTemplate) {
|
|
221
288
|
let rval = false;
|
|
222
|
-
RequireRatchet.notNullOrUndefined(contact,
|
|
223
|
-
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(landingUrl,
|
|
224
|
-
RequireRatchet.true(this.urlIsOnAllowedOrigin(landingUrl),
|
|
289
|
+
RequireRatchet.notNullOrUndefined(contact, "contact");
|
|
290
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(landingUrl, "landingUrl");
|
|
291
|
+
RequireRatchet.true(this.urlIsOnAllowedOrigin(landingUrl), "landingUrl is not on an allowed origin for redirect");
|
|
225
292
|
if (contact?.type && StringRatchet.trimToNull(contact?.value)) {
|
|
226
293
|
const prov = this.singleUseCodeProvider(contact, true);
|
|
227
294
|
rval = await prov.createCodeAndSendMagicLink(contact, relyingPartyName, landingUrl, metaIn, ttlSeconds, overrideDestinationContact, customTemplate);
|
|
228
295
|
}
|
|
229
296
|
else {
|
|
230
|
-
ErrorRatchet.throwFormattedErr(
|
|
297
|
+
ErrorRatchet.throwFormattedErr("Cannot send - invalid contact %j", contact);
|
|
231
298
|
}
|
|
232
299
|
return rval;
|
|
233
300
|
}
|
|
234
|
-
async
|
|
301
|
+
async createAccountByThirdParty(thirdParty, origin, inLabel) {
|
|
302
|
+
let rval = null;
|
|
303
|
+
RequireRatchet.notNullOrUndefined(thirdParty, "thirdParty");
|
|
304
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(thirdParty.thirdParty, "thirdParty");
|
|
305
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(thirdParty.thirdPartyId, "thirdPartyId");
|
|
306
|
+
const old = await this.opts.storageProvider.findEntryByThirdPartyId(thirdParty.thirdParty, thirdParty.thirdPartyId);
|
|
307
|
+
if (old) {
|
|
308
|
+
ErrorRatchet.throwFormattedErr("Cannot create - account already exists for %j", thirdParty);
|
|
309
|
+
}
|
|
310
|
+
const label = inLabel || thirdParty.thirdParty + " " + thirdParty.thirdPartyId;
|
|
311
|
+
const newUser = new WardenEntryBuilder(label).withThirdPartyAuthentication([thirdParty]).entry;
|
|
312
|
+
rval = await this.opts.storageProvider.saveEntry(newUser);
|
|
313
|
+
return rval;
|
|
314
|
+
}
|
|
315
|
+
async createAccount(contact, origin, sendCode, label, tags) {
|
|
235
316
|
let rval = null;
|
|
236
317
|
if (WardenUtils.validContact(contact)) {
|
|
237
318
|
const old = await this.opts.storageProvider.findEntryByContact(contact);
|
|
238
319
|
if (old) {
|
|
239
|
-
ErrorRatchet.throwFormattedErr(
|
|
240
|
-
}
|
|
241
|
-
const guid = StringRatchet.createType4Guid();
|
|
242
|
-
const now = Date.now();
|
|
243
|
-
const newUser = {
|
|
244
|
-
userId: guid,
|
|
245
|
-
userLabel: label || 'User ' + guid,
|
|
246
|
-
contactMethods: [contact],
|
|
247
|
-
tags: tags || [],
|
|
248
|
-
webAuthnAuthenticators: [],
|
|
249
|
-
createdEpochMS: now,
|
|
250
|
-
updatedEpochMS: now,
|
|
251
|
-
};
|
|
252
|
-
const next = await this.opts.storageProvider.saveEntry(newUser);
|
|
253
|
-
rval = next.userId;
|
|
254
|
-
if (this?.opts?.eventProcessor) {
|
|
255
|
-
await this.opts.eventProcessor.userCreated(next);
|
|
320
|
+
ErrorRatchet.throwFormattedErr("Cannot create - account already exists for %j", contact);
|
|
256
321
|
}
|
|
322
|
+
const newUser = new WardenEntryBuilder(label || contact.value).withContacts([contact]).withTags(tags).entry;
|
|
323
|
+
rval = await this.opts.storageProvider.saveEntry(newUser);
|
|
257
324
|
if (sendCode) {
|
|
258
|
-
Logger.info(
|
|
259
|
-
await this.sendExpiringValidationToken(contact);
|
|
325
|
+
Logger.info("New user %j created and send requested - sending", rval);
|
|
326
|
+
await this.sendExpiringValidationToken(contact, origin);
|
|
260
327
|
}
|
|
261
328
|
}
|
|
262
329
|
else {
|
|
263
|
-
ErrorRatchet.throwFormattedErr(
|
|
330
|
+
ErrorRatchet.throwFormattedErr("Cannot create - invalid contact (missing or invalid fields)");
|
|
264
331
|
}
|
|
265
332
|
return rval;
|
|
266
333
|
}
|
|
334
|
+
async saveNewUser(newUser) {
|
|
335
|
+
const next = await this.opts.storageProvider.saveEntry(newUser);
|
|
336
|
+
if (this?.opts?.eventProcessor) {
|
|
337
|
+
await this.opts.eventProcessor.userCreated(next);
|
|
338
|
+
}
|
|
339
|
+
return next;
|
|
340
|
+
}
|
|
267
341
|
async addContactMethodToUser(userId, contact) {
|
|
268
342
|
let rval = false;
|
|
269
343
|
if (StringRatchet.trimToNull(userId) && WardenUtils.validContact(contact)) {
|
|
270
344
|
const otherUser = await this.opts.storageProvider.findEntryByContact(contact);
|
|
271
345
|
if (otherUser && otherUser.userId !== userId) {
|
|
272
|
-
ErrorRatchet.throwFormattedErr(
|
|
346
|
+
ErrorRatchet.throwFormattedErr("Cannot add contact to this user, another user already has that contact");
|
|
273
347
|
}
|
|
274
348
|
const curUser = await this.opts.storageProvider.findEntryById(userId);
|
|
275
349
|
if (!curUser) {
|
|
276
|
-
ErrorRatchet.throwFormattedErr(
|
|
350
|
+
ErrorRatchet.throwFormattedErr("Cannot add contact to this user, user does not exist");
|
|
277
351
|
}
|
|
278
352
|
curUser.contactMethods.push(contact);
|
|
279
353
|
await this.opts.storageProvider.saveEntry(curUser);
|
|
280
354
|
rval = true;
|
|
281
355
|
}
|
|
282
356
|
else {
|
|
283
|
-
ErrorRatchet.throwFormattedErr(
|
|
357
|
+
ErrorRatchet.throwFormattedErr("Cannot add - invalid config : %s %j", userId, contact);
|
|
284
358
|
}
|
|
285
359
|
return rval;
|
|
286
360
|
}
|
|
@@ -289,32 +363,32 @@ export class WardenService {
|
|
|
289
363
|
if (StringRatchet.trimToNull(userId) && WardenUtils.validContact(contact)) {
|
|
290
364
|
const curUser = await this.opts.storageProvider.findEntryById(userId);
|
|
291
365
|
if (!curUser) {
|
|
292
|
-
ErrorRatchet.throwFormattedErr(
|
|
366
|
+
ErrorRatchet.throwFormattedErr("Cannot remove contact from this user, user does not exist");
|
|
293
367
|
}
|
|
294
368
|
curUser.contactMethods = (curUser.contactMethods || []).filter((s) => s.type !== contact.type || s.value !== contact.value);
|
|
295
369
|
if (curUser.contactMethods.length === 0) {
|
|
296
|
-
ErrorRatchet.throwFormattedErr(
|
|
370
|
+
ErrorRatchet.throwFormattedErr("Cannot remove the last contact method from a user");
|
|
297
371
|
}
|
|
298
372
|
await this.opts.storageProvider.saveEntry(curUser);
|
|
299
373
|
rval = await this.opts.storageProvider.findEntryById(userId);
|
|
300
374
|
}
|
|
301
375
|
else {
|
|
302
|
-
ErrorRatchet.throwFormattedErr(
|
|
376
|
+
ErrorRatchet.throwFormattedErr("Cannot add - invalid config : %s %j", userId, contact);
|
|
303
377
|
}
|
|
304
378
|
return rval;
|
|
305
379
|
}
|
|
306
380
|
async generateWebAuthnRegistrationChallengeForLoggedInUser(userId, origin) {
|
|
307
381
|
if (!origin || !this.opts.allowedOrigins.includes(origin)) {
|
|
308
|
-
throw new Error(
|
|
382
|
+
throw new Error("Invalid origin : " + origin);
|
|
309
383
|
}
|
|
310
384
|
const asUrl = new URL(origin);
|
|
311
385
|
const rpID = asUrl.hostname;
|
|
312
386
|
const entry = await this.opts.storageProvider.findEntryById(userId);
|
|
313
387
|
if (!entry) {
|
|
314
|
-
throw ErrorRatchet.fErr(
|
|
388
|
+
throw ErrorRatchet.fErr("Cannot generateWebAuthnRegistrationChallengeForLoggedInUser - no user %s / %s", userId, origin);
|
|
315
389
|
}
|
|
316
390
|
if (!entry?.webAuthnAuthenticators?.length) {
|
|
317
|
-
Logger.info(
|
|
391
|
+
Logger.info("Entry has no webAuthnAuthenticators");
|
|
318
392
|
entry.webAuthnAuthenticators = [];
|
|
319
393
|
}
|
|
320
394
|
const options = await generateRegistrationOptions({
|
|
@@ -322,67 +396,67 @@ export class WardenService {
|
|
|
322
396
|
rpID: rpID,
|
|
323
397
|
userID: StringRatchet.stringToUint8Array(entry.userId),
|
|
324
398
|
userName: entry.userLabel,
|
|
325
|
-
attestationType:
|
|
399
|
+
attestationType: "none",
|
|
326
400
|
excludeCredentials: entry.webAuthnAuthenticators.map((authenticator) => ({
|
|
327
401
|
id: authenticator.credentialPublicKeyBase64,
|
|
328
|
-
transports: authenticator.transports
|
|
329
|
-
}))
|
|
402
|
+
transports: authenticator.transports
|
|
403
|
+
}))
|
|
330
404
|
});
|
|
331
405
|
await this.opts.storageProvider.updateUserChallenge(entry.userId, rpID, options.challenge);
|
|
332
406
|
return options;
|
|
333
407
|
}
|
|
334
408
|
async storeAuthnRegistration(userId, origin, applicationName, deviceLabel, data) {
|
|
335
|
-
Logger.info(
|
|
409
|
+
Logger.info("Store authn data : %j", data);
|
|
336
410
|
let rval = null;
|
|
337
411
|
try {
|
|
338
412
|
if (!origin || !this.opts.allowedOrigins.includes(origin)) {
|
|
339
|
-
throw new Error(
|
|
413
|
+
throw new Error("Invalid origin : " + origin);
|
|
340
414
|
}
|
|
341
415
|
const asUrl = new URL(origin);
|
|
342
416
|
const rpID = asUrl.hostname;
|
|
343
417
|
const user = await this.opts.storageProvider.findEntryById(userId);
|
|
344
418
|
if (!user) {
|
|
345
|
-
throw ErrorRatchet.fErr(
|
|
419
|
+
throw ErrorRatchet.fErr("Cannot storeAuthnRegistration - no user %s / %s", userId, origin);
|
|
346
420
|
}
|
|
347
421
|
const expectedChallenge = await this.opts.storageProvider.fetchCurrentUserChallenge(user.userId, rpID);
|
|
348
422
|
const vrOpts = {
|
|
349
423
|
response: data,
|
|
350
424
|
expectedChallenge: expectedChallenge,
|
|
351
425
|
expectedOrigin: origin,
|
|
352
|
-
expectedRPID: rpID
|
|
426
|
+
expectedRPID: rpID
|
|
353
427
|
};
|
|
354
|
-
Logger.info(
|
|
428
|
+
Logger.info("Calling verifyRegistrationResponse: %j", vrOpts);
|
|
355
429
|
const verification = await verifyRegistrationResponse(vrOpts);
|
|
356
|
-
Logger.info(
|
|
430
|
+
Logger.info("verifyRegistrationResponse Result : %j", verification);
|
|
357
431
|
rval = {
|
|
358
432
|
updatedEntry: null,
|
|
359
433
|
registrationResponseId: data.id,
|
|
360
|
-
result: verification.verified ? WardenStoreRegistrationResponseType.Verified : WardenStoreRegistrationResponseType.Failed
|
|
434
|
+
result: verification.verified ? WardenStoreRegistrationResponseType.Verified : WardenStoreRegistrationResponseType.Failed
|
|
361
435
|
};
|
|
362
436
|
if (rval.result === WardenStoreRegistrationResponseType.Verified) {
|
|
363
|
-
Logger.info(
|
|
437
|
+
Logger.info("Storing registration");
|
|
364
438
|
const newAuth = {
|
|
365
439
|
origin: origin,
|
|
366
|
-
applicationName: applicationName ||
|
|
367
|
-
deviceLabel: deviceLabel ||
|
|
440
|
+
applicationName: applicationName || "Unknown Application",
|
|
441
|
+
deviceLabel: deviceLabel || "Unknown Device",
|
|
368
442
|
counter: verification.registrationInfo.credential.counter,
|
|
369
443
|
credentialBackedUp: verification.registrationInfo.credentialBackedUp,
|
|
370
444
|
credentialDeviceType: verification.registrationInfo.credentialDeviceType,
|
|
371
445
|
credentialIdBase64: verification.registrationInfo.credential.id,
|
|
372
|
-
credentialPublicKeyBase64: Base64Ratchet.uint8ArrayToBase64UrlString(verification.registrationInfo.credential.publicKey)
|
|
446
|
+
credentialPublicKeyBase64: Base64Ratchet.uint8ArrayToBase64UrlString(verification.registrationInfo.credential.publicKey)
|
|
373
447
|
};
|
|
374
448
|
user.webAuthnAuthenticators = (user.webAuthnAuthenticators || []).filter((wa) => wa.credentialIdBase64 !== newAuth.credentialIdBase64);
|
|
375
449
|
user.webAuthnAuthenticators.push(newAuth);
|
|
376
450
|
const storedUser = await this.opts.storageProvider.saveEntry(user);
|
|
377
451
|
rval.updatedEntry = storedUser;
|
|
378
|
-
Logger.info(
|
|
452
|
+
Logger.info("Stored auth : %j", storedUser);
|
|
379
453
|
}
|
|
380
454
|
}
|
|
381
455
|
catch (err) {
|
|
382
456
|
rval = {
|
|
383
457
|
registrationResponseId: data.id,
|
|
384
458
|
result: WardenStoreRegistrationResponseType.Error,
|
|
385
|
-
error: ErrorRatchet.safeStringifyErr(err)
|
|
459
|
+
error: ErrorRatchet.safeStringifyErr(err)
|
|
386
460
|
};
|
|
387
461
|
}
|
|
388
462
|
return rval;
|
|
@@ -395,70 +469,95 @@ export class WardenService {
|
|
|
395
469
|
async generateWebAuthnAuthenticationChallenge(user, origin) {
|
|
396
470
|
const userAuthenticators = user.webAuthnAuthenticators;
|
|
397
471
|
if (!origin || !this.opts.allowedOrigins.includes(origin)) {
|
|
398
|
-
throw new Error(
|
|
472
|
+
throw new Error("Invalid origin : " + origin);
|
|
399
473
|
}
|
|
400
474
|
const asUrl = new URL(origin);
|
|
401
475
|
const rpID = asUrl.hostname;
|
|
402
476
|
const out = userAuthenticators.map((authenticator) => {
|
|
403
477
|
const next = {
|
|
404
478
|
id: authenticator.credentialIdBase64,
|
|
405
|
-
transports: authenticator.transports
|
|
479
|
+
transports: authenticator.transports
|
|
406
480
|
};
|
|
407
481
|
return next;
|
|
408
482
|
});
|
|
409
483
|
const opts = {
|
|
410
484
|
rpID: rpID,
|
|
411
485
|
allowCredentials: out,
|
|
412
|
-
userVerification:
|
|
486
|
+
userVerification: "preferred"
|
|
413
487
|
};
|
|
414
488
|
const options = await generateAuthenticationOptions(opts);
|
|
415
489
|
await this.opts.storageProvider.updateUserChallenge(user.userId, rpID, options.challenge);
|
|
416
490
|
return options;
|
|
417
491
|
}
|
|
418
|
-
async sendExpiringValidationToken(request) {
|
|
492
|
+
async sendExpiringValidationToken(request, origin) {
|
|
419
493
|
let rval = false;
|
|
420
494
|
if (request?.type && StringRatchet.trimToNull(request?.value)) {
|
|
421
495
|
const prov = this.singleUseCodeProvider(request, false);
|
|
422
|
-
rval = await prov.createAndSendNewCode(request, this.opts.relyingPartyName);
|
|
496
|
+
rval = await prov.createAndSendNewCode(request, this.opts.relyingPartyName, origin);
|
|
423
497
|
}
|
|
424
498
|
else {
|
|
425
|
-
ErrorRatchet.throwFormattedErr(
|
|
499
|
+
ErrorRatchet.throwFormattedErr("Cannot send - invalid request %j", request);
|
|
426
500
|
}
|
|
427
501
|
return rval;
|
|
428
502
|
}
|
|
429
503
|
async processLogin(request, origin) {
|
|
430
|
-
Logger.info(
|
|
431
|
-
let rval =
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
504
|
+
Logger.info("Processing login : %s : %j", origin, request);
|
|
505
|
+
let rval = null;
|
|
506
|
+
const requestErrors = WardenUtils.loginRequestErrors(request);
|
|
507
|
+
if (requestErrors.length > 0) {
|
|
508
|
+
throw ErrorRatchet.fErr("Invalid login request : %j", requestErrors);
|
|
509
|
+
}
|
|
510
|
+
if (request.type === WardenLoginRequestType.ThirdParty) {
|
|
511
|
+
const provider = (this.options.thirdPartyAuthenticationProviders ?? [])
|
|
512
|
+
.find(s => s.handlesThirdParty(request.thirdPartyToken.thirdParty));
|
|
513
|
+
if (provider) {
|
|
514
|
+
const auth = await provider.validateTokenAndReturnThirdPartyUserId(request.thirdPartyToken, origin);
|
|
515
|
+
if (auth) {
|
|
516
|
+
rval = await this.opts.storageProvider.findEntryByThirdPartyId(auth.thirdParty, auth.thirdPartyId);
|
|
517
|
+
if (!rval && request.createUserIfMissing) {
|
|
518
|
+
Logger.info("Found no existing user for %j, creating", auth);
|
|
519
|
+
let label = auth.thirdParty + " " + auth.thirdPartyId;
|
|
520
|
+
if (provider.extractUserLabelFromAuthentication) {
|
|
521
|
+
label = await provider.extractUserLabelFromAuthentication(auth);
|
|
522
|
+
}
|
|
523
|
+
rval = await this.createAccountByThirdParty(auth, origin, label);
|
|
524
|
+
Logger.info("Finished create, new id is %s", rval.userId);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
Logger.warn("Authentication failed for %j", request.thirdPartyToken, request.createUserIfMissing);
|
|
529
|
+
}
|
|
445
530
|
}
|
|
446
|
-
|
|
447
|
-
|
|
531
|
+
else {
|
|
532
|
+
Logger.warn("Could not find any provider to handle third party token %j", request.thirdPartyToken);
|
|
448
533
|
}
|
|
449
534
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
535
|
+
else {
|
|
536
|
+
let user = StringRatchet.trimToNull(request?.userId)
|
|
537
|
+
? await this.opts.storageProvider.findEntryById(request?.userId)
|
|
538
|
+
: await this.opts.storageProvider.findEntryByContact(request.contact);
|
|
539
|
+
if (!user) {
|
|
540
|
+
Logger.info("User not found, and createUserIfMissing=%s / %j", request.createUserIfMissing, request.contact);
|
|
541
|
+
if (request.createUserIfMissing && request.contact) {
|
|
542
|
+
user = await this.createAccount(request.contact, origin);
|
|
543
|
+
Logger.info("Finished create, new id is %s", user.userId);
|
|
544
|
+
}
|
|
545
|
+
if (!user) {
|
|
546
|
+
ErrorRatchet.throwFormattedErr("No user found for %j / %s", request?.contact, request?.userId);
|
|
547
|
+
}
|
|
458
548
|
}
|
|
459
|
-
|
|
460
|
-
|
|
549
|
+
let loginSuccess = false;
|
|
550
|
+
if (request.webAuthn) {
|
|
551
|
+
loginSuccess = await this.loginWithWebAuthnRequest(user, origin, request.webAuthn);
|
|
461
552
|
}
|
|
553
|
+
else if (StringRatchet.trimToNull(request.expiringToken)) {
|
|
554
|
+
const prov = this.singleUseCodeProvider(request.contact, false);
|
|
555
|
+
loginSuccess = await prov.checkCode(request.contact.value, request.expiringToken);
|
|
556
|
+
if (!loginSuccess) {
|
|
557
|
+
ErrorRatchet.throwFormattedErr("Cannot login - token is invalid for this user");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
rval = loginSuccess ? user : null;
|
|
462
561
|
}
|
|
463
562
|
return rval;
|
|
464
563
|
}
|
|
@@ -470,7 +569,7 @@ export class WardenService {
|
|
|
470
569
|
const auth = (user.webAuthnAuthenticators || []).find((s) => s.credentialIdBase64 === data.id);
|
|
471
570
|
if (!auth) {
|
|
472
571
|
const allIds = (user.webAuthnAuthenticators || []).map((s) => s.credentialIdBase64);
|
|
473
|
-
throw ErrorRatchet.fErr(
|
|
572
|
+
throw ErrorRatchet.fErr("Could not find authenticator %s (%s) for user %s (avail were : %j)", data.id, data.id, user.userId, allIds);
|
|
474
573
|
}
|
|
475
574
|
const vrOpts = {
|
|
476
575
|
response: data,
|
|
@@ -480,8 +579,8 @@ export class WardenService {
|
|
|
480
579
|
credential: {
|
|
481
580
|
counter: auth.counter,
|
|
482
581
|
id: auth.credentialIdBase64,
|
|
483
|
-
publicKey: Base64Ratchet.base64UrlStringToBytes(auth.credentialPublicKeyBase64)
|
|
484
|
-
}
|
|
582
|
+
publicKey: Base64Ratchet.base64UrlStringToBytes(auth.credentialPublicKeyBase64)
|
|
583
|
+
}
|
|
485
584
|
};
|
|
486
585
|
const verification = await verifyAuthenticationResponse(vrOpts);
|
|
487
586
|
if (verification.verified) {
|
|
@@ -496,7 +595,7 @@ export class WardenService {
|
|
|
496
595
|
ent = await this.opts.storageProvider.saveEntry(ent);
|
|
497
596
|
}
|
|
498
597
|
else {
|
|
499
|
-
Logger.info(
|
|
598
|
+
Logger.info("Not removing - no such user as %s", userId);
|
|
500
599
|
}
|
|
501
600
|
return ent;
|
|
502
601
|
}
|
|
@@ -512,7 +611,7 @@ export class WardenService {
|
|
|
512
611
|
rval = true;
|
|
513
612
|
}
|
|
514
613
|
else {
|
|
515
|
-
Logger.warn(
|
|
614
|
+
Logger.warn("Cannot remove non-existent user : %s", userId);
|
|
516
615
|
}
|
|
517
616
|
}
|
|
518
617
|
return rval;
|