Skip to content

Commit

Permalink
IMN-522 Client-assertion validation (#956)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefano Hu <[email protected]>
  • Loading branch information
taglioni-r and shuyec authored Oct 4, 2024
1 parent 0a94f11 commit 1ed411a
Show file tree
Hide file tree
Showing 16 changed files with 2,092 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/client-assertion-validation/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CLIENT_ASSERTION_AUDIENCE="test.interop.pagopa.it"
37 changes: 37 additions & 0 deletions packages/client-assertion-validation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "pagopa-interop-client-assertion-validation",
"private": true,
"version": "1.0.0",
"description": "PagoPA Interoperability utility to validate client assertion",
"main": "dist",
"type": "module",
"exports": {
".": "./dist/index.js"
},
"scripts": {
"test": "vitest",
"lint": "eslint . --ext .ts,.tsx",
"lint:autofix": "eslint . --ext .ts,.tsx --fix",
"format:check": "prettier --check src",
"format:write": "prettier --write src",
"build": "tsc",
"check": "tsc --project tsconfig.check.json"
},
"license": "Apache-2.0",
"dependencies": {
"jsonwebtoken": "9.0.2",
"pagopa-interop-commons-test": "workspace:*",
"pagopa-interop-models": "workspace:*",
"pagopa-interop-commons": "workspace:*",
"ts-pattern": "5.2.0",
"zod": "3.23.8"
},
"devDependencies": {
"@types/jsonwebtoken": "9.0.6",
"@types/node": "20.14.6",
"eslint": "8.57.0",
"prettier": "2.8.8",
"typescript": "5.4.5",
"vitest": "1.6.0"
}
}
11 changes: 11 additions & 0 deletions packages/client-assertion-validation/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { z } from "zod";

const ClientAssertionValidationConfig = z
.object({
CLIENT_ASSERTION_AUDIENCE: z.string(),
})
.transform((c) => ({
clientAssertionAudience: c.CLIENT_ASSERTION_AUDIENCE,
}));

export const config = ClientAssertionValidationConfig.parse(process.env);
325 changes: 325 additions & 0 deletions packages/client-assertion-validation/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
import { ApiError } from "pagopa-interop-models";

export const errorCodes = {
clientAssertionValidationFailure: "0001",
unexpectedClientAssertionSignatureVerificationError: "0002",
invalidAssertionType: "0003",
invalidGrantType: "0004",
invalidAudienceFormat: "0005",
invalidAudience: "0006",
invalidClientAssertionFormat: "0007",
unexpectedClientAssertionPayload: "0008",
jtiNotFound: "0009",
issuedAtNotFound: "0010",
expNotFound: "0011",
issuerNotFound: "0012",
subjectNotFound: "0013",
invalidSubject: "0014",
invalidPurposeIdClaimFormat: "0015",
kidNotFound: "0016",
invalidClientAssertionSignatureType: "0017",
tokenExpiredError: "0018",
jsonWebTokenError: "0019",
notBeforeError: "0020",
inactivePurpose: "0021",
inactiveAgreement: "0022",
inactiveEService: "0023",
invalidClientIdFormat: "0024",
invalidSubjectFormat: "0025",
digestClaimNotFound: "0026",
invalidHashLength: "0027",
invalidHashAlgorithm: "0028",
algorithmNotFound: "0029",
algorithmNotAllowed: "0030",
purposeIdNotProvided: "0031",
invalidKidFormat: "0032",
clientAssertionInvalidClaims: "0033",
invalidSignature: "0034",
};

export type ErrorCodes = keyof typeof errorCodes;

export function clientAssertionValidationFailure(
details: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Client assertion validation failed: ${details}`,
code: "clientAssertionValidationFailure",
title: "Client assertion validation failed",
});
}

export function unexpectedClientAssertionSignatureVerificationError(): ApiError<ErrorCodes> {
return new ApiError({
detail: `Unexpected client assertion signature verification error`,
code: "unexpectedClientAssertionSignatureVerificationError",
title: "Unexpected client assertion signature verification error",
});
}

export function invalidAssertionType(
assertionType: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Assertion type not valid: ${assertionType}`,
code: "invalidAssertionType",
title: "Assertion type not valid",
});
}

export function invalidGrantType(grantType: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Grant type not valid: ${grantType}`,
code: "invalidGrantType",
title: "Grant type not valid",
});
}

export function invalidAudienceFormat(): ApiError<ErrorCodes> {
return new ApiError({
detail: `Audience must be an array`,
code: "invalidAudienceFormat",
title: "Invalid audience format",
});
}

export function invalidAudience(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Unexpected client assertion audience",
code: "invalidAudience",
title: "Invalid audience",
});
}

export function invalidClientAssertionFormat(): ApiError<ErrorCodes> {
return new ApiError({
detail: `Invalid format for Client assertion`,
code: "invalidClientAssertionFormat",
title: "Invalid format for Client assertion",
});
}

export function unexpectedClientAssertionPayload(
message: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Unexpected client assertion payload: ${message}`,
code: "unexpectedClientAssertionPayload",
title: "Invalid client assertion payload",
});
}

export function jtiNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: `JTI not found in client assertion`,
code: "jtiNotFound",
title: "JTI not found",
});
}

export function issuedAtNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: `IAT not found in client assertion`,
code: "issuedAtNotFound",
title: "IAT not found",
});
}

export function expNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: `EXP not found in client assertion`,
code: "expNotFound",
title: "EXP not found",
});
}

export function issuerNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: `Issuer not found in client assertion`,
code: "issuerNotFound",
title: "ISS not found",
});
}

export function subjectNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Subject not found in client assertion",
code: "subjectNotFound",
title: "Subject not found",
});
}

export function invalidSubject(subject?: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Subject claim value ${subject} does not correspond to provided client_id parameter`,
code: "invalidSubject",
title: "Invalid subject",
});
}

export function invalidPurposeIdClaimFormat(
purposeId: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Purpose Id claim ${purposeId} is not a valid UUID`,
code: "invalidPurposeIdClaimFormat",
title: "Invalid purposeId claim format",
});
}

export function kidNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: `KID not found in client assertion`,
code: "kidNotFound",
title: "KID not found",
});
}

export function invalidClientAssertionSignatureType(
clientAssertionSignatureType: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Client assertion signature's type not valid: ${clientAssertionSignatureType}`,
code: "invalidClientAssertionSignatureType",
title: "Token expired in client assertion signature validation",
});
}

export function tokenExpiredError(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Token expired in client assertion signature validation",
code: "tokenExpiredError",
title: "Token expired",
});
}

export function jsonWebTokenError(errorMessage: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Invalid JWT format in client assertion signature validation. Reason: ${errorMessage}`,
code: "jsonWebTokenError",
title: "Invalid JWT format",
});
}

export function notBeforeError(): ApiError<ErrorCodes> {
return new ApiError({
detail:
"Current time is before not before time in client assertion signature validation",
code: "notBeforeError",
title: "Current time is before not before time",
});
}

export function inactivePurpose(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Purpose is not active",
code: "inactivePurpose",
title: "Purpose is not active",
});
}

export function inactiveEService(): ApiError<ErrorCodes> {
return new ApiError({
detail: "E-Service is not active",
code: "inactiveEService",
title: "E-Service is not active",
});
}

export function inactiveAgreement(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Agreement is not active",
code: "inactiveAgreement",
title: "Agreement is not active",
});
}

export function invalidClientIdFormat(clientId: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Client id ${clientId} is not a valid UUID`,
code: "invalidClientIdFormat",
title: "Invalid client id format",
});
}

export function invalidSubjectFormat(subject: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Subject claim ${subject} is not a valid UUID`,
code: "invalidSubjectFormat",
title: "Invalid subject format",
});
}

export function digestClaimNotFound(message: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Digest claim not found. Reason: ${message}`,
code: "digestClaimNotFound",
title: "Digest claim not found",
});
}

export function invalidHashLength(alg: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Invalid hash length for algorithm ${alg}`,
code: "invalidHashLength",
title: "Invalid hash length",
});
}

export function invalidHashAlgorithm(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Invalid hash algorithm",
code: "invalidHashAlgorithm",
title: "Invalid hash algorithm",
});
}

export function algorithmNotFound(): ApiError<ErrorCodes> {
return new ApiError({
detail: "ALG not found in client assertion",
code: "algorithmNotFound",
title: "ALG not found",
});
}

export function algorithmNotAllowed(algorithm: string): ApiError<ErrorCodes> {
return new ApiError({
detail: `Algorithm ${algorithm} is not allowed`,
code: "algorithmNotAllowed",
title: "ALG not allowed",
});
}

export function purposeIdNotProvided(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Claim purposeId does not exist in this assertion",
code: "purposeIdNotProvided",
title: "Purpose Id not provided",
});
}

export function invalidKidFormat(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Unexpected format for kid",
code: "invalidKidFormat",
title: "Invalid KID format",
});
}

export function clientAssertionInvalidClaims(
details: string
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Client assertion validation failure. Reason: ${details}`,
code: "clientAssertionInvalidClaims",
title: "Invalid claims in header or payload",
});
}

export function invalidSignature(): ApiError<ErrorCodes> {
return new ApiError({
detail: "Client assertion signature is invalid",
code: "invalidSignature",
title: "Invalid signature",
});
}
2 changes: 2 additions & 0 deletions packages/client-assertion-validation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./validation.js";
export * from "./types.js";
Loading

0 comments on commit 1ed411a

Please sign in to comment.