Files
docker-registry-aio/app/server/utils/token.ts
T
ysj7705andCursor 0fd00bfa37 Add Docker Registry AIO with Nuxt Token Auth and admin UI.
Compose brings up registry:3 plus a Prisma/SQLite Nuxt app for docker login, ACL management, and image browsing with shadcn-vue.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 16:12:12 +09:00

88 lines
2.2 KiB
TypeScript

import {
createPrivateKey,
randomUUID,
X509Certificate,
} from 'node:crypto'
import { readFileSync } from 'node:fs'
import { SignJWT, importPKCS8 } from 'jose'
import { ensureTokenCerts } from './certs'
export type RegistryAccess = {
type: string
name: string
actions: string[]
}
type TokenMaterial = {
privateKeyPem: string
x5c: string
}
let cached: TokenMaterial | null = null
function toPkcs8Pem(privateKeyPem: string): string {
const key = createPrivateKey(privateKeyPem)
return key.export({ type: 'pkcs8', format: 'pem' }).toString()
}
function pemToX5C(certPem: string): string {
return certPem
.replace(/-----BEGIN CERTIFICATE-----/g, '')
.replace(/-----END CERTIFICATE-----/g, '')
.replace(/\s+/g, '')
}
function loadTokenMaterial(): TokenMaterial {
if (cached) return cached
const { keyPath, certPath } = ensureTokenCerts()
const rawPrivateKeyPem = readFileSync(keyPath, 'utf8')
const privateKeyPem = toPkcs8Pem(rawPrivateKeyPem)
const certPem = readFileSync(certPath, 'utf8')
// Validate certificate parses
void new X509Certificate(certPem)
cached = {
privateKeyPem,
x5c: pemToX5C(certPem),
}
return cached
}
export async function signRegistryToken(options: {
subject: string
access: RegistryAccess[]
expiresInSeconds?: number
}): Promise<{ token: string; expiresIn: number; issuedAt: string }> {
const { privateKeyPem, x5c } = loadTokenMaterial()
const privateKey = await importPKCS8(privateKeyPem, 'RS256')
const issuer = process.env.REGISTRY_AUTH_ISSUER || 'registry-aio'
const audience = process.env.REGISTRY_AUTH_SERVICE || 'registry'
const expiresIn = options.expiresInSeconds ?? 600
const now = Math.floor(Date.now() / 1000)
const issuedAt = new Date(now * 1000).toISOString()
const token = await new SignJWT({
access: options.access,
})
.setProtectedHeader({
alg: 'RS256',
typ: 'JWT',
// Registry v3 trusts tokens via x5c against rootcertbundle
x5c: [x5c],
})
.setIssuer(issuer)
.setSubject(options.subject)
.setAudience(audience)
.setIssuedAt(now)
.setNotBefore(now - 10)
.setExpirationTime(now + expiresIn)
.setJti(randomUUID())
.sign(privateKey)
return { token, expiresIn, issuedAt }
}