349 lines
11 KiB
TypeScript
349 lines
11 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { TypedRequest } from '@api.global/typedrequest';
|
|
import { OpsServer } from '../ts/opsserver/index.js';
|
|
import { DcRouterDb } from '../ts/db/index.js';
|
|
import * as plugins from '../ts/plugins.js';
|
|
import * as interfaces from '../ts_interfaces/index.js';
|
|
|
|
const testPort = 3110;
|
|
const baseUrl = `http://localhost:${testPort}/typedrequest`;
|
|
const bootstrapPassword = 'temporary-bootstrap-password';
|
|
const persistedPassword = 'persisted-admin-password';
|
|
|
|
let previousAdminPassword: string | undefined;
|
|
let opsServer: OpsServer;
|
|
let testDb: DcRouterDb;
|
|
let storagePath: string;
|
|
let dbName: string;
|
|
let bootstrapIdentity: interfaces.data.IIdentity;
|
|
let persistedIdentity: interfaces.data.IIdentity;
|
|
let createdUserId: string;
|
|
|
|
const createStatusRequest = () => new TypedRequest<interfaces.requests.IReq_GetAdminBootstrapStatus>(
|
|
baseUrl,
|
|
'getAdminBootstrapStatus',
|
|
);
|
|
|
|
const createLoginRequest = () => new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
|
baseUrl,
|
|
'adminLoginWithUsernameAndPassword',
|
|
);
|
|
|
|
const createFakeDcRouter = (portArg: number, dcRouterDbArg?: DcRouterDb) => ({
|
|
options: {
|
|
opsServerPort: portArg,
|
|
dbConfig: { enabled: true },
|
|
adminAuth: {
|
|
idpClient: {
|
|
loginWithEmailAndPassword: async () => ({
|
|
jwt: 'idp-jwt',
|
|
refreshToken: 'idp-refresh-token',
|
|
user: {
|
|
id: 'idp-user-1',
|
|
data: {
|
|
name: 'Wrong IdP User',
|
|
username: 'wrong@example.com',
|
|
email: 'wrong@example.com',
|
|
status: 'active',
|
|
connectedOrgs: [],
|
|
},
|
|
},
|
|
}),
|
|
stop: async () => {},
|
|
},
|
|
},
|
|
},
|
|
typedrouter: new plugins.typedrequest.TypedRouter(),
|
|
dcRouterDb: dcRouterDbArg,
|
|
});
|
|
|
|
const restartOpsServer = async () => {
|
|
await opsServer.stop();
|
|
opsServer = new OpsServer(createFakeDcRouter(testPort, testDb) as any);
|
|
await opsServer.start();
|
|
};
|
|
|
|
tap.test('setup db-backed OpsServer admin bootstrap test', async () => {
|
|
previousAdminPassword = process.env.DCROUTER_ADMIN_PASSWORD;
|
|
process.env.DCROUTER_ADMIN_PASSWORD = bootstrapPassword;
|
|
|
|
storagePath = plugins.path.join(
|
|
plugins.os.tmpdir(),
|
|
`dcrouter-admin-bootstrap-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
);
|
|
|
|
DcRouterDb.resetInstance();
|
|
dbName = `dcrouter-admin-bootstrap-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
testDb = DcRouterDb.getInstance({
|
|
storagePath,
|
|
dbName,
|
|
});
|
|
await testDb.start();
|
|
await testDb.getDb().mongoDb.createCollection('__test_init');
|
|
|
|
opsServer = new OpsServer(createFakeDcRouter(testPort, testDb) as any);
|
|
await opsServer.start();
|
|
});
|
|
|
|
tap.test('reports bootstrap required without auto-persisting an admin', async () => {
|
|
const status = await createStatusRequest().fire({});
|
|
|
|
expect(status.dbEnabled).toEqual(true);
|
|
expect(status.dbReady).toEqual(true);
|
|
expect(status.hasPersistentAdmin).toEqual(false);
|
|
expect(status.needsBootstrap).toEqual(true);
|
|
expect(status.ephemeralAdminAvailable).toEqual(true);
|
|
expect(status.idpGlobalConfigured).toEqual(true);
|
|
});
|
|
|
|
tap.test('allows temporary bootstrap admin login before persisted admin exists', async () => {
|
|
const response = await createLoginRequest().fire({
|
|
username: 'admin',
|
|
password: bootstrapPassword,
|
|
});
|
|
|
|
if (!response.identity) {
|
|
throw new Error('Expected bootstrap login identity');
|
|
}
|
|
bootstrapIdentity = response.identity;
|
|
expect(bootstrapIdentity.role).toEqual('admin');
|
|
});
|
|
|
|
tap.test('creates the initial persisted admin explicitly', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_CreateInitialAdminUser>(
|
|
baseUrl,
|
|
'createInitialAdminUser',
|
|
);
|
|
|
|
const response = await request.fire({
|
|
identity: bootstrapIdentity,
|
|
email: 'Admin@Example.com',
|
|
name: 'Persisted Admin',
|
|
password: persistedPassword,
|
|
enableIdpGlobalAuth: true,
|
|
});
|
|
|
|
expect(response.success).toEqual(true);
|
|
expect(response.user?.role).toEqual('admin');
|
|
expect(response.user?.authSources).toContain('local');
|
|
expect(response.user?.authSources).toContain('idp.global');
|
|
if (!response.identity) {
|
|
throw new Error('Expected persisted admin identity');
|
|
}
|
|
persistedIdentity = response.identity;
|
|
});
|
|
|
|
tap.test('disables bootstrap mode after persisted admin exists', async () => {
|
|
const status = await createStatusRequest().fire({});
|
|
|
|
expect(status.hasPersistentAdmin).toEqual(true);
|
|
expect(status.needsBootstrap).toEqual(false);
|
|
expect(status.ephemeralAdminAvailable).toEqual(false);
|
|
});
|
|
|
|
tap.test('rejects the old temporary admin after persisted admin creation', async () => {
|
|
let rejected = false;
|
|
try {
|
|
await createLoginRequest().fire({
|
|
username: 'admin',
|
|
password: bootstrapPassword,
|
|
});
|
|
} catch {
|
|
rejected = true;
|
|
}
|
|
|
|
expect(rejected).toEqual(true);
|
|
});
|
|
|
|
tap.test('rejects the old temporary admin identity after persisted admin creation', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
|
|
baseUrl,
|
|
'verifyIdentity',
|
|
);
|
|
const response = await request.fire({ identity: bootstrapIdentity });
|
|
|
|
expect(response.valid).toEqual(false);
|
|
});
|
|
|
|
tap.test('authenticates the persisted admin locally by normalized email', async () => {
|
|
const response = await createLoginRequest().fire({
|
|
username: 'admin@example.com',
|
|
password: persistedPassword,
|
|
authSource: 'local',
|
|
});
|
|
|
|
if (!response.identity) {
|
|
throw new Error('Expected persisted admin login identity');
|
|
}
|
|
expect(response.identity.userId).toEqual(persistedIdentity.userId);
|
|
});
|
|
|
|
tap.test('persists users across OpsServer restart', async () => {
|
|
const oldPersistedIdentity = persistedIdentity;
|
|
await restartOpsServer();
|
|
|
|
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
|
|
baseUrl,
|
|
'verifyIdentity',
|
|
);
|
|
const verifyResponse = await verifyRequest.fire({ identity: oldPersistedIdentity });
|
|
expect(verifyResponse.valid).toEqual(false);
|
|
|
|
const loginResponse = await createLoginRequest().fire({
|
|
username: 'admin@example.com',
|
|
password: persistedPassword,
|
|
authSource: 'local',
|
|
});
|
|
|
|
if (!loginResponse.identity) {
|
|
throw new Error('Expected persisted admin login identity after restart');
|
|
}
|
|
expect(loginResponse.identity.userId).toEqual(oldPersistedIdentity.userId);
|
|
persistedIdentity = loginResponse.identity;
|
|
});
|
|
|
|
tap.test('rejects idp.global login when IdP email does not match local account', async () => {
|
|
let rejected = false;
|
|
try {
|
|
await createLoginRequest().fire({
|
|
username: 'admin@example.com',
|
|
password: 'idp-password',
|
|
authSource: 'idp.global',
|
|
});
|
|
} catch {
|
|
rejected = true;
|
|
}
|
|
|
|
expect(rejected).toEqual(true);
|
|
});
|
|
|
|
tap.test('creates a persisted non-admin user explicitly', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_CreateUser>(baseUrl, 'createUser');
|
|
const response = await request.fire({
|
|
identity: persistedIdentity,
|
|
email: 'operator@example.com',
|
|
name: 'Operator User',
|
|
role: 'user',
|
|
password: 'operator-password',
|
|
});
|
|
|
|
expect(response.success).toEqual(true);
|
|
expect(response.user?.role).toEqual('user');
|
|
expect(response.user?.email).toEqual('operator@example.com');
|
|
if (!response.user?.id) {
|
|
throw new Error('Expected created user id');
|
|
}
|
|
createdUserId = response.user.id;
|
|
});
|
|
|
|
tap.test('rejects deleting the current persisted admin user', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_DeleteUser>(baseUrl, 'deleteUser');
|
|
const response = await request.fire({
|
|
identity: persistedIdentity,
|
|
id: persistedIdentity.userId,
|
|
});
|
|
|
|
expect(response.success).toEqual(false);
|
|
});
|
|
|
|
tap.test('deletes a persisted non-current user', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_DeleteUser>(baseUrl, 'deleteUser');
|
|
const response = await request.fire({
|
|
identity: persistedIdentity,
|
|
id: createdUserId,
|
|
});
|
|
|
|
expect(response.success).toEqual(true);
|
|
});
|
|
|
|
tap.test('lists persisted users without password material', async () => {
|
|
const request = new TypedRequest<interfaces.requests.IReq_ListUsers>(baseUrl, 'listUsers');
|
|
const response = await request.fire({ identity: persistedIdentity });
|
|
|
|
expect(response.users.length).toEqual(1);
|
|
expect(response.users[0].email).toEqual('Admin@Example.com');
|
|
expect((response.users[0] as any).password).toBeUndefined();
|
|
});
|
|
|
|
tap.test('rejects temporary bootstrap admin when persisted-user database is unavailable', async () => {
|
|
await testDb.stop();
|
|
|
|
const status = await createStatusRequest().fire({});
|
|
expect(status.dbEnabled).toEqual(true);
|
|
expect(status.dbReady).toEqual(false);
|
|
expect(status.needsBootstrap).toEqual(false);
|
|
expect(status.ephemeralAdminAvailable).toEqual(false);
|
|
|
|
let rejected = false;
|
|
try {
|
|
await createLoginRequest().fire({
|
|
username: 'admin',
|
|
password: bootstrapPassword,
|
|
});
|
|
} catch {
|
|
rejected = true;
|
|
}
|
|
|
|
expect(rejected).toEqual(true);
|
|
});
|
|
|
|
tap.test('cleanup db-backed OpsServer admin bootstrap test', async () => {
|
|
await opsServer.stop();
|
|
await testDb.stop();
|
|
DcRouterDb.resetInstance();
|
|
await plugins.fs.promises.rm(storagePath, { recursive: true, force: true });
|
|
|
|
if (previousAdminPassword === undefined) {
|
|
delete process.env.DCROUTER_ADMIN_PASSWORD;
|
|
} else {
|
|
process.env.DCROUTER_ADMIN_PASSWORD = previousAdminPassword;
|
|
}
|
|
});
|
|
|
|
tap.test('does not offer bootstrap while configured database is unavailable', async () => {
|
|
const unavailablePort = 3111;
|
|
const unavailableBaseUrl = `http://localhost:${unavailablePort}/typedrequest`;
|
|
const previousUnavailableAdminPassword = process.env.DCROUTER_ADMIN_PASSWORD;
|
|
process.env.DCROUTER_ADMIN_PASSWORD = 'unavailable-bootstrap-password';
|
|
DcRouterDb.resetInstance();
|
|
|
|
const unavailableOpsServer = new OpsServer(createFakeDcRouter(unavailablePort) as any);
|
|
try {
|
|
await unavailableOpsServer.start();
|
|
const status = await new TypedRequest<interfaces.requests.IReq_GetAdminBootstrapStatus>(
|
|
unavailableBaseUrl,
|
|
'getAdminBootstrapStatus',
|
|
).fire({});
|
|
|
|
expect(status.dbEnabled).toEqual(true);
|
|
expect(status.dbReady).toEqual(false);
|
|
expect(status.needsBootstrap).toEqual(false);
|
|
expect(status.ephemeralAdminAvailable).toEqual(false);
|
|
|
|
let rejected = false;
|
|
try {
|
|
await new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
|
unavailableBaseUrl,
|
|
'adminLoginWithUsernameAndPassword',
|
|
).fire({
|
|
username: 'admin',
|
|
password: 'unavailable-bootstrap-password',
|
|
});
|
|
} catch {
|
|
rejected = true;
|
|
}
|
|
|
|
expect(rejected).toEqual(true);
|
|
} finally {
|
|
await unavailableOpsServer.stop();
|
|
DcRouterDb.resetInstance();
|
|
if (previousUnavailableAdminPassword === undefined) {
|
|
delete process.env.DCROUTER_ADMIN_PASSWORD;
|
|
} else {
|
|
process.env.DCROUTER_ADMIN_PASSWORD = previousUnavailableAdminPassword;
|
|
}
|
|
}
|
|
});
|
|
|
|
export default tap.start();
|