@bitblit/ratchet-warden-common 6.0.146-alpha → 6.0.148-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 (54) hide show
  1. package/package.json +4 -3
  2. package/src/build/ratchet-warden-common-info.ts +19 -0
  3. package/src/client/provider/warden-client-abstract-recent-login-provider.ts +53 -0
  4. package/src/client/provider/warden-client-current-logged-in-jwt-token-provider.ts +3 -0
  5. package/src/client/provider/warden-client-recent-login-provider.ts +11 -0
  6. package/src/client/provider/warden-client-storage-based-logged-in-user-provider.ts +54 -0
  7. package/src/client/provider/warden-client-storage-based-recent-login-provider.ts +46 -0
  8. package/src/client/provider/warden-client-transient-memory-logged-in-user-provider.ts +19 -0
  9. package/src/client/provider/warden-client-transient-memory-recent-login-provider.ts +15 -0
  10. package/src/client/provider/warden-command-exchange-provider.ts +8 -0
  11. package/src/client/provider/warden-logged-in-user-provider.ts +7 -0
  12. package/src/client/provider/warden-logged-in-user-wrapper.ts +7 -0
  13. package/src/client/provider/warden-recent-login-descriptor.ts +6 -0
  14. package/src/client/provider/warden-user-service-event-processing-provider.ts +14 -0
  15. package/src/client/provider/warden-user-service-options.ts +17 -0
  16. package/src/client/warden-client.spec.ts +32 -0
  17. package/src/client/warden-client.ts +239 -0
  18. package/src/client/warden-delegating-current-user-providing-user-service-event-processing-provider.ts +76 -0
  19. package/src/client/warden-user-service.spec.ts +29 -0
  20. package/src/client/warden-user-service.ts +434 -0
  21. package/src/common/command/add-web-authn-registration-to-logged-in-user.ts +7 -0
  22. package/src/common/command/create-account.ts +8 -0
  23. package/src/common/command/remove-web-authn-registration.ts +4 -0
  24. package/src/common/command/send-magic-link.ts +15 -0
  25. package/src/common/command/warden-command-response.ts +24 -0
  26. package/src/common/command/warden-command.ts +29 -0
  27. package/src/common/command/warden-contact-lookup.ts +7 -0
  28. package/src/common/command/warden-custom-template-descriptor.ts +7 -0
  29. package/src/common/command/web-authn-object-wrapper.ts +3 -0
  30. package/src/common/error/warden-error-codes.ts +9 -0
  31. package/src/common/error/warden-error.ts +122 -0
  32. package/src/common/model/warden-contact-type.ts +4 -0
  33. package/src/common/model/warden-contact.ts +6 -0
  34. package/src/common/model/warden-customer-message-type.ts +5 -0
  35. package/src/common/model/warden-entry-summary.ts +9 -0
  36. package/src/common/model/warden-entry.ts +14 -0
  37. package/src/common/model/warden-jwt-token.ts +15 -0
  38. package/src/common/model/warden-login-request-type.ts +6 -0
  39. package/src/common/model/warden-login-request.ts +15 -0
  40. package/src/common/model/warden-login-results.ts +8 -0
  41. package/src/common/model/warden-login-third-party-token.ts +5 -0
  42. package/src/common/model/warden-permission.ts +4 -0
  43. package/src/common/model/warden-role.ts +8 -0
  44. package/src/common/model/warden-store-registration-response-type.ts +5 -0
  45. package/src/common/model/warden-store-registration-response.ts +9 -0
  46. package/src/common/model/warden-team-role-mapping.ts +4 -0
  47. package/src/common/model/warden-team.ts +5 -0
  48. package/src/common/model/warden-third-party-authentication.ts +6 -0
  49. package/src/common/model/warden-user-decoration.ts +10 -0
  50. package/src/common/model/warden-web-authn-entry-summary.ts +6 -0
  51. package/src/common/model/warden-web-authn-entry.ts +14 -0
  52. package/src/common/model/warden-web-authn-transport-future-type.ts +8 -0
  53. package/src/common/util/warden-utils.spec.ts +15 -0
  54. package/src/common/util/warden-utils.ts +284 -0
@@ -0,0 +1,29 @@
1
+ import { WardenUserService } from './warden-user-service.js';
2
+ import { WardenUserServiceOptions } from './provider/warden-user-service-options.js';
3
+
4
+ import { WardenClientRecentLoginProvider } from './provider/warden-client-recent-login-provider.js';
5
+ import { WardenLoggedInUserProvider } from './provider/warden-logged-in-user-provider.js';
6
+ import { WardenClient } from './warden-client.js';
7
+ import { WardenUserServiceEventProcessingProvider } from './provider/warden-user-service-event-processing-provider.js';
8
+ import { describe, expect, test } from 'vitest';
9
+ import { mock } from 'vitest-mock-extended';
10
+
11
+ describe('#wardenUserService', function () {
12
+ // Currently disabled because this seems to hang forever on github actions (2023-03-20)
13
+ test('should instantiate', async () => {
14
+ const wuso: WardenUserServiceOptions<any> = {
15
+ recentLoginProvider: mock<WardenClientRecentLoginProvider>(),
16
+ loggedInUserProvider: mock<WardenLoggedInUserProvider<any>>(),
17
+ wardenClient: mock<WardenClient>(),
18
+ eventProcessor: mock<WardenUserServiceEventProcessingProvider<any>>(),
19
+ loginCheckTimerPingSeconds: 60,
20
+ autoLoginHandlingThresholdSeconds: 60,
21
+ allowAutoRefresh: true,
22
+ applicationName: 'TEST-APP',
23
+ };
24
+
25
+ const wus: WardenUserService<any> = new WardenUserService<any>(wuso);
26
+ expect(wus).not.toBeNull();
27
+ wus.cleanShutDown();
28
+ });
29
+ });
@@ -0,0 +1,434 @@
1
+ import { Logger } from "@bitblit/ratchet-common/logger/logger";
2
+ import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
3
+ import { JwtDecodeOnlyRatchet } from "@bitblit/ratchet-common/jwt/jwt-decode-only-ratchet";
4
+ import { Subscription, timer } from "rxjs";
5
+ import { WardenUserServiceOptions } from "./provider/warden-user-service-options.js";
6
+ import { WardenLoggedInUserWrapper } from "./provider/warden-logged-in-user-wrapper.js";
7
+ import { WardenContact } from "../common/model/warden-contact.js";
8
+ import { WardenJwtToken } from "../common/model/warden-jwt-token.js";
9
+ import { WardenLoginResults } from "../common/model/warden-login-results.js";
10
+ import { WardenLoginRequest } from "../common/model/warden-login-request.js";
11
+
12
+ import {
13
+ AuthenticationResponseJSON,
14
+ PublicKeyCredentialRequestOptionsJSON,
15
+ RegistrationResponseJSON,
16
+ startAuthentication,
17
+ StartAuthenticationOpts,
18
+ startRegistration,
19
+ StartRegistrationOpts
20
+ } from "@simplewebauthn/browser";
21
+ import { WardenEntrySummary } from "../common/model/warden-entry-summary.js";
22
+ import { WardenUtils } from "../common/util/warden-utils.js";
23
+ import { WardenLoginRequestType } from "../common/model/warden-login-request-type";
24
+ import { WardenTeamRoleMapping } from "../common/model/warden-team-role-mapping.ts";
25
+ import { WardenUserDecoration } from "../common/model/warden-user-decoration.ts";
26
+ import { WardenCommand } from "../common/command/warden-command.ts";
27
+ import { WardenCommandResponse } from "../common/command/warden-command-response.ts";
28
+
29
+ /**
30
+ * A service that handles logging in, saving the current user, watching
31
+ * for expiration, auto-refreshing the token, wrapped around a
32
+ * warden-client.
33
+ *
34
+ * T is the type of user object contained in the
35
+ */
36
+ export class WardenUserService<T> {
37
+ private loggedInTimerSubscription: Subscription;
38
+ private _autoRefreshEnabled: boolean = false;
39
+
40
+ constructor(private options: WardenUserServiceOptions<T>) {
41
+ Logger.info('Initializing user service');
42
+ // Immediately read from storage if there is something there
43
+ const stored: WardenLoggedInUserWrapper<T> = this.options.loggedInUserProvider.fetchLoggedInUserWrapper();
44
+ if (WardenUtils.wrapperIsExpired(stored)) {
45
+ // Not treating this as a logout since it basically never logged in, just clearing it
46
+ Logger.info('Stored token is expired, removing it');
47
+ this.options.loggedInUserProvider.logOutUser();
48
+ } else {
49
+ // Fire the login event in case anything needs to know about the current user
50
+ this.options.eventProcessor.onSuccessfulLogin(stored);
51
+ }
52
+
53
+ const timerSeconds: number = this.options.loginCheckTimerPingSeconds || 2.5;
54
+ this.loggedInTimerSubscription = timer(0, timerSeconds * 1000).subscribe((t) => this.checkForAutoLogoutOrRefresh(t));
55
+ }
56
+
57
+ public cleanShutDown(): void {
58
+ if (this.loggedInTimerSubscription) {
59
+ this.loggedInTimerSubscription.unsubscribe();
60
+ }
61
+ }
62
+
63
+ public get serviceOptions(): WardenUserServiceOptions<T> {
64
+ return this.options;
65
+ }
66
+
67
+ public async createAccount(contact: WardenContact, sendCode?: boolean, label?: string, tags?: string[]): Promise<string> {
68
+ const rval: string = await this.options.wardenClient.createAccount(contact, sendCode, label, tags);
69
+
70
+ if (this.options.recentLoginProvider && StringRatchet.trimToNull(rval)) {
71
+ this.options.recentLoginProvider.saveNewUser(rval, label, contact);
72
+ }
73
+
74
+ return rval;
75
+ }
76
+
77
+ public async addContactToLoggedInUser(contact: WardenContact): Promise<boolean> {
78
+ return this.options.wardenClient.addContactToLoggedInUser(contact);
79
+ }
80
+
81
+ public get autoRefreshEnabled(): boolean {
82
+ return this._autoRefreshEnabled;
83
+ }
84
+
85
+ public set autoRefreshEnabled(newValue: boolean) {
86
+ if (newValue) {
87
+ if (this.options.allowAutoRefresh) {
88
+ this._autoRefreshEnabled = true;
89
+ } else {
90
+ throw new Error('Cannot enable auto-refresh - this is disabled in the user service options');
91
+ }
92
+ } else {
93
+ this._autoRefreshEnabled = false;
94
+ }
95
+ }
96
+
97
+ public async checkForAutoLogoutOrRefresh(t: number): Promise<void> {
98
+ Logger.debug('Checking for auto-logout or refresh : %s', t);
99
+ // This code will cause an auto-logout if the token is already expired, but not if it is CLOSE to expiration
100
+ const current: WardenLoggedInUserWrapper<T> = this.fetchLoggedInUserWrapper();
101
+ if (current) {
102
+ const thresholdSeconds: number = this.options.autoLoginHandlingThresholdSeconds || 10; // Default to 10 seconds
103
+ const secondsLeft: number = current.expirationEpochSeconds - Math.floor(Date.now() / 1000);
104
+ if (secondsLeft < thresholdSeconds) {
105
+ if (this.autoRefreshEnabled) {
106
+ Logger.info('Under threshold, initiating auto-refresh');
107
+ const result: WardenLoggedInUserWrapper<T> = await this.refreshToken();
108
+ this.options.eventProcessor.onAutomaticTokenRefresh(result);
109
+ } else {
110
+ Logger.info('Under threshold, initiating auto-logout');
111
+ this.logout();
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ public logout(): void {
118
+ this.options.loggedInUserProvider.logOutUser();
119
+ this.options.eventProcessor.onLogout();
120
+ }
121
+
122
+ public fetchLoggedInUserId(): string {
123
+ const tmp: WardenLoggedInUserWrapper<T> = this.options.loggedInUserProvider.fetchLoggedInUserWrapper();
124
+ const rval: string = tmp?.userObject?.wardenData?.userId;
125
+ return rval;
126
+ }
127
+
128
+ public fetchLoggedInUserWrapper(): WardenLoggedInUserWrapper<T> {
129
+ let tmp: WardenLoggedInUserWrapper<T> = this.options.loggedInUserProvider.fetchLoggedInUserWrapper();
130
+ if (tmp) {
131
+ if (WardenUtils.wrapperIsExpired(tmp)) {
132
+ // This is belt-and-suspenders for when the window was not open - during normal operation either
133
+ // auto-logout thread or auto-refresh thread would have handled this
134
+ Logger.info('Token is expired - auto logout triggered');
135
+ this.logout();
136
+ tmp = null;
137
+ }
138
+ }
139
+ return tmp;
140
+ }
141
+
142
+ public loggedInUserHasGlobalRole(roleId: string): boolean {
143
+ let rval: boolean = false;
144
+
145
+ const token: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
146
+ rval = token ? WardenUtils.userHasGlobalRole(WardenUtils.wardenUserDecorationFromToken(token), roleId) : false;
147
+
148
+ return rval;
149
+ }
150
+
151
+ public loggedInUserHasRoleOnTeam(teamId: string, roleId: string): boolean {
152
+ let rval: boolean = false;
153
+
154
+ const token: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
155
+ rval = token ? WardenUtils.userHasRoleOnTeam(WardenUtils.wardenUserDecorationFromToken(token), teamId, roleId) : false;
156
+
157
+ return rval;
158
+ }
159
+
160
+ public isLoggedIn(): boolean {
161
+ const t: WardenLoggedInUserWrapper<T> = this.fetchLoggedInUserWrapper();
162
+ return !!t;
163
+ }
164
+
165
+ public fetchLoggedInUserJwtObject(): WardenJwtToken<T> {
166
+ const t: WardenLoggedInUserWrapper<T> = this.fetchLoggedInUserWrapper();
167
+ return t ? t.userObject : null;
168
+ }
169
+
170
+ public fetchLoggedInUserJwtToken(): string {
171
+ const t: WardenLoggedInUserWrapper<T> = this.fetchLoggedInUserWrapper();
172
+ return t ? t.jwtToken : null;
173
+ }
174
+
175
+ public fetchLoggedInUserObject(): T {
176
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
177
+ return t?.user;
178
+ }
179
+
180
+ public fetchLoggedInProxyObject(): T {
181
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
182
+ return t?.proxy;
183
+ }
184
+
185
+ public fetchLoggedInGlobalRoleIds(): string[] {
186
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
187
+ return t?.globalRoleIds;
188
+ }
189
+
190
+ public fetchLoggedInTeamRoleMappingsGlobalRoleIds(): WardenTeamRoleMapping[] {
191
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
192
+ return t?.teamRoleMappings;
193
+ }
194
+
195
+ public fetchLoggedInUserExpirationEpochSeconds(): number {
196
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
197
+ return t ? t.exp : null;
198
+ }
199
+
200
+ public fetchLoggedInUserRemainingSeconds(): number {
201
+ const t: WardenJwtToken<T> = this.fetchLoggedInUserJwtObject();
202
+ return t ? t.exp - Math.floor(Date.now() / 1000) : null;
203
+ }
204
+
205
+ private async updateLoggedInUserFromTokenString(token: string): Promise<WardenLoggedInUserWrapper<T>> {
206
+ let rval: WardenLoggedInUserWrapper<T> = null;
207
+ if (!StringRatchet.trimToNull(token)) {
208
+ Logger.info('Called updateLoggedInUserFromTokenString with empty string - logging out');
209
+ this.logout();
210
+ } else {
211
+ Logger.info('updateLoggedInUserFromTokenString : %s', token);
212
+
213
+ const parsed: WardenJwtToken<T> = JwtDecodeOnlyRatchet.decodeTokenNoVerify<WardenJwtToken<T>>(token);
214
+ if (parsed) {
215
+ rval = {
216
+ userObject: parsed,
217
+ jwtToken: token,
218
+ expirationEpochSeconds: parsed.exp,
219
+ };
220
+ this.options.loggedInUserProvider.setLoggedInUserWrapper(rval);
221
+ this.updateRecentLoginsFromWardenEntrySummary(parsed.wardenData); // In case we have a recent logins tracker
222
+ this.options.eventProcessor.onSuccessfulLogin(rval);
223
+ } else {
224
+ Logger.warn('Failed to parse token %s - ignoring login and triggering failure');
225
+ this.options.eventProcessor.onLoginFailure('Could not parse token string');
226
+ }
227
+ }
228
+ return rval;
229
+ }
230
+
231
+ public async refreshToken(): Promise<WardenLoggedInUserWrapper<T>> {
232
+ let rval: WardenLoggedInUserWrapper<T> = null;
233
+ const currentWrapper: WardenLoggedInUserWrapper<T> = this.fetchLoggedInUserWrapper();
234
+ if (!currentWrapper) {
235
+ Logger.info('Could not refresh - no token available');
236
+ } else {
237
+ const newToken: string = await this.options.wardenClient.refreshJwtToken(currentWrapper.jwtToken);
238
+ rval = await this.updateLoggedInUserFromTokenString(newToken);
239
+ }
240
+ return rval;
241
+ }
242
+
243
+ // Passthru for convenience
244
+ public async sendExpiringCode(contact: WardenContact): Promise<boolean> {
245
+ return this.options.wardenClient.sendExpiringValidationToken(contact);
246
+ }
247
+
248
+ private async processWardenLoginResults(resp: WardenLoginResults): Promise<WardenLoggedInUserWrapper<T>> {
249
+ let rval: WardenLoggedInUserWrapper<T> = null;
250
+ if (resp) {
251
+ Logger.info('Warden: response : %j ', resp);
252
+ if (resp.jwtToken) {
253
+ Logger.info('Applying login');
254
+ rval = await this.updateLoggedInUserFromTokenString(resp.jwtToken);
255
+ } else if (resp.error) {
256
+ this.options.eventProcessor.onLoginFailure(resp.error);
257
+ } else {
258
+ Logger.error('Response contained neither token nor error');
259
+ this.options.eventProcessor.onLoginFailure('Response contained neither token nor error');
260
+ }
261
+ } else {
262
+ Logger.error('Login call failed');
263
+ this.options.eventProcessor.onLoginFailure('Login call returned null');
264
+ }
265
+ return rval;
266
+ }
267
+
268
+ private updateRecentLoginsFromWardenEntrySummary(res: WardenEntrySummary): void {
269
+ // Only store if we have a provider, and it was a successful login
270
+ if (this.options.recentLoginProvider && res) {
271
+ Logger.info('UserService : Saving recent login %j', res);
272
+ this.options.recentLoginProvider.saveRecentLogin(res);
273
+ } else {
274
+ Logger.info('Not saving recent login - no storage configured or no data passed');
275
+ }
276
+ }
277
+
278
+ private updateRecentLoginsFromLoggedInUserWrapper(res: WardenLoggedInUserWrapper<T>): void {
279
+ this.updateRecentLoginsFromWardenEntrySummary(res?.userObject?.wardenData);
280
+ }
281
+
282
+ public async executeWebAuthnBasedLogin(userId: string): Promise<WardenLoggedInUserWrapper<T>> {
283
+ const resp: WardenLoginResults = await this.executeWebAuthnLoginToWardenLoginResults(userId);
284
+ const rval: WardenLoggedInUserWrapper<T> = await this.processWardenLoginResults(resp);
285
+ this.updateRecentLoginsFromLoggedInUserWrapper(rval);
286
+ return rval;
287
+ }
288
+
289
+ public async removeWebAuthnRegistrationFromLoggedInUser(input: string): Promise<WardenEntrySummary> {
290
+ const rval: WardenEntrySummary = await this.options.wardenClient.removeWebAuthnRegistrationFromLoggedInUser(input);
291
+ return rval;
292
+ }
293
+
294
+ public async removeContactFromLoggedInUser(input: WardenContact): Promise<WardenEntrySummary> {
295
+ const rval: WardenEntrySummary = await this.options.wardenClient.removeContactFromLoggedInUser(input);
296
+ return rval;
297
+ }
298
+
299
+ public async executeValidationTokenBasedLogin(
300
+ contact: WardenContact,
301
+ token: string,
302
+ createUserIfMissing?: boolean,
303
+ ): Promise<WardenLoggedInUserWrapper<T>> {
304
+ Logger.info('Warden: executeValidationTokenBasedLogin : %j : %s : %s', contact, token, createUserIfMissing);
305
+ const resp: WardenLoginResults = await this.options.wardenClient.performLoginCmd({
306
+ type: WardenLoginRequestType.ExpiringToken,
307
+ contact: contact,
308
+ expiringToken: token,
309
+ createUserIfMissing: createUserIfMissing,
310
+ });
311
+ const rval: WardenLoggedInUserWrapper<T> = await this.processWardenLoginResults(resp);
312
+ this.updateRecentLoginsFromLoggedInUserWrapper(rval);
313
+ return rval;
314
+ }
315
+
316
+ public async executeThirdPartyTokenBasedLogin(
317
+ thirdParty: string,
318
+ token: string,
319
+ createUserIfMissing?: boolean,
320
+ ): Promise<WardenLoggedInUserWrapper<T>> {
321
+ Logger.info('Warden: executeThirdPartyTokenBasedLogin : %j : %s : %s', thirdParty, token, createUserIfMissing);
322
+ const resp: WardenLoginResults = await this.options.wardenClient.performLoginCmd({
323
+ type: WardenLoginRequestType.ThirdParty,
324
+ thirdPartyToken: {
325
+ thirdParty: thirdParty,
326
+ token: token,
327
+ },
328
+ createUserIfMissing: createUserIfMissing,
329
+ });
330
+ const rval: WardenLoggedInUserWrapper<T> = await this.processWardenLoginResults(resp);
331
+ this.updateRecentLoginsFromLoggedInUserWrapper(rval);
332
+ return rval;
333
+ }
334
+
335
+ public async saveCurrentDeviceAsWebAuthnForCurrentUser(): Promise<WardenEntrySummary> {
336
+ const input: StartRegistrationOpts = await this.options.wardenClient.generateWebAuthnRegistrationChallengeForLoggedInUser();
337
+
338
+ const creds: RegistrationResponseJSON = await startRegistration(input);
339
+
340
+ const deviceLabel: string = StringRatchet.trimToEmpty(
341
+ this.options?.deviceLabelGenerator ? this.options.deviceLabelGenerator() : this.defaultDeviceLabelGenerator(),
342
+ );
343
+
344
+ const output: WardenEntrySummary = await this.options.wardenClient.addWebAuthnRegistrationToLoggedInUser(
345
+ this.options.applicationName,
346
+ deviceLabel,
347
+ creds,
348
+ );
349
+ this.updateRecentLoginsFromWardenEntrySummary(output);
350
+ return output;
351
+ }
352
+
353
+
354
+ public async exportWebAuthnRegistrationEntryForLoggedInUser(origin: string): Promise<string> {
355
+ return this.options.wardenClient.exportWebAuthnRegistrationEntryForLoggedInUser(origin);
356
+ }
357
+ public async importWebAuthnRegistrationEntryForLoggedInUser(token: string): Promise<boolean> {
358
+ return this.options.wardenClient.importWebAuthnRegistrationEntryForLoggedInUser(token);
359
+ }
360
+
361
+ private defaultDeviceLabelGenerator(): string {
362
+ let rval: string = '';
363
+ if (navigator) {
364
+ // Chromium based browsers have something useful in this field (2023-10-26)
365
+ if (
366
+ navigator['userAgentData'] &&
367
+ navigator['userAgentData']['brands'] &&
368
+ navigator['userAgentData']['brands'][1] &&
369
+ navigator['userAgentData']['brands'][1]['brand']
370
+ ) {
371
+ rval = navigator['userAgentData']['brands'][1]['brand'];
372
+ } else {
373
+ rval = navigator.userAgent;
374
+ }
375
+ if (navigator.platform) {
376
+ rval += ' on ' + navigator.platform;
377
+ }
378
+ } else {
379
+ rval = 'Unknown device';
380
+ }
381
+ return rval;
382
+ }
383
+
384
+ public async executeWebAuthnLoginToWardenLoginResults(userId: string): Promise<WardenLoginResults> {
385
+ let rval: WardenLoginResults = null;
386
+ try {
387
+ // Add it to the list
388
+ const resp: PublicKeyCredentialRequestOptionsJSON =
389
+ await this.options.wardenClient.generateWebAuthnAuthenticationChallengeForUserId(userId);
390
+ const input: StartAuthenticationOpts = {
391
+ optionsJSON: resp,
392
+ useBrowserAutofill: false,
393
+ verifyBrowserAutofillInput: false,
394
+ };
395
+ Logger.info('Got login challenge : %j', input);
396
+ const creds: AuthenticationResponseJSON = await startAuthentication(input);
397
+ Logger.info('Got creds: %j', creds);
398
+
399
+ const loginCmd: WardenLoginRequest = {
400
+ type: WardenLoginRequestType.WebAuthn,
401
+ userId: userId,
402
+ webAuthn: creds,
403
+ };
404
+ rval = await this.options.wardenClient.performLoginCmd(loginCmd);
405
+ if (rval?.jwtToken) {
406
+ //rval = true;
407
+ }
408
+ } catch (err) {
409
+ Logger.error('WebauthN Failed : %s', err);
410
+ }
411
+ return rval;
412
+ }
413
+
414
+
415
+ public async executeThirdPartyLoginToWardenLoginResults(thirdParty: string, token: string): Promise<WardenLoginResults> {
416
+ let rval: WardenLoginResults = null;
417
+ try {
418
+ const loginCmd: WardenLoginRequest = {
419
+ type: WardenLoginRequestType.ThirdParty,
420
+ thirdPartyToken: {
421
+ thirdParty: thirdParty,
422
+ token: token,
423
+ }
424
+ };
425
+ rval = await this.options.wardenClient.performLoginCmd(loginCmd);
426
+ if (rval?.jwtToken) {
427
+ //rval = true;
428
+ }
429
+ } catch (err) {
430
+ Logger.error('Third party Failed : %s', err);
431
+ }
432
+ return rval;
433
+ }
434
+ }
@@ -0,0 +1,7 @@
1
+ import { WebAuthnObjectWrapper } from './web-authn-object-wrapper.js';
2
+
3
+ export interface AddWebAuthnRegistrationToLoggedInUser {
4
+ webAuthn: WebAuthnObjectWrapper;
5
+ applicationName: string;
6
+ deviceLabel: string;
7
+ }
@@ -0,0 +1,8 @@
1
+ import { WardenContact } from '../model/warden-contact.js';
2
+
3
+ export interface CreateAccount {
4
+ contact: WardenContact;
5
+ sendCode?: boolean;
6
+ label?: string;
7
+ tags?: string[];
8
+ }
@@ -0,0 +1,4 @@
1
+ export interface RemoveWebAuthnRegistration {
2
+ userId: string;
3
+ credentialId: string;
4
+ }
@@ -0,0 +1,15 @@
1
+ import { WardenContact } from '../model/warden-contact.js';
2
+ import { WardenContactLookup } from './warden-contact-lookup.js';
3
+ import { WardenCustomTemplateDescriptor } from './warden-custom-template-descriptor.js';
4
+
5
+ // You must set either contact or contactLookup, but not both
6
+ export interface SendMagicLink {
7
+ overrideDestinationContact?: WardenContact; // If this is set, then the link will be a login for the userId/contact below, but sent to this address
8
+
9
+ contactLookup?: WardenContactLookup;
10
+ contact?: WardenContact;
11
+ landingUrl: string;
12
+ meta?: Record<string, string>;
13
+ ttlSeconds?: number;
14
+ customTemplate?: WardenCustomTemplateDescriptor;
15
+ }
@@ -0,0 +1,24 @@
1
+ import { WebAuthnObjectWrapper } from './web-authn-object-wrapper.js';
2
+ import { WardenLoginResults } from '../model/warden-login-results.js';
3
+ import { WardenEntrySummary } from '../model/warden-entry-summary.js';
4
+
5
+ export interface WardenCommandResponse {
6
+ createAccount?: string;
7
+ sendMagicLink?: boolean;
8
+ generateWebAuthnAuthenticationChallengeForUserId?: WebAuthnObjectWrapper;
9
+ generateWebAuthnRegistrationChallengeForLoggedInUser?: WebAuthnObjectWrapper;
10
+ removeWebAuthnRegistration?: WardenEntrySummary;
11
+ sendExpiringValidationToken?: boolean;
12
+ addWebAuthnRegistrationToLoggedInUser?: WardenEntrySummary;
13
+ addContactToLoggedInUser?: boolean;
14
+ performLogin?: WardenLoginResults;
15
+ refreshJwtToken?: string;
16
+
17
+ removeWebAuthnRegistrationFromLoggedInUser?: WardenEntrySummary;
18
+ removeContactFromLoggedInUser?: WardenEntrySummary;
19
+
20
+ exportWebAuthnRegistrationEntryForLoggedInUser?: string;
21
+ importWebAuthnRegistrationEntryForLoggedInUser?: boolean;
22
+
23
+ error?: string;
24
+ }
@@ -0,0 +1,29 @@
1
+ import { CreateAccount } from './create-account.js';
2
+ import { WardenContact } from '../model/warden-contact.js';
3
+ import { RemoveWebAuthnRegistration } from './remove-web-authn-registration.js';
4
+ import { WardenLoginRequest } from '../model/warden-login-request.js';
5
+ import { SendMagicLink } from './send-magic-link.js';
6
+ import { AddWebAuthnRegistrationToLoggedInUser } from './add-web-authn-registration-to-logged-in-user.js';
7
+
8
+ export interface WardenCommand {
9
+ createAccount?: CreateAccount;
10
+ sendMagicLink?: SendMagicLink;
11
+ generateWebAuthnAuthenticationChallengeForUserId?: string;
12
+ generateWebAuthnRegistrationChallengeForLoggedInUser?: boolean;
13
+ sendExpiringValidationToken?: WardenContact;
14
+ addWebAuthnRegistrationToLoggedInUser?: AddWebAuthnRegistrationToLoggedInUser;
15
+ addContactToLoggedInUser?: WardenContact;
16
+
17
+ removeWebAuthnRegistrationFromLoggedInUser?: string;
18
+ removeContactFromLoggedInUser?: WardenContact;
19
+
20
+ removeWebAuthnRegistration?: RemoveWebAuthnRegistration;
21
+
22
+ performLogin?: WardenLoginRequest;
23
+ refreshJwtToken?: string;
24
+
25
+ exportWebAuthnRegistrationEntryForLoggedInUser?: string; // Pass the target origin
26
+ importWebAuthnRegistrationEntryForLoggedInUser?: string;
27
+
28
+
29
+ }
@@ -0,0 +1,7 @@
1
+ import { WardenContactType } from '../model/warden-contact-type.js';
2
+
3
+ // Used when you want to look up a particular contact by userId/contact type on the server side
4
+ export interface WardenContactLookup {
5
+ userId?: string;
6
+ contactType?: WardenContactType;
7
+ }
@@ -0,0 +1,7 @@
1
+ export interface WardenCustomTemplateDescriptor {
2
+ textVersion?: string;
3
+ htmlVersion?: string;
4
+ baseLayout?: string;
5
+ subjectLine?: string;
6
+ meta?: Record<string, string>;
7
+ }
@@ -0,0 +1,3 @@
1
+ export interface WebAuthnObjectWrapper {
2
+ dataAsJson: string;
3
+ }
@@ -0,0 +1,9 @@
1
+ // NOTE: This is a psuedo-enum to fix some issues with Typescript enums. See: https://exploringjs.com/tackling-ts/ch_enum-alternatives.html for details
2
+
3
+ export const WardenErrorCode = {
4
+ Unspecified: 100,
5
+ InvalidLoginToken: 200,
6
+ ExpiredLoginToken: 300,
7
+ } as const;
8
+
9
+ export type WardenErrorCode = (typeof WardenErrorCode)[keyof typeof WardenErrorCode];