Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(root): Create admin config for instances #1749

Merged
merged 2 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"username": "admin",
"password": "secret",
"userAuth": {
"username": "user",
"password": "secret"
Expand Down
20 changes: 11 additions & 9 deletions src/api/controller/api/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,30 @@ import { auth } from 'config';
import jwt from 'jsonwebtoken';
import { ADMIN_ROLE, ROOT_ROLE } from '../../../common/tools/tenantTools';

export const postLogin = date => ctx => {
export const postLogin = date => async ctx => {
if (!ctx.ezMasterConfig) {
throw new Error('Invalid EzMaster configuration.');
}

if (!ctx.ezMasterConfig.username) {
throw new Error('Invalid EzMaster configuration: missing username');
const {
username: usernameAdmin,
password: passwordAdmin,
} = await ctx.tenantCollection.findOneByName(ctx.tenant.toLowerCase());

if (!usernameAdmin) {
throw new Error('Invalid instance configuration: missing username');
}

if (!ctx.ezMasterConfig.password) {
throw new Error('Invalid EzMaster configuration: missing password.');
if (!passwordAdmin) {
throw new Error('Invalid instance configuration: missing password.');
}

const { username, password } = ctx.request.body;
const userAuth = get(ctx, 'ezMasterConfig.userAuth', {});
const rootAuth = get(ctx, 'ezMasterConfig.rootAuth', {});

let role;
if (
username === ctx.ezMasterConfig.username &&
password === ctx.ezMasterConfig.password
) {
if (username === usernameAdmin && password === passwordAdmin) {
role = ADMIN_ROLE;
}

Expand Down
93 changes: 59 additions & 34 deletions src/api/controller/api/login.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,62 @@ import { ADMIN_ROLE } from '../../../common/tools/tenantTools';
const expDate = Date.now();

describe('login', () => {
it('should set ctx.status to 401, if ctx.body.username do not match with config', () => {
it('should set ctx.status to 401, if ctx.body.username do not match with config', async () => {
const ctx = {
ezMasterConfig: {
username: 'admin',
password: 'secret',
ezMasterConfig: {},
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'default',
request: {
body: {
username: 'not admin',
password: 'secret',
},
},
};
login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.status).toBe(401);
});

it('should set ctx.status to 401, if ctx.body.password do not match with config', () => {
it('should set ctx.status to 401, if ctx.body.password do not match with config', async () => {
const ctx = {
ezMasterConfig: {
username: 'admin',
password: 'secret',
ezMasterConfig: {},
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'default',
request: {
body: {
username: 'user',
password: `not secret`,
},
},
};
login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.status).toBe(401);
});

it('should return header token and set cookie with cookie token for admin when password and user name match config', () => {
it('should return header token and set cookie with cookie token for admin when password and user name match config', async () => {
let setCall;
const ctx = {
ezMasterConfig: {
username: 'user',
password: 'secret',
ezMasterConfig: {},
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'test',
tenant: 'default',
request: {
body: {
username: 'user',
username: 'admin',
password: 'secret',
},
},
Expand All @@ -62,11 +73,11 @@ describe('login', () => {
},
};

login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.body).toEqual({
token: jwt.sign(
{
username: 'user',
username: 'admin',
role: ADMIN_ROLE,
exp: Math.ceil(expDate / 1000) + auth.expiresIn,
},
Expand All @@ -75,10 +86,10 @@ describe('login', () => {
role: ADMIN_ROLE,
});
expect(setCall).toEqual([
'lodex_token_test',
'lodex_token_default',
jwt.sign(
{
username: 'user',
username: 'admin',
role: ADMIN_ROLE,
exp: Math.ceil(expDate / 1000) + auth.expiresIn,
},
Expand All @@ -89,60 +100,74 @@ describe('login', () => {
});

describe('user authentication', () => {
it('should set ctx.status to 401, if ctx.body.username do not match with userAuth config', () => {
it('should set ctx.status to 401, if ctx.body.username do not match with userAuth config', async () => {
const ctx = {
ezMasterConfig: {
username: 'admin',
password: 'secret',
userAuth: {
username: 'user',
password: 'secret',
},
},
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'default',
request: {
body: {
username: 'not user',
password: 'secret',
},
},
};
login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.status).toBe(401);
});

it('should set ctx.status to 401, if ctx.body.password do not match with config', () => {
it('should set ctx.status to 401, if ctx.body.password do not match with config', async () => {
const ctx = {
ezMasterConfig: {
username: 'admin',
password: 'secret',
userAuth: {
username: 'user',
password: 'secret',
},
},
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'default',
request: {
body: {
username: 'user',
password: `not secret`,
},
},
};
login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.status).toBe(401);
});

it('should return header token and set cookie with cookie token for user when password and user name match userAuth config', () => {
it('should return header token and set cookie with cookie token for user when password and user name match userAuth config', async () => {
let setCall;
const ctx = {
ezMasterConfig: {
username: 'admin',
password: 'secret',
userAuth: {
username: 'user',
password: 'secret',
},
},
tenant: 'test',
tenantCollection: {
findOneByName: () => ({
username: 'admin',
password: 'secret',
}),
},
tenant: 'default',
request: {
body: {
username: 'user',
Expand All @@ -156,7 +181,7 @@ describe('login', () => {
},
};

login(expDate)(ctx);
await login(expDate)(ctx);
expect(ctx.body).toEqual({
token: jwt.sign(
{
Expand All @@ -169,7 +194,7 @@ describe('login', () => {
role: 'user',
});
expect(setCall).toEqual([
'lodex_token_test',
'lodex_token_default',
jwt.sign(
{
username: 'user',
Expand Down
20 changes: 18 additions & 2 deletions src/api/controller/rootAdmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ app.use(async (ctx, next) => {
if (!ctx.state.cookie) {
ctx.status = 401;
ctx.cookies.set('lodex_token_root', '', { expires: new Date() });
ctx.body = 'No authentication token found';
ctx.body = { message: 'No authentication token found' };
return;
}

if (ctx.state.cookie.role !== ROOT_ROLE) {
ctx.status = 401;
ctx.cookies.set('lodex_token_root', '', { expires: new Date() });
ctx.body = 'No root token found';
ctx.body = { message: 'No root token found' };
return;
}

Expand All @@ -55,6 +55,8 @@ const postTenant = async ctx => {
name,
description,
author,
username: 'admin',
password: 'secret',
createdAt: new Date(),
});
const queue = createWorkerQueue(name, 1);
Expand All @@ -63,6 +65,19 @@ const postTenant = async ctx => {
}
};

const putTenant = async (ctx, id) => {
const { description, author, username, password } = ctx.request.body;
const tenantExists = await ctx.tenantCollection.findOneById(id);

if (!tenantExists) {
ctx.throw(403, `Invalid id: "${id}"`);
}

const update = { description, author, username, password };
await ctx.tenantCollection.update(id, update);
ctx.body = await ctx.tenantCollection.findAll();
};

const deleteTenant = async ctx => {
const { _id, name } = ctx.request.body;
const tenantExists = await ctx.tenantCollection.findOne({
Expand All @@ -82,6 +97,7 @@ const deleteTenant = async ctx => {

app.use(route.get('/tenant', getTenant));
app.use(route.post('/tenant', postTenant));
app.use(route.put('/tenant/:id', putTenant));
app.use(route.delete('/tenant', deleteTenant));

app.use(async ctx => {
Expand Down
6 changes: 4 additions & 2 deletions src/api/controller/testController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import mount from 'koa-mount';
import repositoryMiddleware, {
mongoRootAdminClient,
} from '../services/repositoryMiddleware';
import { DEFAULT_TENANT } from '../../common/tools/tenantTools';

const app = new koa();

Expand All @@ -21,8 +22,9 @@ app.use(
await ctx.db.collection('dataset').remove({});
await ctx.db.collection('subresource').remove({});
await ctx.db.collection('enrichment').remove({});
await ctx.rootAdminDb.collection('tenant').remove({});

await ctx.rootAdminDb
.collection('tenant')
.remove({ name: { $ne: DEFAULT_TENANT } });
ctx.body = { status: 'ok' };
}),
);
Expand Down
19 changes: 16 additions & 3 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,30 @@ serverAdapter.setBasePath('/bull');
const initQueueAndBullDashboard = async () => {
bullBoard.initBullBoard(serverAdapter);

const defaultQueue = createWorkerQueue(DEFAULT_TENANT, 1);
bullBoard.addDashboardQueue(DEFAULT_TENANT, defaultQueue);

// Get current tenants
const adminDb = await mongoClient('admin');
const tenantCollection = await tenant(adminDb);

const tenants = await tenantCollection.findAll();
tenants.forEach(tenant => {
const queue = createWorkerQueue(tenant.name, 1);
bullBoard.addDashboardQueue(tenant.name, queue);
});

// if tenant `default` is not in the database, we add it
if (!tenants.find(tenant => tenant.name === DEFAULT_TENANT)) {
await tenantCollection.create({
name: DEFAULT_TENANT,
description: 'Instance par défaut',
author: 'Root',
username: 'admin',
password: 'secret',
createdAt: new Date(),
});
const defaultQueue = createWorkerQueue(DEFAULT_TENANT, 1);
bullBoard.addDashboardQueue(DEFAULT_TENANT, defaultQueue);
// TODO: create default instance config.
}
};

initQueueAndBullDashboard();
Expand Down
5 changes: 0 additions & 5 deletions src/api/services/ezMasterConfig.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import expect from 'expect';

export const validateConfig = config => {
expect(config).toMatchObject({
username: /.+/,
password: /.+/,
});

if (config.userAuth) {
expect(config.userAuth).toMatchObject({
username: /.+/,
Expand Down
12 changes: 0 additions & 12 deletions src/api/services/ezMasterConfig.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@ import ezMasterConfig, { validateConfig } from './ezMasterConfig';

describe('ezMasterConfig', () => {
describe('validateConfig', () => {
it('should throw if username is not present', () => {
expect(() => validateConfig({})).toThrow();
});

it('should throw if password is not present', () => {
expect(() =>
validateConfig({
username: 'toto',
}),
).toThrow();
});

it('should throw if userAuth but no userAuth.username', () => {
expect(() =>
validateConfig({
Expand Down
Loading