Skip to content

Commit

Permalink
fix: add some dpop unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Jul 30, 2024
1 parent 1a54e69 commit c24a898
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 4 deletions.
124 changes: 124 additions & 0 deletions packages/common/lib/__tests__/dpop.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { createDPoP, getCreateDPoPOptions, verifyDPoP } from '../dpop';

describe('dpop', () => {
const alg = 'HS256';
const jwk = { kty: 'Ed25519', crv: 'Ed25519', x: '123', y: '123' };
const jwtIssuer = { alg, jwk };
const htm = 'POST';
const htu = 'https://example.com/token';
const nonce = 'nonce';
const jwtPayloadProps = { htm, htu, nonce } as const;
const jwtHeaderProps = { alg, jwk, typ: 'dpop+jwt' };
const unsignedDpop =
'eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJFZDI1NTE5IiwiY3J2IjoiRWQyNTUxOSIsIngiOiIxMjMiLCJ5IjoiMTIzIn0sInR5cCI6ImRwb3Arand0In0.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9leGFtcGxlLmNvbS90b2tlbiIsIm5vbmNlIjoibm9uY2UiLCJpYXQiOjE3MjIzMjcxOTQsImp0aSI6Ijk4OWNiZTc4LWI1ZTYtNDViYS1iYjMzLWQ0MGE4ZGEwZjFhYSJ9';

it('should create a dpop with valid options', async () => {
const dpop = await createDPoP({
jwtIssuer,
jwtPayloadProps,
createJwtCallback: async (dpopJwtIssuerWithContext, jwt) => {
expect(dpopJwtIssuerWithContext.alg).toEqual(alg);
expect(dpopJwtIssuerWithContext.jwk).toEqual(jwk);
expect(dpopJwtIssuerWithContext.dPoPSigningAlgValuesSupported).toBeUndefined();
expect(dpopJwtIssuerWithContext.type).toEqual('dpop');

expect(jwt.header).toStrictEqual(jwtHeaderProps);
expect(jwt.payload).toStrictEqual({
...jwtPayloadProps,
iat: expect.any(Number),
jti: expect.any(String),
});

return unsignedDpop;
},
});

expect(unsignedDpop).toEqual(dpop);
expect.assertions(7);
});

it('should create a dpop with valid createDPoPOptions', async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { htm, htu, ...rest } = jwtPayloadProps;
const options = getCreateDPoPOptions(
{
jwtIssuer,
jwtPayloadProps: rest,
createJwtCallback: async (dpopJwtIssuerWithContext, jwt) => {
expect(dpopJwtIssuerWithContext.alg).toEqual(alg);
expect(dpopJwtIssuerWithContext.jwk).toEqual(jwk);
expect(dpopJwtIssuerWithContext.dPoPSigningAlgValuesSupported).toBeUndefined();
expect(dpopJwtIssuerWithContext.type).toEqual('dpop');

expect(jwt.header).toStrictEqual(jwtHeaderProps);
expect(jwt.payload).toStrictEqual({
...jwtPayloadProps,
iat: expect.any(Number),
jti: expect.any(String),
});

return unsignedDpop;
},
},
htu + '?123412341#xyaksdjfaksdjf',
);

const dpop = await createDPoP(options);

expect(unsignedDpop).toEqual(dpop);
expect.assertions(7);
});

it('verify dpop fails if jwtVerifyCallback throws an error', async () => {
await expect(
verifyDPoP(
{
headers: { dpop: unsignedDpop },
fullUrl: htu + '?123412341#xyaksdjfaksdjf',
method: 'POST',
},
{
jwtVerifyCallback: async () => {
throw new Error('jwtVerifyCallback');
},
expectedNonce: 'nonce',
expectAccessToken: false,
now: 1722327194,
},
),
).rejects.toThrow();
});

it('should verify a dpop with valid options', async () => {
const dpop = await verifyDPoP(
{
headers: { dpop: unsignedDpop },
fullUrl: htu + '?123412341#xyaksdjfaksdjf',
method: 'POST',
},
{
jwtVerifyCallback: async (jwtVerifier, jwt) => {
expect(jwtVerifier.method).toEqual('jwk');
expect(jwtVerifier.jwk).toEqual(jwk);
expect(jwtVerifier.type).toEqual('dpop');
expect(jwtVerifier.alg).toEqual(alg);

expect(jwt.header).toStrictEqual(jwtHeaderProps);
expect(jwt.payload).toStrictEqual({
...jwtPayloadProps,
iat: expect.any(Number),
jti: expect.any(String),
});
expect(jwt.raw).toEqual(unsignedDpop);

return true;
},
expectAccessToken: false,
expectedNonce: 'nonce',
now: 1722327194,
},
);
expect(dpop).toStrictEqual(jwk);
expect.assertions(6);
});
});
9 changes: 6 additions & 3 deletions packages/common/lib/dpop/DPoP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export interface DPoPVerifyOptions {
maxIatAgeInSeconds?: number;
expectAccessToken?: boolean;
jwtVerifyCallback: DPoPVerifyJwtCallback;
now?: number;
}

export async function verifyDPoP(
Expand Down Expand Up @@ -164,10 +165,12 @@ export async function verifyDPoP(
}

// Validate iat claim
const { nowSkewedPast, nowSkewedFuture } = getNowSkewed();
const { nowSkewedPast, nowSkewedFuture } = getNowSkewed(options.now);
if (
dPoPPayload.iat > nowSkewedFuture + (options.maxIatAgeInSeconds ?? 300) ||
dPoPPayload.iat < nowSkewedPast - (options.maxIatAgeInSeconds ?? 300)
// iat claim is to far in the future
nowSkewedPast - (options.maxIatAgeInSeconds ?? 300) > dPoPPayload.iat ||
// iat claim is too old
nowSkewedFuture + (options.maxIatAgeInSeconds ?? 300) < dPoPPayload.iat
) {
// 5 minute window
throw new Error('invalid_dpop_proof. Invalid iat claim');
Expand Down
2 changes: 1 addition & 1 deletion packages/common/lib/jwt/Jwt.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type JwtHeader = jwtDecodeJwtHeader & {
alg?: string;
x5c?: string[];
kid?: string;
jwk?: JWK & { kty: string };
jwk?: JWK;
jwt?: string;
} & Record<string, unknown>;

Expand Down

0 comments on commit c24a898

Please sign in to comment.