Skip to content
Merged
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
7 changes: 4 additions & 3 deletions interop/__tests__/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@
'403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388'
}
);
const cat = await validator.validate(token, 'mac', {
const result = await validator.validate(token, 'mac', {
issuer: 'eyevinn'
});
expect(cat).toBeDefined();
expect(cat!.claims.iss).toBe('eyevinn');
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims.iss).toBe('eyevinn');

Check warning on line 38 in interop/__tests__/validate.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion
});
});
7 changes: 5 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,13 @@ const validator = new CAT({
const base64encoded =
'0YRDoQEEoQRMU3ltbWV0cmljMjU2eKZkOTAxMDNhNzAxNzU2MzZmNjE3MDNhMmYyZjYxNzMyZTY1Nzg2MTZkNzA2YzY1MmU2MzZmNmQwMjY1NmE2ZjZlNjE3MzAzNzgxODYzNmY2MTcwM2EyZjJmNmM2OTY3Njg3NDJlNjU3ODYxNmQ3MDZjNjUyZTYzNmY2ZDA0MWE1NjEyYWViMDA1MWE1NjEwZDlmMDA2MWE1NjEwZDlmMDA3NDIwYjcxSKuCk/+kFmlY';
try {
const cat = await validator.validate(base64encoded, 'mac', {
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'coap://as.example.com'
});
console.log(cat.claims);
if (result.error) {
console.log(result.error.message);
}
console.log(result.cat?.claims);
} catch (err) {
// Not valid
console.log(err);
Expand Down
105 changes: 55 additions & 50 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ describe('CAT', () => {
)
}
});
const cat = await validator.validate(base64encoded!, 'mac', {
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'coap://as.example.com'
});
expect(cat).toBeDefined();
expect(cat!.claims).toEqual({
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims).toEqual({
iss: 'coap://as.example.com'
});
});
Expand All @@ -57,11 +58,12 @@ describe('CAT', () => {
)
}
});
const cat = await validator.validate(base64encoded, 'mac', {
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'coap://as.example.com'
});
expect(cat).toBeDefined();
expect(cat!.claims).toEqual({
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims).toEqual({
iss: 'coap://as.example.com'
});
});
Expand All @@ -78,11 +80,12 @@ describe('CAT', () => {
},
expectCwtTag: true
});
const cat = await validator.validate(base64encoded, 'mac', {
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'coap://jonas.example.com'
});
expect(cat).toBeDefined();
expect(cat!.claims).toEqual({
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims).toEqual({
iss: 'coap://jonas.example.com',
iat: 1741985961,
nbf: 1741985961
Expand Down Expand Up @@ -183,53 +186,53 @@ describe('CAT claims', () => {
test('fail if wrong issuer', async () => {
const base64encoded =
'2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2VKMBZWpvbmFzBhpn12U-BRpn12U-WCDf1xHvhcnvyXUxd-DP4RAbayc8nC2PLJPPPbF3S00ruw';
await expect(
validator.validate(base64encoded, 'mac', {
issuer: 'coap://jonas.example.com'
})
).rejects.toThrow(InvalidIssuerError);
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'coap://jonas.example.com'
});
expect(result.error).toBeInstanceOf(InvalidIssuerError);
});

test('pass if token has not expired', async () => {
const base64encoded =
'2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2U6MEGnUCOrsGGmfXRKwFGmfXRKxYIOM6yRx830uqAamWFv1amFYRa5vaV2z5lIQTqFEvFh8z';
const cat = validator.validate(base64encoded, 'mac', {
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn'
});
expect(cat).toBeDefined();
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
});

test('fail if token expired', async () => {
const base64encoded =
'2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2U6MEGmfXP_YGGmfXQAsFGmfXQAtYINTT_KlOyhaV6NaSxFXkqJWfBagSkPkem10dysoA-C0w';
await expect(
validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn'
})
).rejects.toThrow(TokenExpiredError);

const result = await validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn'
});
expect(result.error).toBeInstanceOf(TokenExpiredError);
});

test('pass if token has a valid audience', async () => {
// {"aud": ["one", "two"]}
const base64encoded =
'2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2V6MDgmNvbmVjdHdvBhpn12R8BRpn12R8WCAdnSbUN4KbIvaHLn-q4f4YRpfq6ERYotByjbIyZ-EkfQ';
const cat = validator.validate(base64encoded, 'mac', {
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn',
audience: ['one', 'three']
});
expect(cat).toBeDefined();
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
});

test('fail if token has an invalid audience', async () => {
// {"aud": ["one", "two"]}
const base64encoded =
'2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2V6MDgmNvbmVjdHdvBhpn12R8BRpn12R8WCAdnSbUN4KbIvaHLn-q4f4YRpfq6ERYotByjbIyZ-EkfQ';
await expect(
validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn',
audience: ['three']
})
).rejects.toThrow(InvalidAudienceError);
const result = await validator.validate(base64encoded, 'mac', {
issuer: 'eyevinn',
audience: ['three']
});
expect(result.error).toBeInstanceOf(InvalidAudienceError);
});

test('fail if token is not active yet', async () => {
Expand All @@ -245,11 +248,10 @@ describe('CAT claims', () => {
kid: 'Symmetric256'
}
);
await expect(
validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn'
})
).rejects.toThrow(TokenNotActiveError);
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn'
});
expect(result.error).toBeInstanceOf(TokenNotActiveError);
});

test('pass if token is active', async () => {
Expand All @@ -265,10 +267,11 @@ describe('CAT claims', () => {
kid: 'Symmetric256'
}
);
const cat = await validator.validate(base64encoded!, 'mac', {
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn'
});
expect(cat).toBeDefined();
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
});

test('pass if token has a catu claim that matches url', async () => {
Expand All @@ -293,17 +296,17 @@ describe('CAT claims', () => {
kid: 'Symmetric256'
}
);
const cat = await validator.validate(base64encoded!, 'mac', {
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn',
url: new URL('https://example.com/content/path/file.m3u8')
});
expect(cat).toBeDefined();
await expect(
validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn',
url: new URL('https://example.com/content/path/file.ts')
})
).rejects.toThrow(UriNotAllowedError);
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
const result2 = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn',
url: new URL('https://example.com/content/path/file.ts')
});
expect(result2.error).toBeInstanceOf(UriNotAllowedError);
});

test('can provide CWT Id claim', async () => {
Expand All @@ -318,11 +321,12 @@ describe('CAT claims', () => {
kid: 'Symmetric256'
}
);
const cat = await validator.validate(base64encoded!, 'mac', {
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn'
});
expect(cat).toBeDefined();
expect(cat!.claims.cti).toEqual('a47019af6305d3652a918ae356cc2ca2');
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims.cti).toEqual('a47019af6305d3652a918ae356cc2ca2');
});

test('can auto generate a CWT Id claim', async () => {
Expand All @@ -337,10 +341,11 @@ describe('CAT claims', () => {
generateCwtId: true
}
);
const cat = await validator.validate(base64encoded!, 'mac', {
const result = await validator.validate(base64encoded!, 'mac', {
issuer: 'eyevinn'
});
expect(cat).toBeDefined();
expect(cat!.claims.cti).toBeDefined();
expect(result.error).not.toBeDefined();
expect(result.cat).toBeDefined();
expect(result.cat!.claims.cti).toBeDefined();
});
});
18 changes: 14 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export interface CatGenerateOptions {
generateCwtId?: boolean;
}

export interface CatValidationResult {
cat?: CommonAccessToken;
error?: Error;
}

/**
* Options for the CAT object
*
Expand Down Expand Up @@ -76,7 +81,7 @@ export class CAT {
token: string,
type: CatValidationTypes,
opts: CatValidationOptions
) {
): Promise<CatValidationResult> {
const tokenWithoutPadding = token.trim();
let cat;
if (type == 'mac') {
Expand Down Expand Up @@ -114,11 +119,16 @@ export class CAT {
throw new Error('Unsupported validation type');
}
if (cat) {
const valid = await cat.isValid(opts);
if (valid) {
return cat;
try {
const valid = await cat.isValid(opts);
if (valid) {
return { cat, error: undefined };
}
} catch (err) {
return { cat, error: err as Error };
}
}
return { error: new Error('Unable to parse token') };
}

public async generate(
Expand Down
20 changes: 17 additions & 3 deletions src/validators/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,28 @@ export class HttpValidator {
url = new URL(`https://${host}${request.url}`);
}

let cat;
// Check for token in headers first
if (request.headers['cta-common-access-token']) {
const token = Array.isArray(request.headers['cta-common-access-token'])
? request.headers['cta-common-access-token'][0]
: request.headers['cta-common-access-token'];
try {
const cat = await validator.validate(token, 'mac', {
const result = await validator.validate(token, 'mac', {
issuer: this.opts.issuer,
audience: this.opts.audience,
url
});
return { status: 200, claims: cat?.claims };
cat = result.cat;
if (!result.error) {
return { status: 200, claims: cat?.claims };
} else {
return {
status: 401,
message: result.error.message,
claims: cat?.claims
};
}
} catch (err) {
if (
err instanceof InvalidIssuerError ||
Expand All @@ -125,7 +135,11 @@ export class HttpValidator {
err instanceof TokenExpiredError ||
err instanceof UriNotAllowedError
) {
return { status: 401, message: (err as Error).message };
return {
status: 401,
message: (err as Error).message,
claims: cat?.claims
};
} else {
console.log(`Internal error`, err);
return { status: 500, message: (err as Error).message };
Expand Down
Loading