-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
IMN-522 Client-assertion validation (#956)
Co-authored-by: Stefano Hu <[email protected]>
- Loading branch information
1 parent
0a94f11
commit 1ed411a
Showing
16 changed files
with
2,092 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CLIENT_ASSERTION_AUDIENCE="test.interop.pagopa.it" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./validation.js"; | ||
export * from "./types.js"; |
Oops, something went wrong.