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,120 @@
|
||||
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'
|
||||
|
||||
function digest(value) { return createHash('sha256').update(value).digest() }
|
||||
function token(prefix, random, bytes = 32) { return `${prefix}_${random(bytes).toString('base64url')}` }
|
||||
function sameDigest(value, expected) {
|
||||
if (typeof value !== 'string' || expected === null) return false
|
||||
const actual = digest(value)
|
||||
return actual.byteLength === expected.byteLength && timingSafeEqual(actual, expected)
|
||||
}
|
||||
|
||||
export class ManagementTransitionStore {
|
||||
constructor({ now = Date.now, random = randomBytes, ttlMs = 30_000 } = {}) {
|
||||
this.now = now
|
||||
this.random = random
|
||||
this.ttlMs = ttlMs
|
||||
this.transitions = new Map()
|
||||
this.continuations = new Map()
|
||||
}
|
||||
|
||||
prune() {
|
||||
const now = this.now()
|
||||
for (const [id, value] of this.transitions) if (value.expiresAt <= now) this.transitions.delete(id)
|
||||
for (const [key, value] of this.continuations) if (value.expiresAt <= now) this.continuations.delete(key)
|
||||
}
|
||||
|
||||
create({ account, instanceId, sessionId, sourceOrigin, sourceDshOrigin, sourceDshSessionId, mode, isolatedEntry, candidateOrigin }) {
|
||||
this.prune()
|
||||
const transitionId = token('dshmt', this.random, 16)
|
||||
const nonce = candidateOrigin === null ? null : token('dshmn', this.random)
|
||||
const expiresAt = this.now() + this.ttlMs
|
||||
this.transitions.set(transitionId, {
|
||||
accountId: account.accountId,
|
||||
accountRevision: account.revision,
|
||||
managementAccessVersion: account.managementAccess.version,
|
||||
instanceId,
|
||||
sessionId,
|
||||
sourceOrigin,
|
||||
sourceDshOrigin,
|
||||
sourceDshSessionId,
|
||||
mode,
|
||||
isolatedEntry,
|
||||
candidateOrigin,
|
||||
nonceDigest: nonce === null ? null : digest(nonce),
|
||||
proofDigest: null,
|
||||
expiresAt,
|
||||
})
|
||||
return Object.freeze({
|
||||
transitionId,
|
||||
nonce,
|
||||
candidateOrigin,
|
||||
expiresAt: new Date(expiresAt).toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
probe({ transitionId, nonce, candidateOrigin, sourceOrigin, instanceId }) {
|
||||
this.prune()
|
||||
const transition = this.transitions.get(transitionId)
|
||||
if (transition === undefined || transition.candidateOrigin !== candidateOrigin
|
||||
|| transition.sourceOrigin !== sourceOrigin || transition.instanceId !== instanceId
|
||||
|| !sameDigest(nonce, transition.nonceDigest)) return undefined
|
||||
transition.nonceDigest = null
|
||||
const proof = token('dshmpf', this.random)
|
||||
transition.proofDigest = digest(proof)
|
||||
return Object.freeze({ proof, expiresAt: new Date(transition.expiresAt).toISOString() })
|
||||
}
|
||||
|
||||
inspect({ transitionId, proof, account, sessionId }) {
|
||||
this.prune()
|
||||
const transition = this.transitions.get(transitionId)
|
||||
if (transition === undefined || transition.accountId !== account.accountId
|
||||
|| transition.accountRevision !== account.revision
|
||||
|| transition.managementAccessVersion !== account.managementAccess.version
|
||||
|| transition.sessionId !== sessionId
|
||||
|| (transition.candidateOrigin !== null && !sameDigest(proof, transition.proofDigest))) return undefined
|
||||
return transition
|
||||
}
|
||||
|
||||
consume(value) {
|
||||
const transition = this.inspect(value)
|
||||
if (transition === undefined) return undefined
|
||||
const { transitionId } = value
|
||||
this.transitions.delete(transitionId)
|
||||
return transition
|
||||
}
|
||||
|
||||
createContinuation({ account, targetOrigin, sourceDshOrigin, sourceDshSessionId }) {
|
||||
if (targetOrigin === null) return null
|
||||
this.prune()
|
||||
const continuation = token('dshmc', this.random)
|
||||
const expiresAt = this.now() + this.ttlMs
|
||||
this.continuations.set(digest(continuation).toString('hex'), {
|
||||
accountId: account.accountId,
|
||||
accountRevision: account.revision,
|
||||
managementAccessVersion: account.managementAccess.version,
|
||||
targetOrigin,
|
||||
sourceDshOrigin,
|
||||
sourceDshSessionId,
|
||||
expiresAt,
|
||||
})
|
||||
return Object.freeze({ token: continuation, expiresAt: new Date(expiresAt).toISOString() })
|
||||
}
|
||||
|
||||
consumeContinuation({ token: value, account, targetOrigin }) {
|
||||
this.prune()
|
||||
if (typeof value !== 'string' || value.length > 512) return undefined
|
||||
const key = digest(value).toString('hex')
|
||||
const continuation = this.continuations.get(key)
|
||||
this.continuations.delete(key)
|
||||
if (continuation === undefined || continuation.accountId !== account.accountId
|
||||
|| continuation.accountRevision !== account.revision
|
||||
|| continuation.managementAccessVersion !== account.managementAccess.version
|
||||
|| continuation.targetOrigin !== targetOrigin) return undefined
|
||||
return continuation
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.transitions.clear()
|
||||
this.continuations.clear()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user