2024-03-22 02:25:36 +00:00
|
|
|
import * as jose from 'jose'
|
|
|
|
import nock from 'nock'
|
|
|
|
import {getIDTokenClaims} from '../src/oidc'
|
|
|
|
|
|
|
|
describe('getIDTokenClaims', () => {
|
|
|
|
const originalEnv = process.env
|
2024-08-08 22:10:57 +00:00
|
|
|
const issuer = 'https://token.actions.example.ghe.com'
|
2024-03-22 02:25:36 +00:00
|
|
|
const audience = 'nobody'
|
|
|
|
const requestToken = 'token'
|
|
|
|
const openidConfigPath = '/.well-known/openid-configuration'
|
|
|
|
const jwksPath = '/.well-known/jwks.json'
|
|
|
|
const tokenPath = '/token'
|
|
|
|
const openIDConfig = {jwks_uri: `${issuer}${jwksPath}`}
|
|
|
|
|
|
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
|
|
let key: any
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
process.env = {
|
|
|
|
...originalEnv,
|
|
|
|
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
|
|
|
|
ACTIONS_ID_TOKEN_REQUEST_TOKEN: requestToken
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate JWT signing key
|
|
|
|
key = await jose.generateKeyPair('PS256')
|
|
|
|
|
|
|
|
// Create JWK and JWKS
|
|
|
|
const jwk = await jose.exportJWK(key.publicKey)
|
|
|
|
const jwks = {keys: [jwk]}
|
|
|
|
|
|
|
|
nock(issuer).get(openidConfigPath).reply(200, openIDConfig)
|
|
|
|
nock(issuer).get(jwksPath).reply(200, jwks)
|
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
process.env = originalEnv
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when ID token is valid', () => {
|
|
|
|
const claims = {
|
|
|
|
iss: issuer,
|
|
|
|
aud: audience,
|
|
|
|
ref: 'ref',
|
|
|
|
sha: 'sha',
|
|
|
|
repository: 'repo',
|
|
|
|
event_name: 'push',
|
2024-06-05 21:36:23 +00:00
|
|
|
job_workflow_ref: 'job_workflow_ref',
|
|
|
|
workflow_ref: 'workflow',
|
2024-03-22 02:25:36 +00:00
|
|
|
repository_id: '1',
|
|
|
|
repository_owner_id: '1',
|
|
|
|
runner_environment: 'github-hosted',
|
|
|
|
run_id: '1',
|
|
|
|
run_attempt: '1'
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const jwt = await new jose.SignJWT(claims)
|
|
|
|
.setProtectedHeader({alg: 'PS256'})
|
|
|
|
.sign(key.privateKey)
|
|
|
|
|
|
|
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('returns the ID token claims', async () => {
|
2024-08-08 22:10:57 +00:00
|
|
|
const result = await getIDTokenClaims()
|
2024-03-22 02:25:36 +00:00
|
|
|
expect(result).toEqual(claims)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when ID token is missing required claims', () => {
|
|
|
|
const claims = {
|
|
|
|
iss: issuer,
|
|
|
|
aud: audience
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const jwt = await new jose.SignJWT(claims)
|
|
|
|
.setProtectedHeader({alg: 'PS256'})
|
|
|
|
.sign(key.privateKey)
|
|
|
|
|
|
|
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('throws an error', async () => {
|
2024-08-08 22:10:57 +00:00
|
|
|
await expect(getIDTokenClaims()).rejects.toThrow(/missing claims/i)
|
2024-03-22 02:25:36 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when ID has the wrong issuer', () => {
|
|
|
|
const claims = {foo: 'bar', iss: 'foo', aud: 'nobody'}
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const jwt = await new jose.SignJWT(claims)
|
|
|
|
.setProtectedHeader({alg: 'PS256'})
|
|
|
|
.sign(key.privateKey)
|
|
|
|
|
|
|
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('throws an error', async () => {
|
2024-08-08 22:10:57 +00:00
|
|
|
await expect(getIDTokenClaims()).rejects.toThrow(/issuer mismatch/i)
|
2024-03-22 02:25:36 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when ID has the wrong audience', () => {
|
|
|
|
const claims = {foo: 'bar', iss: issuer, aud: 'bar'}
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const jwt = await new jose.SignJWT(claims)
|
|
|
|
.setProtectedHeader({alg: 'PS256'})
|
|
|
|
.sign(key.privateKey)
|
|
|
|
|
|
|
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('throw an error', async () => {
|
2024-08-08 22:10:57 +00:00
|
|
|
await expect(getIDTokenClaims()).rejects.toThrow(/verification failed/i)
|
2024-03-22 02:25:36 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when openid config cannot be retrieved', () => {
|
|
|
|
const claims = {foo: 'bar', iss: issuer, aud: 'nobody'}
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const jwt = await new jose.SignJWT(claims)
|
|
|
|
.setProtectedHeader({alg: 'PS256'})
|
|
|
|
.sign(key.privateKey)
|
|
|
|
|
|
|
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
|
|
|
|
|
|
// Disable the openid config endpoint
|
|
|
|
nock.removeInterceptor({
|
|
|
|
proto: 'https',
|
|
|
|
hostname: 'example.com',
|
|
|
|
port: '443',
|
|
|
|
method: 'GET',
|
|
|
|
path: openidConfigPath
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('throws an error', async () => {
|
2024-08-08 22:10:57 +00:00
|
|
|
await expect(getIDTokenClaims()).rejects.toThrow(/failed to get id/i)
|
2024-03-22 02:25:36 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|