Orginize
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Opaque key-handle registry: TTL, optional max uses, revocation (no key material).
|
||||
*/
|
||||
|
||||
/** @typedef {{ id: string, purpose: string, algorithm: string, atMs: number, expiresAtMs: number | null, maxUses: number, uses: number, scopes: string[], revoked: boolean }} BareOsSecretHandleRow */
|
||||
|
||||
/** @type {Map<string, BareOsSecretHandleRow>} */
|
||||
const handles = new Map()
|
||||
let seq = 0
|
||||
|
||||
const MAX_HANDLES = 4096
|
||||
|
||||
/**
|
||||
* @param {{ purpose?: string, algorithm?: string, ttlMs?: number | null, maxUses?: number, scopes?: string[] }} opts
|
||||
*/
|
||||
export function bareOsSecretHandleAcquire(opts = {}) {
|
||||
if (handles.size >= MAX_HANDLES) {
|
||||
for (const [id, h] of handles) {
|
||||
if (h.revoked || bareOsSecretHandleIsExpired(h)) {
|
||||
handles.delete(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
const purpose = String(opts.purpose || 'default').trim().slice(0, 64) || 'default'
|
||||
const algorithm = String(opts.algorithm || 'opaque').trim().slice(0, 32) || 'opaque'
|
||||
const ttlRaw =
|
||||
opts.ttlMs != null
|
||||
? Number.parseInt(String(opts.ttlMs), 10)
|
||||
: Number.NaN
|
||||
const ttlMs =
|
||||
Number.isFinite(ttlRaw) && ttlRaw > 0
|
||||
? Math.min(ttlRaw, 86400000)
|
||||
: null
|
||||
const maxUses =
|
||||
opts.maxUses != null && Number.isFinite(Number(opts.maxUses)) && Number(opts.maxUses) > 0
|
||||
? Math.min(1e6, Math.floor(Number(opts.maxUses)))
|
||||
: 1e9
|
||||
const scopes = Array.isArray(opts.scopes)
|
||||
? opts.scopes
|
||||
.map((s) => String(s).trim().slice(0, 48))
|
||||
.filter(Boolean)
|
||||
.slice(0, 8)
|
||||
: []
|
||||
const atMs = Date.now()
|
||||
const id = `kh_${++seq}_${atMs.toString(36)}`
|
||||
const row = {
|
||||
id,
|
||||
purpose,
|
||||
algorithm,
|
||||
atMs,
|
||||
expiresAtMs: ttlMs != null ? atMs + ttlMs : null,
|
||||
maxUses,
|
||||
uses: 0,
|
||||
scopes: scopes.length ? scopes : ['default'],
|
||||
revoked: false
|
||||
}
|
||||
handles.set(id, row)
|
||||
return {
|
||||
handle: id,
|
||||
algorithm,
|
||||
atMs,
|
||||
ttlMs,
|
||||
scopes: row.scopes,
|
||||
zeroizeAfterMs: ttlMs
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {BareOsSecretHandleRow} h */
|
||||
function bareOsSecretHandleIsExpired(h) {
|
||||
if (h.revoked) return true
|
||||
if (h.expiresAtMs != null && Date.now() > h.expiresAtMs) return true
|
||||
if (h.uses >= h.maxUses) return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function bareOsSecretHandleTouch(id) {
|
||||
const h = handles.get(String(id || ''))
|
||||
if (!h || bareOsSecretHandleIsExpired(h)) return false
|
||||
h.uses++
|
||||
if (h.uses >= h.maxUses) h.revoked = true
|
||||
return !h.revoked
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*/
|
||||
export function bareOsSecretHandleRelease(id) {
|
||||
const h = handles.get(String(id || ''))
|
||||
if (h) h.revoked = true
|
||||
handles.delete(String(id || ''))
|
||||
}
|
||||
|
||||
export function bareOsSecretHandlePruneExpired() {
|
||||
const now = Date.now()
|
||||
for (const [id, h] of handles) {
|
||||
if (h.revoked || (h.expiresAtMs != null && now > h.expiresAtMs) || h.uses >= h.maxUses)
|
||||
handles.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-secret snapshot for `/proc` / posture. */
|
||||
export function bareOsSecretHandleSnapshot() {
|
||||
bareOsSecretHandlePruneExpired()
|
||||
let active = 0
|
||||
for (const h of handles.values()) {
|
||||
if (!bareOsSecretHandleIsExpired(h)) active++
|
||||
}
|
||||
return {
|
||||
schema: 1,
|
||||
totalRegistered: handles.size,
|
||||
activeHandles: active,
|
||||
cap: MAX_HANDLES,
|
||||
atMs: Date.now()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user