@bitblit/ratchet-warden-server 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.
Files changed (26) hide show
  1. package/package.json +9 -8
  2. package/src/build/ratchet-warden-server-info.ts +19 -0
  3. package/src/server/provider/warden-default-send-magic-link-command-validator.ts +21 -0
  4. package/src/server/provider/warden-default-user-decoration-provider.ts +23 -0
  5. package/src/server/provider/warden-dynamo-storage-provider-options.ts +8 -0
  6. package/src/server/provider/warden-dynamo-storage-provider.ts +278 -0
  7. package/src/server/provider/warden-event-processing-provider.ts +10 -0
  8. package/src/server/provider/warden-mailer-and-expiring-code-ratchet-single-use-code-provider.ts +155 -0
  9. package/src/server/provider/warden-mailer-and-expiring-code-ratchet-single-user-provider-options.ts +10 -0
  10. package/src/server/provider/warden-message-sending-provider.ts +14 -0
  11. package/src/server/provider/warden-no-op-event-processing-provider.ts +12 -0
  12. package/src/server/provider/warden-s3-single-file-storage-provider-options.ts +6 -0
  13. package/src/server/provider/warden-s3-single-file-storage-provider.ts +139 -0
  14. package/src/server/provider/warden-send-magic-link-command-validator.ts +12 -0
  15. package/src/server/provider/warden-single-use-code-provider.ts +27 -0
  16. package/src/server/provider/warden-storage-provider.ts +22 -0
  17. package/src/server/provider/warden-third-party-authentication-provider.ts +18 -0
  18. package/src/server/provider/warden-twilio-verify-single-use-code-provider-options.ts +5 -0
  19. package/src/server/provider/warden-twilio-verify-single-use-code-provider.ts +38 -0
  20. package/src/server/provider/warden-user-decoration-provider.ts +10 -0
  21. package/src/server/warden-authorizer.ts +130 -0
  22. package/src/server/warden-entry-builder.ts +56 -0
  23. package/src/server/warden-service-options.ts +20 -0
  24. package/src/server/warden-service.spec.ts +102 -0
  25. package/src/server/warden-service.ts +815 -0
  26. package/src/server/warden-web-authn-export-token.ts +6 -0
@@ -0,0 +1,139 @@
1
+ // Service for interacting with positions for a given user
2
+ import { WardenContact } from '@bitblit/ratchet-warden-common/common/model/warden-contact';
3
+ import { WardenEntrySummary } from '@bitblit/ratchet-warden-common/common/model/warden-entry-summary';
4
+ import { WardenEntry } from '@bitblit/ratchet-warden-common/common/model/warden-entry';
5
+ import { WardenUtils } from '@bitblit/ratchet-warden-common/common/util/warden-utils';
6
+ import { WardenStorageProvider } from './warden-storage-provider.js';
7
+ import { WardenS3SingleFileStorageProviderOptions } from './warden-s3-single-file-storage-provider-options.js';
8
+ import { PutObjectOutput, S3Client } from '@aws-sdk/client-s3';
9
+ import { S3CacheRatchet } from '@bitblit/ratchet-aws/s3/s3-cache-ratchet';
10
+ import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
11
+ import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
12
+
13
+ /*
14
+ The most quick and dirty implementation of the storage provider. Not a good choice if you have
15
+ multiple users, etc, since it has no synchronization, etc. Only really useful for very low traffic
16
+ websites, or getting a demo hacked out quickly
17
+ */
18
+ export class WardenS3SingleFileStorageProvider implements WardenStorageProvider {
19
+ private ratchet: S3CacheRatchet;
20
+ constructor(
21
+ private s3: S3Client,
22
+ private options: WardenS3SingleFileStorageProviderOptions,
23
+ ) {
24
+ this.ratchet = new S3CacheRatchet(this.s3, this.options.bucket);
25
+ }
26
+
27
+ public async listUserSummaries(): Promise<WardenEntrySummary[]> {
28
+ const allData: WardenEntry[] = (await this.fetchDataFile()).entries;
29
+ const rval: WardenEntrySummary[] = allData.map((d) => WardenUtils.stripWardenEntryToSummary(d));
30
+ return rval;
31
+ }
32
+
33
+ public async fetchDataFile(): Promise<WardenS3SingleFileStorageProviderDataFile> {
34
+ let data: WardenS3SingleFileStorageProviderDataFile =
35
+ await this.ratchet.fetchCacheFileAsObject<WardenS3SingleFileStorageProviderDataFile>(this.options.dataFileKey);
36
+ data = data || {
37
+ entries: [],
38
+ challenges: [],
39
+ };
40
+ return data;
41
+ }
42
+
43
+ public async storeDataFile(file: WardenS3SingleFileStorageProviderDataFile): Promise<PutObjectOutput> {
44
+ let rval: PutObjectOutput = null;
45
+ if (file) {
46
+ rval = await this.ratchet.writeObjectToCacheFile(this.options.dataFileKey, file);
47
+ }
48
+ return rval;
49
+ }
50
+
51
+ public async fetchCurrentUserChallenge(userId: string, relyingPartyId: string): Promise<string> {
52
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
53
+ const entry: WardenS3SingleFileStorageProviderChallengeRecord = (data.challenges || []).find(
54
+ (d) => d.userId === userId && d.relyingPartyId === relyingPartyId,
55
+ );
56
+ if (!entry) {
57
+ ErrorRatchet.throwFormattedErr('fetchCurrentUserChallenge: Could not find user %s', userId);
58
+ }
59
+
60
+ return entry.challenge;
61
+ }
62
+
63
+ public async findEntryByContact(contact: WardenContact): Promise<WardenEntry> {
64
+ let rval: WardenEntry = null;
65
+ if (contact?.type && StringRatchet.trimToNull(contact?.value)) {
66
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
67
+ rval = (data.entries || []).find((d) => !!(d.contactMethods || []).find((x) => x.type === contact.type && x.value === contact.value));
68
+ }
69
+ return rval;
70
+ }
71
+
72
+ public async findEntryByThirdPartyId(thirdParty: string, thirdPartyId: string): Promise<WardenEntry> {
73
+ let rval: WardenEntry = null;
74
+ if (StringRatchet.trimToNull(thirdParty) && StringRatchet.trimToNull(thirdPartyId)) {
75
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
76
+ rval = (data.entries || []).find((d) => !!(d.thirdPartyAuthenticators || []).find((x) => x.thirdParty===thirdParty && x.thirdPartyId===thirdPartyId));
77
+ }
78
+ return rval;
79
+ }
80
+
81
+
82
+
83
+ public async findEntryById(userId: string): Promise<WardenEntry> {
84
+ let rval: WardenEntry = null;
85
+ if (StringRatchet.trimToNull(userId)) {
86
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
87
+ rval = (data.entries || []).find((d) => d.userId === userId);
88
+ }
89
+ return rval;
90
+ }
91
+
92
+ public async removeEntry(userId: string): Promise<boolean> {
93
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
94
+ data.entries = (data.entries || []).filter((d) => d.userId !== userId);
95
+ await this.storeDataFile(data);
96
+ return true;
97
+ }
98
+
99
+ public async saveEntry(entry: WardenEntry): Promise<WardenEntry> {
100
+ let rval: WardenEntry = null;
101
+ if (entry && entry.userId) {
102
+ const now: number = Date.now();
103
+ entry.createdEpochMS = entry.createdEpochMS || now;
104
+ entry.updatedEpochMS = now;
105
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
106
+ data.entries = (data.entries || []).filter((d) => d.userId !== entry.userId);
107
+ data.entries.push(entry);
108
+ await this.storeDataFile(data);
109
+ rval = await this.findEntryById(entry.userId);
110
+ }
111
+ return rval;
112
+ }
113
+
114
+ public async updateUserChallenge(userId: string, relyingPartyId: string, challenge: string): Promise<boolean> {
115
+ const data: WardenS3SingleFileStorageProviderDataFile = await this.fetchDataFile();
116
+ data.challenges = (data.challenges || []).filter((d) => d.userId !== userId || d.relyingPartyId !== relyingPartyId);
117
+ data.challenges.push({
118
+ userId: userId,
119
+ relyingPartyId: relyingPartyId,
120
+ challenge: challenge,
121
+ updatedEpochMS: Date.now(),
122
+ });
123
+ // Update the file
124
+ await this.storeDataFile(data);
125
+ return true;
126
+ }
127
+ }
128
+
129
+ export interface WardenS3SingleFileStorageProviderChallengeRecord {
130
+ userId: string;
131
+ relyingPartyId: string;
132
+ challenge: string;
133
+ updatedEpochMS: number;
134
+ }
135
+
136
+ export interface WardenS3SingleFileStorageProviderDataFile {
137
+ entries: WardenEntry[];
138
+ challenges: WardenS3SingleFileStorageProviderChallengeRecord[];
139
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This object checks if a magic link request may be processed.
3
+ *
4
+ * Should throw an error if not allowed
5
+ *
6
+ */
7
+ import { WardenEntry } from '@bitblit/ratchet-warden-common/common/model/warden-entry';
8
+ import { SendMagicLink } from '@bitblit/ratchet-warden-common/common/command/send-magic-link';
9
+
10
+ export interface WardenSendMagicLinkCommandValidator {
11
+ allowMagicLinkCommand(cmd: SendMagicLink, origin: string, loggedInUser: WardenEntry): Promise<void>;
12
+ }
@@ -0,0 +1,27 @@
1
+ import { WardenContactType } from '@bitblit/ratchet-warden-common/common/model/warden-contact-type';
2
+ import { WardenContact } from '@bitblit/ratchet-warden-common/common/model/warden-contact';
3
+ import { WardenCustomTemplateDescriptor } from '@bitblit/ratchet-warden-common/common/command/warden-custom-template-descriptor';
4
+
5
+ /**
6
+ * Classes implementing WardenSingleUseCodeProvider are able to
7
+ * generate single-use codes for a user, and to validate a code
8
+ * provided by the user
9
+ **/
10
+
11
+ export interface WardenSingleUseCodeProvider {
12
+ handlesContactType(type: WardenContactType): boolean;
13
+ // Origin is the url of the page that sent the request - put into the template to allow clickthru
14
+ createAndSendNewCode(contact: WardenContact, relyingPartyName: string, origin: string): Promise<boolean>;
15
+ checkCode(contactValue: string, code: string): Promise<boolean>;
16
+ // The code is to login the loginContact. That is also where the code is sent, unless
17
+ // destination contact is set (usually for admin/demo purposes)
18
+ createCodeAndSendMagicLink?(
19
+ loginContact: WardenContact,
20
+ relyingPartyName: string,
21
+ landingUrl: string,
22
+ metaIn?: Record<string, string>,
23
+ ttlSeconds?: number,
24
+ destinationContact?: WardenContact,
25
+ customTemplate?: WardenCustomTemplateDescriptor,
26
+ ): Promise<boolean>;
27
+ }
@@ -0,0 +1,22 @@
1
+ import { WardenContact } from '@bitblit/ratchet-warden-common/common/model/warden-contact';
2
+ import { WardenEntrySummary } from '@bitblit/ratchet-warden-common/common/model/warden-entry-summary';
3
+ import { WardenEntry } from '@bitblit/ratchet-warden-common/common/model/warden-entry';
4
+
5
+ /**
6
+ * Classes implementing WardenStorageProvider perform store and
7
+ * retrieval functions for the SimpleAuthenticationProvider
8
+ */
9
+
10
+ export interface WardenStorageProvider {
11
+ findEntryById(userId: string): Promise<WardenEntry>;
12
+ findEntryByContact(contact: WardenContact): Promise<WardenEntry>;
13
+ findEntryByThirdPartyId(thirdParty: string, thirdPartyId: string): Promise<WardenEntry>;
14
+ saveEntry(entry: WardenEntry): Promise<WardenEntry>;
15
+ removeEntry(userId: string): Promise<boolean>;
16
+
17
+ // User challenges are stored on a per-user, per-rpid basis
18
+ updateUserChallenge(userId: string, relyingPartyId: string, challenge: string): Promise<boolean>;
19
+ fetchCurrentUserChallenge(userId: string, relyingPartyId: string): Promise<string>;
20
+
21
+ listUserSummaries(): Promise<WardenEntrySummary[]>;
22
+ }
@@ -0,0 +1,18 @@
1
+ import { WardenLoginThirdPartyToken } from "@bitblit/ratchet-warden-common/common/model/warden-login-third-party-token";
2
+ import {
3
+ WardenThirdPartyAuthentication
4
+ } from "@bitblit/ratchet-warden-common/common/model/warden-third-party-authentication";
5
+
6
+ /**
7
+ * Classes implementing WardenThirdPartyAuthenticator are able to
8
+ * validate tokens from a third party and if they are valid, return
9
+ * the user id in that system. Warden can then lookup the local user
10
+ * by that user id
11
+ *
12
+ */
13
+
14
+ export interface WardenThirdPartyAuthenticationProvider {
15
+ handlesThirdParty(thirdParty: string): boolean;
16
+ validateTokenAndReturnThirdPartyUserId(input: WardenLoginThirdPartyToken, origin: string): Promise<WardenThirdPartyAuthentication>;
17
+ extractUserLabelFromAuthentication?(auth: WardenThirdPartyAuthentication): Promise<string>;
18
+ }
@@ -0,0 +1,5 @@
1
+ export interface WardenTwilioVerifySingleUseCodeProviderOptions {
2
+ accountSID: string;
3
+ authToken: string;
4
+ verifyServiceSID: string;
5
+ }
@@ -0,0 +1,38 @@
1
+ // Service for sending codes via twilio verify
2
+ import { WardenTwilioVerifySingleUseCodeProviderOptions } from './warden-twilio-verify-single-use-code-provider-options.js';
3
+ import { Logger } from '@bitblit/ratchet-common/logger/logger';
4
+ import { WardenSingleUseCodeProvider } from './warden-single-use-code-provider.js';
5
+ import { TwilioVerifyRatchet } from '@bitblit/ratchet-common/third-party/twilio/twilio-verify-ratchet';
6
+ import { WardenContactType } from '@bitblit/ratchet-warden-common/common/model/warden-contact-type';
7
+ import { WardenContact } from '@bitblit/ratchet-warden-common/common/model/warden-contact';
8
+
9
+ export class WardenTwilioVerifySingleUseCodeProvider implements WardenSingleUseCodeProvider {
10
+ private _cacheTwilioVerifyRatchet: TwilioVerifyRatchet;
11
+
12
+ constructor(private optsPromise: Promise<WardenTwilioVerifySingleUseCodeProviderOptions>) {}
13
+
14
+ private async twilioVerifyRatchet(): Promise<TwilioVerifyRatchet> {
15
+ if (!this._cacheTwilioVerifyRatchet) {
16
+ const opts: WardenTwilioVerifySingleUseCodeProviderOptions = await this.optsPromise;
17
+ this._cacheTwilioVerifyRatchet = new TwilioVerifyRatchet(opts.accountSID, opts.authToken, opts.verifyServiceSID);
18
+ }
19
+ return this._cacheTwilioVerifyRatchet;
20
+ }
21
+
22
+ public handlesContactType(type: WardenContactType): boolean {
23
+ return type === WardenContactType.TextCapablePhoneNumber;
24
+ }
25
+
26
+ public async createAndSendNewCode(contact: WardenContact, _relyingPartyNameIgnored: string, _origin: string): Promise<boolean> {
27
+ // Twilio verify does not let you set the message
28
+ const ratchet: TwilioVerifyRatchet = await this.twilioVerifyRatchet();
29
+ const rval: any = await ratchet.sendVerificationTokenUsingTwilioVerify(contact.value);
30
+ Logger.debug('sendMessage was : %j', rval);
31
+ return !!rval;
32
+ }
33
+ public async checkCode(contactValue: string, code: string): Promise<boolean> {
34
+ const ratchet: TwilioVerifyRatchet = await this.twilioVerifyRatchet();
35
+ const rval: boolean = await ratchet.simpleCheckVerificationTokenUsingTwilioVerify(contactValue, code);
36
+ return rval;
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The user details gets jammed into the JWT token upon login. If one is not provided,
3
+ * the default only puts the user id and label in there
4
+ */
5
+ import { WardenEntry } from '@bitblit/ratchet-warden-common/common/model/warden-entry';
6
+ import { WardenUserDecoration } from '@bitblit/ratchet-warden-common/common/model/warden-user-decoration';
7
+
8
+ export interface WardenUserDecorationProvider<T> {
9
+ fetchDecoration(wardenUser: WardenEntry): Promise<WardenUserDecoration<T>>;
10
+ }
@@ -0,0 +1,130 @@
1
+ import { WardenServiceOptions } from "./warden-service-options.js";
2
+ import { WardenContact } from "@bitblit/ratchet-warden-common/common/model/warden-contact";
3
+ import { WardenEntry } from "@bitblit/ratchet-warden-common/common/model/warden-entry";
4
+ import { WardenUtils } from "@bitblit/ratchet-warden-common/common/util/warden-utils";
5
+ import { WardenUserDecoration } from "@bitblit/ratchet-warden-common/common/model/warden-user-decoration";
6
+ import { WardenJwtToken } from "@bitblit/ratchet-warden-common/common/model/warden-jwt-token";
7
+
8
+ export class WardenAuthorizer {
9
+
10
+ constructor(private opts: WardenServiceOptions) {
11
+ }
12
+
13
+ // Passthru for very common use case
14
+ public findEntryByContact(contact: WardenContact): Promise<WardenEntry> {
15
+ return this.opts.storageProvider.findEntryByContact(contact);
16
+ }
17
+
18
+ // Passthru for very common use case
19
+ public findEntryById(userId: string): Promise<WardenEntry> {
20
+ return this.opts.storageProvider.findEntryById(userId);
21
+ }
22
+
23
+ public async findDecoratorById(userId: string): Promise<WardenUserDecoration<any>> {
24
+ const user: WardenEntry = await this.findEntryById(userId);
25
+ const rval: WardenUserDecoration<any> = user ? await this.opts.userDecorationProvider.fetchDecoration(user) : null;
26
+ return rval;
27
+ }
28
+
29
+
30
+ public async userHasGlobalRoleById(userId: string, roleId: string): Promise<boolean> {
31
+ return this.userHasGlobalRole(await this.findDecoratorById(userId), roleId);
32
+ }
33
+
34
+ public async userHasRoleOnTeamById(userId: string,teamId: string, roleId: string): Promise<boolean> {
35
+ return this.userHasRolesOnTeam(await this.findDecoratorById(userId), teamId, [roleId], true);
36
+ }
37
+
38
+ public async userHasAtLeastOneGlobalRoleById(userId: string, roleIds: string[]): Promise<boolean> {
39
+ return this.userHasGlobalRoles(await this.findDecoratorById(userId), roleIds, false);
40
+ }
41
+
42
+ public async userHasAtLeastOneRoleOnTeamById(userId: string, teamId: string, roleIds: string[]): Promise<boolean> {
43
+ return this.userHasRolesOnTeam(await this.findDecoratorById(userId), teamId, roleIds, false);
44
+ }
45
+
46
+
47
+ public async userHasAllGlobalRolesById(userId: string, roleIds: string[]): Promise<boolean> {
48
+ return this.userHasGlobalRoles(await this.findDecoratorById(userId), roleIds, true);
49
+ }
50
+
51
+ public async userHasAllRolesOnTeamById(userId: string, teamId: string, roleIds: string[]): Promise<boolean> {
52
+ return this.userHasRolesOnTeam(await this.findDecoratorById(userId), teamId, roleIds, true);
53
+ }
54
+
55
+
56
+ public async userHasGlobalRolesById(userId: string, roleIds: string[], combineWithAnd: boolean): Promise<boolean> {
57
+ return WardenUtils.userHasGlobalRoles(await this.findDecoratorById(userId), roleIds, combineWithAnd);
58
+ }
59
+
60
+ public async userHasRolesOnTeamById(userId: string, teamId: string, roleIds: string[], combineWithAnd: boolean): Promise<boolean> {
61
+ return WardenUtils.userHasRolesOnTeam(await this.findDecoratorById(userId), teamId, roleIds, combineWithAnd);
62
+ }
63
+
64
+ // Just a synonym since that is how some people think
65
+ public async userIsTeamMemberById(userId: string, teamId: string): Promise<boolean> {
66
+ return WardenUtils.userIsTeamMember(await this.findDecoratorById(userId),teamId);
67
+ }
68
+
69
+ public async userHasAnyRoleOnTeamById(userId: string, teamId: string): Promise<boolean> {
70
+ return WardenUtils.userHasAnyRoleOnTeam(await this.findDecoratorById(userId),teamId);
71
+ }
72
+
73
+ public async usersTeamMembershipsById(userId: string): Promise<string[]> {
74
+ return WardenUtils.usersTeamMemberships(await this.findDecoratorById(userId));
75
+ }
76
+
77
+
78
+ // Simple passthrus for convenience
79
+ public userHasGlobalRole(user: WardenUserDecoration<any>, roleId: string): boolean {
80
+ return WardenUtils.userHasGlobalRoles(user, [roleId], true);
81
+ }
82
+
83
+ public userHasRoleOnTeam(user: WardenUserDecoration<any>,teamId: string, roleId: string): boolean {
84
+ return WardenUtils.userHasRolesOnTeam(user, teamId, [roleId], true);
85
+ }
86
+
87
+ public userHasAtLeastOneGlobalRole(user: WardenUserDecoration<any>, roleIds: string[]): boolean {
88
+ return WardenUtils.userHasGlobalRoles(user, roleIds, false);
89
+ }
90
+
91
+ public userHasAtLeastOneRoleOnTeam(user: WardenUserDecoration<any>, teamId: string, roleIds: string[]): boolean {
92
+ return WardenUtils.userHasRolesOnTeam(user, teamId, roleIds, false);
93
+ }
94
+
95
+
96
+ public userHasAllGlobalRoles(user: WardenUserDecoration<any>, roleIds: string[]): boolean {
97
+ return WardenUtils.userHasGlobalRoles(user, roleIds, true);
98
+ }
99
+
100
+ public userHasAllRolesOnTeam(user: WardenUserDecoration<any>, teamId: string, roleIds: string[]): boolean {
101
+ return WardenUtils.userHasRolesOnTeam(user, teamId, roleIds, true);
102
+ }
103
+
104
+
105
+ public userHasGlobalRoles(user: WardenUserDecoration<any>, roleIds: string[], combineWithAnd: boolean): boolean {
106
+ return WardenUtils.userHasGlobalRoles(user, roleIds, combineWithAnd);
107
+ }
108
+
109
+ public userHasRolesOnTeam(user: WardenUserDecoration<any>, teamId: string, roleIds: string[], combineWithAnd: boolean): boolean {
110
+ return WardenUtils.userHasRolesOnTeam(user, teamId, roleIds, combineWithAnd);
111
+ }
112
+
113
+ public wardenUserDecorationFromToken(jwt: WardenJwtToken<any>): WardenUserDecoration<any> {
114
+ return WardenUtils.wardenUserDecorationFromToken(jwt);
115
+ }
116
+
117
+ // Just a synonym since that is how some people think
118
+ public userIsTeamMember(user: WardenUserDecoration<any>, teamId: string): boolean {
119
+ return WardenUtils.userIsTeamMember(user,teamId);
120
+ }
121
+
122
+ public userHasAnyRoleOnTeam(user: WardenUserDecoration<any>, teamId: string): boolean {
123
+ return WardenUtils.userHasAnyRoleOnTeam(user,teamId);
124
+ }
125
+
126
+ public usersTeamMemberships(user: WardenUserDecoration<any>): string[] {
127
+ return WardenUtils.usersTeamMemberships(user);
128
+ }
129
+
130
+ }
@@ -0,0 +1,56 @@
1
+ import { WardenEntry } from "@bitblit/ratchet-warden-common/common/model/warden-entry";
2
+ import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
3
+ import { WardenContact } from "@bitblit/ratchet-warden-common/common/model/warden-contact";
4
+ import {
5
+ WardenThirdPartyAuthentication
6
+ } from "@bitblit/ratchet-warden-common/common/model/warden-third-party-authentication";
7
+
8
+ export class WardenEntryBuilder {
9
+
10
+ private readonly _entry: WardenEntry;
11
+
12
+ constructor(label?: string ) {
13
+ const guid: string = StringRatchet.createShortUid();
14
+ const now: number = Date.now();
15
+ this._entry = {
16
+ userId: guid,
17
+ userLabel: label || 'User '+guid,
18
+ contactMethods: [],
19
+ tags: [],
20
+ webAuthnAuthenticators: [],
21
+ thirdPartyAuthenticators: [],
22
+ createdEpochMS: now,
23
+ updatedEpochMS: now,
24
+ };
25
+ }
26
+
27
+ public withTags(tags: string[]): WardenEntryBuilder {
28
+ this._entry.tags = tags ?? [];
29
+ return this;
30
+ }
31
+
32
+ public withThirdPartyAuthentication(thirdPartyAuthenticators: [WardenThirdPartyAuthentication]): WardenEntryBuilder {
33
+ this._entry.thirdPartyAuthenticators = thirdPartyAuthenticators ?? [];
34
+ return this;
35
+ }
36
+
37
+ public withLabel(label: string): WardenEntryBuilder {
38
+ this._entry.userLabel = label;
39
+ return this;
40
+ }
41
+
42
+ public withLabelFromContact(contact: WardenContact): WardenEntryBuilder {
43
+ this._entry.userLabel = contact.value;
44
+ return this;
45
+ }
46
+
47
+ public withContacts(contacts: [WardenContact]): WardenEntryBuilder {
48
+ this._entry.contactMethods = contacts ?? [];
49
+ return this;
50
+ }
51
+
52
+ public get entry(): WardenEntry {
53
+ return this._entry;
54
+ }
55
+
56
+ }
@@ -0,0 +1,20 @@
1
+ import { WardenStorageProvider } from './provider/warden-storage-provider.js';
2
+ import { JwtRatchetLike } from '@bitblit/ratchet-node-only/jwt/jwt-ratchet-like';
3
+ import { WardenUserDecorationProvider } from './provider/warden-user-decoration-provider.js';
4
+ import { WardenEventProcessingProvider } from './provider/warden-event-processing-provider.js';
5
+ import { WardenSingleUseCodeProvider } from './provider/warden-single-use-code-provider.js';
6
+ import { WardenSendMagicLinkCommandValidator } from './provider/warden-send-magic-link-command-validator.js';
7
+ import { WardenThirdPartyAuthenticationProvider } from "./provider/warden-third-party-authentication-provider.js";
8
+
9
+ export interface WardenServiceOptions {
10
+ // Human-readable title for your website
11
+ relyingPartyName: string;
12
+ allowedOrigins: string[];
13
+ singleUseCodeProviders: WardenSingleUseCodeProvider[];
14
+ storageProvider: WardenStorageProvider;
15
+ jwtRatchet: JwtRatchetLike;
16
+ userDecorationProvider?: WardenUserDecorationProvider<any>;
17
+ eventProcessor?: WardenEventProcessingProvider;
18
+ sendMagicLinkCommandValidator?: WardenSendMagicLinkCommandValidator;
19
+ thirdPartyAuthenticationProviders?: WardenThirdPartyAuthenticationProvider[];
20
+ }
@@ -0,0 +1,102 @@
1
+ import { WardenStorageProvider } from "./provider/warden-storage-provider.js";
2
+
3
+ import { WardenService } from "./warden-service.js";
4
+ import { WardenServiceOptions } from "./warden-service-options.js";
5
+ import { WardenContactType } from "@bitblit/ratchet-warden-common/common/model/warden-contact-type";
6
+ import { WardenEntry } from "@bitblit/ratchet-warden-common/common/model/warden-entry";
7
+ import { WardenLoginRequest } from "@bitblit/ratchet-warden-common/common/model/warden-login-request";
8
+ import { WardenCommand } from "@bitblit/ratchet-warden-common/common/command/warden-command";
9
+ import { WardenCommandResponse } from "@bitblit/ratchet-warden-common/common/command/warden-command-response";
10
+ import { WardenLoginResults } from "@bitblit/ratchet-warden-common/common/model/warden-login-results";
11
+
12
+ import { JwtRatchet } from "@bitblit/ratchet-node-only/jwt/jwt-ratchet";
13
+
14
+ import { WardenUserDecorationProvider } from "./provider/warden-user-decoration-provider.js";
15
+ import { WardenSingleUseCodeProvider } from "./provider/warden-single-use-code-provider.js";
16
+ import { beforeEach, describe, expect, test } from "vitest";
17
+ import { mock, MockProxy } from "vitest-mock-extended";
18
+ import { WardenLoginRequestType } from "@bitblit/ratchet-warden-common/common/model/warden-login-request-type";
19
+
20
+ //let mockJwtRatchet: MockProxy<JwtRatchetLike>;
21
+ let mockWardenStorageProvider: MockProxy<WardenStorageProvider>;
22
+ let mockWardenSingleUseCodeProvider: MockProxy<WardenSingleUseCodeProvider>;
23
+ let mockUserDecorationProvider: MockProxy<WardenUserDecorationProvider<any>>;
24
+ let opts: WardenServiceOptions;
25
+
26
+ describe('#WardenService', () => {
27
+ beforeEach(() => {
28
+ //mockJwtRatchet = mock<JwtRatchetLike>();;
29
+ mockWardenStorageProvider = mock<WardenStorageProvider>();
30
+ mockWardenSingleUseCodeProvider = mock<WardenSingleUseCodeProvider>();
31
+ mockUserDecorationProvider = mock<WardenUserDecorationProvider<string>>();
32
+
33
+ opts = {
34
+ // Human-readable title for your website
35
+ relyingPartyName: 'rp',
36
+ allowedOrigins: ['origin'],
37
+
38
+ storageProvider: mockWardenStorageProvider,
39
+ singleUseCodeProviders: [mockWardenSingleUseCodeProvider],
40
+ jwtRatchet: new JwtRatchet({ encryptionKeyPromise: Promise.resolve('asdf') }), //mockJwtRatchet,
41
+ userDecorationProvider: mockUserDecorationProvider,
42
+ eventProcessor: undefined,
43
+ };
44
+ });
45
+
46
+ test('Should login', async () => {
47
+ const svc: WardenService = new WardenService(opts);
48
+
49
+ const loginReq: WardenLoginRequest = {
50
+ type: WardenLoginRequestType.ExpiringToken,
51
+ //userId: string;
52
+ contact: { type: WardenContactType.EmailAddress, value: 'test@test.com' },
53
+ //webAuthn?: AuthenticationResponseJSON;
54
+ expiringToken: '12345',
55
+ //jwtTokenToRefresh?: string;
56
+ };
57
+ const cmd: WardenCommand = {
58
+ performLogin: loginReq,
59
+ };
60
+
61
+ const origin: string = 'localhost';
62
+ mockWardenStorageProvider.findEntryByContact.mockResolvedValue({
63
+ userId: '12345',
64
+ userLabel: 'Test User',
65
+ contactMethods: [{ type: WardenContactType.EmailAddress, value: 'test@test.com' }],
66
+ tags: ['test'],
67
+ webAuthnAuthenticators: [],
68
+ thirdPartyAuthenticators: [],
69
+ createdEpochMS: 1234,
70
+ updatedEpochMS: 1235,
71
+ });
72
+ mockUserDecorationProvider.fetchDecoration.mockResolvedValue({
73
+ userTokenData: { a: 'b', c: 1 },
74
+ proxyUserTokenData: null,
75
+ userTokenExpirationSeconds: 3600,
76
+ teamRoleMappings: [{ teamId: 'WARDEN', roleId: 'USER' }],
77
+ globalRoleIds: [],
78
+ });
79
+ mockWardenSingleUseCodeProvider.handlesContactType.mockReturnValue(true);
80
+ mockWardenSingleUseCodeProvider.checkCode.mockResolvedValue(true);
81
+
82
+ const out: WardenCommandResponse = await svc.processCommandToResponse(cmd, origin, null);
83
+ const loginResults: WardenLoginResults = out.performLogin;
84
+ //const parsedJwt: any = opts.jwtRatchet.decodeToken(loginResults.jwtToken);
85
+
86
+ //Logger.info('LOGIN: %j : JWT: %j', loginResults, parsedJwt);
87
+ expect(out).toBeTruthy();
88
+ expect(loginResults).toBeTruthy();
89
+ });
90
+
91
+ // Need to implement
92
+ test('Should create and account', async () => {
93
+ const svc: WardenService = new WardenService(opts);
94
+
95
+ mockWardenStorageProvider.findEntryByContact.mockResolvedValue(null);
96
+ mockWardenStorageProvider.saveEntry.mockResolvedValue({ userId: 'test' } as WardenEntry);
97
+ mockWardenSingleUseCodeProvider.handlesContactType.mockReturnValue(true);
98
+
99
+ const res: WardenEntry = await svc.createAccount({ type: WardenContactType.EmailAddress, value: 'test@test.com' }, 'testorigin.com',false, 'Test', []);
100
+ expect(res.userId).toEqual('test');
101
+ });
102
+ });