UNPKG

@bitblit/ratchet-warden-server

Version:

Typescript library to simplify using simplewebauthn and secondary auth methods over GraphQL

214 lines 8.66 kB
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