diff --git a/config.json b/config.json
index 0164242b7..c6224c263 100644
--- a/config.json
+++ b/config.json
@@ -1,6 +1,4 @@
{
- "username": "admin",
- "password": "secret",
"userAuth": {
"username": "user",
"password": "secret"
diff --git a/src/api/controller/api/login.js b/src/api/controller/api/login.js
index 52643e77f..b9c458166 100644
--- a/src/api/controller/api/login.js
+++ b/src/api/controller/api/login.js
@@ -7,17 +7,22 @@ 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;
@@ -25,10 +30,7 @@ export const postLogin = date => ctx => {
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;
}
diff --git a/src/api/controller/api/login.spec.js b/src/api/controller/api/login.spec.js
index ca5162be0..d97500686 100644
--- a/src/api/controller/api/login.spec.js
+++ b/src/api/controller/api/login.spec.js
@@ -7,12 +7,16 @@ 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',
@@ -20,16 +24,20 @@ describe('login', () => {
},
},
};
- 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',
@@ -37,21 +45,24 @@ describe('login', () => {
},
},
};
- 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',
},
},
@@ -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,
},
@@ -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,
},
@@ -89,16 +100,21 @@ 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',
@@ -106,20 +122,25 @@ describe('login', () => {
},
},
};
- 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',
@@ -127,22 +148,26 @@ describe('login', () => {
},
},
};
- 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',
@@ -156,7 +181,7 @@ describe('login', () => {
},
};
- login(expDate)(ctx);
+ await login(expDate)(ctx);
expect(ctx.body).toEqual({
token: jwt.sign(
{
@@ -169,7 +194,7 @@ describe('login', () => {
role: 'user',
});
expect(setCall).toEqual([
- 'lodex_token_test',
+ 'lodex_token_default',
jwt.sign(
{
username: 'user',
diff --git a/src/api/controller/rootAdmin.js b/src/api/controller/rootAdmin.js
index 146d48240..de10804ca 100644
--- a/src/api/controller/rootAdmin.js
+++ b/src/api/controller/rootAdmin.js
@@ -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;
}
@@ -55,6 +55,8 @@ const postTenant = async ctx => {
name,
description,
author,
+ username: 'admin',
+ password: 'secret',
createdAt: new Date(),
});
const queue = createWorkerQueue(name, 1);
@@ -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({
@@ -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 => {
diff --git a/src/api/controller/testController.js b/src/api/controller/testController.js
index 2861d16bd..9a09d1964 100644
--- a/src/api/controller/testController.js
+++ b/src/api/controller/testController.js
@@ -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();
@@ -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' };
}),
);
diff --git a/src/api/index.js b/src/api/index.js
index f304281a5..e6f800ad2 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -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();
diff --git a/src/api/services/ezMasterConfig.js b/src/api/services/ezMasterConfig.js
index 95bef3e6e..1a542dd1a 100644
--- a/src/api/services/ezMasterConfig.js
+++ b/src/api/services/ezMasterConfig.js
@@ -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: /.+/,
diff --git a/src/api/services/ezMasterConfig.spec.js b/src/api/services/ezMasterConfig.spec.js
index e4af19eb3..76f95d6f7 100644
--- a/src/api/services/ezMasterConfig.spec.js
+++ b/src/api/services/ezMasterConfig.spec.js
@@ -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({
diff --git a/src/app/js/root-admin/Tenants.js b/src/app/js/root-admin/Tenants.js
index a62e65c73..33e95b9b8 100644
--- a/src/app/js/root-admin/Tenants.js
+++ b/src/app/js/root-admin/Tenants.js
@@ -4,6 +4,7 @@ import 'react-toastify/dist/ReactToastify.css';
import PropTypes from 'prop-types';
import AddBoxIcon from '@mui/icons-material/AddBox';
import DeleteIcon from '@mui/icons-material/Delete';
+import EditIcon from '@mui/icons-material/Edit';
import { getHost } from '../../../common/uris';
import CreateTenantDialog from './CreateTenantDialog';
@@ -16,6 +17,7 @@ import {
GridToolbarFilterButton,
} from '@mui/x-data-grid';
import { Button, Tooltip } from '@mui/material';
+import UpdateTenantDialog from './UpdateTenantDialog';
const baseUrl = getHost();
@@ -23,6 +25,7 @@ const Tenants = ({ handleLogout }) => {
const [tenants, setTenants] = useState([]);
const [openCreateTenantDialog, setOpenCreateTenantDialog] = useState(false);
const [openDeleteTenantDialog, setOpenDeleteTenantDialog] = useState(false);
+ const [tenantToUpdate, setTenantToUpdate] = useState(null);
const onChangeTenants = changedTenants => {
if (changedTenants instanceof Array) {
@@ -95,6 +98,55 @@ const Tenants = ({ handleLogout }) => {
});
};
+ const updateTenant = (id, updatedTenant) => {
+ fetch(`/rootAdmin/tenant/${id}`, {
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Lodex-Tenant': 'admin',
+ },
+ method: 'PUT',
+ body: JSON.stringify(updatedTenant),
+ })
+ .then(response => {
+ if (response.status === 401) {
+ handleLogout();
+ return;
+ }
+
+ if (response.status === 403) {
+ toast.error('Action non autorisée', {
+ position: 'top-right',
+ autoClose: 5000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ theme: 'light',
+ });
+ return;
+ }
+
+ if (response.status === 200) {
+ if (response.status === 200) {
+ toast.success('Instance modifiée', {
+ position: 'top-right',
+ autoClose: 5000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ theme: 'light',
+ });
+ }
+ }
+
+ return response.json();
+ })
+ .then(data => {
+ onChangeTenants(data);
+ setTenantToUpdate(null);
+ });
+ };
+
const deleteTenant = (_id, name) => {
fetch('/rootAdmin/tenant', {
credentials: 'include',
@@ -230,6 +282,21 @@ const Tenants = ({ handleLogout }) => {
);
},
},
+ {
+ field: 'update',
+ headerName: 'Modifier',
+ width: 150,
+ renderCell: params => {
+ return (
+
+ );
+ },
+ },
{
field: 'delete',
headerName: 'Supprimer',
@@ -264,6 +331,12 @@ const Tenants = ({ handleLogout }) => {
handleClose={() => setOpenCreateTenantDialog(false)}
createAction={addTenant}
/>
+ setTenantToUpdate(null)}
+ updateAction={updateTenant}
+ />
+
setOpenDeleteTenantDialog(false)}
diff --git a/src/app/js/root-admin/UpdateTenantDialog.js b/src/app/js/root-admin/UpdateTenantDialog.js
new file mode 100644
index 000000000..9e6f1d270
--- /dev/null
+++ b/src/app/js/root-admin/UpdateTenantDialog.js
@@ -0,0 +1,103 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import {
+ Dialog,
+ DialogContent,
+ DialogActions,
+ DialogTitle,
+ Button,
+ TextField,
+} from '@mui/material';
+
+const UpdateTenantDialog = ({ tenant, handleClose, updateAction }) => {
+ const [description, setDescription] = useState('');
+ const [author, setAuthor] = useState('');
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+
+ useEffect(() => {
+ setDescription(tenant?.description || '');
+ setAuthor(tenant?.author || '');
+ setUsername(tenant?.username || '');
+ setPassword(tenant?.password || '');
+ }, [tenant]);
+
+ return (
+
+ );
+};
+
+UpdateTenantDialog.propTypes = {
+ tenant: PropTypes.object,
+ handleClose: PropTypes.func.isRequired,
+ updateAction: PropTypes.func.isRequired,
+};
+
+export default UpdateTenantDialog;