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

V0.2 develop #21

Merged
merged 10 commits into from
Jan 7, 2020
12 changes: 10 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,24 @@ declare class ServerBase {
* Path to the file in which messages should get logged (in case `logToFile` is `true`).
* @default './server-state.log'
*/
logFilePath?: string
logFilePath?: string,
/**
* A callback that checks whether the current user (of the request) is authorized to access the resource
* @param req The HTTP request to evaluate tokens or other forms of authentication
* @param authorizedGroups The groups that have the authorization to access the resource
* @return Is authorized? In other words: Is there an intersection between the groups the user belongs to and `authorizedGroups`?
*/
isAuthorized?: (req: Express.Request, authorizedGroups: string[]) => boolean,
});

/**
* Adds a server module function (as specified in https://github.com/server-state/specs/blob/master/terminology/server-module-function.md) under a given name resulting in a server module function (as specified in https://github.com/server-state/specs/blob/master/terminology/server-module.md), making it available under `/api/v1/[name]`.
* @param name The name of the module. Must be unique (i.e., not registered before). Otherwise, the module will be skipped and an error message logged.
* @param moduleFunction The function defining the module (SMF). Must either return a Promise for or a JSON serializable value or throw with an error message in case of failure (resulting in the error message getting logged and a `HTTP 500` response).
* @param authorizedGroups Groups authorized to access this module
* @param moduleOptions Options getting passed to the SMF as first argument. Can be any type, but usually will be a configuration object.
*/
addModule(name: string, moduleFunction: (options: any) => any | Promise<any>, moduleOptions?: any)
addModule(name: string, moduleFunction: (options: any) => any | Promise<any>, authorizedGroups?: string[], moduleOptions?: any)

/**
* Attaches the server base to the passed Express `app`, handling routes under `/api/` there.
Expand Down
2 changes: 1 addition & 1 deletion index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@server-state/server-base",
"version": "0.1.3",
"version": "0.2.0",
"description": "Server-side (NodeJS based) implementation of the architecture (no modules included)",
"main": "index.js",
"scripts": {
Expand Down
36 changes: 31 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,29 @@ const logger = require('./lib/logger');
module.exports = class ServerStateBase {
/**
* Create a new instance of `ServerStateBase` that will, upon calling `init(app)`, add the relevant API endpoints to the express app
* @param {Object<{isAuthorized?: function}>} [config]
*/
constructor(config) {
this.modules = {};
this.args = {};
this.config = config;
this.authorizedGroups = {};
this.config = config || {isAuthorized: null};
logger._configure(config);
}

/**
* Add module to server for json output.
* @param {string} name Module name (without prefixes)
* @param {function} fn
* @param {string[]} [authorizedGroups=[]]
* @param {*} [options=undefined]
*/
addModule(name, fn, options) {
addModule(name, fn, authorizedGroups, options) {
if (this.modules[name])
logger.warn(`Module already used: ${name}. Skipping`);
else {
this.modules[name] = fn;
this.authorizedGroups[name] = authorizedGroups;
this.args[name] = options;
}
}
Expand All @@ -41,18 +45,33 @@ module.exports = class ServerStateBase {
init(app) {
// Apply modules
for (let module in this.modules) {
if (Object.prototype.hasOwnProperty.call(this.modules, module))
if (Object.prototype.hasOwnProperty.call(this.modules, module)) {
app.get(`/api/v1/${module}/permissions`, async (req, res) => {
if (this.config.isAuthorized && typeof this.config.isAuthorized === 'function') {
if (!this.config.isAuthorized(req, this.authorizedGroups[module])) {
return res.status(403).send();
}
}
const result = await this.authorizedGroups[module];
return res.json(result);
});
app.get('/api/v1/' + module, async (req, res) => {
try {
if (this.config.isAuthorized && typeof this.config.isAuthorized === 'function') {
if (!this.config.isAuthorized(req, this.authorizedGroups[module])) {
return res.status(403).send();
}
}
const result = await this.modules[module](this.args[module]);
return res.json(result);
} catch (e) {
logger.error(module, e.message);
res.status(500).send(
return res.status(500).send(
`An error occurred while running the module ${module}. Please check your server logs or contact your administrator.`
);
}
});
}
}

// Pull requests from all modules and send them
Expand All @@ -62,7 +81,14 @@ module.exports = class ServerStateBase {
for (let module in this.modules) {
if (Object.prototype.hasOwnProperty.call(this.modules, module)) {
try {
result[module] = await this.modules[module](this.args[module]);
if (this.config.isAuthorized && typeof this.config.isAuthorized === 'function') {
if (this.config.isAuthorized(req, this.authorizedGroups[module])) {
result[module] = await this.modules[module](this.args[module]);
}
} else {

result[module] = await this.modules[module](this.args[module]);
}
} catch (e) {
logger.error(module, e.message);
res.status(500).send(
Expand Down
Loading