@bitblit/ratchet-warden-server 4.0.84-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.
Files changed (55) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/License.txt +13 -0
  3. package/README.md +38 -0
  4. package/dist/cjs/build/ratchet-warden-server-info.js +18 -0
  5. package/dist/cjs/index.js +18 -0
  6. package/dist/cjs/server/provider/warden-default-user-decoration-provider.js +15 -0
  7. package/dist/cjs/server/provider/warden-event-processing-provider.js +2 -0
  8. package/dist/cjs/server/provider/warden-mailer-message-sending-provider-options.js +2 -0
  9. package/dist/cjs/server/provider/warden-mailer-message-sending-provider.js +36 -0
  10. package/dist/cjs/server/provider/warden-message-sending-provider.js +2 -0
  11. package/dist/cjs/server/provider/warden-no-op-event-processing-provider.js +8 -0
  12. package/dist/cjs/server/provider/warden-s3-single-file-storage-provider-options.js +2 -0
  13. package/dist/cjs/server/provider/warden-s3-single-file-storage-provider.js +90 -0
  14. package/dist/cjs/server/provider/warden-storage-provider.js +2 -0
  15. package/dist/cjs/server/provider/warden-twilio-text-message-sending-provider-options.js +2 -0
  16. package/dist/cjs/server/provider/warden-twilio-text-message-sending-provider.js +31 -0
  17. package/dist/cjs/server/provider/warden-user-decoration-provider.js +2 -0
  18. package/dist/cjs/server/warden-service-options.js +2 -0
  19. package/dist/cjs/server/warden-service.js +456 -0
  20. package/dist/es/build/ratchet-warden-server-info.js +14 -0
  21. package/dist/es/index.js +15 -0
  22. package/dist/es/server/provider/warden-default-user-decoration-provider.js +11 -0
  23. package/dist/es/server/provider/warden-event-processing-provider.js +1 -0
  24. package/dist/es/server/provider/warden-mailer-message-sending-provider-options.js +1 -0
  25. package/dist/es/server/provider/warden-mailer-message-sending-provider.js +32 -0
  26. package/dist/es/server/provider/warden-message-sending-provider.js +1 -0
  27. package/dist/es/server/provider/warden-no-op-event-processing-provider.js +4 -0
  28. package/dist/es/server/provider/warden-s3-single-file-storage-provider-options.js +1 -0
  29. package/dist/es/server/provider/warden-s3-single-file-storage-provider.js +86 -0
  30. package/dist/es/server/provider/warden-storage-provider.js +1 -0
  31. package/dist/es/server/provider/warden-twilio-text-message-sending-provider-options.js +1 -0
  32. package/dist/es/server/provider/warden-twilio-text-message-sending-provider.js +27 -0
  33. package/dist/es/server/provider/warden-user-decoration-provider.js +1 -0
  34. package/dist/es/server/warden-service-options.js +1 -0
  35. package/dist/es/server/warden-service.js +450 -0
  36. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  37. package/dist/tsconfig.es.tsbuildinfo +1 -0
  38. package/dist/tsconfig.types.tsbuildinfo +1 -0
  39. package/dist/types/build/ratchet-warden-server-info.d.ts +5 -0
  40. package/dist/types/index.d.ts +18 -0
  41. package/dist/types/server/provider/warden-default-user-decoration-provider.d.ts +9 -0
  42. package/dist/types/server/provider/warden-event-processing-provider.d.ts +8 -0
  43. package/dist/types/server/provider/warden-mailer-message-sending-provider-options.d.ts +5 -0
  44. package/dist/types/server/provider/warden-mailer-message-sending-provider.d.ts +13 -0
  45. package/dist/types/server/provider/warden-message-sending-provider.d.ts +10 -0
  46. package/dist/types/server/provider/warden-no-op-event-processing-provider.d.ts +9 -0
  47. package/dist/types/server/provider/warden-s3-single-file-storage-provider-options.d.ts +4 -0
  48. package/dist/types/server/provider/warden-s3-single-file-storage-provider.d.ts +29 -0
  49. package/dist/types/server/provider/warden-storage-provider.d.ts +14 -0
  50. package/dist/types/server/provider/warden-twilio-text-message-sending-provider-options.d.ts +5 -0
  51. package/dist/types/server/provider/warden-twilio-text-message-sending-provider.d.ts +10 -0
  52. package/dist/types/server/provider/warden-user-decoration-provider.d.ts +8 -0
  53. package/dist/types/server/warden-service-options.d.ts +16 -0
  54. package/dist/types/server/warden-service.d.ts +27 -0
  55. package/package.json +71 -0
@@ -0,0 +1,456 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WardenService = void 0;
4
+ const server_1 = require("@simplewebauthn/server");
5
+ const ratchet_warden_common_1 = require("@bitblit/ratchet-warden-common");
6
+ const ratchet_aws_1 = require("@bitblit/ratchet-aws");
7
+ const ratchet_common_1 = require("@bitblit/ratchet-common");
8
+ const warden_default_user_decoration_provider_1 = require("./provider/warden-default-user-decoration-provider");
9
+ const warden_no_op_event_processing_provider_1 = require("./provider/warden-no-op-event-processing-provider");
10
+ class WardenService {
11
+ constructor(inOptions) {
12
+ this.inOptions = inOptions;
13
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(inOptions, 'options');
14
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(inOptions.relyingPartyName, 'options.relyingPartyName');
15
+ ratchet_common_1.RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.allowedOrigins, 'options.allowedOrigins');
16
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(inOptions.storageProvider, 'options.storageProvider');
17
+ ratchet_common_1.RequireRatchet.notNullUndefinedOrEmptyArray(inOptions.messageSendingProviders, 'options.messageSendingProviders');
18
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(inOptions.expiringCodeProvider, 'options.expiringCodeProvider');
19
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(inOptions.jwtRatchet, 'options.jwtRatchet');
20
+ this.opts = Object.assign({ userTokenDataProvider: new warden_default_user_decoration_provider_1.WardenDefaultUserDecorationProvider(), eventProcessor: new warden_no_op_event_processing_provider_1.WardenNoOpEventProcessingProvider() }, inOptions);
21
+ this.expiringCodeRatchet = new ratchet_aws_1.ExpiringCodeRatchet(this.opts.expiringCodeProvider);
22
+ }
23
+ get options() {
24
+ return Object.assign({}, this.opts);
25
+ }
26
+ findEntryByContact(contact) {
27
+ return this.opts.storageProvider.findEntryByContact(contact);
28
+ }
29
+ async processCommandStringToString(cmdString, origin, loggedInUserId) {
30
+ let rval = null;
31
+ try {
32
+ const cmd = JSON.parse(cmdString);
33
+ const resp = await this.processCommandToResponse(cmd, origin, loggedInUserId);
34
+ if (resp === null) {
35
+ ratchet_common_1.Logger.warn('Response was null for %s %s %s', cmdString, origin, loggedInUserId);
36
+ }
37
+ else {
38
+ rval = JSON.stringify(resp);
39
+ }
40
+ }
41
+ catch (err) {
42
+ const errString = ratchet_common_1.ErrorRatchet.safeStringifyErr(err);
43
+ ratchet_common_1.Logger.error('Failed %s : %j', errString, cmdString, err);
44
+ rval = JSON.stringify({ error: errString });
45
+ }
46
+ return rval;
47
+ }
48
+ async processCommandToResponse(cmd, origin, loggedInUserId) {
49
+ let rval = null;
50
+ if (cmd) {
51
+ ratchet_common_1.Logger.info('Processing command : UserID: %s Origin: %s Command: %j', loggedInUserId, origin, cmd);
52
+ if (cmd.sendExpiringValidationToken) {
53
+ rval = { sendExpiringValidationToken: await this.sendExpiringValidationToken(cmd.sendExpiringValidationToken) };
54
+ }
55
+ else if (cmd.generateWebAuthnAuthenticationChallengeForUserId) {
56
+ const tmp = await this.generateWebAuthnAuthenticationChallengeForUserId(cmd.generateWebAuthnAuthenticationChallengeForUserId, origin);
57
+ rval = { generateWebAuthnAuthenticationChallengeForUserId: { dataAsJson: JSON.stringify(tmp) } };
58
+ }
59
+ else if (cmd.createAccount) {
60
+ rval = {
61
+ createAccount: await this.createAccount(cmd.createAccount.contact, cmd.createAccount.sendCode, cmd.createAccount.label, cmd.createAccount.tags),
62
+ };
63
+ }
64
+ else if (cmd.generateWebAuthnRegistrationChallengeForLoggedInUser) {
65
+ if (!ratchet_common_1.StringRatchet.trimToNull(loggedInUserId)) {
66
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('This requires a logged in user');
67
+ }
68
+ const tmp = await this.generateWebAuthnRegistrationChallengeForLoggedInUser(loggedInUserId, origin);
69
+ rval = { generateWebAuthnRegistrationChallengeForLoggedInUser: { dataAsJson: JSON.stringify(tmp) } };
70
+ }
71
+ else if (cmd.addContactToLoggedInUser) {
72
+ if (!ratchet_warden_common_1.WardenUtils.validContact(cmd.addContactToLoggedInUser)) {
73
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot add, invalid contact %j', cmd.addContactToLoggedInUser);
74
+ }
75
+ else {
76
+ const out = await this.addContactMethodToUser(loggedInUserId, cmd.addContactToLoggedInUser);
77
+ rval = { addContactToLoggedInUser: out };
78
+ }
79
+ }
80
+ else if (cmd.addWebAuthnRegistrationToLoggedInUser) {
81
+ if (!ratchet_common_1.StringRatchet.trimToNull(loggedInUserId)) {
82
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('This requires a logged in user');
83
+ }
84
+ const data = JSON.parse(cmd.addWebAuthnRegistrationToLoggedInUser.dataAsJson);
85
+ const out = await this.storeAuthnRegistration(loggedInUserId, origin, data);
86
+ if (out.updatedEntry) {
87
+ rval = { addWebAuthnRegistrationToLoggedInUser: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(out.updatedEntry) };
88
+ }
89
+ else if (out.error) {
90
+ rval = { error: out.error };
91
+ }
92
+ else {
93
+ rval = { error: 'Cannot happen - neither user nor error set' };
94
+ }
95
+ }
96
+ else if (cmd.removeWebAuthnRegistration) {
97
+ const modified = await this.removeSingleWebAuthnRegistration(cmd.removeWebAuthnRegistration.userId, cmd.removeWebAuthnRegistration.credentialId);
98
+ rval = {
99
+ removeWebAuthnRegistration: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(modified),
100
+ };
101
+ }
102
+ else if (cmd.removeWebAuthnRegistrationFromLoggedInUser) {
103
+ const modified = await this.removeSingleWebAuthnRegistration(loggedInUserId, cmd.removeWebAuthnRegistrationFromLoggedInUser);
104
+ rval = {
105
+ removeWebAuthnRegistrationFromLoggedInUser: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(modified),
106
+ };
107
+ }
108
+ else if (cmd.removeContactFromLoggedInUser) {
109
+ const output = await this.removeContactMethodFromUser(loggedInUserId, cmd.removeContactFromLoggedInUser);
110
+ rval = {
111
+ removeContactFromLoggedInUser: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(output),
112
+ };
113
+ }
114
+ else if (cmd.performLogin) {
115
+ const loginData = cmd.performLogin;
116
+ const loginOk = await this.processLogin(loginData, origin);
117
+ ratchet_common_1.Logger.info('Performing login - login auth check was : %s', loginOk);
118
+ if (loginOk) {
119
+ const user = ratchet_common_1.StringRatchet.trimToNull(loginData.userId)
120
+ ? await this.opts.storageProvider.findEntryById(loginData.userId)
121
+ : await this.opts.storageProvider.findEntryByContact(loginData.contact);
122
+ const decoration = await this.opts.userDecorationProvider.fetchDecoration(user);
123
+ const wardenToken = {
124
+ loginData: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(user),
125
+ user: decoration.userTokenData,
126
+ roles: ratchet_warden_common_1.WardenUtils.teamRolesToRoles(decoration.userTeamRoles),
127
+ proxy: null,
128
+ };
129
+ const jwtToken = await this.opts.jwtRatchet.createTokenString(wardenToken, decoration.userTokenExpirationSeconds);
130
+ const output = {
131
+ request: loginData,
132
+ userId: user.userId,
133
+ jwtToken: jwtToken,
134
+ };
135
+ rval = { performLogin: output };
136
+ }
137
+ else {
138
+ rval = { error: 'Login failed' };
139
+ }
140
+ }
141
+ else if (cmd.refreshJwtToken) {
142
+ const parsed = await this.opts.jwtRatchet.decodeToken(cmd.refreshJwtToken, ratchet_common_1.ExpiredJwtHandling.THROW_EXCEPTION);
143
+ const user = await this.opts.storageProvider.findEntryById(parsed.loginData.userId);
144
+ const decoration = await this.opts.userDecorationProvider.fetchDecoration(user);
145
+ const wardenToken = {
146
+ loginData: ratchet_warden_common_1.WardenUtils.stripWardenEntryToSummary(user),
147
+ user: decoration.userTokenData,
148
+ roles: ratchet_warden_common_1.WardenUtils.teamRolesToRoles(decoration.userTeamRoles),
149
+ proxy: null,
150
+ };
151
+ const newToken = await this.opts.jwtRatchet.createTokenString(wardenToken, decoration.userTokenExpirationSeconds);
152
+ rval = {
153
+ refreshJwtToken: newToken,
154
+ };
155
+ }
156
+ }
157
+ else {
158
+ rval = { error: 'No command sent' };
159
+ }
160
+ return rval;
161
+ }
162
+ async createAccount(contact, sendCode, label, tags) {
163
+ var _a;
164
+ let rval = null;
165
+ if (ratchet_warden_common_1.WardenUtils.validContact(contact)) {
166
+ const old = await this.opts.storageProvider.findEntryByContact(contact);
167
+ if (!!old) {
168
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot create - account already exists for %j', contact);
169
+ }
170
+ const prov = this.senderForContact(contact);
171
+ if (!prov) {
172
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot create - no sending provider for type %s', contact.type);
173
+ }
174
+ const guid = ratchet_common_1.StringRatchet.createType4Guid();
175
+ const now = Date.now();
176
+ const newUser = {
177
+ userId: guid,
178
+ userLabel: label || 'User ' + guid,
179
+ contactMethods: [contact],
180
+ tags: tags || [],
181
+ webAuthnAuthenticators: [],
182
+ createdEpochMS: now,
183
+ updatedEpochMS: now,
184
+ };
185
+ const next = await this.opts.storageProvider.saveEntry(newUser);
186
+ rval = next.userId;
187
+ if ((_a = this === null || this === void 0 ? void 0 : this.opts) === null || _a === void 0 ? void 0 : _a.eventProcessor) {
188
+ await this.opts.eventProcessor.userCreated(next);
189
+ }
190
+ if (sendCode) {
191
+ ratchet_common_1.Logger.info('New user %j created and send requested - sending', next);
192
+ await this.sendExpiringValidationToken(contact);
193
+ }
194
+ }
195
+ else {
196
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot create - invalid contact (missing or invalid fields)');
197
+ }
198
+ return rval;
199
+ }
200
+ async addContactMethodToUser(userId, contact) {
201
+ let rval = false;
202
+ if (ratchet_common_1.StringRatchet.trimToNull(userId) && ratchet_warden_common_1.WardenUtils.validContact(contact)) {
203
+ const otherUser = await this.opts.storageProvider.findEntryByContact(contact);
204
+ if (otherUser && otherUser.userId !== userId) {
205
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot add contact to this user, another user already has that contact');
206
+ }
207
+ const curUser = await this.opts.storageProvider.findEntryById(userId);
208
+ if (!curUser) {
209
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot add contact to this user, user does not exist');
210
+ }
211
+ curUser.contactMethods.push(contact);
212
+ await this.opts.storageProvider.saveEntry(curUser);
213
+ rval = true;
214
+ }
215
+ else {
216
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot add - invalid config : %s %j', userId, contact);
217
+ }
218
+ return rval;
219
+ }
220
+ async removeContactMethodFromUser(userId, contact) {
221
+ let rval = null;
222
+ if (ratchet_common_1.StringRatchet.trimToNull(userId) && ratchet_warden_common_1.WardenUtils.validContact(contact)) {
223
+ const curUser = await this.opts.storageProvider.findEntryById(userId);
224
+ if (!curUser) {
225
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot remove contact from this user, user does not exist');
226
+ }
227
+ curUser.contactMethods = (curUser.contactMethods || []).filter((s) => s.type !== contact.type || s.value !== contact.value);
228
+ if (curUser.contactMethods.length === 0) {
229
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot remove the last contact method from a user');
230
+ }
231
+ await this.opts.storageProvider.saveEntry(curUser);
232
+ rval = await this.opts.storageProvider.findEntryById(userId);
233
+ }
234
+ else {
235
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot add - invalid config : %s %j', userId, contact);
236
+ }
237
+ return rval;
238
+ }
239
+ async generateWebAuthnRegistrationChallengeForLoggedInUser(userId, origin) {
240
+ if (!origin || !this.opts.allowedOrigins.includes(origin)) {
241
+ throw new Error('Invalid origin : ' + origin);
242
+ }
243
+ const asUrl = new URL(origin);
244
+ const rpID = asUrl.hostname;
245
+ const entry = await this.opts.storageProvider.findEntryById(userId);
246
+ const options = (0, server_1.generateRegistrationOptions)({
247
+ rpName: this.opts.relyingPartyName,
248
+ rpID: rpID,
249
+ userID: entry.userId,
250
+ userName: entry.userLabel,
251
+ attestationType: 'none',
252
+ excludeCredentials: entry.webAuthnAuthenticators.map((authenticator) => ({
253
+ id: ratchet_common_1.Base64Ratchet.base64StringToBuffer(authenticator.credentialPublicKeyBase64),
254
+ type: 'public-key',
255
+ transports: authenticator.transports,
256
+ })),
257
+ });
258
+ await this.opts.storageProvider.updateUserChallenge(entry.userId, rpID, options.challenge);
259
+ return options;
260
+ }
261
+ async storeAuthnRegistration(userId, origin, data) {
262
+ ratchet_common_1.Logger.info('Store authn data : %j', data);
263
+ let rval = null;
264
+ try {
265
+ if (!origin || !this.opts.allowedOrigins.includes(origin)) {
266
+ throw new Error('Invalid origin : ' + origin);
267
+ }
268
+ const asUrl = new URL(origin);
269
+ const rpID = asUrl.hostname;
270
+ const user = await this.opts.storageProvider.findEntryById(userId);
271
+ const expectedChallenge = await this.opts.storageProvider.fetchCurrentUserChallenge(user.userId, rpID);
272
+ const vrOpts = {
273
+ response: data,
274
+ expectedChallenge: expectedChallenge,
275
+ expectedOrigin: origin,
276
+ expectedRPID: rpID,
277
+ };
278
+ ratchet_common_1.Logger.info('Calling verifyRegistrationResponse: %j', vrOpts);
279
+ const verification = await (0, server_1.verifyRegistrationResponse)(vrOpts);
280
+ ratchet_common_1.Logger.info('verifyRegistrationResponse Result : %j', verification);
281
+ rval = {
282
+ updatedEntry: null,
283
+ registrationResponseId: data.id,
284
+ result: verification.verified ? ratchet_warden_common_1.WardenStoreRegistrationResponseType.Verified : ratchet_warden_common_1.WardenStoreRegistrationResponseType.Failed,
285
+ };
286
+ if (rval.result === ratchet_warden_common_1.WardenStoreRegistrationResponseType.Verified) {
287
+ ratchet_common_1.Logger.info('Storing registration');
288
+ const newAuth = {
289
+ counter: verification.registrationInfo.counter,
290
+ credentialBackedUp: verification.registrationInfo.credentialBackedUp,
291
+ credentialDeviceType: verification.registrationInfo.credentialDeviceType,
292
+ credentialIdBase64: data.id,
293
+ credentialPublicKeyBase64: ratchet_common_1.Base64Ratchet.generateBase64VersionOfBuffer(Buffer.from(verification.registrationInfo.credentialPublicKey)),
294
+ };
295
+ user.webAuthnAuthenticators = (user.webAuthnAuthenticators || []).filter((wa) => wa.credentialIdBase64 !== newAuth.credentialIdBase64);
296
+ user.webAuthnAuthenticators.push(newAuth);
297
+ const storedUser = await this.opts.storageProvider.saveEntry(user);
298
+ rval.updatedEntry = storedUser;
299
+ ratchet_common_1.Logger.info('Stored auth : %j', storedUser);
300
+ }
301
+ }
302
+ catch (err) {
303
+ rval = {
304
+ registrationResponseId: data.id,
305
+ result: ratchet_warden_common_1.WardenStoreRegistrationResponseType.Error,
306
+ error: ratchet_common_1.ErrorRatchet.safeStringifyErr(err),
307
+ };
308
+ }
309
+ return rval;
310
+ }
311
+ async generateWebAuthnAuthenticationChallengeForUserId(userId, origin) {
312
+ const user = await this.opts.storageProvider.findEntryById(userId);
313
+ const rval = await this.generateWebAuthnAuthenticationChallenge(user, origin);
314
+ return rval;
315
+ }
316
+ async generateWebAuthnAuthenticationChallenge(user, origin) {
317
+ const userAuthenticators = user.webAuthnAuthenticators;
318
+ if (!origin || !this.opts.allowedOrigins.includes(origin)) {
319
+ throw new Error('Invalid origin : ' + origin);
320
+ }
321
+ const asUrl = new URL(origin);
322
+ const rpID = asUrl.hostname;
323
+ const out = userAuthenticators.map((authenticator) => {
324
+ const next = {
325
+ id: Buffer.from(authenticator.credentialIdBase64, 'base64'),
326
+ type: 'public-key',
327
+ transports: authenticator.transports,
328
+ };
329
+ return next;
330
+ });
331
+ const options = (0, server_1.generateAuthenticationOptions)({
332
+ allowCredentials: out,
333
+ userVerification: 'preferred',
334
+ });
335
+ await this.opts.storageProvider.updateUserChallenge(user.userId, rpID, options.challenge);
336
+ return options;
337
+ }
338
+ senderForContact(contact) {
339
+ let rval = null;
340
+ if (contact === null || contact === void 0 ? void 0 : contact.type) {
341
+ rval = (this.opts.messageSendingProviders || []).find((p) => p.handlesContactType(contact.type));
342
+ }
343
+ return rval;
344
+ }
345
+ async sendExpiringValidationToken(request) {
346
+ let rval = false;
347
+ if ((request === null || request === void 0 ? void 0 : request.type) && ratchet_common_1.StringRatchet.trimToNull(request === null || request === void 0 ? void 0 : request.value)) {
348
+ const prov = this.senderForContact(request);
349
+ if (prov) {
350
+ const token = await this.expiringCodeRatchet.createNewCode({
351
+ context: request.value,
352
+ length: 6,
353
+ alphabet: '0123456789',
354
+ timeToLiveSeconds: 300,
355
+ tags: ['Login'],
356
+ });
357
+ const msg = await prov.formatMessage(request, ratchet_warden_common_1.WardenCustomerMessageType.ExpiringCode, {
358
+ code: token.code,
359
+ relyingPartyName: this.opts.relyingPartyName,
360
+ });
361
+ rval = await prov.sendMessage(request, msg);
362
+ }
363
+ else {
364
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('No provider found for contact type %s', request.type);
365
+ }
366
+ }
367
+ else {
368
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot send - invalid request %j', request);
369
+ }
370
+ return rval;
371
+ }
372
+ async processLogin(request, origin) {
373
+ ratchet_common_1.Logger.info('Processing login : %s : %j', origin, request);
374
+ let rval = false;
375
+ ratchet_common_1.RequireRatchet.notNullOrUndefined(request, 'request');
376
+ ratchet_common_1.RequireRatchet.true(!!ratchet_common_1.StringRatchet.trimToNull(request === null || request === void 0 ? void 0 : request.userId) || ratchet_warden_common_1.WardenUtils.validContact(request === null || request === void 0 ? void 0 : request.contact), 'Invalid contact and no userId');
377
+ ratchet_common_1.RequireRatchet.true(!!(request === null || request === void 0 ? void 0 : request.webAuthn) || !!ratchet_common_1.StringRatchet.trimToNull(request === null || request === void 0 ? void 0 : request.expiringToken), 'You must provide one of webAuthn or expiringToken');
378
+ ratchet_common_1.RequireRatchet.true(!(request === null || request === void 0 ? void 0 : request.webAuthn) || !ratchet_common_1.StringRatchet.trimToNull(request === null || request === void 0 ? void 0 : request.expiringToken), 'WebAuthn and ExpiringToken may not BOTH be set');
379
+ const user = ratchet_common_1.StringRatchet.trimToNull(request === null || request === void 0 ? void 0 : request.userId)
380
+ ? await this.opts.storageProvider.findEntryById(request === null || request === void 0 ? void 0 : request.userId)
381
+ : await this.opts.storageProvider.findEntryByContact(request.contact);
382
+ if (!user) {
383
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('No user found for %j / %s', request === null || request === void 0 ? void 0 : request.contact, request === null || request === void 0 ? void 0 : request.userId);
384
+ }
385
+ if (request.webAuthn) {
386
+ rval = await this.loginWithWebAuthnRequest(user, origin, request.webAuthn);
387
+ }
388
+ else if (ratchet_common_1.StringRatchet.trimToNull(request.expiringToken)) {
389
+ const lookup = await this.expiringCodeRatchet.checkCode(ratchet_common_1.StringRatchet.trimToEmpty(request.expiringToken), ratchet_common_1.StringRatchet.trimToEmpty(request.contact.value), true);
390
+ if (lookup) {
391
+ rval = true;
392
+ }
393
+ else {
394
+ ratchet_common_1.ErrorRatchet.throwFormattedErr('Cannot login - token is invalid for this user');
395
+ }
396
+ }
397
+ return rval;
398
+ }
399
+ async loginWithWebAuthnRequest(user, origin, data) {
400
+ let rval = false;
401
+ const asUrl = new URL(origin);
402
+ const rpID = asUrl.hostname;
403
+ const expectedChallenge = await this.opts.storageProvider.fetchCurrentUserChallenge(user.userId, rpID);
404
+ const auth = (user.webAuthnAuthenticators || []).find((s) => s.credentialIdBase64 === data.id);
405
+ if (!auth) {
406
+ throw new Error(`Could not find authenticator ${data.id} for user ${user.userId}`);
407
+ }
408
+ const authenticator = {
409
+ counter: auth.counter,
410
+ credentialID: ratchet_common_1.Base64Ratchet.base64StringToBuffer(auth.credentialIdBase64),
411
+ credentialPublicKey: ratchet_common_1.Base64Ratchet.base64StringToBuffer(auth.credentialPublicKeyBase64),
412
+ };
413
+ const vrOpts = {
414
+ response: data,
415
+ expectedChallenge,
416
+ expectedOrigin: origin,
417
+ expectedRPID: rpID,
418
+ authenticator,
419
+ };
420
+ const verification = await (0, server_1.verifyAuthenticationResponse)(vrOpts);
421
+ if (verification.verified) {
422
+ rval = true;
423
+ }
424
+ return rval;
425
+ }
426
+ async removeSingleWebAuthnRegistration(userId, key) {
427
+ let ent = await this.opts.storageProvider.findEntryById(userId);
428
+ if (ent) {
429
+ ent.webAuthnAuthenticators = (ent.webAuthnAuthenticators || []).filter((s) => s.credentialIdBase64 !== key);
430
+ ent = await this.opts.storageProvider.saveEntry(ent);
431
+ }
432
+ else {
433
+ ratchet_common_1.Logger.info('Not removing - no such user as %s', userId);
434
+ }
435
+ return ent;
436
+ }
437
+ async removeUser(userId) {
438
+ var _a;
439
+ let rval = false;
440
+ if (ratchet_common_1.StringRatchet.trimToNull(userId)) {
441
+ const oldUser = await this.opts.storageProvider.findEntryById(userId);
442
+ if (oldUser) {
443
+ await this.opts.storageProvider.removeEntry(userId);
444
+ if ((_a = this === null || this === void 0 ? void 0 : this.opts) === null || _a === void 0 ? void 0 : _a.eventProcessor) {
445
+ await this.opts.eventProcessor.userRemoved(oldUser);
446
+ }
447
+ rval = true;
448
+ }
449
+ else {
450
+ ratchet_common_1.Logger.warn('Cannot remove non-existent user : %s', userId);
451
+ }
452
+ }
453
+ return rval;
454
+ }
455
+ }
456
+ exports.WardenService = WardenService;
@@ -0,0 +1,14 @@
1
+ export class RatchetWardenServerInfo {
2
+ constructor() { }
3
+ static buildInformation() {
4
+ const val = {
5
+ version: '84',
6
+ hash: 'f4d435f9dba4b8b2eaefb31582fcf44290522d7f',
7
+ branch: 'alpha-2023-03-12-7',
8
+ tag: 'alpha-2023-03-12-7',
9
+ timeBuiltISO: '2023-03-12T22:04:40-0700',
10
+ notes: '',
11
+ };
12
+ return val;
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ export * from './build/ratchet-warden-server-info';
2
+ export * from './server/warden-service-options';
3
+ export * from './server/warden-service';
4
+ export * from './server/provider/warden-default-user-decoration-provider';
5
+ export * from './server/provider/warden-event-processing-provider';
6
+ export * from './server/provider/warden-mailer-message-sending-provider-options';
7
+ export * from './server/provider/warden-mailer-message-sending-provider';
8
+ export * from './server/provider/warden-message-sending-provider';
9
+ export * from './server/provider/warden-no-op-event-processing-provider';
10
+ export * from './server/provider/warden-s3-single-file-storage-provider-options';
11
+ export * from './server/provider/warden-s3-single-file-storage-provider';
12
+ export * from './server/provider/warden-storage-provider';
13
+ export * from './server/provider/warden-twilio-text-message-sending-provider-options';
14
+ export * from './server/provider/warden-twilio-text-message-sending-provider';
15
+ export * from './server/provider/warden-user-decoration-provider';
@@ -0,0 +1,11 @@
1
+ import { WardenUtils } from '@bitblit/ratchet-warden-common';
2
+ export class WardenDefaultUserDecorationProvider {
3
+ async fetchDecoration(wardenUser) {
4
+ const rval = {
5
+ userTokenData: WardenUtils.stripWardenEntryToSummary(wardenUser),
6
+ userTokenExpirationSeconds: 3600,
7
+ userTeamRoles: [{ team: 'WARDEN', role: 'USER' }],
8
+ };
9
+ return rval;
10
+ }
11
+ }
@@ -0,0 +1,32 @@
1
+ import { WardenContactType } from '@bitblit/ratchet-warden-common';
2
+ import { Logger } from '@bitblit/ratchet-common';
3
+ export class WardenMailerMessageSendingProvider {
4
+ static defaultOptions() {
5
+ const rval = {
6
+ emailBaseLayoutName: undefined,
7
+ expiringTokenHtmlTemplateName: 'expiring-token-request-email',
8
+ expiringTokenTxtTemplateName: undefined,
9
+ };
10
+ return rval;
11
+ }
12
+ constructor(mailer, options = WardenMailerMessageSendingProvider.defaultOptions()) {
13
+ this.mailer = mailer;
14
+ this.options = options;
15
+ }
16
+ async formatMessage(contact, messageType, context) {
17
+ const rts = {
18
+ destinationAddresses: [contact.value],
19
+ subject: 'Your login token',
20
+ };
21
+ await this.mailer.fillEmailBody(rts, context, this.options.expiringTokenHtmlTemplateName, this.options.expiringTokenTxtTemplateName, this.options.emailBaseLayoutName);
22
+ return rts;
23
+ }
24
+ handlesContactType(type) {
25
+ return type === WardenContactType.EmailAddress;
26
+ }
27
+ async sendMessage(contact, message) {
28
+ const rval = await this.mailer.sendEmail(message);
29
+ Logger.debug('SendRawEmailResponse was : %j', rval);
30
+ return !!rval;
31
+ }
32
+ }
@@ -0,0 +1,4 @@
1
+ export class WardenNoOpEventProcessingProvider {
2
+ async userCreated(entry) { }
3
+ async userRemoved(entry) { }
4
+ }
@@ -0,0 +1,86 @@
1
+ import { WardenUtils } from '@bitblit/ratchet-warden-common';
2
+ import { S3CacheRatchet } from '@bitblit/ratchet-aws';
3
+ import { ErrorRatchet, StringRatchet } from '@bitblit/ratchet-common';
4
+ export class WardenS3SingleFileStorageProvider {
5
+ constructor(s3, options) {
6
+ this.s3 = s3;
7
+ this.options = options;
8
+ this.ratchet = new S3CacheRatchet(this.s3, this.options.bucket);
9
+ }
10
+ async listUserSummaries() {
11
+ const allData = (await this.fetchDataFile()).entries;
12
+ const rval = allData.map((d) => WardenUtils.stripWardenEntryToSummary(d));
13
+ return rval;
14
+ }
15
+ async fetchDataFile() {
16
+ let data = await this.ratchet.fetchCacheFileAsObject(this.options.dataFileKey);
17
+ data = data || {
18
+ entries: [],
19
+ challenges: [],
20
+ };
21
+ return data;
22
+ }
23
+ async storeDataFile(file) {
24
+ let rval = null;
25
+ if (file) {
26
+ rval = await this.ratchet.writeObjectToCacheFile(this.options.dataFileKey, file);
27
+ }
28
+ return rval;
29
+ }
30
+ async fetchCurrentUserChallenge(userId, relyingPartyId) {
31
+ const data = await this.fetchDataFile();
32
+ const entry = (data.challenges || []).find((d) => d.userId === userId && d.relyingPartyId === relyingPartyId);
33
+ if (!entry) {
34
+ ErrorRatchet.throwFormattedErr('fetchCurrentUserChallenge: Could not find user %s', userId);
35
+ }
36
+ return entry.challenge;
37
+ }
38
+ async findEntryByContact(contact) {
39
+ let rval = null;
40
+ if (contact?.type && StringRatchet.trimToNull(contact?.value)) {
41
+ const data = await this.fetchDataFile();
42
+ rval = (data.entries || []).find((d) => !!(d.contactMethods || []).find((x) => x.type === contact.type && x.value === contact.value));
43
+ }
44
+ return rval;
45
+ }
46
+ async findEntryById(userId) {
47
+ let rval = null;
48
+ if (StringRatchet.trimToNull(userId)) {
49
+ const data = await this.fetchDataFile();
50
+ rval = (data.entries || []).find((d) => d.userId === userId);
51
+ }
52
+ return rval;
53
+ }
54
+ async removeEntry(userId) {
55
+ const data = await this.fetchDataFile();
56
+ data.entries = (data.entries || []).filter((d) => d.userId !== userId);
57
+ await this.storeDataFile(data);
58
+ return true;
59
+ }
60
+ async saveEntry(entry) {
61
+ let rval = null;
62
+ if (entry && entry.userId) {
63
+ const now = Date.now();
64
+ entry.createdEpochMS = entry.createdEpochMS || now;
65
+ entry.updatedEpochMS = now;
66
+ const data = await this.fetchDataFile();
67
+ data.entries = (data.entries || []).filter((d) => d.userId !== entry.userId);
68
+ data.entries.push(entry);
69
+ await this.storeDataFile(data);
70
+ rval = await this.findEntryById(entry.userId);
71
+ }
72
+ return rval;
73
+ }
74
+ async updateUserChallenge(userId, relyingPartyId, challenge) {
75
+ const data = await this.fetchDataFile();
76
+ data.challenges = (data.challenges || []).filter((d) => d.userId !== userId || d.relyingPartyId !== relyingPartyId);
77
+ data.challenges.push({
78
+ userId: userId,
79
+ relyingPartyId: relyingPartyId,
80
+ challenge: challenge,
81
+ updatedEpochMS: Date.now(),
82
+ });
83
+ await this.storeDataFile(data);
84
+ return true;
85
+ }
86
+ }
@@ -0,0 +1 @@
1
+ export {};