Skip to content

Commit

Permalink
feat: added utility function for decoding token's protected header
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Dec 16, 2020
1 parent 55b7781 commit fa29d68
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@
"import": "./dist/node/webcrypto/esm/util/base64url.js",
"require": "./dist/node/webcrypto/cjs/util/base64url.js"
},
"./util/decode_protected_header": {
"browser": "./dist/browser/util/decode_protected_header.js",
"import": "./dist/node/esm/util/decode_protected_header.js",
"require": "./dist/node/cjs/util/decode_protected_header.js"
},
"./webcrypto/util/decode_protected_header": {
"import": "./dist/node/webcrypto/esm/util/decode_protected_header.js",
"require": "./dist/node/webcrypto/cjs/util/decode_protected_header.js"
},
"./util/errors": {
"browser": "./dist/browser/util/errors.js",
"import": "./dist/node/esm/util/errors.js",
Expand Down
58 changes: 58 additions & 0 deletions src/util/decode_protected_header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ok as assert } from 'assert'

import { decode as base64url } from './base64url.js'
import { decoder } from '../lib/buffer_utils.js'
import isObject from '../lib/is_object.js'
import type { JWSHeaderParameters, JWEHeaderParameters } from '../types.d'

export type ProtectedHeaderParameters = JWSHeaderParameters & JWEHeaderParameters

/**
* Decodes the Protected Header of a JWE/JWS/JWT token utilizing any encoding.
*
* @example
* ```
* // ESM import
* import decodeProtectedHeader from 'jose/util/decode_protected_header'
* ```
*
* @example
* ```
* // CJS import
* const { default: decodeProtectedHeader } = require('jose/util/decode_protected_header')
* ```
*
* @example
* ```
* // usage
* const protectedHeader = decodeProtectedHeader(token)
* console.log(protectedHeader)
* ```
*
* @param token JWE/JWS/JWT token in any encoding.
*/
export default function decodeProtectedHeader(token: string | object) {
let protectedB64u!: string

if (typeof token === 'string') {
const parts = token.split('.')
if (parts.length === 3 || parts.length === 5) {
;[protectedB64u] = parts
}
} else if (typeof token === 'object' && token) {
if ('protected' in token) {
protectedB64u = (<{ protected: string }>token).protected
} else {
throw new TypeError('Token does not contain a Protected Header')
}
}

try {
assert(typeof protectedB64u === 'string' && protectedB64u)
const result = JSON.parse(decoder.decode(base64url(protectedB64u!)))
assert(isObject(result))
return <ProtectedHeaderParameters>result
} catch (err) {
throw new TypeError('Invalid Token or Protected Header formatting')
}
}
94 changes: 94 additions & 0 deletions test/util/decode_protected_header.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import test from 'ava';

const root = !('WEBCRYPTO' in process.env) ? '#dist' : '#dist/webcrypto';

import(`${root}/util/decode_protected_header`).then(
({ default: decodeProtectedHeader }) => {
test('invalid inputs', (t) => {
t.throws(() => decodeProtectedHeader(null), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('.'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('ew..'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('bnVsbA..'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('W10..'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('...'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('ew....'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('bnVsbA....'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('W10....'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader('.....'), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader({ protected: null }), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader({ protected: 'ew' }), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader({ protected: 'bnVsbA' }), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader({ protected: 'W10' }), {
instanceOf: TypeError,
message: 'Invalid Token or Protected Header formatting',
});

t.throws(() => decodeProtectedHeader({}), {
instanceOf: TypeError,
message: 'Token does not contain a Protected Header',
});

t.deepEqual(decodeProtectedHeader('eyJhbGciOiJIUzI1NiJ9..'), { alg: 'HS256' });
t.deepEqual(decodeProtectedHeader('eyJhbGciOiJIUzI1NiJ9....'), { alg: 'HS256' });
t.deepEqual(decodeProtectedHeader({ protected: 'eyJhbGciOiJIUzI1NiJ9' }), { alg: 'HS256' });
});
},
(err) => {
test('failed to import', (t) => {
console.error(err);
t.fail();
});
},
);
1 change: 1 addition & 0 deletions tsconfig/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"../src/util/errors.ts",
"../src/util/generate_key_pair.ts",
"../src/util/generate_secret.ts",
"../src/util/decode_protected_header.ts",
"../src/util/random.ts"
],
"compilerOptions": {
Expand Down

0 comments on commit fa29d68

Please sign in to comment.