207 lines
7.3 KiB
TypeScript
207 lines
7.3 KiB
TypeScript
|
|
import { assertEquals, assertRejects } from 'jsr:@std/assert';
|
||
|
|
import { describe, it } from 'jsr:@std/testing/bdd';
|
||
|
|
import { TypedRequest } from '@api.global/typedrequest';
|
||
|
|
import {
|
||
|
|
createTestContainer,
|
||
|
|
getTestPorts,
|
||
|
|
loginAndGetIdentity,
|
||
|
|
TEST_ACCESS_KEY,
|
||
|
|
} from './helpers/server.helper.ts';
|
||
|
|
import { ObjectStorageContainer } from '../ts/index.ts';
|
||
|
|
import type { IReq_CreateBucket, IReq_ListBuckets } from '../ts_interfaces/requests/buckets.ts';
|
||
|
|
import type {
|
||
|
|
IReq_AddCredential,
|
||
|
|
IReq_GetCredentials,
|
||
|
|
IReq_RemoveCredential,
|
||
|
|
} from '../ts_interfaces/requests/credentials.ts';
|
||
|
|
import type * as interfaces from '../ts_interfaces/index.ts';
|
||
|
|
|
||
|
|
const PORT_INDEX = 8;
|
||
|
|
const ports = getTestPorts(PORT_INDEX);
|
||
|
|
const url = `http://localhost:${ports.uiPort}/typedrequest`;
|
||
|
|
const storageDirectory = `.nogit/testdata-${PORT_INDEX}`;
|
||
|
|
|
||
|
|
const cleanupStorageDirectory = async () => {
|
||
|
|
try {
|
||
|
|
await Deno.remove(storageDirectory, { recursive: true });
|
||
|
|
} catch (error) {
|
||
|
|
if (!(error instanceof Deno.errors.NotFound)) {
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
describe('Credential persistence', { sanitizeResources: false, sanitizeOps: false }, () => {
|
||
|
|
it('persists managed credentials across restart and refreshes the internal client', async () => {
|
||
|
|
await cleanupStorageDirectory();
|
||
|
|
|
||
|
|
let activeContainer: ObjectStorageContainer | null = null;
|
||
|
|
|
||
|
|
const stopContainer = async () => {
|
||
|
|
if (!activeContainer) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await activeContainer.stop();
|
||
|
|
} finally {
|
||
|
|
activeContainer = null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
activeContainer = createTestContainer(PORT_INDEX);
|
||
|
|
await activeContainer.start();
|
||
|
|
|
||
|
|
let identity: interfaces.data.IIdentity = await loginAndGetIdentity(ports.uiPort);
|
||
|
|
|
||
|
|
const addCredential = new TypedRequest<IReq_AddCredential>(url, 'addCredential');
|
||
|
|
await addCredential.fire({
|
||
|
|
identity,
|
||
|
|
accessKeyId: 'persisted-key',
|
||
|
|
secretAccessKey: 'persisted-secret',
|
||
|
|
});
|
||
|
|
|
||
|
|
const removeCredential = new TypedRequest<IReq_RemoveCredential>(url, 'removeCredential');
|
||
|
|
await removeCredential.fire({ identity, accessKeyId: TEST_ACCESS_KEY });
|
||
|
|
|
||
|
|
const getCredentials = new TypedRequest<IReq_GetCredentials>(url, 'getCredentials');
|
||
|
|
const credentialsBeforeRestart = await getCredentials.fire({ identity });
|
||
|
|
assertEquals(credentialsBeforeRestart.credentials.length, 1);
|
||
|
|
assertEquals(credentialsBeforeRestart.credentials[0].accessKeyId, 'persisted-key');
|
||
|
|
|
||
|
|
const listBuckets = new TypedRequest<IReq_ListBuckets>(url, 'listBuckets');
|
||
|
|
const bucketsBeforeRestart = await listBuckets.fire({ identity });
|
||
|
|
assertEquals(Array.isArray(bucketsBeforeRestart.buckets), true);
|
||
|
|
|
||
|
|
await stopContainer();
|
||
|
|
|
||
|
|
activeContainer = createTestContainer(PORT_INDEX);
|
||
|
|
await activeContainer.start();
|
||
|
|
|
||
|
|
identity = await loginAndGetIdentity(ports.uiPort);
|
||
|
|
|
||
|
|
const credentialsAfterRestart = await getCredentials.fire({ identity });
|
||
|
|
assertEquals(credentialsAfterRestart.credentials.length, 1);
|
||
|
|
assertEquals(credentialsAfterRestart.credentials[0].accessKeyId, 'persisted-key');
|
||
|
|
|
||
|
|
const createBucket = new TypedRequest<IReq_CreateBucket>(url, 'createBucket');
|
||
|
|
await createBucket.fire({ identity, bucketName: 'persisted-creds-bucket' });
|
||
|
|
|
||
|
|
const bucketsAfterRestart = await listBuckets.fire({ identity });
|
||
|
|
assertEquals(
|
||
|
|
bucketsAfterRestart.buckets.some((bucket) => bucket.name === 'persisted-creds-bucket'),
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
} finally {
|
||
|
|
await stopContainer();
|
||
|
|
await cleanupStorageDirectory();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('lets explicit environment credentials override persisted managed credentials', async () => {
|
||
|
|
const portIndex = 10;
|
||
|
|
const envPorts = getTestPorts(portIndex);
|
||
|
|
const envUrl = `http://localhost:${envPorts.uiPort}/typedrequest`;
|
||
|
|
const envStorageDirectory = `.nogit/testdata-${portIndex}`;
|
||
|
|
const previousAccessKey = Deno.env.get('OBJST_ACCESS_KEY');
|
||
|
|
const previousSecretKey = Deno.env.get('OBJST_SECRET_KEY');
|
||
|
|
let container: ObjectStorageContainer | null = null;
|
||
|
|
|
||
|
|
const cleanupEnvStorageDirectory = async () => {
|
||
|
|
try {
|
||
|
|
await Deno.remove(envStorageDirectory, { recursive: true });
|
||
|
|
} catch (error) {
|
||
|
|
if (!(error instanceof Deno.errors.NotFound)) {
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
await cleanupEnvStorageDirectory();
|
||
|
|
await Deno.mkdir(`${envStorageDirectory}/.objectstorage`, { recursive: true });
|
||
|
|
await Deno.writeTextFile(
|
||
|
|
`${envStorageDirectory}/.objectstorage/admin-config.json`,
|
||
|
|
JSON.stringify({
|
||
|
|
accessCredentials: [{ accessKeyId: 'persisted-key', secretAccessKey: 'persisted-secret' }],
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
|
||
|
|
Deno.env.set('OBJST_ACCESS_KEY', 'env-key');
|
||
|
|
Deno.env.set('OBJST_SECRET_KEY', 'env-secret');
|
||
|
|
|
||
|
|
container = createTestContainer(portIndex, { storageDirectory: envStorageDirectory });
|
||
|
|
await container.start();
|
||
|
|
|
||
|
|
const identity = await loginAndGetIdentity(envPorts.uiPort);
|
||
|
|
const getCredentials = new TypedRequest<IReq_GetCredentials>(envUrl, 'getCredentials');
|
||
|
|
const response = await getCredentials.fire({ identity });
|
||
|
|
|
||
|
|
assertEquals(response.credentials.length, 1);
|
||
|
|
assertEquals(response.credentials[0].accessKeyId, 'env-key');
|
||
|
|
} finally {
|
||
|
|
if (container) {
|
||
|
|
await container.stop();
|
||
|
|
}
|
||
|
|
if (previousAccessKey === undefined) {
|
||
|
|
Deno.env.delete('OBJST_ACCESS_KEY');
|
||
|
|
} else {
|
||
|
|
Deno.env.set('OBJST_ACCESS_KEY', previousAccessKey);
|
||
|
|
}
|
||
|
|
if (previousSecretKey === undefined) {
|
||
|
|
Deno.env.delete('OBJST_SECRET_KEY');
|
||
|
|
} else {
|
||
|
|
Deno.env.set('OBJST_SECRET_KEY', previousSecretKey);
|
||
|
|
}
|
||
|
|
await cleanupEnvStorageDirectory();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not persist rejected credential replacements', async () => {
|
||
|
|
const portIndex = 11;
|
||
|
|
const rejectPorts = getTestPorts(portIndex);
|
||
|
|
const rejectUrl = `http://localhost:${rejectPorts.uiPort}/typedrequest`;
|
||
|
|
const rejectStorageDirectory = `.nogit/testdata-${portIndex}`;
|
||
|
|
let container: ObjectStorageContainer | null = null;
|
||
|
|
|
||
|
|
const cleanupRejectStorageDirectory = async () => {
|
||
|
|
try {
|
||
|
|
await Deno.remove(rejectStorageDirectory, { recursive: true });
|
||
|
|
} catch (error) {
|
||
|
|
if (!(error instanceof Deno.errors.NotFound)) {
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
await cleanupRejectStorageDirectory();
|
||
|
|
container = createTestContainer(portIndex, { storageDirectory: rejectStorageDirectory });
|
||
|
|
await container.start();
|
||
|
|
|
||
|
|
await assertRejects(() =>
|
||
|
|
container!.replaceAccessCredentials([
|
||
|
|
{ accessKeyId: 'duplicate-key', secretAccessKey: 'secret-a' },
|
||
|
|
{ accessKeyId: 'duplicate-key', secretAccessKey: 'secret-b' },
|
||
|
|
])
|
||
|
|
);
|
||
|
|
|
||
|
|
const identity = await loginAndGetIdentity(rejectPorts.uiPort);
|
||
|
|
const getCredentials = new TypedRequest<IReq_GetCredentials>(rejectUrl, 'getCredentials');
|
||
|
|
const response = await getCredentials.fire({ identity });
|
||
|
|
|
||
|
|
assertEquals(response.credentials.length, 1);
|
||
|
|
assertEquals(response.credentials[0].accessKeyId, TEST_ACCESS_KEY);
|
||
|
|
await assertRejects(() =>
|
||
|
|
Deno.readTextFile(`${rejectStorageDirectory}/.objectstorage/admin-config.json`)
|
||
|
|
);
|
||
|
|
} finally {
|
||
|
|
if (container) {
|
||
|
|
await container.stop();
|
||
|
|
}
|
||
|
|
await cleanupRejectStorageDirectory();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|