dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
|
||||
function digest(value) { return createHash('sha256').update(value).digest('hex') }
|
||||
function token(random) { return `dshcap_${random(32).toString('base64url')}` }
|
||||
|
||||
export function capabilityTarget(path) {
|
||||
if (typeof path !== 'string' || path.length === 0 || path.length > 8_192 || !path.startsWith('/')) {
|
||||
throw new TypeError('capability target is invalid')
|
||||
}
|
||||
const value = new URL(path, 'http://capability.internal')
|
||||
if (value.origin !== 'http://capability.internal' || value.hash !== '') throw new TypeError('capability target is invalid')
|
||||
return `${value.pathname}${value.search}`
|
||||
}
|
||||
|
||||
export class CapabilityStore {
|
||||
constructor({ now = Date.now, random = randomBytes, ttlMs = 5_000 } = {}) {
|
||||
this.now = now
|
||||
this.random = random
|
||||
this.ttlMs = ttlMs
|
||||
this.capabilities = new Map()
|
||||
}
|
||||
|
||||
prune() {
|
||||
const now = this.now()
|
||||
for (const [key, value] of this.capabilities) if (value.expiresAt <= now) this.capabilities.delete(key)
|
||||
}
|
||||
|
||||
issue(session, account, { audience, method, target }) {
|
||||
if (!['management', 'maintenance', 'plugin'].includes(audience)) throw new TypeError('capability audience is invalid')
|
||||
if (!/^(?:GET|HEAD|POST|PUT|DELETE)$/.test(method ?? '')) throw new TypeError('capability method is invalid')
|
||||
this.prune()
|
||||
const value = token(this.random)
|
||||
this.capabilities.set(digest(value), {
|
||||
audience,
|
||||
method,
|
||||
target: capabilityTarget(target),
|
||||
sessionId: session.sessionId,
|
||||
accountId: account.accountId,
|
||||
mainCredentialVersion: account.mainCredential.version,
|
||||
managementAdditionalCredentialVersion: account.managementAdditionalCredential.enabled
|
||||
? account.managementAdditionalCredential.version : null,
|
||||
managementAccessVersion: account.managementAccess.version,
|
||||
expiresAt: this.now() + this.ttlMs,
|
||||
})
|
||||
return Object.freeze({ token: value, expiresAt: new Date(this.now() + this.ttlMs).toISOString() })
|
||||
}
|
||||
|
||||
consume(value, account, { audience, method, target }) {
|
||||
this.prune()
|
||||
if (typeof value !== 'string' || value.length > 512) return undefined
|
||||
const key = digest(value)
|
||||
const capability = this.capabilities.get(key)
|
||||
this.capabilities.delete(key)
|
||||
const additionalVersion = account.managementAdditionalCredential.enabled
|
||||
? account.managementAdditionalCredential.version : null
|
||||
if (capability === undefined || capability.accountId !== account.accountId
|
||||
|| capability.mainCredentialVersion !== account.mainCredential.version
|
||||
|| capability.managementAdditionalCredentialVersion !== additionalVersion
|
||||
|| capability.managementAccessVersion !== account.managementAccess.version
|
||||
|| capability.audience !== audience || capability.method !== method
|
||||
|| capability.target !== capabilityTarget(target)) return undefined
|
||||
return Object.freeze({ sessionId: capability.sessionId, audience: capability.audience })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user