@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.
- package/package.json +9 -8
- package/src/build/ratchet-warden-server-info.ts +19 -0
- package/src/server/provider/warden-default-send-magic-link-command-validator.ts +21 -0
- package/src/server/provider/warden-default-user-decoration-provider.ts +23 -0
- package/src/server/provider/warden-dynamo-storage-provider-options.ts +8 -0
- package/src/server/provider/warden-dynamo-storage-provider.ts +278 -0
- package/src/server/provider/warden-event-processing-provider.ts +10 -0
- package/src/server/provider/warden-mailer-and-expiring-code-ratchet-single-use-code-provider.ts +155 -0
- package/src/server/provider/warden-mailer-and-expiring-code-ratchet-single-user-provider-options.ts +10 -0
- package/src/server/provider/warden-message-sending-provider.ts +14 -0
- package/src/server/provider/warden-no-op-event-processing-provider.ts +12 -0
- package/src/server/provider/warden-s3-single-file-storage-provider-options.ts +6 -0
- package/src/server/provider/warden-s3-single-file-storage-provider.ts +139 -0
- package/src/server/provider/warden-send-magic-link-command-validator.ts +12 -0
- package/src/server/provider/warden-single-use-code-provider.ts +27 -0
- package/src/server/provider/warden-storage-provider.ts +22 -0
- package/src/server/provider/warden-third-party-authentication-provider.ts +18 -0
- package/src/server/provider/warden-twilio-verify-single-use-code-provider-options.ts +5 -0
- package/src/server/provider/warden-twilio-verify-single-use-code-provider.ts +38 -0
- package/src/server/provider/warden-user-decoration-provider.ts +10 -0
- package/src/server/warden-authorizer.ts +130 -0
- package/src/server/warden-entry-builder.ts +56 -0
- package/src/server/warden-service-options.ts +20 -0
- package/src/server/warden-service.spec.ts +102 -0
- package/src/server/warden-service.ts +815 -0
- 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,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
|
+
});
|