Files
jose/test/jws/flattened.sign.test.ts
Filip Skokan 4d970fe995 refactor: validate each token once rather than once per layer
Route compact, flattened, general, and JWT operations through shared JWS
and JWE cores so each token is validated, parsed, and encoded once.
Consolidate critical-header and algorithm-option validation.

Signed-off-by: Filip Skokan <[email protected]>
2026-07-29 18:50:54 +02:00

161 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'ava'
import { FlattenedSign } from '../../src/index.js'
test.before(async (t) => {
const encode = TextEncoder.prototype.encode.bind(new TextEncoder())
t.context.payload = encode('It’s a dangerous business, Frodo, going out your door.')
t.context.secret = new Uint8Array(32)
})
test('FlattenedSign', async (t) => {
{
const jws = await new FlattenedSign(t.context.payload)
.setProtectedHeader({ alg: 'HS256' })
.setUnprotectedHeader({ foo: 'bar' })
.sign(t.context.secret)
t.deepEqual(jws, {
header: {
foo: 'bar',
},
payload: 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4',
protected: 'eyJhbGciOiJIUzI1NiJ9',
signature: 'UKohvCM6JaKEJlDt7ApBPgcQMW4lmp-UGXfwPmCfUaA',
})
}
{
const jws = await new FlattenedSign(t.context.payload)
.setProtectedHeader({ alg: 'HS256' })
.sign(t.context.secret)
t.deepEqual(jws, {
protected: 'eyJhbGciOiJIUzI1NiJ9',
payload: 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4',
signature: 'UKohvCM6JaKEJlDt7ApBPgcQMW4lmp-UGXfwPmCfUaA',
})
}
{
const jws = await new FlattenedSign(t.context.payload)
.setUnprotectedHeader({ alg: 'HS256' })
.sign(t.context.secret)
t.deepEqual(jws, {
header: {
alg: 'HS256',
},
payload: 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4',
signature: 'O7HdMZ_6_aEQWLGGItmCKN3pf8-nZ9mHnPfT7rrPCwk',
})
}
{
for (const value of [
undefined,
null,
{},
'',
'foo',
1,
0,
true,
false,
[],
new FlattenedSign(new Uint8Array()),
]) {
t.throws(() => new FlattenedSign(value), {
instanceOf: TypeError,
message: 'payload must be an instance of Uint8Array',
})
}
}
})
test('FlattenedSign.prototype.setProtectedHeader', (t) => {
t.throws(
() => new FlattenedSign(t.context.payload).setProtectedHeader({}).setProtectedHeader({}),
{
instanceOf: TypeError,
message: 'setProtectedHeader can only be called once',
},
)
})
test('FlattenedSign.prototype.setUnprotectedHeader', (t) => {
t.throws(
() => new FlattenedSign(t.context.payload).setUnprotectedHeader({}).setUnprotectedHeader({}),
{
instanceOf: TypeError,
message: 'setUnprotectedHeader can only be called once',
},
)
})
test('FlattenedSign.prototype.sign must have a JOSE header', async (t) => {
await t.throwsAsync(new FlattenedSign(t.context.payload).sign(t.context.secret), {
code: 'ERR_JWS_INVALID',
message: 'either setProtectedHeader or setUnprotectedHeader must be called before #sign()',
})
})
test('FlattenedSign.prototype.sign JOSE header must be disjoint', async (t) => {
await t.throwsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader({ alg: 'HS256' })
.setUnprotectedHeader({ alg: 'HS256' })
.sign(t.context.secret),
{
code: 'ERR_JWS_INVALID',
message: 'JWS Protected and JWS Unprotected Header Parameter names must be disjoint',
},
)
})
test('FlattenedSign.prototype.sign JOSE header have an alg', async (t) => {
await t.throwsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader({})
.setUnprotectedHeader({})
.sign(t.context.secret),
{
code: 'ERR_JWS_INVALID',
message: 'JWS "alg" (Algorithm) Header Parameter missing or invalid',
},
)
await t.notThrowsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader({ alg: 'HS256' })
.setUnprotectedHeader({ foo: 'bar' })
.sign(t.context.secret),
)
await t.notThrowsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader({ alg: 'HS256' })
.sign(t.context.secret),
)
await t.notThrowsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader({ foo: 'bar' })
.setUnprotectedHeader({ alg: 'HS256' })
.sign(t.context.secret),
)
await t.notThrowsAsync(
new FlattenedSign(t.context.payload)
.setUnprotectedHeader({ alg: 'HS256' })
.sign(t.context.secret),
)
})
test('FlattenedSign only uses serialized protected header parameters', async (t) => {
const inherited = Object.create({ alg: 'HS256' })
const nonEnumerable = Object.defineProperty({}, 'alg', { value: 'HS256' })
for (const protectedHeader of [inherited, nonEnumerable]) {
await t.throwsAsync(
new FlattenedSign(t.context.payload)
.setProtectedHeader(protectedHeader)
.sign(t.context.secret),
{
code: 'ERR_JWS_INVALID',
message: 'JWS "alg" (Algorithm) Header Parameter missing or invalid',
},
)
}
})