feat(api): advance capability and secure operations slices
This commit is contained in:
@@ -192,6 +192,78 @@ describe('DeleteInstanceOperation', () => {
|
||||
expect(db.prepare('SELECT COUNT(*) count FROM jobs').get()).toEqual({ count: 0 });
|
||||
});
|
||||
|
||||
it.each([
|
||||
['risk_level', 'R2'],
|
||||
['target_origin', 'http://192.168.1.99'],
|
||||
['method', 'POST'],
|
||||
['path', '/api/v1/instances/someone-else'],
|
||||
['canonical_query', 'force=true'],
|
||||
['body_digest', 'a'.repeat(64)],
|
||||
['content_type', 'application/json'],
|
||||
['nonce', 'tampered-nonce'],
|
||||
])(
|
||||
'fails closed and invalidates a confirmation with tampered durable %s binding',
|
||||
async (column, value) => {
|
||||
const { db, instances, operation } = fixture();
|
||||
const instance = await instances.create({ name: 'A', origin: 'http://192.168.1.10' });
|
||||
const prepared = await operation.prepare({
|
||||
operationId: 'deleteInstance',
|
||||
targets: [{ instanceId: instance.id, revision: 1 }],
|
||||
parameters: { parameterSchemaId: DELETE_INSTANCE_PARAMETER_SCHEMA_ID, fields: [] },
|
||||
});
|
||||
db.prepare(`UPDATE operation_preparations SET ${column}=? WHERE id=?`).run(
|
||||
value,
|
||||
prepared.id,
|
||||
);
|
||||
|
||||
await expect(
|
||||
operation.executeDelete({
|
||||
instanceId: instance.id,
|
||||
revision: 1,
|
||||
preparationId: prepared.id,
|
||||
confirmationToken: prepared.confirmationToken,
|
||||
actor: 'loopback-control-plane',
|
||||
requestId: 'tampered-binding',
|
||||
}),
|
||||
).rejects.toMatchObject({ code: 'CONFIRMATION_INVALID' });
|
||||
expect(await instances.get(instance.id)).toBeDefined();
|
||||
expect(
|
||||
db.prepare('SELECT status FROM operation_preparations WHERE id=?').get(prepared.id),
|
||||
).toEqual({ status: 'invalidated' });
|
||||
expect(db.prepare('SELECT COUNT(*) count FROM jobs').get()).toEqual({ count: 0 });
|
||||
},
|
||||
);
|
||||
|
||||
it('fails closed when the current target origin no longer matches the durable binding', async () => {
|
||||
const { db, instances, operation } = fixture();
|
||||
const instance = await instances.create({ name: 'A', origin: 'http://192.168.1.10' });
|
||||
const prepared = await operation.prepare({
|
||||
operationId: 'deleteInstance',
|
||||
targets: [{ instanceId: instance.id, revision: 1 }],
|
||||
parameters: { parameterSchemaId: DELETE_INSTANCE_PARAMETER_SCHEMA_ID, fields: [] },
|
||||
});
|
||||
db.prepare('UPDATE instances SET base_url=? WHERE id=?').run(
|
||||
'http://192.168.1.11',
|
||||
instance.id,
|
||||
);
|
||||
|
||||
await expect(
|
||||
operation.executeDelete({
|
||||
instanceId: instance.id,
|
||||
revision: 1,
|
||||
preparationId: prepared.id,
|
||||
confirmationToken: prepared.confirmationToken,
|
||||
actor: 'loopback-control-plane',
|
||||
requestId: 'changed-origin',
|
||||
}),
|
||||
).rejects.toMatchObject({ code: 'CONFIRMATION_INVALID' });
|
||||
expect(await instances.get(instance.id)).toBeDefined();
|
||||
expect(
|
||||
db.prepare('SELECT status FROM operation_preparations WHERE id=?').get(prepared.id),
|
||||
).toEqual({ status: 'invalidated' });
|
||||
expect(db.prepare('SELECT COUNT(*) count FROM jobs').get()).toEqual({ count: 0 });
|
||||
});
|
||||
|
||||
it('returns typed target errors after consuming a correctly bound confirmation', async () => {
|
||||
const { db, instances, operation } = fixture();
|
||||
const instance = await instances.create({ name: 'A', origin: 'http://192.168.1.10' });
|
||||
|
||||
Reference in New Issue
Block a user