Skip to content

Commit

Permalink
implements isValidCredentialId; throws error from getUserCredentialRe…
Browse files Browse the repository at this point in the history
…cordById for untracked ids; implements getCredentialInfo as alias for getUserCredentialRecordById; renames allocateAllStatuses to allocateSupportedStatuses
  • Loading branch information
kezike committed Mar 16, 2024
1 parent f9cc374 commit 0ed69e1
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 22 deletions.
47 changes: 27 additions & 20 deletions src/credential-status-manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
} from './errors.js';
import {
DidMethod,
MAX_ID_LENGTH,
getCredentialSubjectObject,
getDateString,
getSigningMaterial,
isValidCredentialId,
signCredential,
validateCredential
} from './helpers.js';
Expand Down Expand Up @@ -350,6 +352,13 @@ export abstract class BaseCredentialStatusManager {
// Note: This assumes that uuid will never generate an ID that
// conflicts with an ID that has already been tracked in the event log
credentialCopy.id = this.generateUserCredentialId();
} else {
if(!isValidCredentialId(credentialCopy.id)) {
throw new BadRequestError({
message: 'The ID must be a URL, UUID, or DID ' +
`that is no more than ${MAX_ID_LENGTH} characters in length.`
});
}
}

// validate credential before attaching status
Expand All @@ -365,7 +374,10 @@ export abstract class BaseCredentialStatusManager {
// only search for credential if it was passed with an ID
if (credentialContainsId) {
// retrieve record for credential with given ID
const credentialRecord = await this.getUserCredentialRecordById(credentialCopy.id, options);
let credentialRecord;
try {
credentialRecord = await this.getUserCredentialRecordById(credentialCopy.id, options);
} catch (error) {}

// do not allocate new entry if ID is already being tracked
if (credentialRecord) {
Expand Down Expand Up @@ -583,7 +595,7 @@ export abstract class BaseCredentialStatusManager {
}

// allocates all supported statuses
async allocateAllStatuses(credential: VerifiableCredential): Promise<VerifiableCredential> {
async allocateSupportedStatuses(credential: VerifiableCredential): Promise<VerifiableCredential> {
return this.allocateStatus({ credential, statusPurposes: SUPPORTED_STATUS_PURPOSES });
}

Expand All @@ -595,13 +607,6 @@ export abstract class BaseCredentialStatusManager {
// retrieve record for credential with given ID
const oldCredentialRecord = await this.getUserCredentialRecordById(credentialId, options);

// unable to find credential with given ID
if (!oldCredentialRecord) {
throw new NotFoundError({
message: `Unable to find credential with ID "${credentialId}".`
});
}

// retrieve relevant credential info
const { statusInfo, ...oldCredentialRecordRest } = oldCredentialRecord;

Expand Down Expand Up @@ -766,14 +771,6 @@ export abstract class BaseCredentialStatusManager {
async getStatus(credentialId: string, options?: DatabaseConnectionOptions): Promise<CredentialStatusInfo> {
// retrieve user credential record
const record = await this.getUserCredentialRecordById(credentialId, options);

// unable to find credential with given ID
if (!record) {
throw new NotFoundError({
message: `Unable to find credential with ID "${credentialId}".`
});
}

return record.statusInfo;
}

Expand Down Expand Up @@ -1269,19 +1266,29 @@ export abstract class BaseCredentialStatusManager {
}

// retrieves user credential record by ID
async getUserCredentialRecordById(userCredentialId: string, options?: DatabaseConnectionOptions): Promise<UserCredentialRecord | null> {
async getUserCredentialRecordById(userCredentialId: string, options?: DatabaseConnectionOptions): Promise<UserCredentialRecord> {
let record;
try {
record = await this.getRecordById(this.userCredentialTableName, userCredentialId, options);
if (!record) {
throw new NotFoundError({
message: `Unable to find credential with ID "${userCredentialId}".`
});
}
} catch (error: any) {
if (error instanceof CustomError) {
throw error;
}
throw new InternalServerError({
message: `Unable to get user credential with ID "${userCredentialId}": ${error.message}`
message: `Unable to get info for credential with ID "${userCredentialId}": ${error.message}`
});
}
return record as UserCredentialRecord | null;
return record as UserCredentialRecord;
}

// alias for getUserCredentialRecordById
async getCredentialInfo(userCredentialId: string, options?: DatabaseConnectionOptions): Promise<UserCredentialRecord> {
return this.getUserCredentialRecordById(userCredentialId, options);
}

// retrieves config record by ID
Expand Down
11 changes: 11 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*!
* Copyright (c) 2024 Digital Credentials Consortium. All rights reserved.
*/
import * as uuid from 'uuid';
import * as vc1Context from 'credentials-context';
import * as vc2Context from '@digitalbazaar/credentials-v2-context';
import * as vcBitstringStatusListContext from '@digitalbazaar/vc-bitstring-status-list-context';
Expand Down Expand Up @@ -28,6 +29,9 @@ const didKeyDriver = DidKey.driver();
// Document loader
const documentLoader = securityLoader().build();

// Max length for IDs
export const MAX_ID_LENGTH = 64;

// DID method used to sign credentials
export enum DidMethod {
Key = 'key',
Expand Down Expand Up @@ -192,6 +196,13 @@ function extractId(objectOrString: any): string {
return objectOrString.id;
}

// determines if credential ID is valid
export function isValidCredentialId(credentialId: string): boolean {
const isValidFormat = URL.canParse(credentialId) || uuid.validate(credentialId);
const isValidLength = credentialId.length <= MAX_ID_LENGTH;
return isValidFormat && isValidLength;
}

// retrieves current timestamp
export function getDateString(): string {
return (new Date()).toISOString();
Expand Down
4 changes: 2 additions & 2 deletions test/credential-status-manager-mongodb.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ describe('MongoDB Credential Status Manager', () => {
expect(databaseState.valid).to.be.true;
});

it('tests allocateRevocationStatus and getUserCredentialRecordById', async () => {
it('tests allocateRevocationStatus and getCredentialInfo', async () => {
// allocate status for credential
const credentialWithStatus = await statusManager.allocateRevocationStatus(unsignedCredential1) as any;

// check user credential info
const credentialRecord = await statusManager.getUserCredentialRecordById(credentialWithStatus.id);
const credentialRecord = await statusManager.getCredentialInfo(credentialWithStatus.id);
checkUserCredentialInfo(credentialWithStatus.id, credentialRecord, 1, true);

// check if database has valid configuration
Expand Down

0 comments on commit 0ed69e1

Please sign in to comment.