import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import nacl from 'tweetnacl'
import sealedBox from 'tweetnacl-sealedbox-js'
import putActionsSecret from './put-secret.ts'
import {
decodeBase64,
encodeBase64,
sealActionsSecret,
} from './sealed-box.ts'
function utf8(bytes: Uint8Array) {
return new TextDecoder().decode(bytes)
}
describe('sealActionsSecret', () => {
it('round-trips through a tweetnacl sealed box', () => {
const keyPair = nacl.box.keyPair()
const publicKey = encodeBase64(keyPair.publicKey)
const sealed = sealActionsSecret('cache-token-value', publicKey)
const opened = sealedBox.open(
decodeBase64(sealed),
keyPair.publicKey,
keyPair.secretKey,
)
assert.equal(utf8(opened), 'cache-token-value')
})
})
describe('putActionsSecret', () => {
it('encrypts, PUTs, and never returns the plaintext', async () => {
const keyPair = nacl.box.keyPair()
const calls: Array<{
path: string
method?: string
body?: unknown
}> = []
const result = await putActionsSecret(
{
owner: 'kentcdodds',
repo: 'kody',
name: 'nx_self_hosted_remote_cache_access_token',
value: 'super-secret-token',
},
async (options) => {
calls.push({
path: options.path,
method: options.method,
body: options.body,
})
if (options.path.endsWith('/public-key')) {
return {
account: 'bot',
url: options.path,
ok: true,
status: 200,
statusText: 'OK',
headers: {},
data: {
key_id: 'key-123',
key: encodeBase64(keyPair.publicKey),
},
text: '',
}
}
return {
account: 'bot',
url: options.path,
ok: true,
status: 201,
statusText: 'Created',
headers: {},
data: null,
text: '',
}
},
)
assert.deepEqual(result, {
owner: 'kentcdodds',
repo: 'kody',
name: 'NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN',
environment: null,
created: true,
updated: false,
status: 201,
keyId: 'key-123',
generated: false,
})
assert.equal(
JSON.stringify(result).includes('super-secret-token'),
false,
)
assert.equal(calls[0]?.path, '/repos/kentcdodds/kody/actions/secrets/public-key')
assert.equal(
calls[1]?.path,
'/repos/kentcdodds/kody/actions/secrets/NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN',
)
assert.equal(calls[1]?.method, 'PUT')
const body = calls[1]?.body as {
encrypted_value: string
key_id: string
}
assert.equal(body.key_id, 'key-123')
assert.equal(body.encrypted_value.includes('super-secret-token'), false)
const opened = sealedBox.open(
decodeBase64(body.encrypted_value),
keyPair.publicKey,
keyPair.secretKey,
)
assert.equal(utf8(opened), 'super-secret-token')
})
it('rejects missing value and generateBytes together', async () => {
await assert.rejects(
() => putActionsSecret({ owner: 'kentcdodds', repo: 'kody', name: 'FOO' }),
/Provide value or generateBytes/,
)
})
})