feat(api): advance capability and secure operations slices
This commit is contained in:
@@ -29,11 +29,19 @@ export class DeleteInstanceOperationError extends Error {
|
||||
|
||||
interface PreparationRow {
|
||||
operation_id: string;
|
||||
risk_level: string;
|
||||
status: string;
|
||||
target_instance_id: string;
|
||||
target_revision: number;
|
||||
target_origin: string;
|
||||
method: string;
|
||||
path: string;
|
||||
canonical_query: string;
|
||||
body_digest: string;
|
||||
content_type: string;
|
||||
parameter_schema_id: string;
|
||||
parameters_digest: string;
|
||||
nonce: string;
|
||||
token_digest: string;
|
||||
expires_at: string;
|
||||
}
|
||||
@@ -128,8 +136,10 @@ export class DeleteInstanceOperation {
|
||||
this.db
|
||||
.prepare(
|
||||
`INSERT INTO operation_preparations
|
||||
(id,operation_id,risk_level,status,target_instance_id,target_revision,parameter_schema_id,parameters_digest,token_digest,requested_by,request_id,expires_at,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
(id,operation_id,risk_level,status,target_instance_id,target_revision,target_origin,method,path,
|
||||
canonical_query,body_digest,content_type,parameter_schema_id,parameters_digest,nonce,token_digest,
|
||||
requested_by,request_id,expires_at,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
)
|
||||
.run(
|
||||
id,
|
||||
@@ -138,8 +148,15 @@ export class DeleteInstanceOperation {
|
||||
'prepared',
|
||||
target.instanceId,
|
||||
target.revision,
|
||||
current.origin,
|
||||
'DELETE',
|
||||
`/api/v1/instances/${target.instanceId}`,
|
||||
'',
|
||||
digest(''),
|
||||
'',
|
||||
DELETE_INSTANCE_PARAMETER_SCHEMA_ID,
|
||||
PARAMETERS_DIGEST,
|
||||
`delete-${id}`,
|
||||
digest(token),
|
||||
ACTOR,
|
||||
requestId,
|
||||
@@ -169,8 +186,9 @@ export class DeleteInstanceOperation {
|
||||
this.db.transaction(() => {
|
||||
const row = this.db
|
||||
.prepare(
|
||||
`SELECT operation_id,status,target_instance_id,target_revision,parameter_schema_id,
|
||||
parameters_digest,token_digest,expires_at
|
||||
`SELECT operation_id,risk_level,status,target_instance_id,target_revision,target_origin,
|
||||
method,path,canonical_query,body_digest,content_type,parameter_schema_id,
|
||||
parameters_digest,nonce,token_digest,expires_at
|
||||
FROM operation_preparations WHERE id=?`,
|
||||
)
|
||||
.get(input.preparationId) as PreparationRow | undefined;
|
||||
@@ -180,15 +198,44 @@ export class DeleteInstanceOperation {
|
||||
'Confirmation could not be accepted',
|
||||
);
|
||||
|
||||
const secretValid =
|
||||
row.expires_at > now &&
|
||||
equalDigest(row.token_digest, digest(input.confirmationToken)) &&
|
||||
const confirmationValid =
|
||||
row.expires_at > now && equalDigest(row.token_digest, digest(input.confirmationToken));
|
||||
if (!confirmationValid) return;
|
||||
|
||||
const expectedPath = `/api/v1/instances/${input.instanceId}`;
|
||||
const bindingValid =
|
||||
row.operation_id === OPERATION_ID &&
|
||||
row.risk_level === 'R3' &&
|
||||
row.target_instance_id === input.instanceId &&
|
||||
row.target_revision === input.revision &&
|
||||
row.method === 'DELETE' &&
|
||||
row.path === expectedPath &&
|
||||
row.canonical_query === '' &&
|
||||
row.body_digest === digest('') &&
|
||||
row.content_type === '' &&
|
||||
row.parameter_schema_id === DELETE_INSTANCE_PARAMETER_SCHEMA_ID &&
|
||||
row.parameters_digest === PARAMETERS_DIGEST;
|
||||
if (!secretValid) return;
|
||||
row.parameters_digest === PARAMETERS_DIGEST &&
|
||||
row.nonce === `delete-${input.preparationId}`;
|
||||
if (!bindingValid) {
|
||||
this.db
|
||||
.prepare(
|
||||
"UPDATE operation_preparations SET status='invalidated',consumed_at=?,updated_at=? WHERE id=? AND status='prepared'",
|
||||
)
|
||||
.run(now, now, input.preparationId);
|
||||
return;
|
||||
}
|
||||
|
||||
const current = this.db
|
||||
.prepare('SELECT base_url,config_revision FROM instances WHERE id=?')
|
||||
.get(input.instanceId) as { base_url: string; config_revision: number } | undefined;
|
||||
if (current && current.base_url !== row.target_origin) {
|
||||
this.db
|
||||
.prepare(
|
||||
"UPDATE operation_preparations SET status='invalidated',consumed_at=?,updated_at=? WHERE id=? AND status='prepared'",
|
||||
)
|
||||
.run(now, now, input.preparationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// A correctly authenticated confirmation is one-shot, including stale/missing target results.
|
||||
this.db
|
||||
@@ -196,9 +243,6 @@ export class DeleteInstanceOperation {
|
||||
"UPDATE operation_preparations SET status='consumed',consumed_at=?,updated_at=? WHERE id=? AND status='prepared'",
|
||||
)
|
||||
.run(now, now, input.preparationId);
|
||||
const current = this.db
|
||||
.prepare('SELECT config_revision FROM instances WHERE id=?')
|
||||
.get(input.instanceId) as { config_revision: number } | undefined;
|
||||
if (!current) {
|
||||
targetError = 'NOT_FOUND';
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user