@bitblit/ratchet-warden-server
Version:
Typescript library to simplify using simplewebauthn and secondary auth methods over GraphQL
214 lines • 8.66 kB
JavaScript
import { Logger } from '@bitblit/ratchet-common/logger/logger';
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
import { WardenUtils } from '@bitblit/ratchet-warden-common/common/util/warden-utils';
export class WardenDynamoStorageProvider {
ddb;
options;
static EXPIRING_CODE_PROVIDER_KEY = '__EXPIRING_CODE_DATA';
constructor(ddb, options) {
this.ddb = ddb;
this.options = options;
}
createDecoration(decoration) {
return {
globalRoleIds: [],
teamRoleMappings: this.options.defaultTeamRoleMappings,
userTokenExpirationSeconds: this.options.defaultTokenExpirationSeconds,
userTokenData: decoration,
proxyUserTokenData: null
};
}
async updateRoles(userId, roles) {
const rval = await this.fetchInternalByUserId(userId);
if (rval) {
rval.decoration ??= this.createDecoration(null);
rval.decoration.teamRoleMappings = roles;
await this.updateInternal(rval);
}
else {
throw ErrorRatchet.fErr('Cannot update roles - no entry found for %s', userId);
}
return this.fetchDecorationById(userId);
}
async updateTokenExpirationSeconds(userId, newValue) {
const rval = await this.fetchInternalByUserId(userId);
if (rval) {
rval.decoration ??= this.createDecoration(null);
rval.decoration.userTokenExpirationSeconds = newValue;
await this.updateInternal(rval);
}
else {
throw ErrorRatchet.fErr('Cannot update roles - no entry found for %s', userId);
}
return this.fetchDecorationById(userId);
}
async updateDecoration(userId, decoration) {
const rval = await this.fetchInternalByUserId(userId);
if (rval) {
rval.decoration ??= this.createDecoration(null);
rval.decoration.userTokenData = decoration;
await this.updateInternal(rval);
}
else {
throw ErrorRatchet.fErr('Cannot update roles - no entry found for %s', userId);
}
return this.fetchDecorationById(userId);
}
async fetchDecoration(wardenUser) {
return this.fetchDecorationById(wardenUser.userId);
}
async fetchDecorationById(userId) {
const rval = await this.fetchInternalByUserId(userId);
return rval?.decoration;
}
async fetchExpiringCodes() {
let rval = await this.ddb.simpleGet(this.options.tableName, {
userId: WardenDynamoStorageProvider.EXPIRING_CODE_PROVIDER_KEY
});
if (!rval) {
rval = {
userId: WardenDynamoStorageProvider.EXPIRING_CODE_PROVIDER_KEY,
values: []
};
await this.updateInternal(rval);
}
return rval;
}
async updateInternal(val) {
return this.ddb.simplePut(this.options.tableName, val);
}
async checkCode(code, context, deleteOnMatch) {
const codes = await this.fetchExpiringCodes();
const rval = codes.values.find((c) => c.code === code && c.context === context);
if (rval) {
if (deleteOnMatch) {
codes.values = codes.values.filter((c) => c.code !== code || c.context !== context);
await this.updateInternal(codes);
}
return true;
}
else {
return !!rval;
}
}
async storeCode(code) {
const codes = await this.fetchExpiringCodes();
codes.values.push(code);
const now = Date.now();
codes.values = codes.values.filter(c => c.expiresEpochMS > now);
const stored = await this.updateInternal(codes);
return stored ? true : false;
}
static contactToSearchString(contact) {
const toFind = `${contact.type}:${contact.value}`;
return toFind;
}
static thirdPartyToSearchString(thirdParty, thirdPartyId) {
const toFind = `${thirdParty}:${thirdPartyId}`;
return toFind;
}
async fetchInternalByUserId(userId) {
return this.ddb.simpleGet(this.options.tableName, { userId: userId });
}
async fetchCurrentUserChallenge(userId, relyingPartyId) {
const rval = await this.fetchInternalByUserId(userId);
const cuc = rval ? rval.currentUserChallenges.find((c) => c.startsWith(relyingPartyId)) : null;
return cuc ? cuc.substring(relyingPartyId.length + 1) : null;
}
async findEntryByThirdPartyId(thirdParty, thirdPartyId) {
const toFind = WardenDynamoStorageProvider.thirdPartyToSearchString(thirdParty, thirdPartyId);
const scan = {
TableName: this.options.tableName,
FilterExpression: 'contains(#thirdPartySearchString,:thirdPartySearchString)',
ExpressionAttributeNames: {
'#thirdPartySearchString': 'thirdPartySearchString',
},
ExpressionAttributeValues: {
':thirdPartySearchString': toFind
}
};
const results = await this.ddb.fullyExecuteScan(scan);
if (results && results.length > 0) {
const rval = results[0];
return rval.entry;
}
else {
Logger.info('No results found for %s', toFind);
return null;
}
}
async findEntryByContact(contact) {
const toFind = WardenDynamoStorageProvider.contactToSearchString(contact);
const scan = {
TableName: this.options.tableName,
FilterExpression: 'contains(#contactSearchString,:contactSearchString)',
ExpressionAttributeNames: {
'#contactSearchString': 'contactSearchString',
},
ExpressionAttributeValues: {
':contactSearchString': toFind
}
};
const results = await this.ddb.fullyExecuteScan(scan);
if (results && results.length > 0) {
const rval = results[0];
return rval.entry;
}
else {
Logger.info('No results found for %s', toFind);
return null;
}
}
async findEntryById(userId) {
const rval = await this.fetchInternalByUserId(userId);
return rval ? rval.entry : null;
}
async listUserSummaries() {
const scan = {
TableName: this.options.tableName,
};
const results = await this.ddb.fullyExecuteScan(scan);
const rval = results.map(wd => {
return WardenUtils.stripWardenEntryToSummary(wd.entry);
});
return rval;
}
async removeEntry(userId) {
const tmp = await this.ddb.simpleDelete(this.options.tableName, { userId: userId });
return tmp.Attributes ? true : false;
}
async saveEntry(entry) {
let rval = await this.fetchInternalByUserId(entry.userId);
if (!rval) {
rval = {
userId: entry.userId,
entry: entry,
currentUserChallenges: [],
decoration: this.createDecoration(null),
contactSearchString: 'ContactSearch: ' + (entry.contactMethods || []).map((cm) => WardenDynamoStorageProvider.contactToSearchString(cm)).join(' '),
thirdPartySearchString: '3rdPartySearch: ' + (entry.thirdPartyAuthenticators || []).map((item) => WardenDynamoStorageProvider.thirdPartyToSearchString(item.thirdParty, item.thirdPartyId)).join(' '),
};
}
rval.entry = entry;
const now = Date.now();
rval.entry.updatedEpochMS = now;
rval.entry.createdEpochMS = rval.entry.createdEpochMS || now;
const saved = await this.updateInternal(rval);
Logger.silly('Saved %j', saved);
const postSaveLookup = await this.fetchInternalByUserId(entry.userId);
return postSaveLookup.entry;
}
async updateUserChallenge(userId, relyingPartyId, challenge) {
const rval = await this.fetchInternalByUserId(userId);
if (!rval) {
throw ErrorRatchet.fErr('Cannot update user challenge - no entry found for %s', userId);
}
rval.currentUserChallenges = (rval.currentUserChallenges || []).filter((c) => !c.startsWith(relyingPartyId));
const cuc = relyingPartyId + ':' + challenge;
rval.currentUserChallenges.push(cuc);
const saved = await this.updateInternal(rval);
Logger.silly('Saved %j', saved);
return saved.Attributes ? true : false;
}
}
//# sourceMappingURL=warden-dynamo-storage-provider.js.map