@bitblit/ratchet-warden-common 6.0.145-alpha → 6.0.147-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/package.json +4 -3
- package/src/build/ratchet-warden-common-info.ts +19 -0
- package/src/client/provider/warden-client-abstract-recent-login-provider.ts +53 -0
- package/src/client/provider/warden-client-current-logged-in-jwt-token-provider.ts +3 -0
- package/src/client/provider/warden-client-recent-login-provider.ts +11 -0
- package/src/client/provider/warden-client-storage-based-logged-in-user-provider.ts +54 -0
- package/src/client/provider/warden-client-storage-based-recent-login-provider.ts +46 -0
- package/src/client/provider/warden-client-transient-memory-logged-in-user-provider.ts +19 -0
- package/src/client/provider/warden-client-transient-memory-recent-login-provider.ts +15 -0
- package/src/client/provider/warden-command-exchange-provider.ts +8 -0
- package/src/client/provider/warden-logged-in-user-provider.ts +7 -0
- package/src/client/provider/warden-logged-in-user-wrapper.ts +7 -0
- package/src/client/provider/warden-recent-login-descriptor.ts +6 -0
- package/src/client/provider/warden-user-service-event-processing-provider.ts +14 -0
- package/src/client/provider/warden-user-service-options.ts +17 -0
- package/src/client/warden-client.spec.ts +32 -0
- package/src/client/warden-client.ts +239 -0
- package/src/client/warden-delegating-current-user-providing-user-service-event-processing-provider.ts +76 -0
- package/src/client/warden-user-service.spec.ts +29 -0
- package/src/client/warden-user-service.ts +434 -0
- package/src/common/command/add-web-authn-registration-to-logged-in-user.ts +7 -0
- package/src/common/command/create-account.ts +8 -0
- package/src/common/command/remove-web-authn-registration.ts +4 -0
- package/src/common/command/send-magic-link.ts +15 -0
- package/src/common/command/warden-command-response.ts +24 -0
- package/src/common/command/warden-command.ts +29 -0
- package/src/common/command/warden-contact-lookup.ts +7 -0
- package/src/common/command/warden-custom-template-descriptor.ts +7 -0
- package/src/common/command/web-authn-object-wrapper.ts +3 -0
- package/src/common/error/warden-error-codes.ts +9 -0
- package/src/common/error/warden-error.ts +122 -0
- package/src/common/model/warden-contact-type.ts +4 -0
- package/src/common/model/warden-contact.ts +6 -0
- package/src/common/model/warden-customer-message-type.ts +5 -0
- package/src/common/model/warden-entry-summary.ts +9 -0
- package/src/common/model/warden-entry.ts +14 -0
- package/src/common/model/warden-jwt-token.ts +15 -0
- package/src/common/model/warden-login-request-type.ts +6 -0
- package/src/common/model/warden-login-request.ts +15 -0
- package/src/common/model/warden-login-results.ts +8 -0
- package/src/common/model/warden-login-third-party-token.ts +5 -0
- package/src/common/model/warden-permission.ts +4 -0
- package/src/common/model/warden-role.ts +8 -0
- package/src/common/model/warden-store-registration-response-type.ts +5 -0
- package/src/common/model/warden-store-registration-response.ts +9 -0
- package/src/common/model/warden-team-role-mapping.ts +4 -0
- package/src/common/model/warden-team.ts +5 -0
- package/src/common/model/warden-third-party-authentication.ts +6 -0
- package/src/common/model/warden-user-decoration.ts +10 -0
- package/src/common/model/warden-web-authn-entry-summary.ts +6 -0
- package/src/common/model/warden-web-authn-entry.ts +14 -0
- package/src/common/model/warden-web-authn-transport-future-type.ts +8 -0
- package/src/common/util/warden-utils.spec.ts +15 -0
- package/src/common/util/warden-utils.ts +284 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitblit/ratchet-warden-common",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.147-alpha",
|
|
4
4
|
"description": "Typescript library to simplify using simplewebauthn and secondary auth methods over GraphQL",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
|
+
"src/**",
|
|
8
9
|
"lib/**",
|
|
9
10
|
"bin/**"
|
|
10
11
|
],
|
|
@@ -46,12 +47,12 @@
|
|
|
46
47
|
},
|
|
47
48
|
"license": "Apache-2.0",
|
|
48
49
|
"dependencies": {
|
|
49
|
-
"@bitblit/ratchet-common": "6.0.
|
|
50
|
+
"@bitblit/ratchet-common": "6.0.147-alpha",
|
|
50
51
|
"@simplewebauthn/browser": "13.2.2",
|
|
51
52
|
"jwt-decode": "4.0.0"
|
|
52
53
|
},
|
|
53
54
|
"peerDependencies": {
|
|
54
|
-
"@bitblit/ratchet-common": "^6.0.
|
|
55
|
+
"@bitblit/ratchet-common": "^6.0.147-alpha",
|
|
55
56
|
"@simplewebauthn/browser": "^13.2.2",
|
|
56
57
|
"jwt-decode": "^4.0.0"
|
|
57
58
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BuildInformation } from '@bitblit/ratchet-common/build/build-information';
|
|
2
|
+
|
|
3
|
+
export class RatchetWardenCommonInfo {
|
|
4
|
+
// Empty constructor prevents instantiation
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
6
|
+
private constructor() {}
|
|
7
|
+
|
|
8
|
+
public static buildInformation(): BuildInformation {
|
|
9
|
+
const val: BuildInformation = {
|
|
10
|
+
version: 'LOCAL-SNAPSHOT',
|
|
11
|
+
hash: 'LOCAL-HASH',
|
|
12
|
+
branch: 'LOCAL-BRANCH',
|
|
13
|
+
tag: 'LOCAL-TAG',
|
|
14
|
+
timeBuiltISO: 'LOCAL-TIME-ISO',
|
|
15
|
+
notes: 'LOCAL-NOTES',
|
|
16
|
+
};
|
|
17
|
+
return val;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { WardenClientRecentLoginProvider } from './warden-client-recent-login-provider.js';
|
|
2
|
+
import { WardenRecentLoginDescriptor } from './warden-recent-login-descriptor.js';
|
|
3
|
+
|
|
4
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
5
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
6
|
+
import { WardenEntrySummary } from '../../common/model/warden-entry-summary.js';
|
|
7
|
+
import { WardenContact } from '../../common/model/warden-contact.js';
|
|
8
|
+
import { WardenUtils } from '../../common/util/warden-utils.js';
|
|
9
|
+
|
|
10
|
+
export abstract class WardenClientAbstractRecentLoginProvider implements WardenClientRecentLoginProvider {
|
|
11
|
+
public abstract fetchCache(): WardenRecentLoginDescriptor[];
|
|
12
|
+
public abstract updateCache(newValue: WardenRecentLoginDescriptor[]);
|
|
13
|
+
|
|
14
|
+
public saveRecentLogin(entry: WardenEntrySummary): void {
|
|
15
|
+
if (entry?.userId) {
|
|
16
|
+
Logger.info('Saving recent login : %j', entry);
|
|
17
|
+
let list: WardenRecentLoginDescriptor[] = this.fetchCache();
|
|
18
|
+
list = list.filter((s) => s.user.userId !== entry.userId);
|
|
19
|
+
list.push({
|
|
20
|
+
user: entry,
|
|
21
|
+
lastLoginEpochMS: Date.now(),
|
|
22
|
+
});
|
|
23
|
+
this.updateCache(list);
|
|
24
|
+
} else {
|
|
25
|
+
Logger.warn('Cannot save recent login - no login provided : %s', entry);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public saveNewUser(userId: string, label: string, contact: WardenContact): void {
|
|
30
|
+
if (StringRatchet.trimToNull(userId) && WardenUtils.validContact(contact)) {
|
|
31
|
+
this.saveRecentLogin({
|
|
32
|
+
userId: userId,
|
|
33
|
+
contactMethods: [contact],
|
|
34
|
+
webAuthnAuthenticatorSummaries: [],
|
|
35
|
+
userLabel: label,
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
Logger.warn('Cannot save new user - invalid data : %s : %j', userId, contact);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public removeUser(userId: string): void {
|
|
43
|
+
let list: WardenRecentLoginDescriptor[] = this.fetchCache();
|
|
44
|
+
list = list.filter((c) => c.user.userId !== userId);
|
|
45
|
+
this.updateCache(list);
|
|
46
|
+
}
|
|
47
|
+
public fetchAllLogins(): WardenRecentLoginDescriptor[] {
|
|
48
|
+
return Object.assign([], this.fetchCache());
|
|
49
|
+
}
|
|
50
|
+
public clearAllLogins(): void {
|
|
51
|
+
this.updateCache([]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { WardenRecentLoginDescriptor } from './warden-recent-login-descriptor.js';
|
|
2
|
+
import { WardenEntrySummary } from '../../common/model/warden-entry-summary.js';
|
|
3
|
+
import { WardenContact } from '../../common/model/warden-contact.js';
|
|
4
|
+
|
|
5
|
+
export interface WardenClientRecentLoginProvider {
|
|
6
|
+
saveRecentLogin(entry: WardenEntrySummary): void;
|
|
7
|
+
saveNewUser(userId: string, label: string, contact: WardenContact): void;
|
|
8
|
+
removeUser(userId: string): void;
|
|
9
|
+
fetchAllLogins(): WardenRecentLoginDescriptor[];
|
|
10
|
+
clearAllLogins(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { WardenLoggedInUserProvider } from "./warden-logged-in-user-provider.js";
|
|
2
|
+
import { WardenLoggedInUserWrapper } from "./warden-logged-in-user-wrapper.js";
|
|
3
|
+
|
|
4
|
+
import { RequireRatchet } from "@bitblit/ratchet-common/lang/require-ratchet";
|
|
5
|
+
import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
|
|
6
|
+
import { Logger } from "@bitblit/ratchet-common/logger/logger";
|
|
7
|
+
|
|
8
|
+
export class WardenClientStorageBasedLoggedInUserProvider<T> implements WardenLoggedInUserProvider<T> {
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private storageProv: Storage | (() => Storage),
|
|
12
|
+
private storageKey: string,
|
|
13
|
+
) {
|
|
14
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(this.storageKey, 'storageKey');
|
|
15
|
+
RequireRatchet.notNullOrUndefined(this.storageProv, 'storageProv');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get storage(): Storage {
|
|
19
|
+
if (typeof this.storageProv === 'function') {
|
|
20
|
+
return this.storageProv();
|
|
21
|
+
} else {
|
|
22
|
+
return this.storageProv;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public fetchLoggedInUserWrapper(): WardenLoggedInUserWrapper<T> {
|
|
27
|
+
const storage: Storage = this.storage;
|
|
28
|
+
if (storage) {
|
|
29
|
+
const asString: string = storage.getItem(this.storageKey);
|
|
30
|
+
const rval: WardenLoggedInUserWrapper<T> = StringRatchet.trimToNull(asString) ? JSON.parse(asString) : null;
|
|
31
|
+
return rval;
|
|
32
|
+
} else {
|
|
33
|
+
Logger.debug('Tried to fetch logged in user before storage ready - returning null');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public logOutUser(): void {
|
|
39
|
+
this.setLoggedInUserWrapper(null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public setLoggedInUserWrapper(wrapper: WardenLoggedInUserWrapper<T>) {
|
|
43
|
+
const storage: Storage = this.storage;
|
|
44
|
+
if (storage) {
|
|
45
|
+
if (wrapper) {
|
|
46
|
+
storage.setItem(this.storageKey, JSON.stringify(wrapper));
|
|
47
|
+
} else {
|
|
48
|
+
storage.removeItem(this.storageKey);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
Logger.warn('Tried to set logged in user before storage was ready, ignoring : %j', wrapper);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WardenRecentLoginDescriptor } from './warden-recent-login-descriptor.js';
|
|
2
|
+
|
|
3
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
4
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
5
|
+
import { WardenClientAbstractRecentLoginProvider } from './warden-client-abstract-recent-login-provider.js';
|
|
6
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
7
|
+
|
|
8
|
+
export class WardenClientStorageBasedRecentLoginProvider extends WardenClientAbstractRecentLoginProvider {
|
|
9
|
+
constructor(
|
|
10
|
+
private storageProv: Storage | (() => Storage),
|
|
11
|
+
private storageKey: string,
|
|
12
|
+
) {
|
|
13
|
+
super();
|
|
14
|
+
RequireRatchet.notNullOrUndefined(this.storageProv, 'storageProv');
|
|
15
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(this.storageKey, 'storageKey');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get storage(): Storage {
|
|
19
|
+
if (typeof this.storageProv === 'function') {
|
|
20
|
+
return this.storageProv();
|
|
21
|
+
} else {
|
|
22
|
+
return this.storageProv;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fetchCache(): WardenRecentLoginDescriptor[] {
|
|
27
|
+
const storage: Storage = this.storage;
|
|
28
|
+
if (storage) {
|
|
29
|
+
const asString: string = StringRatchet.trimToNull(storage.getItem(StringRatchet.trimToNull(this.storageKey)));
|
|
30
|
+
return asString ? JSON.parse(asString) : [];
|
|
31
|
+
} else {
|
|
32
|
+
Logger.debug('Tried to fetch cache before storage was ready, returning empty');
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
updateCache(newValue: WardenRecentLoginDescriptor[]) {
|
|
38
|
+
const storage: Storage = this.storage;
|
|
39
|
+
if (storage) {
|
|
40
|
+
const asString: string = newValue ? JSON.stringify(newValue) : '[]';
|
|
41
|
+
storage.setItem(StringRatchet.trimToNull(this.storageKey), asString);
|
|
42
|
+
} else {
|
|
43
|
+
Logger.debug('Tried to update cache before storage was ready, ignoring update to %j', newValue);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WardenLoggedInUserProvider } from "./warden-logged-in-user-provider.js";
|
|
2
|
+
import { WardenLoggedInUserWrapper } from "./warden-logged-in-user-wrapper.js";
|
|
3
|
+
|
|
4
|
+
export class WardenClientTransientMemoryLoggedInUserProvider<T> implements WardenLoggedInUserProvider<T> {
|
|
5
|
+
private wrapper: WardenLoggedInUserWrapper<T>;
|
|
6
|
+
|
|
7
|
+
public fetchLoggedInUserWrapper(): WardenLoggedInUserWrapper<T> {
|
|
8
|
+
return this.wrapper;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public logOutUser(): void {
|
|
12
|
+
this.wrapper = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public setLoggedInUserWrapper(wrapper: WardenLoggedInUserWrapper<T>) {
|
|
16
|
+
this.wrapper = wrapper;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WardenRecentLoginDescriptor } from './warden-recent-login-descriptor.js';
|
|
2
|
+
import { WardenClientAbstractRecentLoginProvider } from './warden-client-abstract-recent-login-provider.js';
|
|
3
|
+
|
|
4
|
+
// Stores everything in transient memory - basically just for testing
|
|
5
|
+
export class WardenClientTransientMemoryRecentLoginProvider extends WardenClientAbstractRecentLoginProvider {
|
|
6
|
+
private _cache: WardenRecentLoginDescriptor[] = [];
|
|
7
|
+
|
|
8
|
+
fetchCache(): WardenRecentLoginDescriptor[] {
|
|
9
|
+
return this._cache;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
updateCache(newValue: WardenRecentLoginDescriptor[]) {
|
|
13
|
+
this._cache = newValue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { WardenLoggedInUserWrapper } from "./warden-logged-in-user-wrapper.js";
|
|
2
|
+
|
|
3
|
+
export interface WardenLoggedInUserProvider<T> {
|
|
4
|
+
fetchLoggedInUserWrapper(): WardenLoggedInUserWrapper<T>;
|
|
5
|
+
setLoggedInUserWrapper(wrapper: WardenLoggedInUserWrapper<T>);
|
|
6
|
+
logOutUser(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { WardenLoggedInUserWrapper } from './warden-logged-in-user-wrapper.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Notifies the containing system when significant events happen
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface WardenUserServiceEventProcessingProvider<T> {
|
|
8
|
+
onLogout(): void;
|
|
9
|
+
onSuccessfulLogin(newUser: WardenLoggedInUserWrapper<T>): void;
|
|
10
|
+
onLoginFailure(reason: string): void;
|
|
11
|
+
|
|
12
|
+
onAutomaticTokenRefresh(refreshUser: WardenLoggedInUserWrapper<T>): void;
|
|
13
|
+
onAutomaticLogout(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WardenLoggedInUserProvider } from './warden-logged-in-user-provider.js';
|
|
2
|
+
import { WardenClient } from '../warden-client.js';
|
|
3
|
+
import { WardenUserServiceEventProcessingProvider } from './warden-user-service-event-processing-provider.js';
|
|
4
|
+
import { WardenClientRecentLoginProvider } from './warden-client-recent-login-provider.js';
|
|
5
|
+
|
|
6
|
+
export interface WardenUserServiceOptions<T> {
|
|
7
|
+
recentLoginProvider?: WardenClientRecentLoginProvider;
|
|
8
|
+
loggedInUserProvider: WardenLoggedInUserProvider<T>;
|
|
9
|
+
wardenClient: WardenClient;
|
|
10
|
+
eventProcessor: WardenUserServiceEventProcessingProvider<T>;
|
|
11
|
+
loginCheckTimerPingSeconds?: number;
|
|
12
|
+
autoLoginHandlingThresholdSeconds?: number;
|
|
13
|
+
allowAutoRefresh?: boolean;
|
|
14
|
+
|
|
15
|
+
applicationName: string;
|
|
16
|
+
deviceLabelGenerator?: () => string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { WardenClient } from './warden-client.js';
|
|
2
|
+
|
|
3
|
+
import { WardenCommandExchangeProvider } from './provider/warden-command-exchange-provider.js';
|
|
4
|
+
import { WardenClientCurrentLoggedInJwtTokenProvider } from './provider/warden-client-current-logged-in-jwt-token-provider.js';
|
|
5
|
+
import { WardenCommand } from '../common/command/warden-command.js';
|
|
6
|
+
import { WardenCommandResponse } from '../common/command/warden-command-response.js';
|
|
7
|
+
import { beforeEach, describe, expect, test } from 'vitest';
|
|
8
|
+
import { mock, MockProxy } from 'vitest-mock-extended';
|
|
9
|
+
|
|
10
|
+
let mockCommandExchangeProvider: MockProxy<WardenCommandExchangeProvider>;
|
|
11
|
+
let mockLoggedInJwtTokenProvider: MockProxy<WardenClientCurrentLoggedInJwtTokenProvider>;
|
|
12
|
+
|
|
13
|
+
describe('#wardenClient', function () {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockCommandExchangeProvider = mock<WardenCommandExchangeProvider>();
|
|
16
|
+
mockLoggedInJwtTokenProvider = mock<WardenClientCurrentLoggedInJwtTokenProvider>();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should instantiate and exchange commands', async () => {
|
|
20
|
+
const wc: WardenClient = new WardenClient(mockCommandExchangeProvider, mockLoggedInJwtTokenProvider);
|
|
21
|
+
expect(wc).not.toBeNull();
|
|
22
|
+
const cmd: WardenCommand = {
|
|
23
|
+
generateWebAuthnAuthenticationChallengeForUserId: 'test',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
mockCommandExchangeProvider.sendCommand.mockResolvedValue('{"generateWebAuthnAuthenticationChallengeForUserId": { "dataAsJson": {} }}');
|
|
27
|
+
|
|
28
|
+
const result: WardenCommandResponse = await wc.exchangeCommand(cmd);
|
|
29
|
+
expect(result).not.toBeNull();
|
|
30
|
+
expect(result.generateWebAuthnRegistrationChallengeForLoggedInUser).not.toBeNull();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Service for interacting with positions for a given user
|
|
2
|
+
import { WardenCommand } from "../common/command/warden-command.js";
|
|
3
|
+
import { WardenContact } from "../common/model/warden-contact.js";
|
|
4
|
+
import { WardenCommandExchangeProvider } from "./provider/warden-command-exchange-provider.js";
|
|
5
|
+
import { WardenCommandResponse } from "../common/command/warden-command-response.js";
|
|
6
|
+
|
|
7
|
+
import { RequireRatchet } from "@bitblit/ratchet-common/lang/require-ratchet";
|
|
8
|
+
import { Logger } from "@bitblit/ratchet-common/logger/logger";
|
|
9
|
+
import { ErrorRatchet } from "@bitblit/ratchet-common/lang/error-ratchet";
|
|
10
|
+
import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
|
|
11
|
+
import {
|
|
12
|
+
PublicKeyCredentialRequestOptionsJSON,
|
|
13
|
+
RegistrationResponseJSON,
|
|
14
|
+
StartRegistrationOpts
|
|
15
|
+
} from "@simplewebauthn/browser";
|
|
16
|
+
import { WardenLoginResults } from "../common/model/warden-login-results.js";
|
|
17
|
+
import { WardenLoginRequest } from "../common/model/warden-login-request.js";
|
|
18
|
+
import {
|
|
19
|
+
WardenClientCurrentLoggedInJwtTokenProvider
|
|
20
|
+
} from "./provider/warden-client-current-logged-in-jwt-token-provider.js";
|
|
21
|
+
import { WardenEntrySummary } from "../common/model/warden-entry-summary.js";
|
|
22
|
+
import { WardenContactType } from "../common/model/warden-contact-type.js";
|
|
23
|
+
import {
|
|
24
|
+
AddWebAuthnRegistrationToLoggedInUser
|
|
25
|
+
} from "../common/command/add-web-authn-registration-to-logged-in-user.js";
|
|
26
|
+
import { SendMagicLink } from "../common/command/send-magic-link.js";
|
|
27
|
+
import { WardenLoginRequestType } from "../common/model/warden-login-request-type";
|
|
28
|
+
|
|
29
|
+
export class WardenClient {
|
|
30
|
+
constructor(
|
|
31
|
+
private commandSender: WardenCommandExchangeProvider,
|
|
32
|
+
private jwtProvider: WardenClientCurrentLoggedInJwtTokenProvider,
|
|
33
|
+
) {
|
|
34
|
+
RequireRatchet.notNullOrUndefined(commandSender, 'commandSender');
|
|
35
|
+
RequireRatchet.notNullOrUndefined(jwtProvider, 'jwtProvider');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async exchangeCommand(cmd: WardenCommand, returnErrors?: boolean): Promise<WardenCommandResponse> {
|
|
39
|
+
const asString: string = JSON.stringify(cmd);
|
|
40
|
+
const resp: string = await this.commandSender.sendCommand(asString, this.jwtProvider.fetchCurrentLoggedInJwtToken());
|
|
41
|
+
const parsed: WardenCommandResponse = JSON.parse(resp);
|
|
42
|
+
|
|
43
|
+
if (parsed?.error && !returnErrors) {
|
|
44
|
+
ErrorRatchet.throwFormattedErr('%s', parsed.error);
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async createAccount(contact: WardenContact, sendCode?: boolean, label?: string, tags?: string[]): Promise<string> {
|
|
50
|
+
const cmd: WardenCommand = {
|
|
51
|
+
createAccount: {
|
|
52
|
+
contact: contact,
|
|
53
|
+
sendCode: sendCode,
|
|
54
|
+
label: label,
|
|
55
|
+
tags: tags,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
59
|
+
|
|
60
|
+
return rval.createAccount;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async sendMagicLinkRaw(smlCmd: SendMagicLink): Promise<boolean> {
|
|
64
|
+
if (smlCmd) {
|
|
65
|
+
const cmd: WardenCommand = {
|
|
66
|
+
sendMagicLink: smlCmd,
|
|
67
|
+
};
|
|
68
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
69
|
+
return rval.sendMagicLink;
|
|
70
|
+
} else {
|
|
71
|
+
Logger.warn('Skipping magic link command - none supplied');
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async sendMagicLinkByUserId(
|
|
77
|
+
userId: string,
|
|
78
|
+
landingUrl: string,
|
|
79
|
+
contactType?: WardenContactType,
|
|
80
|
+
meta?: Record<string, string>,
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
const cmd: SendMagicLink = {
|
|
83
|
+
contactLookup: {
|
|
84
|
+
userId: userId,
|
|
85
|
+
contactType: contactType,
|
|
86
|
+
},
|
|
87
|
+
landingUrl: landingUrl,
|
|
88
|
+
meta: meta,
|
|
89
|
+
};
|
|
90
|
+
return this.sendMagicLinkRaw(cmd);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async sendMagicLink(contact: WardenContact, landingUrl: string, meta?: Record<string, string>): Promise<boolean> {
|
|
94
|
+
const cmd: SendMagicLink = {
|
|
95
|
+
contact: contact,
|
|
96
|
+
landingUrl: landingUrl,
|
|
97
|
+
meta: meta,
|
|
98
|
+
};
|
|
99
|
+
return this.sendMagicLinkRaw(cmd);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async generateWebAuthnAuthenticationChallengeForUserId(userId: string): Promise<PublicKeyCredentialRequestOptionsJSON> {
|
|
103
|
+
const cmd: WardenCommand = {
|
|
104
|
+
generateWebAuthnAuthenticationChallengeForUserId: userId,
|
|
105
|
+
};
|
|
106
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
107
|
+
const parsed: PublicKeyCredentialRequestOptionsJSON = JSON.parse(rval.generateWebAuthnAuthenticationChallengeForUserId.dataAsJson);
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public async exportWebAuthnRegistrationEntryForLoggedInUser(origin: string): Promise<string> {
|
|
112
|
+
const cmd: WardenCommand = {
|
|
113
|
+
exportWebAuthnRegistrationEntryForLoggedInUser: origin,
|
|
114
|
+
};
|
|
115
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
116
|
+
return rval.exportWebAuthnRegistrationEntryForLoggedInUser;
|
|
117
|
+
}
|
|
118
|
+
public async importWebAuthnRegistrationEntryForLoggedInUser(token: string): Promise<boolean> {
|
|
119
|
+
const cmd: WardenCommand = {
|
|
120
|
+
importWebAuthnRegistrationEntryForLoggedInUser: token,
|
|
121
|
+
};
|
|
122
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
123
|
+
return rval.importWebAuthnRegistrationEntryForLoggedInUser;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
public async generateWebAuthnRegistrationChallengeForLoggedInUser(): Promise<StartRegistrationOpts> {
|
|
128
|
+
const cmd: WardenCommand = {
|
|
129
|
+
generateWebAuthnRegistrationChallengeForLoggedInUser: true,
|
|
130
|
+
};
|
|
131
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
132
|
+
//Logger.info('generateWebAuthnRegistrationChallengeForLoggedInUser: %j', rval);
|
|
133
|
+
const parsed: StartRegistrationOpts = JSON.parse(rval.generateWebAuthnRegistrationChallengeForLoggedInUser.dataAsJson);
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public async removeWebAuthnRegistration(userId: string, credId: string): Promise<WardenEntrySummary> {
|
|
138
|
+
const cmd: WardenCommand = {
|
|
139
|
+
removeWebAuthnRegistration: {
|
|
140
|
+
userId: userId,
|
|
141
|
+
credentialId: credId,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
145
|
+
return rval.removeWebAuthnRegistration;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public async removeWebAuthnRegistrationFromLoggedInUser(input: string): Promise<WardenEntrySummary> {
|
|
149
|
+
const cmd: WardenCommand = {
|
|
150
|
+
removeWebAuthnRegistrationFromLoggedInUser: input,
|
|
151
|
+
};
|
|
152
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
153
|
+
return rval.removeWebAuthnRegistrationFromLoggedInUser;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public async removeContactFromLoggedInUser(input: WardenContact): Promise<WardenEntrySummary> {
|
|
157
|
+
const cmd: WardenCommand = {
|
|
158
|
+
removeContactFromLoggedInUser: input,
|
|
159
|
+
};
|
|
160
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
161
|
+
return rval.removeContactFromLoggedInUser;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async sendExpiringValidationToken(contact: WardenContact): Promise<boolean> {
|
|
165
|
+
const cmd: WardenCommand = {
|
|
166
|
+
sendExpiringValidationToken: contact,
|
|
167
|
+
};
|
|
168
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
169
|
+
return rval.sendExpiringValidationToken;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public async addContactToLoggedInUser(contact: WardenContact): Promise<boolean> {
|
|
173
|
+
const cmd: WardenCommand = {
|
|
174
|
+
addContactToLoggedInUser: contact,
|
|
175
|
+
};
|
|
176
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
177
|
+
return rval.addContactToLoggedInUser;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public async addWebAuthnRegistrationToLoggedInUser(
|
|
181
|
+
applicationName: string,
|
|
182
|
+
deviceLabel: string,
|
|
183
|
+
data: RegistrationResponseJSON,
|
|
184
|
+
): Promise<WardenEntrySummary> {
|
|
185
|
+
const inCmd: AddWebAuthnRegistrationToLoggedInUser = {
|
|
186
|
+
webAuthn: {
|
|
187
|
+
dataAsJson: JSON.stringify(data),
|
|
188
|
+
},
|
|
189
|
+
applicationName: applicationName,
|
|
190
|
+
deviceLabel: deviceLabel,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const cmd: WardenCommand = {
|
|
194
|
+
addWebAuthnRegistrationToLoggedInUser: inCmd,
|
|
195
|
+
};
|
|
196
|
+
const rval: WardenCommandResponse = await this.exchangeCommand(cmd);
|
|
197
|
+
return rval.addWebAuthnRegistrationToLoggedInUser;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public async performLoginCmd(login: WardenLoginRequest): Promise<WardenLoginResults> {
|
|
201
|
+
const loginCmd: WardenCommand = {
|
|
202
|
+
performLogin: login,
|
|
203
|
+
};
|
|
204
|
+
const cmdResponse: WardenCommandResponse = await this.exchangeCommand(loginCmd);
|
|
205
|
+
|
|
206
|
+
return cmdResponse.performLogin;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public async refreshJwtToken(oldJwtToken: string): Promise<string> {
|
|
210
|
+
let rval: string = null;
|
|
211
|
+
if (StringRatchet.trimToNull(oldJwtToken)) {
|
|
212
|
+
try {
|
|
213
|
+
const resp: WardenCommandResponse = await this.exchangeCommand({ refreshJwtToken: oldJwtToken });
|
|
214
|
+
rval = resp.refreshJwtToken;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
Logger.error('JwtRefresh Failed : %s', err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return rval;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public async executeExpiringTokenBasedLogin(contact: WardenContact, expiringToken: string): Promise<WardenLoginResults> {
|
|
223
|
+
let rval: WardenLoginResults = null;
|
|
224
|
+
try {
|
|
225
|
+
const loginCmd: WardenLoginRequest = {
|
|
226
|
+
type: WardenLoginRequestType.ExpiringToken,
|
|
227
|
+
contact: contact,
|
|
228
|
+
expiringToken: expiringToken,
|
|
229
|
+
};
|
|
230
|
+
rval = await this.performLoginCmd(loginCmd);
|
|
231
|
+
if (rval?.jwtToken) {
|
|
232
|
+
//rval = true;
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
Logger.error('ExpiringToken login Failed : %s', err);
|
|
236
|
+
}
|
|
237
|
+
return rval;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { WardenUserServiceEventProcessingProvider } from './provider/warden-user-service-event-processing-provider.js';
|
|
2
|
+
import { WardenLoggedInUserWrapper } from './provider/warden-logged-in-user-wrapper.js';
|
|
3
|
+
import { BehaviorSubject } from 'rxjs';
|
|
4
|
+
import { WardenClientCurrentLoggedInJwtTokenProvider } from './provider/warden-client-current-logged-in-jwt-token-provider.js';
|
|
5
|
+
import { WardenUtils } from '../common/util/warden-utils.js';
|
|
6
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This class maintains a BehaviorSubject of the current user for things that want to be
|
|
10
|
+
* notified on change without depending on the WardenUserService (most commonly, the
|
|
11
|
+
* API provider with both needs the token for auth'd calls, but also is responsible for
|
|
12
|
+
* doing the login call itself and therefor cannot depend on the UserService which depends
|
|
13
|
+
* on the WardenClient which likely depends on the API provider - prevents a circular
|
|
14
|
+
* dependency to just depend on this)
|
|
15
|
+
*
|
|
16
|
+
* Delegates so that you still can also register other behavior, and just tack this onto it
|
|
17
|
+
*
|
|
18
|
+
* By default this will never serve expired credentials - if a call is made, and the credentials found are
|
|
19
|
+
* expired, they will be cleared and null will be returned
|
|
20
|
+
*/
|
|
21
|
+
export class WardenDelegatingCurrentUserProvidingUserServiceEventProcessingProvider<T>
|
|
22
|
+
implements WardenUserServiceEventProcessingProvider<T>, WardenClientCurrentLoggedInJwtTokenProvider
|
|
23
|
+
{
|
|
24
|
+
private _currentUserSubject: BehaviorSubject<WardenLoggedInUserWrapper<T>> = new BehaviorSubject<WardenLoggedInUserWrapper<T>>(null);
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private wrapped?: Partial<WardenUserServiceEventProcessingProvider<T>>,
|
|
28
|
+
private serveExpiredCredentials: boolean = false,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
public fetchCurrentLoggedInJwtToken(): string {
|
|
32
|
+
let val: WardenLoggedInUserWrapper<T> = this?._currentUserSubject?.getValue();
|
|
33
|
+
if (!this.serveExpiredCredentials && val && WardenUtils.wrapperIsExpired(val)) {
|
|
34
|
+
Logger.info('Current wrapper in the subject is expired - autostripping');
|
|
35
|
+
this.currentUserSubject.next(null);
|
|
36
|
+
val = null;
|
|
37
|
+
}
|
|
38
|
+
return val?.jwtToken;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public get currentUserSubject(): BehaviorSubject<WardenLoggedInUserWrapper<T>> {
|
|
42
|
+
return this._currentUserSubject;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public onAutomaticLogout(): void {
|
|
46
|
+
if (this.wrapped) {
|
|
47
|
+
this.wrapped.onAutomaticLogout();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public onAutomaticTokenRefresh(refreshUser: WardenLoggedInUserWrapper<T>): void {
|
|
52
|
+
if (this?.wrapped?.onAutomaticTokenRefresh) {
|
|
53
|
+
this.wrapped.onAutomaticTokenRefresh(refreshUser);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public onLoginFailure(reason: string): void {
|
|
58
|
+
if (this?.wrapped?.onLoginFailure) {
|
|
59
|
+
this.wrapped.onLoginFailure(reason);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public onLogout(): void {
|
|
64
|
+
if (this?.wrapped?.onLogout) {
|
|
65
|
+
this.wrapped.onLogout();
|
|
66
|
+
}
|
|
67
|
+
this.currentUserSubject.next(null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public onSuccessfulLogin(newUser: WardenLoggedInUserWrapper<T>): void {
|
|
71
|
+
if (this?.wrapped?.onSuccessfulLogin) {
|
|
72
|
+
this.wrapped.onSuccessfulLogin(newUser);
|
|
73
|
+
}
|
|
74
|
+
this.currentUserSubject.next(newUser);
|
|
75
|
+
}
|
|
76
|
+
}
|