Skip to content

Commit

Permalink
feature: add Exception class for error handling (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
alessiofrittoli authored Dec 17, 2024
1 parent 3ae09fe commit 7e2348e
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 64 deletions.
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,56 @@ The module supports the following algorithms:
| | `PS384` | Signature generated/verified with `RSASSA-PSS` keys and `SHA-384`. |
| | `PS512` | Signature generated/verified with `RSASSA-PSS` keys and `SHA-512`. |

#### Error Handling
---

### Error handling

This module throws a new `Exception` when an error occures providing an error code that will help in error handling.

The `ErrorCode` enumerator can be used to handle different errors with ease.

<details>

<summary>`ErrorCode` enum</summary>

| Constant | Description |
|-----------------------|----------------------------------------------------------|
| `UNKNOWN` | Thrown when: |
| | - `Signature.sign()` encounters an unexpected error while creating a signature (mostly due to unsupported routine). The original thrown error is being reported in the `Exception.cause` property. |
| | - `Signature.isValid()` encounters an unexpected error while verifying a signature (mostly due to unsupported routine). The original thrown error is being reported in the `Exception.cause` property. |
| `EMPTY_VALUE` | Thrown when: |
| | `Signature.sign()` has no `data` to sign. |
| | `Signature.isValid()` has no `data` to verify. |
| `INVALID_SIGN` | Thrown when `Signature.isValid()` encounter an invalid signature (altered data, altered signature, wrong PublicKey). |
| `NO_SIGN` | Thrown when `Signature.isValid()` has no `signature` to verify. |
| `NO_PRIVATEKEY` | Thrown when `Signature.sign()` has no Private Key to sign with. |
| `NO_PUBLICKEY` | Thrown when `Signature.isValid()` has no Public Key to verify the signature with. |

</details>

---

<details>

<summary>Example usage</summary>

```ts
import Exception from '@alessiofrittoli/exception'
import Signature from '@alessiofrittoli/crypto-jwt'
import { ErrorCode } from '@alessiofrittoli/crypto-jwt/error'

The methods throw exceptions in the following cases:
try {
Signature.isValid( 'invalid signature', 'Data', 'myscretkey' )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.INVALID_SIGN )
}
}
```

- Missing or invalid parameters (e.g., no data, key, or signature provided).
- Invalid or unsupported algorithm.
- Signature verification failure.
</details>

---

Expand Down
142 changes: 142 additions & 0 deletions __tests__/exception.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import Exception from '@alessiofrittoli/exception'

import Signature from '@/index'
import { ErrorCode } from '@/error'


describe( 'Signature.sign()', () => {

it( 'throws a new Exception when no data to sign has been given', () => {
try {
Signature.sign( '', 'myscretkey', 'HS1' )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )
if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.EMPTY_VALUE )
}
}
} )


it( 'throws a new Exception with unsupported routine', async () => {

try {
Signature.sign( 'Data to be signed.', 'invalid private key', 'RS256' )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.UNKNOWN )
expect( 'cause' in error ).toBe( true )
const cause = error.cause as Error
expect( cause.name ).toBe( 'Error' )
}
}

} )
} )


describe( 'Signature.isValid()', () => {

const data = 'Data to sign.'
const signature = Buffer.from( 'd36d1407db3a339789b5762704db9481271bfc152e338f0aa900139e53bc288f', 'hex' )
const secretKey = 'mysecretkey'

it( 'throws a new Exception when no signature has been given', () => {
try {
Signature.isValid( '', data, secretKey )
} catch ( error ) {

expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.NO_SIGN )
}
}
} )


it( 'throws a new Exception when no data has been given', () => {
try {
Signature.isValid( signature, '', secretKey )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.EMPTY_VALUE )
}
}
} )


it( 'throws a new Exception when no secret or Public Key has given', () => {
try {
Signature.isValid( signature, data, '' )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.NO_PUBLICKEY )
}
}
} )


it( 'throws a new Exception when signature is not valid', () => {
try {
Signature.isValid( 'invalid signature', data, secretKey )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.INVALID_SIGN )
}
}
} )


it( 'throws a new Exception when data has been altered', () => {
try {
Signature.isValid( signature, 'altered data', secretKey )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.INVALID_SIGN )
}
}
} )


it( 'throws a new Exception when using a wrong key', () => {
try {
Signature.isValid( signature, data, 'wrong secret key' )
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.INVALID_SIGN )
}
}
} )


it( 'throws a new Exception with unsupported routine', async () => {

try {
Signature.isValid( signature, data, secretKey, 'DS1' ) // wrong algorithm used
} catch ( error ) {
expect( error ).toBeInstanceOf( Exception )

if ( Exception.isException<string, ErrorCode>( error ) ) {
expect( error.code ).toBe( ErrorCode.UNKNOWN )
expect( 'cause' in error ).toBe( true )
const cause = error.cause as Error
expect( cause.name ).toBe( 'Error' )
}
}

} )

} )
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./error": {
"types": "./dist/error.d.ts",
"import": "./dist/error.mjs",
"require": "./dist/error.js"
},
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.mjs",
Expand All @@ -49,7 +54,8 @@
"test:ci:jsdom": "JSDOM=true pnpm test:ci",
"test:jest": "pnpm test jest.test.ts",
"test:signature": "pnpm test signature.test.ts",
"test:verify": "pnpm test verify.test.ts"
"test:verify": "pnpm test verify.test.ts",
"test:exception": "pnpm test exception.test.ts"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
Expand All @@ -70,6 +76,7 @@
"dependencies": {
"@alessiofrittoli/crypto-algorithm": "^1.2.0",
"@alessiofrittoli/crypto-buffer": "^2.0.1",
"@alessiofrittoli/crypto-key": "^1.0.0"
"@alessiofrittoli/crypto-key": "^1.0.0",
"@alessiofrittoli/exception": "^1.0.0"
}
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum ErrorCode
{
UNKNOWN = 'ERR:UNKNOWN',
EMPTY_VALUE = 'ERR:EMPTYVALUE',
INVALID_SIGN = 'ERR:INVALIDSIGN',
NO_SIGN = 'ERR:NOSIGN',
NO_PRIVATEKEY = 'ERR:NOPRIVATEKEY',
NO_PUBLICKEY = 'ERR:NOPUBLICKEY',
}
Loading

0 comments on commit 7e2348e

Please sign in to comment.