Compare commits
No commits in common. "main" and "update-localdb" have entirely different histories.
main
...
update-loc
73
README.md
73
README.md
@ -19,7 +19,6 @@ In Proxmox VE, follow the following steps:
|
|||||||
- Datastore.Allocate, Datastore.AllocateSpace, Datastore.Audit
|
- Datastore.Allocate, Datastore.AllocateSpace, Datastore.Audit
|
||||||
- User.Modify
|
- User.Modify
|
||||||
- Pool.Audit
|
- Pool.Audit
|
||||||
- SDN.Use (if instances use SDN networks)
|
|
||||||
4. Add a new API Token Permission with path: `/`, select the API token created previously, and role: `proxmoxaas-api`
|
4. Add a new API Token Permission with path: `/`, select the API token created previously, and role: `proxmoxaas-api`
|
||||||
5. Add a new User Permission with path: `/`, select the `proxmoxaas-api` user, and role: `proxmoxaas-api`
|
5. Add a new User Permission with path: `/`, select the `proxmoxaas-api` user, and role: `proxmoxaas-api`
|
||||||
|
|
||||||
@ -68,74 +67,4 @@ server {
|
|||||||
### Result
|
### Result
|
||||||
After these steps, the ProxmoxAAS Dashboard should be available and fully functional at `paas.<FQDN>` or `paas.<FQDN>/dashboard/`.
|
After these steps, the ProxmoxAAS Dashboard should be available and fully functional at `paas.<FQDN>` or `paas.<FQDN>/dashboard/`.
|
||||||
|
|
||||||
# Backends
|
# Backends
|
||||||
|
|
||||||
Backend handlers are used to interface with any number and type of backend data source used to store ProxmoxAAS data. Most data involves users, groups, and membership relationships. The default backends are sufficient to run a small cluster, but additional backend handlers can be created.
|
|
||||||
|
|
||||||
## Interface
|
|
||||||
|
|
||||||
Each backend must implement the following methods:
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>openSession</td>
|
|
||||||
<td>opens a session to the backend by creating a session token</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>closeSession</td>
|
|
||||||
<td>closes a session to the backend</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
Additionally, backends dealing with user data may also need to implement:
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>addUser</td>
|
|
||||||
<td>create a user</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>getUser</td>
|
|
||||||
<td>retrieve user data including membership</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>setUser</td>
|
|
||||||
<td>modify a user</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>delUser</td>
|
|
||||||
<td>delete a user</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>addGroup</td>
|
|
||||||
<td>create a group</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>getGroup</td>
|
|
||||||
<td>retrieve group data including members</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>setGroup</td>
|
|
||||||
<td>modify group data except membership</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>delGroup</td>
|
|
||||||
<td>delete group</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>addUserToGroup</td>
|
|
||||||
<td>add user to group as member</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>delUserFromGroup</td>
|
|
||||||
<td>remove user from group</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
Not all user backends will necessarily implement all the methods fully. For example, backends which do not store group data may not need to implement the group related methods.
|
|
||||||
|
|
||||||
Specific documentation can be found in `src/backends/backends.js`.
|
|
||||||
|
|
||||||
## Multiple Interfaces
|
|
||||||
|
|
||||||
Multiple backends can be specified using the config. During a backend operation involving users, each backend method will be called in the order specified in the config. If the operation is to retrieve user data, the responses will be merged favoring the last backend called.
|
|
@ -25,29 +25,15 @@
|
|||||||
"paasldap": {
|
"paasldap": {
|
||||||
"import": "paasldap.js",
|
"import": "paasldap.js",
|
||||||
"config": {
|
"config": {
|
||||||
"url": "http://paasldap.mydomain.example",
|
"url": "http://paasldap.mydomain.example"
|
||||||
"realm": "ldap"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"instance": {
|
"pve": "pve",
|
||||||
|
"db": "localdb",
|
||||||
|
"auth": {
|
||||||
"pve": "pve"
|
"pve": "pve"
|
||||||
},
|
|
||||||
"users": {
|
|
||||||
"realm": {
|
|
||||||
"pve": [
|
|
||||||
"localdb"
|
|
||||||
],
|
|
||||||
"ldap": [
|
|
||||||
"localdb",
|
|
||||||
"paasldap"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"any": [
|
|
||||||
"localdb",
|
|
||||||
"paasldap"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import url from "url";
|
import url from "url";
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
global.backends = {};
|
const backends = {};
|
||||||
for (const name in global.config.backends) {
|
for (const name in global.config.backends) {
|
||||||
// get files and config
|
// get files and config
|
||||||
const target = global.config.backends[name].import;
|
const target = global.config.backends[name].import;
|
||||||
@ -14,11 +14,17 @@ export default async () => {
|
|||||||
const importPath = `./${path.relative(thisPath, targetPath)}`;
|
const importPath = `./${path.relative(thisPath, targetPath)}`;
|
||||||
// import and add to list of imported handlers
|
// import and add to list of imported handlers
|
||||||
const Backend = (await import(importPath)).default;
|
const Backend = (await import(importPath)).default;
|
||||||
global.backends[name] = new Backend(config);
|
backends[name] = new Backend(config);
|
||||||
console.log(`backends: initialized backend ${name} from ${importPath}`);
|
console.log(`backends: initialized backend ${name} from ${importPath}`);
|
||||||
}
|
}
|
||||||
global.pve = global.backends[global.config.handlers.instance.pve];
|
// assign backends to handlers by type
|
||||||
global.userManager = new USER_BACKEND_MANAGER(global.config.handlers.users);
|
const handlers = global.config.handlers;
|
||||||
|
global.pve = backends[handlers.pve];
|
||||||
|
global.db = backends[handlers.db];
|
||||||
|
global.auth = handlers.auth;
|
||||||
|
Object.keys(global.auth).forEach((e) => {
|
||||||
|
global.auth[e] = backends[global.auth[e]];
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,15 +34,13 @@ export default async () => {
|
|||||||
class BACKEND {
|
class BACKEND {
|
||||||
/**
|
/**
|
||||||
* Opens a session with the backend and creates session tokens if needed
|
* Opens a session with the backend and creates session tokens if needed
|
||||||
* @param {{id: string, realm: string}} user object containing id and realm
|
* @param {{username: string, password: string}} credentials object containing username and password fields
|
||||||
* @param {string} password
|
* @returns {{ok: boolean, status: number, cookies: {name: string, value: string}[]}} response like object with list of session token objects with token name and value
|
||||||
* @returns {{ok: boolean, status: number, message: string, cookies: {name: string, value: string}[]}} response like object with list of session token objects with token name and value
|
|
||||||
*/
|
*/
|
||||||
openSession (user, password) {
|
openSession (credentials) {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
message: "",
|
|
||||||
cookies: []
|
cookies: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -54,138 +58,79 @@ class BACKEND {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AtomicChange {
|
|
||||||
constructor (valid, delta, callback, status = { ok: true, status: 200, message: "" }) {
|
|
||||||
this.valid = valid;
|
|
||||||
this.delta = delta;
|
|
||||||
this.callback = callback;
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the change using the saved delta using the callback function
|
|
||||||
*/
|
|
||||||
async commit () {
|
|
||||||
const res = await this.callback(this.delta);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doNothingCallback (delta) {
|
|
||||||
return { ok: true, status: 200, message: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for backend types that store/interact with user & group data.
|
* Interface for backend types that store/interact with user & group data.
|
||||||
* Not all backends need to implement all interface methods.
|
* Not all backends need to implement all interface methods.
|
||||||
*/
|
*/
|
||||||
class USER_BACKEND extends BACKEND {
|
class USER_BACKEND extends BACKEND {
|
||||||
/**
|
/**
|
||||||
* Validate an add user operation with the following parameters.
|
* Add user to backend
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {Object} attributes user attributes
|
* @param {Object} attributes user attributes
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
addUser (user, attributes, params) {}
|
addUser (user, attributes, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user from backend
|
* Get user from backend
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {Object} containing user data from this backend, null if user does not exist
|
|
||||||
*/
|
*/
|
||||||
getUser (user, params) {}
|
getUser (user, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all users from backend
|
* Modify user in backend
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
|
||||||
* @returns {Array} containing each user data from this backend
|
|
||||||
*/
|
|
||||||
getAllUsers (params) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a set user operation with the following parameters.
|
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {Object} attributes new user attributes to modify
|
* @param {Object} attributes new user attributes to modify
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
setUser (user, attributes, params) {}
|
setUser (user, attributes, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a delete user operation with the following parameters.
|
* Delete user from backend
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
delUser (user, params) {}
|
deluser (user, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate an add group operation with the following parameters.
|
* Add group to backend
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
* @param {{id: string}} group
|
||||||
* @param {{id: string, realm: string}} group
|
|
||||||
* @param {Object} attributes group attributes
|
* @param {Object} attributes group attributes
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
addGroup (group, attributes, params) {}
|
addGroup (group, attributes, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get group from backend
|
* Get group from backend
|
||||||
* @param {{id: string}} group
|
* @param {{id: string}} group
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {Object} containing group data from this backend, null if user does not exist
|
|
||||||
*/
|
*/
|
||||||
getGroup (group, params) {}
|
getGroup (group, params = null) {}
|
||||||
|
/**
|
||||||
|
* Modify group in backend
|
||||||
|
* @param {{id: string}} group
|
||||||
|
* @param {Object} attributes new group attributes to modify
|
||||||
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
|
*/
|
||||||
|
setGroup (group, attributes, params = null) {}
|
||||||
|
/**
|
||||||
|
* Delete group from backend
|
||||||
|
* @param {{id: string}} group
|
||||||
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
|
*/
|
||||||
|
delGroup (group, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all users from backend
|
* Add user to group
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
|
||||||
* @returns {Array} containing each group data from this backend
|
|
||||||
*/
|
|
||||||
getAllGroups (params) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a set group operation with the following parameters.
|
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} group
|
|
||||||
* @param {Object} attributes group attributes
|
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
|
||||||
setGroup (group, attributes, params) {}
|
|
||||||
/**
|
|
||||||
* Validate a del group operation with the following parameters.
|
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} group
|
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
|
||||||
delGroup (group, attributes, params) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate an add user to group operation with the following parameters.
|
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {{id: string}} group
|
* @param {{id: string}} group
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
addUserToGroup (user, group, params) {}
|
addUserToGroup (user, group, params = null) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a remove user from group operation with the following parameters.
|
* Remove user from group
|
||||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
|
||||||
* @param {{id: string, realm: string}} user
|
* @param {{id: string, realm: string}} user
|
||||||
* @param {{id: string}} group
|
* @param {{id: string}} group
|
||||||
* @param {Object} params authentication params, usually req.cookies
|
* @param {Object} params authentication params, usually req.cookies
|
||||||
* @returns {AtomicChange} atomic change object
|
|
||||||
*/
|
*/
|
||||||
delUserFromGroup (user, group, params) {}
|
delUserFromGroup (user, group, params = null) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -202,100 +147,3 @@ export class DB_BACKEND extends USER_BACKEND {}
|
|||||||
* Interface for user auth backends.
|
* Interface for user auth backends.
|
||||||
*/
|
*/
|
||||||
export class AUTH_BACKEND extends USER_BACKEND {}
|
export class AUTH_BACKEND extends USER_BACKEND {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface combining all user backends into a single interface
|
|
||||||
* Calling methods will also call sub handler methods
|
|
||||||
*/
|
|
||||||
class USER_BACKEND_MANAGER extends USER_BACKEND {
|
|
||||||
#config = null;
|
|
||||||
|
|
||||||
constructor (config) {
|
|
||||||
super();
|
|
||||||
this.#config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBackendsByUser (user) {
|
|
||||||
return this.#config.realm[user.realm];
|
|
||||||
}
|
|
||||||
|
|
||||||
addUser (user, attributes, params) {}
|
|
||||||
|
|
||||||
async getUser (user, params) {
|
|
||||||
let userData = {};
|
|
||||||
for (const backend of this.#config.realm[user.realm]) {
|
|
||||||
const backendData = await global.backends[backend].getUser(user, params);
|
|
||||||
if (backendData) {
|
|
||||||
userData = { ...backendData, ...userData };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllUsers (params) {
|
|
||||||
const userData = {};
|
|
||||||
for (const backend of this.#config.any) {
|
|
||||||
const backendData = await global.backends[backend].getAllUsers(params);
|
|
||||||
if (backendData) {
|
|
||||||
for (const user of Object.keys(backendData)) {
|
|
||||||
userData[user] = { ...backendData[user], ...userData[user] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUser (user, attributes, params) {
|
|
||||||
const atomicChanges = [];
|
|
||||||
for (const backend of this.#config.realm[user.realm]) {
|
|
||||||
const atomicChange = await global.backends[backend].setUser(user, attributes, params);
|
|
||||||
if (atomicChange.valid === false) { // if any fails, preemptively exit
|
|
||||||
return atomicChange.status;
|
|
||||||
}
|
|
||||||
atomicChanges.push(atomicChange); // queue callback into array
|
|
||||||
}
|
|
||||||
const response = {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
message: "",
|
|
||||||
allResponses: []
|
|
||||||
};
|
|
||||||
for (const atomicChange of atomicChanges) {
|
|
||||||
const atomicResponse = await atomicChange.commit();
|
|
||||||
if (atomicResponse.ok === false) {
|
|
||||||
response.ok = false;
|
|
||||||
response.status = atomicResponse.status;
|
|
||||||
response.message = atomicResponse.message;
|
|
||||||
}
|
|
||||||
response.allResponses.push(); // execute callback
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
delUser (user, params) {}
|
|
||||||
|
|
||||||
addGroup (group, attributes, params) {}
|
|
||||||
|
|
||||||
getGroup (group, params) {}
|
|
||||||
|
|
||||||
async getAllGroups (params) {
|
|
||||||
const groupData = {};
|
|
||||||
for (const backend of this.#config.any) {
|
|
||||||
const backendData = await global.backends[backend].getAllGroups(params);
|
|
||||||
if (backendData) {
|
|
||||||
for (const group of Object.keys(backendData)) {
|
|
||||||
groupData[group] = { ...backendData[group], ...groupData[group] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return groupData;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGroup (group, attributes, params) {}
|
|
||||||
|
|
||||||
delGroup (group, params) {}
|
|
||||||
|
|
||||||
addUserToGroup (user, group, params) {}
|
|
||||||
|
|
||||||
delUserFromGroup (user, group, params) {}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { readFileSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
import { exit } from "process";
|
import { exit } from "process";
|
||||||
import { AtomicChange, DB_BACKEND, doNothingCallback } from "./backends.js";
|
import { DB_BACKEND } from "./backends.js";
|
||||||
|
|
||||||
export default class LocalDB extends DB_BACKEND {
|
export default class LocalDB extends DB_BACKEND {
|
||||||
#path = null;
|
#path = null;
|
||||||
@ -35,82 +35,74 @@ export default class LocalDB extends DB_BACKEND {
|
|||||||
writeFileSync(this.#path, JSON.stringify(this.#data));
|
writeFileSync(this.#path, JSON.stringify(this.#data));
|
||||||
}
|
}
|
||||||
|
|
||||||
addUser (user, attributes, params) {}
|
addUser (user, attributes, params = null) {
|
||||||
|
const username = `${user.id}@${user.realm}`;
|
||||||
|
attributes = attributes || this.#defaultuser;
|
||||||
|
this.#data.users[username] = attributes;
|
||||||
|
this.#save();
|
||||||
|
}
|
||||||
|
|
||||||
getUser (user, params) {
|
getUser (user, params = null) {
|
||||||
const requestedUser = `${user.id}@${user.realm}`;
|
const username = `${user.id}@${user.realm}`;
|
||||||
const requestingUser = params.username; // assume checkAuth has been run, which already checks that username matches PVE token
|
if (this.#data.users[username]) {
|
||||||
// user can access a user's db data if they are an admin OR are requesting own data
|
return this.#data.users[username];
|
||||||
const authorized = this.#data.users[requestingUser].cluster.admin || requestingUser === requestedUser;
|
|
||||||
if (authorized && this.#data.users[requestedUser]) {
|
|
||||||
return this.#data.users[requestedUser];
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUsers (params) {
|
setUser (user, attributes, params = null) {
|
||||||
const requestingUser = params.username; // assume checkAuth has been run, which already checks that username matches PVE token
|
const username = `${user.id}@${user.realm}`;
|
||||||
if (this.#data.users[requestingUser].cluster.admin === true) {
|
if (this.#data.users[username]) {
|
||||||
return this.#data.users;
|
this.#data.users[username] = attributes;
|
||||||
|
this.#save();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser (user, attributes, params) {
|
delUser (user, params = null) {
|
||||||
if (attributes.resources && attributes.cluster && attributes.templates) {
|
const username = `${user.id}@${user.realm}`;
|
||||||
const username = `${user.id}@${user.realm}`;
|
if (this.#data.users[username]) {
|
||||||
if (this.#data.users[username]) {
|
delete this.#data.users[username];
|
||||||
if (this.#data.users[params.username] && this.#data.users[params.username].cluster.admin) {
|
this.#save();
|
||||||
return new AtomicChange(false,
|
return true;
|
||||||
{
|
|
||||||
username,
|
|
||||||
attributes: {
|
|
||||||
resources: attributes.resources,
|
|
||||||
cluster: attributes.cluster,
|
|
||||||
templates: attributes.templates
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(delta) => {
|
|
||||||
this.#data.users[delta.username] = delta.attributes;
|
|
||||||
this.#save();
|
|
||||||
return { ok: true, status: 200, message: "" };
|
|
||||||
},
|
|
||||||
{ ok: true, status: 200, message: "" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 401, message: `${params.username} is not an admin user in localdb` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// return false;
|
|
||||||
return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 400, message: `${username} was not found in localdb` });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new AtomicChange(true, {}, doNothingCallback, null);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delUser (user, params) {}
|
|
||||||
|
|
||||||
// group methods not implemented because db backend does not store groups
|
// group methods not implemented because db backend does not store groups
|
||||||
addGroup (group, atrributes, params) {}
|
addGroup (group, atrributes, params = null) {}
|
||||||
getGroup (group, params) {}
|
getGroup (group, params = null) {}
|
||||||
getAllGroups (params) {
|
setGroup (group, attributes, params = null) {}
|
||||||
return null;
|
delGroup (group, params = null) {}
|
||||||
|
|
||||||
|
// assume that adding to group also adds to group's pool
|
||||||
|
addUserToGroup (user, group, params = null) {
|
||||||
|
const username = `${user.id}@${user.realm}`;
|
||||||
|
if (this.#data.users[username]) {
|
||||||
|
this.#data.users[username].cluster.pools[group.id] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setGroup (group, attributes, params) {}
|
|
||||||
delGroup (group, params) {}
|
|
||||||
|
|
||||||
// assume that adding to group also adds to group's pool
|
// assume that adding to group also adds to group's pool
|
||||||
addUserToGroup (user, group, params) {}
|
delUserFromGroup (user, group, params = null) {
|
||||||
|
const username = `${user.id}@${user.realm}`;
|
||||||
// assume that adding to group also adds to group's pool
|
if (this.#data.users[username] && this.#data.users[username].cluster.pools[group.id]) {
|
||||||
delUserFromGroup (user, group, params) {}
|
delete this.#data.users[username].cluster.pools[group.id];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { AtomicChange, AUTH_BACKEND, doNothingCallback } from "./backends.js";
|
import { AUTH_BACKEND } from "./backends.js";
|
||||||
import * as setCookie from "set-cookie-parser";
|
import * as setCookie from "set-cookie-parser";
|
||||||
|
|
||||||
export default class PAASLDAP extends AUTH_BACKEND {
|
export default class PAASLDAP extends AUTH_BACKEND {
|
||||||
#url = null;
|
#url = null;
|
||||||
#realm = null;
|
|
||||||
|
|
||||||
constructor (config) {
|
constructor (config) {
|
||||||
super();
|
super();
|
||||||
this.#url = config.url;
|
this.#url = config.url;
|
||||||
this.#realm = config.realm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,7 +15,7 @@ export default class PAASLDAP extends AUTH_BACKEND {
|
|||||||
* @param {*} path HTTP path, prepended with the paas-LDAP API base url
|
* @param {*} path HTTP path, prepended with the paas-LDAP API base url
|
||||||
* @param {*} method HTTP method
|
* @param {*} method HTTP method
|
||||||
* @param {*} body body parameters and data to be sent. Optional.
|
* @param {*} body body parameters and data to be sent. Optional.
|
||||||
* @returns {Object} HTTP response object
|
* @returns {Object} HTTP response object or HTTP error object.
|
||||||
*/
|
*/
|
||||||
async #request (path, method, auth = null, body = null) {
|
async #request (path, method, auth = null, body = null) {
|
||||||
const url = `${this.#url}${path}`;
|
const url = `${this.#url}${path}`;
|
||||||
@ -41,23 +39,19 @@ export default class PAASLDAP extends AUTH_BACKEND {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
const result = error.response;
|
error.ok = false;
|
||||||
result.ok = result.status === 200;
|
error.status = 500;
|
||||||
return result;
|
error.data = {
|
||||||
|
error: error.code
|
||||||
|
};
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleGenericReturn (res) {
|
async openSession (credentials) {
|
||||||
return {
|
const userRealm = credentials.username.split("@").at(-1);
|
||||||
ok: res.ok,
|
const uid = credentials.username.replace(`@${userRealm}`, "");
|
||||||
status: res.status,
|
const content = { uid, password: credentials.password };
|
||||||
message: res.ok ? "" : res.data.error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async openSession (user, password) {
|
|
||||||
const username = user.id;
|
|
||||||
const content = { username, password };
|
|
||||||
const result = await this.#request("/ticket", "POST", null, content);
|
const result = await this.#request("/ticket", "POST", null, content);
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
const cookies = setCookie.parse(result.headers["set-cookie"]);
|
const cookies = setCookie.parse(result.headers["set-cookie"]);
|
||||||
@ -67,118 +61,51 @@ export default class PAASLDAP extends AUTH_BACKEND {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: result.status,
|
status: result.status,
|
||||||
message: "",
|
|
||||||
cookies
|
cookies
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return {
|
return result;
|
||||||
ok: false,
|
|
||||||
status: result.status,
|
|
||||||
message: result.data.error,
|
|
||||||
cookies: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUser (user, attributes, params) {}
|
async addUser (user, attributes, params = null) {
|
||||||
|
return await this.#request(`/users/${user.id}`, "POST", params, attributes);
|
||||||
async getUser (user, params) {
|
|
||||||
if (!params) { // params required, do nothing if params are missing
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const res = await this.#request(`/users/${user.id}`, "GET", params);
|
|
||||||
if (res.ok) { // if ok, return user data
|
|
||||||
return res.data.user;
|
|
||||||
}
|
|
||||||
else { // else return null
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUsers (params) {
|
async getUser (user, params = null) {
|
||||||
if (!params) {
|
return await this.#request(`/users/${user.id}`, "GET", params);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const res = await this.#request("/users", "GET", params);
|
|
||||||
if (res.ok) { // if ok, return user data
|
|
||||||
const users = res.data.users;
|
|
||||||
const usersFormatted = {};
|
|
||||||
// label each user object by user@realm
|
|
||||||
for (const user of users) {
|
|
||||||
usersFormatted[`${user.attributes.uid}@${this.#realm}`] = user;
|
|
||||||
}
|
|
||||||
return usersFormatted;
|
|
||||||
}
|
|
||||||
else { // else return null
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUser (user, attributes, params) {
|
async setUser (user, attributes, params = null) {
|
||||||
if (!attributes.userpassword && !attributes.cn && attributes.sn) {
|
return await this.#request(`/users/${user.id}`, "POST", params, attributes);
|
||||||
return new AtomicChange(true, {}, doNothingCallback, null); // change has no ldap attributes
|
|
||||||
}
|
|
||||||
const ldapAttributes = {};
|
|
||||||
if (attributes.userpassword) {
|
|
||||||
ldapAttributes.userpassword = attributes.userpassword;
|
|
||||||
}
|
|
||||||
if (attributes.cn) {
|
|
||||||
ldapAttributes.cn = attributes.cn;
|
|
||||||
}
|
|
||||||
if (attributes.sn) {
|
|
||||||
ldapAttributes.sn = attributes.sn;
|
|
||||||
}
|
|
||||||
return new AtomicChange(
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
user,
|
|
||||||
ldapAttributes,
|
|
||||||
params
|
|
||||||
},
|
|
||||||
async (delta) => {
|
|
||||||
const res = await this.#request(`/users/${delta.user.id}`, "POST", delta.params, delta.ldapAttributes);
|
|
||||||
return this.#handleGenericReturn(res);
|
|
||||||
},
|
|
||||||
{ ok: true, status: 200, message: "" }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delUser (user, params) {}
|
async delUser (user, params = null) {
|
||||||
|
return await this.#request(`/users/${user.id}`, "DELETE", params);
|
||||||
|
}
|
||||||
|
|
||||||
async addGroup (group, attributes, params) {}
|
async addGroup (group, attributes, params = null) {
|
||||||
|
return await this.#request(`/groups/${group.id}`, "POST", params);
|
||||||
|
}
|
||||||
|
|
||||||
async getGroup (group, params) {
|
async getGroup (group, params = null) {
|
||||||
return await this.#request(`/groups/${group.id}`, "GET", params);
|
return await this.#request(`/groups/${group.id}`, "GET", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllGroups (params) {
|
async setGroup (group, attributes, params = null) {
|
||||||
if (!params) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const res = await this.#request("/groups", "GET", params);
|
|
||||||
if (res.ok) { // if ok, return user data
|
|
||||||
const groups = res.data.groups;
|
|
||||||
const groupsFormatted = {};
|
|
||||||
// label each user object by user@realm
|
|
||||||
for (const group of groups) {
|
|
||||||
groupsFormatted[`${group.attributes.cn}@${this.#realm}`] = group;
|
|
||||||
}
|
|
||||||
return groupsFormatted;
|
|
||||||
}
|
|
||||||
else { // else return null
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setGroup (group, attributes, params) {
|
|
||||||
// not implemented, LDAP groups do not have any attributes to change
|
// not implemented, LDAP groups do not have any attributes to change
|
||||||
return new AtomicChange(true, {}, doNothingCallback, null); ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delGroup (group, params) {}
|
async delGroup (group, params = null) {
|
||||||
|
return await this.#request(`/groups/${group.id}`, "DELETE", params);
|
||||||
|
}
|
||||||
|
|
||||||
async addUserToGroup (user, group, params) {}
|
async addUserToGroup (user, group, params = null) {
|
||||||
|
return await this.#request(`/groups/${group.id}/members/${user.id}`, "POST", params);
|
||||||
|
}
|
||||||
|
|
||||||
async delUserFromGroup (user, group, params) {}
|
async delUserFromGroup (user, group, params = null) {
|
||||||
|
return await this.#request(`/groups/${group.id}/members/${user.id}`, "DELETE", params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,10 @@ export default class PVE extends PVE_BACKEND {
|
|||||||
this.#pveRoot = config.root;
|
this.#pveRoot = config.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
async openSession (user, password) {
|
async openSession (credentials) {
|
||||||
const credentials = { username: `${user.id}@${user.realm}`, password };
|
|
||||||
const response = await global.pve.requestPVE("/access/ticket", "POST", null, credentials);
|
const response = await global.pve.requestPVE("/access/ticket", "POST", null, credentials);
|
||||||
if (!(response.status === 200)) {
|
if (!(response.status === 200)) {
|
||||||
return {
|
return response;
|
||||||
ok: false,
|
|
||||||
status: response.status,
|
|
||||||
message: "Authorization failed",
|
|
||||||
cookies: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const ticket = response.data.data.ticket;
|
const ticket = response.data.data.ticket;
|
||||||
const csrftoken = response.data.data.CSRFPreventionToken;
|
const csrftoken = response.data.data.CSRFPreventionToken;
|
||||||
@ -91,11 +85,7 @@ export default class PVE extends PVE_BACKEND {
|
|||||||
*/
|
*/
|
||||||
async handleResponse (node, result, res) {
|
async handleResponse (node, result, res) {
|
||||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||||
if (result.status !== 200) {
|
if (result.data.data && typeof (result.data.data) === "string" && result.data.data.startsWith("UPID:")) {
|
||||||
res.status(result.status).send({ error: result.statusText });
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
else if (result.data.data && typeof (result.data.data) === "string" && result.data.data.startsWith("UPID:")) {
|
|
||||||
const upid = result.data.data;
|
const upid = result.data.data;
|
||||||
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
||||||
while (taskStatus.data.data.status !== "stopped") {
|
while (taskStatus.data.data.status !== "stopped") {
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
export const router = Router({ mergeParams: true });
|
|
||||||
|
|
||||||
const checkAuth = global.utils.checkAuth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get all groups
|
|
||||||
* responses:
|
|
||||||
* - 200: {auth: true, groups: Array}
|
|
||||||
* - 401: {auth: false}
|
|
||||||
*/
|
|
||||||
router.get("/", async (req, res) => {
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const groups = await global.userManager.getAllGroups(req.cookies);
|
|
||||||
res.status(200).send({ groups });
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get specific group
|
|
||||||
* request:
|
|
||||||
* - groupname: name of group to get
|
|
||||||
* responses:
|
|
||||||
* - 200: {auth: true, group: Object}
|
|
||||||
* - 401: {auth: false}
|
|
||||||
*/
|
|
||||||
router.get("/:groupname", async (req, res) => {
|
|
||||||
const params = {
|
|
||||||
groupname: req.params.groupname
|
|
||||||
};
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const group = await global.userManager.getGroup(params.groupname, req.cookies);
|
|
||||||
res.status(200).send({ group });
|
|
||||||
});
|
|
@ -1,42 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
export const router = Router({ mergeParams: true });
|
|
||||||
|
|
||||||
const checkAuth = global.utils.checkAuth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get all users
|
|
||||||
* responses:
|
|
||||||
* - 200: {auth:true, users: Array}
|
|
||||||
* - 401: {auth: false}
|
|
||||||
*/
|
|
||||||
router.get("/", async (req, res) => {
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const users = await global.userManager.getAllUsers(req.cookies);
|
|
||||||
res.status(200).send({ users });
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get specific user
|
|
||||||
* request:
|
|
||||||
* - username: username (id@realm) of user to get
|
|
||||||
* responses:
|
|
||||||
* - 200: {auth: true, user: Object}
|
|
||||||
* - 401: {auth: false}
|
|
||||||
*/
|
|
||||||
router.get("/:username", async (req, res) => {
|
|
||||||
const params = {
|
|
||||||
username: req.params.username
|
|
||||||
};
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
|
||||||
const user = await global.userManager.getUser(userObj, req.cookies);
|
|
||||||
res.status(200).send({ user });
|
|
||||||
});
|
|
@ -3,8 +3,6 @@ export const router = Router({ mergeParams: true }); ;
|
|||||||
|
|
||||||
const checkAuth = global.utils.checkAuth;
|
const checkAuth = global.utils.checkAuth;
|
||||||
|
|
||||||
global.utils.recursiveImportRoutes(router, "", "access", import.meta.url);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET - check authentication
|
* GET - check authentication
|
||||||
* responses:
|
* responses:
|
||||||
@ -25,12 +23,12 @@ router.get("/", async (req, res) => {
|
|||||||
class CookieFetcher {
|
class CookieFetcher {
|
||||||
#fetchedBackends = [];
|
#fetchedBackends = [];
|
||||||
#cookies = [];
|
#cookies = [];
|
||||||
async fetchBackends (backends, user, password) {
|
async fetchBackends (backends, credentials) {
|
||||||
for (const backend of backends) {
|
for (const backend of backends) {
|
||||||
if (this.#fetchedBackends.indexOf(backend) === -1) {
|
if (this.#fetchedBackends.indexOf(backend) === -1) {
|
||||||
const response = await global.backends[backend].openSession(user, password);
|
const response = await backend.openSession(credentials);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return response.message;
|
return false;
|
||||||
}
|
}
|
||||||
this.#cookies = this.#cookies.concat(response.cookies);
|
this.#cookies = this.#cookies.concat(response.cookies);
|
||||||
this.#fetchedBackends.push(backend);
|
this.#fetchedBackends.push(backend);
|
||||||
@ -39,7 +37,7 @@ class CookieFetcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exportCookies () {
|
exportCookies () {
|
||||||
@ -62,28 +60,24 @@ router.post("/ticket", async (req, res) => {
|
|||||||
password: req.body.password
|
password: req.body.password
|
||||||
};
|
};
|
||||||
const domain = global.config.application.domain;
|
const domain = global.config.application.domain;
|
||||||
// const userRealm = params.username.split("@").at(-1);
|
const userRealm = params.username.split("@").at(-1);
|
||||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
const backends = [global.pve, global.db];
|
||||||
let backends = global.userManager.getBackendsByUser(userObj);
|
if (userRealm in global.auth) {
|
||||||
backends = backends.concat(["pve"]);
|
backends.push(global.auth[userRealm]);
|
||||||
|
}
|
||||||
const cm = new CookieFetcher();
|
const cm = new CookieFetcher();
|
||||||
const error = await cm.fetchBackends(backends, userObj, params.password);
|
const success = await cm.fetchBackends(backends, params);
|
||||||
if (error) {
|
if (!success) {
|
||||||
res.status(401).send({ auth: false, error });
|
res.status(401).send({ auth: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cookies = cm.exportCookies();
|
const cookies = cm.exportCookies();
|
||||||
let minimumExpires = Infinity;
|
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
const expiresDate = new Date(Date.now() + cookie.expiresMSFromNow);
|
const expiresDate = new Date(Date.now() + cookie.expiresMSFromNow);
|
||||||
res.cookie(cookie.name, cookie.value, { domain, path: "/", httpOnly: true, secure: true, expires: expiresDate, sameSite: "none" });
|
res.cookie(cookie.name, cookie.value, { domain, path: "/", httpOnly: true, secure: true, expires: expiresDate });
|
||||||
if (cookie.expiresMSFromNow < minimumExpires) {
|
|
||||||
minimumExpires = cookie.expiresMSFromNow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const expiresDate = new Date(Date.now() + minimumExpires);
|
res.cookie("username", params.username, { domain, path: "/", secure: true });
|
||||||
res.cookie("username", params.username, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" });
|
res.cookie("auth", 1, { domain, path: "/", secure: true });
|
||||||
res.cookie("auth", 1, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" });
|
|
||||||
res.status(200).send({ auth: true });
|
res.status(200).send({ auth: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,10 +94,10 @@ router.delete("/ticket", async (req, res) => {
|
|||||||
const domain = global.config.application.domain;
|
const domain = global.config.application.domain;
|
||||||
const expire = new Date(0);
|
const expire = new Date(0);
|
||||||
for (const cookie in req.cookies) {
|
for (const cookie in req.cookies) {
|
||||||
res.cookie(cookie, "", { domain, path: "/", expires: expire, secure: true, sameSite: "none" });
|
res.cookie(cookie, "", { domain, path: "/", expires: expire });
|
||||||
}
|
}
|
||||||
await global.pve.closeSession(req.cookies);
|
await global.pve.closeSession(req.cookies);
|
||||||
await global.userManager.closeSession(req.cookies);
|
await global.db.closeSession(req.cookies);
|
||||||
res.status(200).send({ auth: false });
|
res.status(200).send({ auth: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -120,16 +114,24 @@ router.post("/password", async (req, res) => {
|
|||||||
password: req.body.password
|
password: req.body.password
|
||||||
};
|
};
|
||||||
|
|
||||||
// check auth
|
const userRealm = params.username.split("@").at(-1);
|
||||||
const auth = await checkAuth(req.cookies, res);
|
const authHandlers = global.config.handlers.auth;
|
||||||
if (!auth) {
|
const userID = params.username.replace(`@${userRealm}`, "");
|
||||||
return;
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
if (userRealm in authHandlers) {
|
||||||
|
const handler = authHandlers[userRealm];
|
||||||
|
const newAttributes = {
|
||||||
|
userpassword: params.password
|
||||||
|
};
|
||||||
|
const response = await handler.setUser(userObj, newAttributes, req.cookies);
|
||||||
|
if (response.ok) {
|
||||||
|
res.status(response.status).send(response.data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.status(response.status).send({ error: response.data.error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.status(501).send({ error: `Auth type ${userRealm} not implemented yet.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
|
||||||
const newAttributes = {
|
|
||||||
userpassword: params.password
|
|
||||||
};
|
|
||||||
const response = await global.userManager.setUser(userObj, newAttributes, req.cookies);
|
|
||||||
res.status(response.status).send(response);
|
|
||||||
});
|
});
|
@ -1,6 +1,7 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
export const router = Router({ mergeParams: true });
|
export const router = Router({ mergeParams: true });
|
||||||
|
|
||||||
|
const db = global.db;
|
||||||
const checkAuth = global.utils.checkAuth;
|
const checkAuth = global.utils.checkAuth;
|
||||||
const approveResources = global.utils.approveResources;
|
const approveResources = global.utils.approveResources;
|
||||||
const getUserResources = global.utils.getUserResources;
|
const getUserResources = global.utils.getUserResources;
|
||||||
@ -13,61 +14,6 @@ const basePath = `/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP}
|
|||||||
|
|
||||||
global.utils.recursiveImportRoutes(router, basePath, "cluster", import.meta.url);
|
global.utils.recursiveImportRoutes(router, basePath, "cluster", import.meta.url);
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get all available cluster pools
|
|
||||||
* returns only pool IDs
|
|
||||||
* responses:
|
|
||||||
* - 200: List of pools
|
|
||||||
* - PVE error
|
|
||||||
*/
|
|
||||||
router.get("/pools", async (req, res) => {
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allPools = await global.pve.requestPVE("/pools", "GET", { token: true });
|
|
||||||
|
|
||||||
if (allPools.status === 200) {
|
|
||||||
const allPoolsIDs = Array.from(allPools.data.data, (x) => x.poolid);
|
|
||||||
res.status(allPools.status).send({ pools: allPoolsIDs });
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(allPools.status).send({ error: allPools.statusMessage });
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET - get all available cluster nodes
|
|
||||||
* uses existing user permissions without elevation
|
|
||||||
* returns only node IDs
|
|
||||||
* responses:
|
|
||||||
* - 200: List of nodes
|
|
||||||
* - PVE error
|
|
||||||
*/
|
|
||||||
router.get("/nodes", async (req, res) => {
|
|
||||||
// check auth
|
|
||||||
const auth = await checkAuth(req.cookies, res);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allNodes = await global.pve.requestPVE("/nodes", "GET", { cookies: req.cookies });
|
|
||||||
|
|
||||||
if (allNodes.status === 200) {
|
|
||||||
const allNodesIDs = Array.from(allNodes.data.data, (x) => x.node);
|
|
||||||
res.status(allNodes.status).send({ nodes: allNodesIDs });
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(allNodes.status).send({ error: allNodes.statusMessage });
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET - get available pcie devices given node and user
|
* GET - get available pcie devices given node and user
|
||||||
* request:
|
* request:
|
||||||
@ -83,14 +29,16 @@ router.get(`/:node(${nodeRegexP})/pci`, async (req, res) => {
|
|||||||
node: req.params.node
|
node: req.params.node
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth
|
// check auth
|
||||||
const auth = await checkAuth(req.cookies, res);
|
const auth = await checkAuth(req.cookies, res);
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const userNodes = (await global.userManager.getUser(userObj, req.cookies)).cluster.nodes;
|
const userNodes = db.getUser(userObj).cluster.nodes;
|
||||||
if (userNodes[params.node] !== true) {
|
if (userNodes[params.node] !== true) {
|
||||||
res.status(401).send({ auth: false, path: params.node });
|
res.status(401).send({ auth: false, path: params.node });
|
||||||
res.end();
|
res.end();
|
||||||
@ -135,7 +83,9 @@ router.post(`${basePath}/resources`, async (req, res) => {
|
|||||||
boot: req.body.boot
|
boot: req.body.boot
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
@ -215,7 +165,9 @@ router.post(`${basePath}/create`, async (req, res) => {
|
|||||||
rootfssize: req.body.rootfssize
|
rootfssize: req.body.rootfssize
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth
|
// check auth
|
||||||
const auth = await checkAuth(req.cookies, res);
|
const auth = await checkAuth(req.cookies, res);
|
||||||
@ -223,7 +175,7 @@ router.post(`${basePath}/create`, async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// get user db config
|
// get user db config
|
||||||
const user = await global.userManager.getUser(userObj, req.cookies);
|
const user = await db.getUser(userObj);
|
||||||
const vmid = Number.parseInt(params.vmid);
|
const vmid = Number.parseInt(params.vmid);
|
||||||
const vmidMin = user.cluster.vmid.min;
|
const vmidMin = user.cluster.vmid.min;
|
||||||
const vmidMax = user.cluster.vmid.max;
|
const vmidMax = user.cluster.vmid.max;
|
||||||
|
@ -130,7 +130,9 @@ router.post("/:disk/resize", async (req, res) => {
|
|||||||
size: req.body.size
|
size: req.body.size
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
@ -190,7 +192,9 @@ router.post("/:disk/move", async (req, res) => {
|
|||||||
delete: req.body.delete
|
delete: req.body.delete
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
@ -311,7 +315,9 @@ router.post("/:disk/create", async (req, res) => {
|
|||||||
iso: req.body.iso
|
iso: req.body.iso
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
export const router = Router({ mergeParams: true }); ;
|
export const router = Router({ mergeParams: true }); ;
|
||||||
|
|
||||||
|
const db = global.db;
|
||||||
const checkAuth = global.utils.checkAuth;
|
const checkAuth = global.utils.checkAuth;
|
||||||
const approveResources = global.utils.approveResources;
|
const approveResources = global.utils.approveResources;
|
||||||
|
|
||||||
@ -31,7 +32,9 @@ router.post("/:netid/create", async (req, res) => {
|
|||||||
name: req.body.name
|
name: req.body.name
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
@ -62,7 +65,7 @@ router.post("/:netid/create", async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// setup action
|
// setup action
|
||||||
const nc = (await global.userManager.getUser(userObj, req.cookies)).templates.network[params.type];
|
const nc = db.getUser(userObj).templates.network[params.type];
|
||||||
const action = {};
|
const action = {};
|
||||||
if (params.type === "lxc") {
|
if (params.type === "lxc") {
|
||||||
action[`net${params.netid}`] = `name=${params.name},bridge=${nc.bridge},ip=${nc.ip},ip6=${nc.ip6},tag=${nc.vlan},type=${nc.type},rate=${params.rate}`;
|
action[`net${params.netid}`] = `name=${params.name},bridge=${nc.bridge},ip=${nc.ip},ip6=${nc.ip6},tag=${nc.vlan},type=${nc.type},rate=${params.rate}`;
|
||||||
@ -101,7 +104,9 @@ router.post("/:netid/modify", async (req, res) => {
|
|||||||
rate: req.body.rate
|
rate: req.body.rate
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth for specific instance
|
// check auth for specific instance
|
||||||
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
|
||||||
|
@ -75,7 +75,9 @@ router.post("/:hostpci/modify", async (req, res) => {
|
|||||||
pcie: req.body.pcie
|
pcie: req.body.pcie
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check if type is qemu
|
// check if type is qemu
|
||||||
if (params.type !== "qemu") {
|
if (params.type !== "qemu") {
|
||||||
@ -160,7 +162,9 @@ router.post("/create", async (req, res) => {
|
|||||||
pcie: req.body.pcie
|
pcie: req.body.pcie
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check if type is qemu
|
// check if type is qemu
|
||||||
if (params.type !== "qemu") {
|
if (params.type !== "qemu") {
|
||||||
|
@ -20,9 +20,7 @@ router.get("/config/:key", async (req, res) => {
|
|||||||
const allowKeys = ["resources"];
|
const allowKeys = ["resources"];
|
||||||
if (allowKeys.includes(params.key)) {
|
if (allowKeys.includes(params.key)) {
|
||||||
const config = global.config;
|
const config = global.config;
|
||||||
const result = {};
|
res.status(200).send(config[params.key]);
|
||||||
result[params.key] = config[params.key];
|
|
||||||
res.status(200).send(result);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res.status(401).send({ auth: false, error: `User is not authorized to access /global/config/${params.key}.` });
|
res.status(401).send({ auth: false, error: `User is not authorized to access /global/config/${params.key}.` });
|
||||||
|
@ -165,10 +165,12 @@ if (schemes.interrupt.enabled) {
|
|||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wsServer.handleUpgrade(req, socket, head, async (socket) => {
|
wsServer.handleUpgrade(req, socket, head, (socket) => {
|
||||||
// get the user pools
|
// get the user pools
|
||||||
const userObj = global.utils.getUserObjFromUsername(cookies.username);
|
const userRealm = cookies.username.split("@").at(-1);
|
||||||
const pools = Object.keys((await global.userManager.getUser(userObj, cookies)).cluster.pools);
|
const userID = cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
const pools = Object.keys(global.db.getUser(userObj).cluster.pools);
|
||||||
// emit the connection to initialize socket
|
// emit the connection to initialize socket
|
||||||
wsServer.emit("connection", socket, cookies.username, pools);
|
wsServer.emit("connection", socket, cookies.username, pools);
|
||||||
});
|
});
|
||||||
|
@ -12,17 +12,15 @@ const getUserResources = global.utils.getUserResources;
|
|||||||
* - 401: {auth: false}
|
* - 401: {auth: false}
|
||||||
*/
|
*/
|
||||||
router.get("/dynamic/resources", async (req, res) => {
|
router.get("/dynamic/resources", async (req, res) => {
|
||||||
const params = {
|
|
||||||
username: req.cookies.username
|
|
||||||
};
|
|
||||||
|
|
||||||
// check auth
|
// check auth
|
||||||
const auth = await checkAuth(req.cookies, res);
|
const auth = await checkAuth(req.cookies, res);
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
const resources = await getUserResources(req, userObj);
|
const resources = await getUserResources(req, userObj);
|
||||||
res.status(200).send(resources);
|
res.status(200).send(resources);
|
||||||
@ -42,7 +40,9 @@ router.get("/config/:key", async (req, res) => {
|
|||||||
key: req.params.key
|
key: req.params.key
|
||||||
};
|
};
|
||||||
|
|
||||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
const userRealm = req.cookies.username.split("@").at(-1);
|
||||||
|
const userID = req.cookies.username.replace(`@${userRealm}`, "");
|
||||||
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
// check auth
|
// check auth
|
||||||
const auth = await checkAuth(req.cookies, res);
|
const auth = await checkAuth(req.cookies, res);
|
||||||
@ -51,7 +51,7 @@ router.get("/config/:key", async (req, res) => {
|
|||||||
}
|
}
|
||||||
const allowKeys = ["resources", "cluster"];
|
const allowKeys = ["resources", "cluster"];
|
||||||
if (allowKeys.includes(params.key)) {
|
if (allowKeys.includes(params.key)) {
|
||||||
const config = await global.userManager.getUser(userObj, req.cookies);
|
const config = global.db.getUser(userObj);
|
||||||
res.status(200).send(config[params.key]);
|
res.status(200).send(config[params.key]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
55
src/utils.js
55
src/utils.js
@ -15,34 +15,18 @@ import { exit } from "process";
|
|||||||
export async function checkAuth (cookies, res, vmpath = null) {
|
export async function checkAuth (cookies, res, vmpath = null) {
|
||||||
let auth = false;
|
let auth = false;
|
||||||
|
|
||||||
const userObj = getUserObjFromUsername(cookies.username); // check if username exists and is valid
|
const userRealm = cookies.username.split("@").at(-1);
|
||||||
if (!userObj) {
|
const userID = cookies.username.replace(`@${userRealm}`, "");
|
||||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: "Username was missing or invalid." });
|
const userObj = { id: userID, realm: userRealm };
|
||||||
|
|
||||||
|
if (global.db.getUser(userObj) === null) {
|
||||||
|
auth = false;
|
||||||
|
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: `User ${cookies.username} not found in localdb.` });
|
||||||
res.end();
|
res.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cookies.PVEAuthCookie) { // check if PVE token exists
|
if (vmpath) {
|
||||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: "Token was missing or invalid." });
|
|
||||||
res.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pveTicket = cookies.PVEAuthCookie;
|
|
||||||
const result = await global.pve.requestPVE("/access/ticket", "POST", null, { username: cookies.username, password: pveTicket });
|
|
||||||
if (result.status !== 200) { // check if PVE token is valid by using /access/ticket to validate ticket with Proxmox
|
|
||||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: "Username did not match token." });
|
|
||||||
res.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((await global.userManager.getUser(userObj, cookies)) === null) { // check if user exists in database
|
|
||||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: `User ${cookies.username} not found in database.` });
|
|
||||||
res.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vmpath) { // if a path is specified, check the permissions on the path
|
|
||||||
const result = await global.pve.requestPVE(`/${vmpath}/config`, "GET", { cookies });
|
const result = await global.pve.requestPVE(`/${vmpath}/config`, "GET", { cookies });
|
||||||
auth = result.status === 200;
|
auth = result.status === 200;
|
||||||
}
|
}
|
||||||
@ -55,7 +39,6 @@ export async function checkAuth (cookies, res, vmpath = null) {
|
|||||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: "User token did not pass authentication check." });
|
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: "User token did not pass authentication check." });
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +113,8 @@ async function getAllInstanceConfigs (req, diskprefixes) {
|
|||||||
*/
|
*/
|
||||||
export async function getUserResources (req, user) {
|
export async function getUserResources (req, user) {
|
||||||
const dbResources = global.config.resources;
|
const dbResources = global.config.resources;
|
||||||
const userResources = (await global.userManager.getUser(user, req.cookies)).resources;
|
const userResources = global.db.getUser(user).resources;
|
||||||
|
|
||||||
// setup disk prefixes object
|
// setup disk prefixes object
|
||||||
const diskprefixes = [];
|
const diskprefixes = [];
|
||||||
for (const resourceName of Object.keys(dbResources)) {
|
for (const resourceName of Object.keys(dbResources)) {
|
||||||
@ -347,7 +331,7 @@ export function getTimeLeft (timeout) {
|
|||||||
/**
|
/**
|
||||||
* Recursively import routes from target folder.
|
* Recursively import routes from target folder.
|
||||||
* @param {Object} router or app object.
|
* @param {Object} router or app object.
|
||||||
* @param {string} baseroute base route of imported modules starting from the current path.
|
* @param {string} baseroute API route for each imported module.
|
||||||
* @param {string} target folder to import modules.
|
* @param {string} target folder to import modules.
|
||||||
* @param {string} from source folder of calling module, optional for imports from the same base directory.
|
* @param {string} from source folder of calling module, optional for imports from the same base directory.
|
||||||
*/
|
*/
|
||||||
@ -378,20 +362,3 @@ export function readJSONFile (path) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} username
|
|
||||||
* @returns {Object | null} user object containing username and realm or null if user does not exist
|
|
||||||
*/
|
|
||||||
export function getUserObjFromUsername (username) {
|
|
||||||
if (username) {
|
|
||||||
const userRealm = username.split("@").at(-1);
|
|
||||||
const userID = username.replace(`@${userRealm}`, "");
|
|
||||||
const userObj = { id: userID, realm: userRealm };
|
|
||||||
return userObj;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -72,7 +72,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cluster": {
|
"cluster": {
|
||||||
"admin": false,
|
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"example-node-0": true,
|
"example-node-0": true,
|
||||||
"example-node-1": true,
|
"example-node-1": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user