Skip to content

Commit

Permalink
Merge branch 'convert-status-list-index-from-number-to-string' into i…
Browse files Browse the repository at this point in the history
…mprove-misleading-access-token-error-message
  • Loading branch information
kezike committed Dec 13, 2023
2 parents 5bd432c + 7b0b213 commit cd0c9a9
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 132 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"prettier": "prettier src --write",
"rebuild": "npm run clear && npm run build",
"test": "npm run lint && npm run test-node",
"test-karma": "node pre-test.js; npm run build-test && karma start karma.conf.js && rm -rf dist/test; node post-test.js",
"test-node": "node pre-test.js; npm run build-test && mocha dist/test/*.spec.js && rm -rf dist/test; node post-test.js"
"test-karma": "npm run build-test && karma start karma.conf.js && rm -rf dist/test",
"test-node": "npm run build-test && mocha dist/test/*.spec.js && rm -rf dist/test"
},
"files": [
"dist",
Expand Down
4 changes: 2 additions & 2 deletions post-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const updateTsconfig = async () => {
fs.writeFileSync(tsconfigFilePath, JSON.stringify(tsconfigJson, null, 2));
};

// combine pre-test subscripts
// combine post-test subscripts
const runPostTest = async () => {
await updatePackageJson();
updateTsconfig();
};

// run pre-test subscripts
// run post-test subscripts
runPostTest();
67 changes: 44 additions & 23 deletions src/credential-status-manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { VerifiableCredential } from '@digitalcredentials/vc-data-model';
import { createCredential, createList, decodeList } from '@digitalcredentials/vc-status-list';
import { Mutex } from 'async-mutex';
import { v4 as uuid } from 'uuid';
import { StatusRepoInconsistencyError } from './errors.js';
import {
BadRequestError,
InconsistentRepositoryError,
NotFoundError,
SnapshotExistsError
} from './errors.js';
import {
DidMethod,
deriveStatusCredentialId,
Expand All @@ -30,8 +35,8 @@ export const CREDENTIAL_STATUS_SNAPSHOT_FILE = 'snapshot.json';

// Credential status manager source control service
export enum CredentialStatusManagerService {
Github = 'github',
Gitlab = 'gitlab'
GitHub = 'github',
GitLab = 'gitlab'
}

// States of credential resulting from caller actions and tracked in status log
Expand Down Expand Up @@ -178,6 +183,11 @@ export abstract class BaseCredentialStatusManager {
credential.id = uuid();
}

// ensure that credential contains the proper status credential context
if (!credential['@context'].includes(CONTEXT_URL_V1)) {
credential['@context'].push(CONTEXT_URL_V1);
}

// retrieve status config data
let {
latestStatusCredentialId,
Expand Down Expand Up @@ -213,8 +223,7 @@ export abstract class BaseCredentialStatusManager {
return {
credential: {
...credential,
credentialStatus,
'@context': [...credential['@context'], CONTEXT_URL_V1]
credentialStatus
},
newStatusCredential: false,
latestStatusCredentialId,
Expand All @@ -228,8 +237,8 @@ export abstract class BaseCredentialStatusManager {
let newStatusCredential = false;
if (latestCredentialsIssuedCounter >= CREDENTIAL_STATUS_LIST_SIZE) {
newStatusCredential = true;
latestStatusCredentialId = this.generateStatusCredentialId();
latestCredentialsIssuedCounter = 0;
latestStatusCredentialId = this.generateStatusCredentialId();
statusCredentialIds.push(latestStatusCredentialId);
}
latestCredentialsIssuedCounter++;
Expand All @@ -250,8 +259,7 @@ export abstract class BaseCredentialStatusManager {
return {
credential: {
...credential,
credentialStatus,
'@context': [...credential['@context'], CONTEXT_URL_V1]
credentialStatus
},
newStatusCredential,
latestStatusCredentialId,
Expand All @@ -265,7 +273,9 @@ export abstract class BaseCredentialStatusManager {
async allocateStatusUnsafe(credential: VerifiableCredential): Promise<VerifiableCredential> {
// report error for compact JWT credentials
if (typeof credential === 'string') {
throw new Error('This library does not support compact JWT credentials.');
throw new BadRequestError({
message: 'This library does not support compact JWT credentials.'
});
}

// attach status to credential
Expand Down Expand Up @@ -369,7 +379,7 @@ export abstract class BaseCredentialStatusManager {
const result = await this.allocateStatusUnsafe(credential);
return result;
} catch(error) {
if (!(error instanceof StatusRepoInconsistencyError)) {
if (!(error instanceof InconsistentRepositoryError)) {
return this.allocateStatus(credential);
} else {
throw error;
Expand All @@ -395,7 +405,9 @@ export abstract class BaseCredentialStatusManager {

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

// retrieve relevant log data
Expand Down Expand Up @@ -426,7 +438,9 @@ export abstract class BaseCredentialStatusManager {

// report error for compact JWT credentials
if (typeof statusCredentialBefore === 'string') {
throw new Error('This library does not support compact JWT credentials.');
throw new BadRequestError({
message: 'This library does not support compact JWT credentials.'
});
}

// update status credential
Expand All @@ -442,10 +456,11 @@ export abstract class BaseCredentialStatusManager {
statusCredentialListDecoded.setStatus(credentialStatusIndex, true); // revoked credential is represented as 1 bit
break;
default:
throw new Error(
'"credentialStatus" must be one of the following values: ' +
`${Object.values(CredentialState).map(v => `'${v}'`).join(', ')}.`
);
throw new BadRequestError({
message:
'"credentialStatus" must be one of the following values: ' +
`${Object.values(CredentialState).map(s => `"${s}"`).join(', ')}.`
});
}
const statusCredentialUrlBase = this.getStatusCredentialUrlBase();
const statusCredentialUrl = `${statusCredentialUrlBase}/${statusCredentialId}`;
Expand Down Expand Up @@ -497,7 +512,7 @@ export abstract class BaseCredentialStatusManager {
const result = await this.updateStatusUnsafe({ credentialId, credentialStatus });
return result;
} catch(error) {
if (!(error instanceof StatusRepoInconsistencyError)) {
if (!(error instanceof InconsistentRepositoryError)) {
return this.updateStatus({
credentialId,
credentialStatus
Expand All @@ -511,7 +526,7 @@ export abstract class BaseCredentialStatusManager {
}
}

// checks status of credential
// checks status of credential with given ID
async checkStatus(credentialId: string): Promise<CredentialStatusLogEntry> {
// find latest relevant log entry for credential with given ID
const { eventLog } = await this.readConfigData();
Expand All @@ -522,7 +537,9 @@ export abstract class BaseCredentialStatusManager {

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

return logEntry;
Expand Down Expand Up @@ -608,7 +625,10 @@ export abstract class BaseCredentialStatusManager {
const credentialIdsUnique = credentialIds.filter((value, index, array) => {
return array.indexOf(value) === index;
});
const hasProperLogEntries = credentialIdsUnique.length === latestCredentialsIssuedCounter;
const hasProperLogEntries = credentialIdsUnique.length ===
(statusCredentialIds.length - 1) *
CREDENTIAL_STATUS_LIST_SIZE +
latestCredentialsIssuedCounter;

// ensure that all checks pass
return hasProperLogDataType && hasProperLogEntries;
Expand Down Expand Up @@ -667,7 +687,7 @@ export abstract class BaseCredentialStatusManager {
// ensure that snapshot data does not exist
const snapshotExists = await this.snapshotDataExists();
if (snapshotExists) {
throw new Error('Snapshot data already exists');
throw new SnapshotExistsError();
}

// retrieve status config data
Expand Down Expand Up @@ -699,7 +719,7 @@ export abstract class BaseCredentialStatusManager {
...configData
} = await this.readSnapshotData();

// this is necssary for cases in which a transactional operation such as
// this is necessary for cases in which a transactional operation such as
// allocateStatus results in a new status credential file but must be
// reversed because of an intermittent interruption
await this.deleteStatusData();
Expand All @@ -717,14 +737,15 @@ export abstract class BaseCredentialStatusManager {
await this.deleteSnapshotData();
}

// cleans up snapshot data
async cleanupSnapshotData(): Promise<void> {
const reposProperlyConfigured = await this.statusReposProperlyConfigured();
const snapshotExists = await this.snapshotDataExists();
if (!reposProperlyConfigured) {
if (snapshotExists) {
await this.restoreSnapshotData();
} else {
throw new StatusRepoInconsistencyError({ statusManager: this });
throw new InconsistentRepositoryError({ statusManager: this });
}
} else {
if (snapshotExists) {
Expand Down
47 changes: 27 additions & 20 deletions src/credential-status-manager-github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
CredentialStatusConfigData,
CredentialStatusSnapshotData
} from './credential-status-manager-base.js';
import { BadRequestError } from './errors.js';
import {
DidMethod,
decodeSystemData,
Expand All @@ -21,24 +22,24 @@ import {
getDateString
} from './helpers.js';

// Type definition for GithubCredentialStatusManager constructor method input
export type GithubCredentialStatusManagerOptions = {
// Type definition for GitHubCredentialStatusManager constructor method input
export type GitHubCredentialStatusManagerOptions = {
ownerAccountName: string;
} & BaseCredentialStatusManagerOptions;

// Minimal set of options required for configuring GithubCredentialStatusManager
// Minimal set of options required for configuring GitHubCredentialStatusManager
const GITHUB_MANAGER_REQUIRED_OPTIONS = [
'ownerAccountName'
].concat(BASE_MANAGER_REQUIRED_OPTIONS) as
Array<keyof GithubCredentialStatusManagerOptions & BaseCredentialStatusManagerOptions>;
Array<keyof GitHubCredentialStatusManagerOptions & BaseCredentialStatusManagerOptions>;

// Implementation of BaseCredentialStatusManager for GitHub
export class GithubCredentialStatusManager extends BaseCredentialStatusManager {
export class GitHubCredentialStatusManager extends BaseCredentialStatusManager {
private readonly ownerAccountName: string;
private repoClient: Octokit;
private metaRepoClient: Octokit;

constructor(options: GithubCredentialStatusManagerOptions) {
constructor(options: GitHubCredentialStatusManagerOptions) {
const {
ownerAccountName,
repoName,
Expand Down Expand Up @@ -69,12 +70,12 @@ export class GithubCredentialStatusManager extends BaseCredentialStatusManager {
}

// ensures proper configuration of GitHub status manager
ensureProperConfiguration(options: GithubCredentialStatusManagerOptions): void {
ensureProperConfiguration(options: GitHubCredentialStatusManagerOptions): void {
const missingOptions = [] as
Array<keyof GithubCredentialStatusManagerOptions & BaseCredentialStatusManagerOptions>;
Array<keyof GitHubCredentialStatusManagerOptions & BaseCredentialStatusManagerOptions>;

const isProperlyConfigured = GITHUB_MANAGER_REQUIRED_OPTIONS.every(
(option: keyof GithubCredentialStatusManagerOptions) => {
(option: keyof GitHubCredentialStatusManagerOptions) => {
if (!options[option]) {
missingOptions.push(option as any);
}
Expand All @@ -83,18 +84,20 @@ export class GithubCredentialStatusManager extends BaseCredentialStatusManager {
);

if (!isProperlyConfigured) {
throw new Error(
'You have neglected to set the following required options for the ' +
'GitHub credential status manager: ' +
`${missingOptions.map(o => `'${o}'`).join(', ')}.`
);
throw new BadRequestError({
message:
'You have neglected to set the following required options for the ' +
'GitHub credential status manager: ' +
`${missingOptions.map(o => `"${o}"`).join(', ')}.`
});
}

if (this.didMethod === DidMethod.Web && !this.didWebUrl) {
throw new Error(
'The value of "didWebUrl" must be provided ' +
'when using "didMethod" of type "web".'
);
throw new BadRequestError({
message:
'The value of "didWebUrl" must be provided ' +
'when using "didMethod" of type "web".'
});
}
}

Expand Down Expand Up @@ -231,7 +234,9 @@ export class GithubCredentialStatusManager extends BaseCredentialStatusManager {
// creates data in status file
async createStatusData(data: VerifiableCredential): Promise<void> {
if (typeof data === 'string') {
throw new Error('This library does not support compact JWT credentials.');
throw new BadRequestError({
message: 'This library does not support compact JWT credentials.'
});
}
const statusCredentialId = deriveStatusCredentialId(data.id as string);
const timestamp = getDateString();
Expand Down Expand Up @@ -272,7 +277,9 @@ export class GithubCredentialStatusManager extends BaseCredentialStatusManager {
// updates data in status file
async updateStatusData(data: VerifiableCredential): Promise<void> {
if (typeof data === 'string') {
throw new Error('This library does not support compact JWT credentials.');
throw new BadRequestError({
message: 'This library does not support compact JWT credentials.'
});
}
const statusCredentialId = deriveStatusCredentialId(data.id as string);
const statusResponse = await this.readStatusResponse();
Expand Down
Loading

0 comments on commit cd0c9a9

Please sign in to comment.