Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add: introduce error codes #20

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion demo/issuer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DisclosureFrame, Hasher, Signer, base64encode } from '@meeco/sd-jwt';
import { DisclosureFrame, Hasher, Signer, base64encode, decodeSDJWT } from '@meeco/sd-jwt';
import { createHash } from 'crypto';
import { JWTHeaderParameters, JWTPayload, KeyLike, SignJWT, importJWK } from 'jose';
import {
Expand Down Expand Up @@ -90,6 +90,8 @@ async function main() {
};

const result = await issuer.createVCSDJWT(vcClaims, payload, sdVCClaimsDisclosureFrame);
const sdjwtvc = decodeSDJWT(result);
console.log(sdjwtvc.disclosures);
console.log(result);
}

Expand Down
204 changes: 202 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,205 @@
export enum SDJWTVCErrorCode {
DefaultError = 'UNKNOWN_ERROR', // default in case no code is provided
InvalidIssuer = 'INVALID_ISSUER',
InvalidIssuedAt = 'INVALID_ISSUED_AT',
InvalidCallback = 'INVALID_CALLBACK',
InvalidAlgorithm = 'INVALID_ALGORITHM',
InvalidPayload = 'INVALID_PAYLOAD',
}

export class SDJWTVCError extends Error {
constructor(message: any) {
super(message);
protected code: SDJWTVCErrorCode;
protected errorType: ErrorType;
protected extraInfo: Record<string, any>;

constructor(errorType: ErrorType, extraInfo: Record<string, any> = {}) {
const errorInfo = ERROR_REGISTRY[errorType];

super(errorInfo.message);

this.errorType = errorType;
this.code = errorInfo.code;
this.extraInfo = extraInfo;

this.name = this.constructor.name;
}

getResponse(): string | Record<string, any> {
return this.message;
}

getCode(): SDJWTVCErrorCode {
return this.code;
}

getErrorType(): ErrorType {
return this.errorType;
}

getExtraInfo(): Record<string, any> {
return this.extraInfo;
}

equals(exception: SDJWTVCError) {
return (
this.getErrorType() === exception.getErrorType() &&
JSON.stringify(this.getExtraInfo()) === JSON.stringify(exception.getExtraInfo())
);
}
}
export type ErrorType = keyof typeof ERROR_REGISTRY;

/**
* TODO: update error messages and codes
*/

const ERROR_REGISTRY = {
hasher_callback_function_is_required: {
message: 'Hasher callback function is required',
code: SDJWTVCErrorCode.InvalidCallback,
},
hasher_algorithm_is_required: {
message: 'Hasher algorithm is required',
code: SDJWTVCErrorCode.InvalidAlgorithm,
},
signer_callback_function_is_required: {
message: 'Signer callback function is required',
code: SDJWTVCErrorCode.InvalidCallback,
},
signer_algorithm_is_required: {
message: 'Signer algorithm is required',
code: SDJWTVCErrorCode.InvalidAlgorithm,
},
vcClaims_is_required: {
message: 'vcClaims is required',
code: SDJWTVCErrorCode.DefaultError,
},
sdJWTPayload_is_required: {
message: 'sdJWTPayload is required',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_issuer_url: {
message: 'Issuer iss (issuer) is required and must be a valid URL',
code: SDJWTVCErrorCode.InvalidIssuer,
},
invalid_issued_at: {
message: 'Payload iat (Issued at - seconds since Unix epoch) is required and must be a number',
code: SDJWTVCErrorCode.InvalidIssuedAt,
},
invalid_cnf: {
message: 'Payload cnf is required and must be a JWK format',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_cnf_jwk: {
message: 'Payload cnf.jwk must be valid JWK format',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_vct_string: {
message: 'vct value MUST be a case-sensitive string',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_vct_url: {
message: 'vct value MUST be a valid URL',
code: SDJWTVCErrorCode.InvalidIssuer,
},
invalid_claims_object: {
message: 'Payload claims is required and must be an object',
code: SDJWTVCErrorCode.DefaultError,
},
reserved_jwt_payload_key_in_claims: {
message: 'Claim contains reserved JWTPayload key',
code: SDJWTVCErrorCode.DefaultError,
},
reserved_jwt_payload_key_in_disclosure_frame: {
message: 'Disclosure frame contains reserved JWTPayload key',
code: SDJWTVCErrorCode.DefaultError,
},
failed_to_create_VCSDJWT: {
message: 'Failed to create VCSDJWT',
code: SDJWTVCErrorCode.DefaultError,
},
missing_key_binding_verifier_callback_function: {
message: 'Missing key binding verifier callback function',
code: SDJWTVCErrorCode.InvalidCallback, // Use appropriate error code
},
missing_aud_nonce_iat_or_sd_hash_in_key_binding_JWT: {
message: 'Missing aud, nonce, iat or sd_hash in key binding JWT',
code: SDJWTVCErrorCode.InvalidPayload, // Use appropriate error code
},
signer_function_is_required: {
message: 'Signer function is required',
code: SDJWTVCErrorCode.InvalidCallback,
},
algo_used_for_Signer_function_is_required: {
message: 'algo used for Signer function is required',
code: SDJWTVCErrorCode.InvalidAlgorithm,
},
failed_to_get_Key_Binding_JWT: {
message: 'Failed to get Key Binding JWT',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_audience_parameter: {
message: 'Invalid audience parameter',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_sdJWT_parameter: {
message: 'Invalid sdJWT parameter',
code: SDJWTVCErrorCode.DefaultError,
},
no_holder_public_key_in_SD_JWT: {
message: 'No holder public key in SD-JWT',
code: SDJWTVCErrorCode.DefaultError,
},
no_disclosures_in_SD_JWT: {
message: 'No disclosures in SD-JWT',
code: SDJWTVCErrorCode.DefaultError,
},
failed_to_verify_key_binding_JWT: {
message: 'Failed to verify key binding JWT: SD JWT holder public key does not match private key',
code: SDJWTVCErrorCode.DefaultError,
},
aud_mismatch: {
message: 'aud mismatch',
code: SDJWTVCErrorCode.DefaultError,
},
nonce_mismatch: {
message: 'nonce mismatch',
code: SDJWTVCErrorCode.DefaultError,
},
sd_hash_mismatch: {
message: 'sd_hash mismatch',
code: SDJWTVCErrorCode.DefaultError,
},
unsupported_algorithm: {
message: 'unsupported algorithm',
code: SDJWTVCErrorCode.DefaultError,
},
invalid_issuer_well_known_url: {
message: 'Invalid issuer well-known URL',
code: SDJWTVCErrorCode.DefaultError,
},
failed_to_fetch_or_parse_response: {
message: 'Failed to fetch or parse the response from {issuerUrl} as JSON. Error: {error.message}',
code: SDJWTVCErrorCode.DefaultError,
},
issuer_public_key_jwk_not_found: {
message: 'Issuer public key JWK not found',
code: SDJWTVCErrorCode.DefaultError,
},
issuer_response_not_found: {
message: 'Issuer response not found',
code: SDJWTVCErrorCode.DefaultError,
},
issuer_response_does_not_contain_jwks_or_jwks_uri: {
message: 'Issuer response does not contain jwks or jwks_uri',
code: SDJWTVCErrorCode.DefaultError,
},
issuer_response_from_wellknown_do_not_match_the_expected_issuer: {
message: "The response from the issuer's well-known URI does not match the expected issuer",
code: SDJWTVCErrorCode.InvalidIssuer,
},
unexpected_url: {
message: 'Unexpected URL',
code: SDJWTVCErrorCode.DefaultError,
},
} as const;
18 changes: 9 additions & 9 deletions src/holder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Disclosure, Hasher, JWK, KeyBindingVerifier, base64encode, decodeJWT, decodeSDJWT } from '@meeco/sd-jwt';
import { SDJWTVCError } from './errors.js';
import { hasherCallbackFn } from './test-utils/helpers.js';
import { CreateSDJWTPayload, JWT, PresentSDJWTPayload, SD_JWT_FORMAT_SEPARATOR, SignerConfig } from './types.js';
import { defaultHashAlgorithm, isValidUrl } from './util.js';
import { hasherCallbackFn } from './test-utils/helpers.js';

export class Holder {
private signer: SignerConfig;
Expand All @@ -14,10 +14,10 @@ export class Holder {
*/
constructor(signer: SignerConfig) {
if (!signer?.callback || typeof signer?.callback !== 'function') {
throw new SDJWTVCError('Signer function is required');
throw new SDJWTVCError('signer_function_is_required');
}
if (!signer?.alg || typeof signer?.alg !== 'string') {
throw new SDJWTVCError('algo used for Signer function is required');
throw new SDJWTVCError('algo_used_for_Signer_function_is_required');
}

this.signer = signer;
Expand Down Expand Up @@ -62,7 +62,7 @@ export class Holder {

return { keyBindingJWT: jwt, nonce };
} catch (error: any) {
throw new SDJWTVCError(`Failed to get Key Binding JWT: ${error.message}`);
throw new SDJWTVCError('failed_to_get_Key_Binding_JWT', { reason: error.message });
}
}

Expand All @@ -83,11 +83,11 @@ export class Holder {
options?: { nonce?: string; audience?: string; keyBindingVerifyCallbackFn?: KeyBindingVerifier },
): Promise<{ vcSDJWTWithkeyBindingJWT: JWT; nonce?: string }> {
if (options.audience && (typeof options.audience !== 'string' || !isValidUrl(options.audience))) {
throw new SDJWTVCError('Invalid audience parameter');
throw new SDJWTVCError('invalid_audience_parameter');
}

if (typeof sdJWT !== 'string' || !sdJWT.includes(SD_JWT_FORMAT_SEPARATOR)) {
throw new SDJWTVCError('Invalid sdJWT parameter');
throw new SDJWTVCError('invalid_sdJWT_parameter');
}

const [sdJWTPayload, _] = sdJWT.split(SD_JWT_FORMAT_SEPARATOR);
Expand All @@ -96,7 +96,7 @@ export class Holder {
const { jwk: holderPublicKeyJWK } = (jwt.payload as CreateSDJWTPayload).cnf || {};

if (!holderPublicKeyJWK) {
throw new SDJWTVCError('No holder public key in SD-JWT');
throw new SDJWTVCError('no_holder_public_key_in_SD_JWT');
}

let sdHashAlgorithm = jwt.payload['_sd_alg'] as string;
Expand Down Expand Up @@ -130,7 +130,7 @@ export class Holder {
*/
revealDisclosures(sdJWT: JWT, disclosedList: Disclosure[]): JWT {
if (typeof sdJWT !== 'string' || !sdJWT.includes(SD_JWT_FORMAT_SEPARATOR)) {
throw new SDJWTVCError('No disclosures in SD-JWT');
throw new SDJWTVCError('no_disclosures_in_SD_JWT');
}

const { disclosures } = decodeSDJWT(sdJWT);
Expand Down Expand Up @@ -174,7 +174,7 @@ export class Holder {
try {
await keyBindingVerifierCallbackFn(keyBindingJWT, holderPublicKeyJWK);
} catch (e) {
throw new SDJWTVCError('Failed to verify key binding JWT: SD JWT holder public key does not match private key');
throw new SDJWTVCError('failed_to_verify_key_binding_JWT');
}
}
}
Loading