Skip to content

Commit

Permalink
V0.2 develop (#21)
Browse files Browse the repository at this point in the history
V0.2 develop
  • Loading branch information
pklaschka authored Jan 7, 2020
2 parents a1db0e0 + 8aa5fad commit 0a226b9
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 60 deletions.
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

0 comments on commit 0a226b9

Please sign in to comment.