UNPKG

ltijs

Version:

Easily turn your web application into a LTI 1.3 Learning Tool.

135 lines (128 loc) 4.29 kB
"use strict"; // Express server const express = require('express'); const bodyParser = require('body-parser'); const https = require('https'); const helmet = require('helmet'); const cookieParser = require('cookie-parser'); const cors = require('cors'); const provAuthDebug = require('debug')('provider:auth'); class Server { constructor(https, ssl, ENCRYPTIONKEY, corsOpt, serverAddon) { this.app = express(); this.server = false; this.ssl = false; if (https) this.ssl = ssl; // Handling URI decode vulnerability this.app.use(async (req, res, next) => { try { decodeURIComponent(req.path); return next(); } catch (err) { return res.status(400).send({ status: 400, error: 'Bad Request', details: { message: 'URIError: Failed to decode param' } }); } }); // Setting up helmet this.app.use(helmet({ frameguard: false, // Disabling frameguard so that Ltijs can send resources to iframes inside LMS's contentSecurityPolicy: false })); // Controlling cors, having in mind that resources in another domain need to be explicitly allowed, and that ltijs controls origin blocking unregistered platforms // This block of code allows cors specifying the host instead of just returnin '*'. And then ltijs blocks requests from unregistered platforms. (Except for whitelisted routes) if (corsOpt === undefined || corsOpt) { this.app.use(cors({ origin: (origin, callback) => { callback(null, true); }, credentials: true })); this.app.options('*', cors()); } this.app.use(bodyParser.urlencoded({ extended: false })); this.app.use(bodyParser.json()); this.app.use(bodyParser.raw()); this.app.use(bodyParser.text()); this.app.use(cookieParser(ENCRYPTIONKEY)); this.app.use(async (req, res, next) => { // Creating Authorization schema LTIK-AUTH-V1 if (req.headers && req.headers.authorization) { const headerParts = req.headers.authorization.split('LTIK-AUTH-V1 Token='); if (headerParts.length > 1) { provAuthDebug('Validating LTIK-AUTH-V1 Authorization schema'); try { const tokenBody = headerParts[1]; // Get ltik const tokenBodyParts = tokenBody.split(','); const ltik = tokenBodyParts[0]; req.token = ltik; // Get additional Authorization headers const additional = tokenBody.split('Additional='); if (additional.length > 1) req.headers.authorization = additional[1]; } catch (err) { provAuthDebug('Error validating LTIK-AUTH-V1 Authorization schema'); provAuthDebug(err); } } } return next(); }); this.app.use(async (req, res, next) => { // Return if req.token is already defined if (req.token) return next(); // Attempt to retrieve ltik from query parameters if (req.query && req.query.ltik) { req.token = req.query.ltik; return next(); } // Attempt to retrieve ltik from body parameters if (req.body && req.body.ltik) { req.token = req.body.ltik; return next(); } // Attempt to retrieve ltik from Bearer Authorization header if (req.headers.authorization) { const parts = req.headers.authorization.split(' '); if (parts.length === 2 && parts[0] === 'Bearer') { req.token = parts[1]; return next(); } } return next(); }); // Executing server addon if (serverAddon) serverAddon(this.app); } listen(port) { return new Promise((resolve, reject) => { if (this.ssl) { this.server = https.createServer(this.ssl, this.app).listen(port); } else { this.server = this.app.listen(port); } this.server.on('listening', () => { resolve(true); }); this.server.on('error', err => { reject(err); }); }); } setStaticPath(path) { this.app.use('/', express.static(path, { index: '_' })); } close() { if (this.server) this.server.close(); } } module.exports = Server;