diff --git a/dist/Provider/Provider.js b/dist/Provider/Provider.js index 5a7f825..3aea661 100644 --- a/dist/Provider/Provider.js +++ b/dist/Provider/Provider.js @@ -40,6 +40,7 @@ var _devMode = /*#__PURE__*/new WeakMap(); var _ltiaas = /*#__PURE__*/new WeakMap(); var _tokenMaxAge = /*#__PURE__*/new WeakMap(); var _cookieOptions = /*#__PURE__*/new WeakMap(); +var _bodyParserOptions = /*#__PURE__*/new WeakMap(); var _setup = /*#__PURE__*/new WeakMap(); var _connectCallback2 = /*#__PURE__*/new WeakMap(); var _deepLinkingCallback2 = /*#__PURE__*/new WeakMap(); @@ -97,6 +98,17 @@ class Provider { signed: true } }); + _classPrivateFieldInitSpec(this, _bodyParserOptions, { + writable: true, + value: { + json: {}, + raw: {}, + text: {}, + urlencoded: { + extended: false + } + } + }); // Setup flag _classPrivateFieldInitSpec(this, _setup, { writable: true, @@ -243,6 +255,10 @@ class Provider { * @param {Array} [options.dynReg.redirectUris] - Additional redirect URIs. (Ex: ['https://tool.example.com/launch']) * @param {Object} [options.dynReg.customParameters] - Custom parameters object. (Ex: { key: 'value' }) * @param {Boolean} [options.dynReg.autoActivate = false] - Platform auto activation flag. If true, every Platform registered dynamically is immediately activated. Defaults to false. + * @param {Object} [options.bodyParserOpt.json = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserjsonoptions) + * @param {Object} [options.bodyParserOpt.raw = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserrawoptions) + * @param {Object} [options.bodyParserOpt.text = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparsertextoptions) + * @param {Object} [options.bodyParserOpt.urlencoded = { extended: false }] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserurlencodedoptions) */ setup(encryptionkey, database, options) { if ((0, _classPrivateFieldGet2.default)(this, _setup)) throw new Error('PROVIDER_ALREADY_SETUP'); @@ -270,8 +286,16 @@ class Provider { if (options.cookies.sameSite) (0, _classPrivateFieldGet2.default)(this, _cookieOptions).sameSite = options.cookies.sameSite; if (options.cookies.domain) (0, _classPrivateFieldGet2.default)(this, _cookieOptions).domain = options.cookies.domain; } + + // BodyParser options + if (options && options.bodyParserOpt) { + if (options.bodyParserOpt.json) (0, _classPrivateFieldGet2.default)(this, _bodyParserOptions).json = options.bodyParserOpt.json; + if (options.bodyParserOpt.raw) (0, _classPrivateFieldGet2.default)(this, _bodyParserOptions).raw = options.bodyParserOpt.raw; + if (options.bodyParserOpt.text) (0, _classPrivateFieldGet2.default)(this, _bodyParserOptions).text = options.bodyParserOpt.text; + if (options.bodyParserOpt.urlencoded) (0, _classPrivateFieldGet2.default)(this, _bodyParserOptions).urlencoded = options.bodyParserOpt.urlencoded; + } (0, _classPrivateFieldSet2.default)(this, _ENCRYPTIONKEY2, encryptionkey); - (0, _classPrivateFieldSet2.default)(this, _server, new Server(options ? options.https : false, options ? options.ssl : false, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2), options ? options.cors : true, options ? options.serverAddon : false)); + (0, _classPrivateFieldSet2.default)(this, _server, new Server(options ? options.https : false, options ? options.ssl : false, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2), options ? options.cors : true, options ? options.serverAddon : false, options ? options.bodyParserOpt : (0, _classPrivateFieldGet2.default)(this, _bodyParserOptions))); /** * @description Express server object. diff --git a/dist/Utils/Server.js b/dist/Utils/Server.js index b54b8d3..33d781f 100644 --- a/dist/Utils/Server.js +++ b/dist/Utils/Server.js @@ -9,7 +9,7 @@ const cookieParser = require('cookie-parser'); const cors = require('cors'); const provAuthDebug = require('debug')('provider:auth'); class Server { - constructor(https, ssl, ENCRYPTIONKEY, corsOpt, serverAddon) { + constructor(https, ssl, ENCRYPTIONKEY, corsOpt, serverAddon, bodyParserOpt) { this.app = express(); this.server = false; this.ssl = false; @@ -49,12 +49,14 @@ class Server { })); this.app.options('*', cors()); } - this.app.use(bodyParser.urlencoded({ + + // Ingest body parser options for each parsertype + this.app.use(bodyParser.urlencoded(bodyParserOpt && bodyParserOpt.urlencoded ? bodyParserOpt.urlencoded : { extended: false })); - this.app.use(bodyParser.json()); - this.app.use(bodyParser.raw()); - this.app.use(bodyParser.text()); + this.app.use(bodyParser.json(bodyParserOpt && bodyParserOpt.json ? bodyParserOpt.json : {})); + this.app.use(bodyParser.raw(bodyParserOpt && bodyParserOpt.raw ? bodyParserOpt.raw : {})); + this.app.use(bodyParser.text(bodyParserOpt && bodyParserOpt.text ? bodyParserOpt.text : {})); this.app.use(cookieParser(ENCRYPTIONKEY)); this.app.use(async (req, res, next) => { // Creating Authorization schema LTIK-AUTH-V1 diff --git a/docs/provider.md b/docs/provider.md index 0e34acd..47bae01 100644 --- a/docs/provider.md +++ b/docs/provider.md @@ -5,11 +5,8 @@ IMS Global Certified - - > Easily turn your web application into a LTI® 1.3 Learning Tool. - [![travisci](https://travis-ci.org/Cvmcosta/ltijs.svg?branch=master)](https://travis-ci.org/Cvmcosta/ltijs) [![codecov](https://codecov.io/gh/Cvmcosta/ltijs/branch/master/graph/badge.svg)](https://codecov.io/gh/Cvmcosta/ltijs) [![Node Version](https://img.shields.io/node/v/ltijs.svg)](https://www.npmjs.com/package/ltijs) @@ -21,18 +18,16 @@ [![APACHE2 License](https://img.shields.io/github/license/cvmcosta/ltijs)](#LICENSE) [![Donate](https://img.shields.io/badge/Donate-Buy%20me%20a%20coffe-blue)](https://www.buymeacoffee.com/UL5fBsi) - -Please ⭐️ us on [GitHub](https://github.com/Cvmcosta/ltijs), it always helps! +Please ⭐️ us on [GitHub](https://github.com/Cvmcosta/ltijs), it always helps! > [Ltijs is LTI® Advantage Complete Certified by IMS](https://site.imsglobal.org/certifications/coursekey/ltijs) -> Ltijs is the first LTI Library to implement the new [LTI® Advantage Dynamic Registration Service](https://cvmcosta.me/ltijs/#/dynamicregistration), now supported by **Moodle 3.10**. +> Ltijs is the first LTI Library to implement the new [LTI® Advantage Dynamic Registration Service](https://cvmcosta.me/ltijs/#/dynamicregistration), now supported by **Moodle 3.10**. > The Dynamic Registration Service turns the LTI Tool registration flow into a fast, completely automatic process. > - [Migrating from version 4](https://cvmcosta.github.io/ltijs/#/migration) > - [CHANGELOG](https://cvmcosta.github.io/ltijs/#/changelog) - ---
@@ -48,13 +43,13 @@ If you need an enterprise-ready LTI deployment, LTIaaS can get you up and runnin Through our consultation services we can help you design, build and maintain your LTI tool. The LTIaaS API is already being used to reach thousands of students across the entire world! > For more information visit [LTIaaS.com](https://ltiaas.com) +> > - [API Documentation](https://ltiaas.com/docs/) > - [Pricing information and simulator](https://ltiaas.com/pricing/) > - [Contact us](https://ltiaas.com/contact-us/) --- - ## Table of Contents - [Introduction](#introduction) @@ -111,31 +106,26 @@ Through our consultation services we can help you design, build and maintain you The Learning Tools Interoperability (LTI®) protocol is a standard for integration of rich learning applications within educational environments. [ref](https://www.imsglobal.org/spec/lti/v1p3/) - -This library implements a tool provider as an [Express](https://expressjs.com/) server, with preconfigured routes and methods that manage the [LTI® 1.3](https://www.imsglobal.org/spec/lti/v1p3/) protocol for you. Making it fast and simple to create a working learning tool with access to every LTI® service, without having to worry about manually implementing any of the security and validation required to do so. +This library implements a tool provider as an [Express](https://expressjs.com/) server, with preconfigured routes and methods that manage the [LTI® 1.3](https://www.imsglobal.org/spec/lti/v1p3/) protocol for you. Making it fast and simple to create a working learning tool with access to every LTI® service, without having to worry about manually implementing any of the security and validation required to do so. --- - ## Feature roadmap -| Feature | Implementation | Documentation | -| --------- | - | - | +| Feature | Implementation | Documentation | +| ---------------------------------------------------------------------------------- | ------------------- | ------------------- | | [Keyset endpoint support](https://cvmcosta.me/ltijs/#/provider?id=keyset-endpoint) |
✔️
|
✔️
| -| [Deep Linking Service Class](https://cvmcosta.me/ltijs/#/deeplinking) |
✔️
|
✔️
| -| [Grading Service Class](https://cvmcosta.me/ltijs/#/grading) |
✔️
|
✔️
| -| [Names and Roles Service Class](https://cvmcosta.me/ltijs/#/namesandroles) |
✔️
|
✔️
| -| [Dynamic Registration Service ](https://cvmcosta.me/ltijs/#/dynamicregistration) |
✔️
|
✔️
| -| Database plugins |
✔️
|
✔️
| -| Revised usability tutorials |
|
| -| Key Rotation |
|
| -| Redis caching |
|
| - +| [Deep Linking Service Class](https://cvmcosta.me/ltijs/#/deeplinking) |
✔️
|
✔️
| +| [Grading Service Class](https://cvmcosta.me/ltijs/#/grading) |
✔️
|
✔️
| +| [Names and Roles Service Class](https://cvmcosta.me/ltijs/#/namesandroles) |
✔️
|
✔️
| +| [Dynamic Registration Service ](https://cvmcosta.me/ltijs/#/dynamicregistration) |
✔️
|
✔️
| +| Database plugins |
✔️
|
✔️
| +| Revised usability tutorials |
|
| +| Key Rotation |
|
| +| Redis caching |
|
| --- - - ## Installation ### Installing the package @@ -144,630 +134,527 @@ This library implements a tool provider as an [Express](https://expressjs.com/) $ npm install ltijs ``` - ### MongoDB This package natively uses mongoDB by default to store and manage the server data, so you need to have it installed, see link bellow for further instructions. - - [Installing mongoDB](https://docs.mongodb.com/manual/administration/install-community/) - +- [Installing mongoDB](https://docs.mongodb.com/manual/administration/install-community/) ### Database Plugins Ltijs can also be used with other databases through database plugins that use the same structure as the main database class. - - [Firestore Plugin](https://github.com/examind-ai/ltijs-firestore) - - - [Sequelize Plugin](https://github.com/Cvmcosta/ltijs-sequelize)(MySQL, PostgreSQL) +- [Firestore Plugin](https://github.com/examind-ai/ltijs-firestore) +- [Sequelize Plugin](https://github.com/Cvmcosta/ltijs-sequelize)(MySQL, PostgreSQL) --- ## Quick start - > Setting up Ltijs - - ```javascript -const path = require('path') +const path = require("path"); -// Require Provider -const lti = require('ltijs').Provider +// Require Provider +const lti = require("ltijs").Provider; // Setup provider -lti.setup('LTIKEY', // Key used to sign cookies and tokens - { // Database configuration - url: 'mongodb://localhost/database', - connection: { user: 'user', pass: 'password' } - }, - { // Options - appRoute: '/', loginRoute: '/login', // Optionally, specify some of the reserved routes - cookies: { - secure: false, // Set secure to true if the testing platform is in a different domain and https is being used - sameSite: '' // Set sameSite to 'None' if the testing platform is in a different domain and https is being used - }, - devMode: true // Set DevMode to false if running in a production environment with https - } -) +lti.setup( + "LTIKEY", // Key used to sign cookies and tokens + { + // Database configuration + url: "mongodb://localhost/database", + connection: { user: "user", pass: "password" }, + }, + { + // Options + appRoute: "/", + loginRoute: "/login", // Optionally, specify some of the reserved routes + cookies: { + secure: false, // Set secure to true if the testing platform is in a different domain and https is being used + sameSite: "", // Set sameSite to 'None' if the testing platform is in a different domain and https is being used + }, + devMode: true, // Set DevMode to false if running in a production environment with https + } +); // Set lti launch callback lti.onConnect((token, req, res) => { - console.log(token) - return res.send('It\'s alive!') -}) + console.log(token); + return res.send("It's alive!"); +}); const setup = async () => { - // Deploy server and open connection to the database - await lti.deploy({ port: 3000 }) // Specifying port. Defaults to 3000 - - // Register platform - await lti.registerPlatform({ - url: 'https://platform.url', - name: 'Platform Name', - clientId: 'TOOLCLIENTID', - authenticationEndpoint: 'https://platform.url/auth', - accesstokenEndpoint: 'https://platform.url/token', - authConfig: { method: 'JWK_SET', key: 'https://platform.url/keyset' } - }) -} + // Deploy server and open connection to the database + await lti.deploy({ port: 3000 }); // Specifying port. Defaults to 3000 + + // Register platform + await lti.registerPlatform({ + url: "https://platform.url", + name: "Platform Name", + clientId: "TOOLCLIENTID", + authenticationEndpoint: "https://platform.url/auth", + accesstokenEndpoint: "https://platform.url/token", + authConfig: { method: "JWK_SET", key: "https://platform.url/keyset" }, + }); +}; -setup() +setup(); ``` ### Implementation example - - [Example Ltijs Server](https://github.com/Cvmcosta/ltijs-demo-server) +- [Example Ltijs Server](https://github.com/Cvmcosta/ltijs-demo-server) - - [Example Client App](https://github.com/Cvmcosta/ltijs-demo-client) +- [Example Client App](https://github.com/Cvmcosta/ltijs-demo-client) --- ## Documentation ### Provider ->The Ltijs Provider Class implements the LTI® 1.3 protocol and services. +> The Ltijs Provider Class implements the LTI® 1.3 protocol and services. #### Provider.app -[Express](https://expressjs.com/) server instance. -**Type**: ```Express``` +[Express](https://expressjs.com/) server instance. +**Type**: `Express` #### Provider.Database -Database object. Allows you to perform the database operations using the same methods used by the internal code. -**Type**: ```Database``` +Database object. Allows you to perform the database operations using the same methods used by the internal code. +**Type**: `Database` #### Provider.Grade + [Grade Class](https://cvmcosta.github.io/ltijs/#/grading), implementing the Assignment and Grade service of the LTI® 1.3 protocol. -**Type**: ```Grade``` +**Type**: `Grade` #### Provider.DeepLinking + [DeepLinking Class](https://cvmcosta.github.io/ltijs/#/deeplinking), implementing the Deep Linking service of the LTI® 1.3 protocol. -**Type**: ```DeepLinking``` +**Type**: `DeepLinking` #### Provider.NamesAndRoles + [NamesAndRoles Class](https://cvmcosta.github.io/ltijs/#/namesandroles), implementing the Names and Roles Provisioning service of the LTI® 1.3 protocol. -**Type**: ```NamesAndRoles``` +**Type**: `NamesAndRoles` -#### Provider.setup(encryptionkey, database [, options]) +#### Provider.setup(encryptionkey, database [, options]) Method used to setup and configure the LTI® provider. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| encryptionkey | `String` | Secret used to sign cookies and encrypt data. |   | -| database | `Object` | Database configuration. | | -| database.url | `String` | Database url (Ex: mongodb://localhost/applicationdb). | | -| database.connection | `Object` | MongoDB database connection options. Can be any option supported by the [MongoDB Driver](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect). | *Optional* | -| database.connection.user | `String` | Database user for authentication, if needed. | *Optional* | -| database.connection.pass | `String` | Database pass for authentication, if needed. | *Optional* | -| database.debug | `Boolean` | If set to true, enables mongoose debug mode. **Default: false**. | *Optional* | -| database.plugin | `Object` | If set, uses the given database plugin instead of the default MongoDB. | *Optional* | -| options | `Object` | LTI Provider options. | *Optional* | -| options.appRoute | `String` | Lti Provider main url. **Default: '/'**. | *Optional* | -| options.loginRoute | `String` | Lti Provider login url. **Default: '/login'**. | *Optional* | -| options.keysetRoute | `String` | Lti Provider public jwk keyset route. **Default: '/keys'**. | *Optional* | -| options.dynRegRoute | `String` | Dynamic registration route. **Default: '/register'**. | *Optional* | -| options.https | `Boolean` | Set this as true in development if you are not using any web server to redirect to your tool (like Nginx) as https and are planning to configure ssl through Express. **Default: false**. | *Optional* | -| options.ssl | `Object` | SSL certificate and key to be used ***if https flag is enabled.*** | *Optional* | -| options.ssl.key | `String` | SSL key. | *Optional* | -| options.ssl.cert | `String` | SSL certificate. | *Optional* | -| options.staticPath | `String` | The path for the static files your application might serve (Ex: _dirname+"/public") | *Optional* | -| options.cors | `Boolean` | If set to false, disables cors. **Default: true**. | *Optional* | -| options.serverAddon | `Function` | Allows the execution of a method inside of the server contructor. Can be used to register middlewares. | *Optional* | -| options.cookies | `Object` | Cookie configuration. Allows you to configure, sameSite and secure parameters. | *Optional* | -| options.cookies.secure | `Boolean` | Cookie secure parameter. If true, only allows cookies to be passed over https. **Default: false**. | *Optional* | -| options.cookies.sameSite | `String` | Cookie sameSite parameter. If cookies are going to be set across domains, set this parameter to 'None'. **Default: Lax**. | *Optional* | -| options.tokenMaxAge | `String` | Sets the idToken max age allowed in seconds. If false, disables max age validation. **Default: 10**. | *Optional* | -| options.devMode | `Boolean` | If true, does not require state and session cookies to be present (If present, they are still validated). This allows Ltijs to work on development environments where cookies cannot be set. **Default: false**. ***THIS SHOULD NOT BE USED IN A PRODUCTION ENVIRONMENT.*** | *Optional* | -| options.ltiaas | `Boolean` | If set to true, disables the creation and validation of the session cookies. Login state cookies are still created, since they are a part of the LTI specification. **Default: false** | *Optional* | -| options.dynReg | `Object` | Setup for the Dynamic Registration Service. | *Optional* | -| options.dynReg.url | `String` | Tool Provider main URL. (Ex: 'https://tool.example.com') | | -| options.dynReg.name | `String` | Tool Provider name. (Ex: 'Tool Provider') | | -| options.dynReg.logo | `String` | Tool Provider logo. (Ex: 'https://client.example.org/logo.png') | *Optional* | -| options.dynReg.description | `String` | Tool Provider description. (Ex: 'Tool description') | *Optional* | -| options.dynReg.redirectUris | `Array` | Additional redirect URIs. (Ex: ['https://tool.example.com/launch']) | *Optional* | -| options.dynReg.customParameters | `Object` | Custom parameters object. (Ex: `{ key: 'value' })` | *Optional* | -| options.dynReg.autoActivate | `Boolean` | Platform auto activation flag. If true, every Platform registered dynamically is immediately activated. **Default: false**. | *Optional* | - -#### async Provider.deploy(options) +| Name | Type | Description | | +| ------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| encryptionkey | `String` | Secret used to sign cookies and encrypt data. |   | +| database | `Object` | Database configuration. | | +| database.url | `String` | Database url (Ex: mongodb://localhost/applicationdb). | | +| database.connection | `Object` | MongoDB database connection options. Can be any option supported by the [MongoDB Driver](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect). | _Optional_ | +| database.connection.user | `String` | Database user for authentication, if needed. | _Optional_ | +| database.connection.pass | `String` | Database pass for authentication, if needed. | _Optional_ | +| database.debug | `Boolean` | If set to true, enables mongoose debug mode. **Default: false**. | _Optional_ | +| database.plugin | `Object` | If set, uses the given database plugin instead of the default MongoDB. | _Optional_ | +| options | `Object` | LTI Provider options. | _Optional_ | +| options.appRoute | `String` | Lti Provider main url. **Default: '/'**. | _Optional_ | +| options.loginRoute | `String` | Lti Provider login url. **Default: '/login'**. | _Optional_ | +| options.keysetRoute | `String` | Lti Provider public jwk keyset route. **Default: '/keys'**. | _Optional_ | +| options.dynRegRoute | `String` | Dynamic registration route. **Default: '/register'**. | _Optional_ | +| options.https | `Boolean` | Set this as true in development if you are not using any web server to redirect to your tool (like Nginx) as https and are planning to configure ssl through Express. **Default: false**. | _Optional_ | +| options.ssl | `Object` | SSL certificate and key to be used **_if https flag is enabled._** | _Optional_ | +| options.ssl.key | `String` | SSL key. | _Optional_ | +| options.ssl.cert | `String` | SSL certificate. | _Optional_ | +| options.staticPath | `String` | The path for the static files your application might serve (Ex: \_dirname+"/public") | _Optional_ | +| options.cors | `Boolean` | If set to false, disables cors. **Default: true**. | _Optional_ | +| options.serverAddon | `Function` | Allows the execution of a method inside of the server contructor. Can be used to register middlewares. | _Optional_ | +| options.cookies | `Object` | Cookie configuration. Allows you to configure, sameSite and secure parameters. | _Optional_ | +| options.cookies.secure | `Boolean` | Cookie secure parameter. If true, only allows cookies to be passed over https. **Default: false**. | _Optional_ | +| options.cookies.sameSite | `String` | Cookie sameSite parameter. If cookies are going to be set across domains, set this parameter to 'None'. **Default: Lax**. | _Optional_ | +| options.tokenMaxAge | `String` | Sets the idToken max age allowed in seconds. If false, disables max age validation. **Default: 10**. | _Optional_ | +| options.devMode | `Boolean` | If true, does not require state and session cookies to be present (If present, they are still validated). This allows Ltijs to work on development environments where cookies cannot be set. **Default: false**. **_THIS SHOULD NOT BE USED IN A PRODUCTION ENVIRONMENT._** | _Optional_ | +| options.ltiaas | `Boolean` | If set to true, disables the creation and validation of the session cookies. Login state cookies are still created, since they are a part of the LTI specification. **Default: false** | _Optional_ | +| options.dynReg | `Object` | Setup for the Dynamic Registration Service. | _Optional_ | +| options.dynReg.url | `String` | Tool Provider main URL. (Ex: 'https://tool.example.com') | | +| options.dynReg.name | `String` | Tool Provider name. (Ex: 'Tool Provider') | | +| options.dynReg.logo | `String` | Tool Provider logo. (Ex: 'https://client.example.org/logo.png') | _Optional_ | +| options.dynReg.description | `String` | Tool Provider description. (Ex: 'Tool description') | _Optional_ | +| options.dynReg.redirectUris | `Array` | Additional redirect URIs. (Ex: ['https://tool.example.com/launch']) | _Optional_ | +| options.dynReg.customParameters | `Object` | Custom parameters object. (Ex: `{ key: 'value' })` | _Optional_ | +| options.dynReg.autoActivate | `Boolean` | Platform auto activation flag. If true, every Platform registered dynamically is immediately activated. **Default: false**. | _Optional_ | + +#### async Provider.deploy(options) Starts listening to a given port for LTI® requests and opens connection to the configured database. - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| options | `Object` | Deployment options. | *Optional* | -| options.port | `Number` | The port the Provider should listen to. **Default: 3000**. | *Optional* | -| options.silent | `Boolean` | If true, supresses the deployment messages. **Default: false**. | *Optional* | -| options.serverless | `Boolean` | If true, Ltijs does not start an Express server instance. This allows usage as a middleware and with services like AWS. Ignores 'port' parameter. **Default: false**. | *Optional* | +| Name | Type | Description | | +| ------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| options | `Object` | Deployment options. | _Optional_ | +| options.port | `Number` | The port the Provider should listen to. **Default: 3000**. | _Optional_ | +| options.silent | `Boolean` | If true, supresses the deployment messages. **Default: false**. | _Optional_ | +| options.serverless | `Boolean` | If true, Ltijs does not start an Express server instance. This allows usage as a middleware and with services like AWS. Ignores 'port' parameter. **Default: false**. | _Optional_ | ##### Returns -- Promise that resolves ```true``` when connection to the database is stablished and the server starts listening. - - +- Promise that resolves `true` when connection to the database is stablished and the server starts listening. - -#### async Provider.close() +#### async Provider.close() Closes connection to database and stops server. ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| options | `Object` | Options. | *Optional* | -| options.silent | `Boolean` | If true, disables shutdown messages. **Default: false**. | *Optional* | - +| Name | Type | Description | | +| -------------- | --------- | -------------------------------------------------------- | ---------- | +| options | `Object` | Options. | _Optional_ | +| options.silent | `Boolean` | If true, disables shutdown messages. **Default: false**. | _Optional_ | ##### Returns -- Promise that resolves ```true``` when the shutdown is complete. - - +- Promise that resolves `true` when the shutdown is complete. -#### Provider.onConnect(connectCallback) +#### Provider.onConnect(connectCallback) Sets the callback method called whenever theres a sucessfull connection, exposing a token object containing the decoded idToken and the usual Express route parameters (Request, Response and Next). - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| connectCallback | `Function` | Callback method called everytime a platform sucessfully launches to the provider main endpoint. |   | - - - - +| Name | Type | Description | | +| --------------- | ---------- | ----------------------------------------------------------------------------------------------- | ------ | +| connectCallback | `Function` | Callback method called everytime a platform sucessfully launches to the provider main endpoint. |   | ##### Examples ```javascript -lti.onConnect(async (token, req, res, next) => { return res.send(token) }) +lti.onConnect(async (token, req, res, next) => { + return res.send(token); +}); ``` -*The default method set to this callback simply fowards the request to the next handler, so the usage of onConnect is optional:* +_The default method set to this callback simply fowards the request to the next handler, so the usage of onConnect is optional:_ ```javascript // Equivalent to onConnect usage above -lti.app.get(lti.appRoute(), async (req, res, next) => { return res.send(res.locals.token) }) +lti.app.get(lti.appRoute(), async (req, res, next) => { + return res.send(res.locals.token); +}); ``` - -#### Provider.onDeepLinking(deepLinkingCallback) +#### Provider.onDeepLinking(deepLinkingCallback) Sets the callback method called whenever theres a sucessfull deep linking request connection, exposing a token object containing the decoded idToken and the usual Express route parameters (Request, Response and Next). Through this callback you can display your Deep Linking view. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| deepLinkingCallback | `Function` | Callback method called everytime a platform sucessfully launches a deep linking request. |   | - - - - +| Name | Type | Description | | +| ------------------- | ---------- | ---------------------------------------------------------------------------------------- | ------ | +| deepLinkingCallback | `Function` | Callback method called everytime a platform sucessfully launches a deep linking request. |   | ##### Examples ```javascript -lti.onDeepLinking(async (token, req, res, next) => { return res.send(token) }) +lti.onDeepLinking(async (token, req, res, next) => { + return res.send(token); +}); ``` -#### Provider.onSessionTimeout(sessionTimeoutCallback) +#### Provider.onSessionTimeout(sessionTimeoutCallback) Sets the callback method called when no valid session is found during a request validation. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| sessionTimeoutCallback | `Function` | Callback method called when no valid session is found during a request validation. |   | - - - - +| Name | Type | Description | | +| ---------------------- | ---------- | ---------------------------------------------------------------------------------- | ------ | +| sessionTimeoutCallback | `Function` | Callback method called when no valid session is found during a request validation. |   | ##### Examples ```javascript -lti.onSessionTimeout(async (req, res, next) => { return res.status(401).send(res.locals.err) }) +lti.onSessionTimeout(async (req, res, next) => { + return res.status(401).send(res.locals.err); +}); ``` -*Ltijs provides a default method for this callback.* - +_Ltijs provides a default method for this callback._ -#### Provider.onInvalidToken(invalidTokenCallback) +#### Provider.onInvalidToken(invalidTokenCallback) Sets the callback method called when the token received fails the validation process. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| invalidTokenCallback | `Function` | Callback method called when the ltik or idtoken received fails the validation process. |   | - - - - +| Name | Type | Description | | +| -------------------- | ---------- | -------------------------------------------------------------------------------------- | ------ | +| invalidTokenCallback | `Function` | Callback method called when the ltik or idtoken received fails the validation process. |   | ##### Examples ```javascript -lti.onInvalidToken(async (req, res, next) => { return res.status(401).send(res.locals.err) }) +lti.onInvalidToken(async (req, res, next) => { + return res.status(401).send(res.locals.err); +}); ``` -*Ltijs provides a default method for this callback.* - +_Ltijs provides a default method for this callback._ -#### Provider.onUnregisteredPlatform(unregisteredPlatformCallback) +#### Provider.onUnregisteredPlatform(unregisteredPlatformCallback) Sets the callback function called when the Platform attempting to login is not registered. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| unregisteredPlatformCallback | `Function` | Callback method called when the Platform attempting to login is not registered. |   | - - - - +| Name | Type | Description | | +| ---------------------------- | ---------- | ------------------------------------------------------------------------------- | ------ | +| unregisteredPlatformCallback | `Function` | Callback method called when the Platform attempting to login is not registered. |   | ##### Examples ```javascript -lti.onUnregisteredPlatform((req, res) => { return res.status(400).send({ status: 400, error: 'Bad Request', details: { message: 'Unregistered Platform!' } }) }) +lti.onUnregisteredPlatform((req, res) => { + return res.status(400).send({ status: 400, error: "Bad Request", details: { message: "Unregistered Platform!" } }); +}); ``` -*Ltijs provides a default method for this callback.* +_Ltijs provides a default method for this callback._ - - -#### Provider.onInactivePlatform(inactivePlatformCallback) +#### Provider.onInactivePlatform(inactivePlatformCallback) Sets the callback function called when the Platform attempting to login is not activated. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| inactivePlatformCallback | `Function` | Callback method called when the Platform attempting to login is not activated. |   | - - - - +| Name | Type | Description | | +| ------------------------ | ---------- | ------------------------------------------------------------------------------ | ------ | +| inactivePlatformCallback | `Function` | Callback method called when the Platform attempting to login is not activated. |   | ##### Examples ```javascript -lti.onInactivePlatform((req, res) => { return res.status(401).send({ status: 401, error: 'Unauthorized', details: { message: 'Platform not active!' } }) }) +lti.onInactivePlatform((req, res) => { + return res.status(401).send({ status: 401, error: "Unauthorized", details: { message: "Platform not active!" } }); +}); ``` -*Ltijs provides a default method for this callback.* - - +_Ltijs provides a default method for this callback._ - - -#### Provider.appRoute() +#### Provider.appRoute() Gets the main application Route that will receive the final decoded Idtoken. - - - - ##### Examples ```javascript -lti.appRoute() +lti.appRoute(); ``` - - -#### Provider.loginRoute() +#### Provider.loginRoute() Gets the login Route responsible for dealing with the OIDC login flow. - - ##### Examples ```javascript -lti.loginRoute() +lti.loginRoute(); ``` - - - -#### Provider.keysetRoute() +#### Provider.keysetRoute() Gets the public JWK keyset Route. - ##### Examples ```javascript -lti.keysetRoute() +lti.keysetRoute(); ``` -#### Provider.dynRegRoute() +#### Provider.dynRegRoute() Gets the dynamic registration Route. - ##### Examples ```javascript -lti.dynRegRoute() +lti.dynRegRoute(); ``` - #### Provider.whitelist(urls) -Whitelists routes to bypass the Ltijs authentication protocol. If validation fails, these routes are still accessed but aren't given an idToken. - +Whitelists routes to bypass the Ltijs authentication protocol. If validation fails, these routes are still accessed but aren't given an idToken. -| Param | Type | Description | -| --- | --- | --- | -| urls | String | Urls to be whitelisted. Optionally you can pass an object containing the route and the specific method. | +| Param | Type | Description | +| ----- | ------------------- | ------------------------------------------------------------------------------------------------------- | +| urls | String | Urls to be whitelisted. Optionally you can pass an object containing the route and the specific method. | ##### Examples ```javascript // Whitelisting routes -lti.whitelist('/log', '/home') +lti.whitelist("/log", "/home"); // Whitelisting routes with specific methods -lti.whitelist('/log', '/home', { route: '/route', method: 'POST' }) +lti.whitelist("/log", "/home", { route: "/route", method: "POST" }); ``` - - -#### async Provider.registerPlatform(platform) +#### async Provider.registerPlatform(platform) Registers a new [Platform](platform.md). - ##### Parameters -| Param | Type | Description | | -| --- | --- | --- | --- | -| platform | Object | Platform config object |   | -| platform.url | String | Platform url. |   | -| platform.name | String | Platform nickname. |   | -| platform.clientId | String | Client Id generated by the platform. |   | -| platform.authenticationEndpoint | String | Authentication endpoint that the tool will use to authenticate within the platform. |   | -| platform.accesstokenEndpoint | String | Access token endpoint that the tool will use to get an access token for the platform. |   | -| platform.authConfig | Object | Authentication method and key for verifying messages from the platform. {method: "RSA_KEY", key:"PUBLIC KEY..."} |   | -| platform.authConfig.method | String | Method of authorization "RSA_KEY" or "JWK_KEY" or "JWK_SET". |   | -| platform.authConfig.key | String | Either the RSA public key provided by the platform, or the JWK key, or the JWK keyset address.|   | - - - +| Param | Type | Description | | +| ------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------ | +| platform | Object | Platform config object |   | +| platform.url | String | Platform url. |   | +| platform.name | String | Platform nickname. |   | +| platform.clientId | String | Client Id generated by the platform. |   | +| platform.authenticationEndpoint | String | Authentication endpoint that the tool will use to authenticate within the platform. |   | +| platform.accesstokenEndpoint | String | Access token endpoint that the tool will use to get an access token for the platform. |   | +| platform.authConfig | Object | Authentication method and key for verifying messages from the platform. {method: "RSA_KEY", key:"PUBLIC KEY..."} |   | +| platform.authConfig.method | String | Method of authorization "RSA_KEY" or "JWK_KEY" or "JWK_SET". |   | +| platform.authConfig.key | String | Either the RSA public key provided by the platform, or the JWK key, or the JWK keyset address. |   | ##### Returns - - - Promise that resolves a [Platform](platform.md). - +- Promise that resolves a [Platform](platform.md). ##### Example ```javascript -await lti.registerPlatform({ - url: 'https://platform.url', - name: 'Platform Name', - clientId: 'TOOLCLIENTID', - authenticationEndpoint: 'https://platform.url/auth', - accesstokenEndpoint: 'https://platform.url/token', - authConfig: { method: 'JWK_SET', key: 'https://platform.url/keyset' } -}) +await lti.registerPlatform({ + url: "https://platform.url", + name: "Platform Name", + clientId: "TOOLCLIENTID", + authenticationEndpoint: "https://platform.url/auth", + accesstokenEndpoint: "https://platform.url/token", + authConfig: { method: "JWK_SET", key: "https://platform.url/keyset" }, +}); ``` - - -#### async Provider.getPlatform(url, clientId) +#### async Provider.getPlatform(url, clientId) Retrieves a [Platform](platform.md). - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| url | `String` | Platform url. |   | -| clientId | `String` | Tool Client Id url. |   | - - - +| Name | Type | Description | | +| -------- | -------- | ------------------- | ------ | +| url | `String` | Platform url. |   | +| clientId | `String` | Tool Client Id url. |   | ##### Returns - - Promise that resolves a [Platform](platform.md). - ##### Example ```javascript -const plat = await lti.getPlatform('https://platform.url', 'TOOLCLIENTID') +const plat = await lti.getPlatform("https://platform.url", "TOOLCLIENTID"); ``` -#### async Provider.getPlatformById(platformId) +#### async Provider.getPlatformById(platformId) Retrieves a [Platform](platform.md). - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| platformId | `String` | Platform Id. |   | - - - - +| Name | Type | Description | | +| ---------- | -------- | ------------ | ------ | +| platformId | `String` | Platform Id. |   | ##### Returns - - Promise that resolves a [Platform](platform.md). - ##### Example ```javascript -const plat = await lti.getPlatformById('asdih1k12poihalkja52') +const plat = await lti.getPlatformById("asdih1k12poihalkja52"); ``` - - -#### async Provider.deletePlatform(url, clientId) +#### async Provider.deletePlatform(url, clientId) Deletes a [Platform](platform.md). - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| url | `String` | Platform url. |   | -| clientId | `String` | Tool Client Id url. |   | - +| Name | Type | Description | | +| -------- | -------- | ------------------- | ------ | +| url | `String` | Platform url. |   | +| clientId | `String` | Tool Client Id url. |   | ##### Returns -- Promise that resolves ```true```. +- Promise that resolves `true`. ##### Example ```javascript -await lti.deletePlatform('https://platform.url', 'TOOLCLIENTID') +await lti.deletePlatform("https://platform.url", "TOOLCLIENTID"); ``` -#### async Provider.deletePlatformById(paltformId) +#### async Provider.deletePlatformById(paltformId) Deletes a [Platform](platform.md). - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| platformId | `String` | Platform Id. |   | - +| Name | Type | Description | | +| ---------- | -------- | ------------ | ------ | +| platformId | `String` | Platform Id. |   | ##### Returns -- Promise that resolves ```true```. +- Promise that resolves `true`. ##### Example ```javascript -await lti.deletePlatformById('60b1fce753c875193d71b') +await lti.deletePlatformById("60b1fce753c875193d71b"); ``` - -#### async Provider.getAllPlatforms() +#### async Provider.getAllPlatforms() Gets all [platforms](platform.md). - - ##### Returns - Promise that resolves a [Platform](platform.md) object array. - ##### Example ```javascript -const platforms = await lti.getAllPlatforms() +const platforms = await lti.getAllPlatforms(); ``` - -#### async Provider.redirect(response, path [, options]) +#### async Provider.redirect(response, path [, options]) Redirects to a new location. Passes Ltik if present. - - - ##### Parameters -| Name | Type | Description | | -| ---- | ---- | ----------- | -------- | -| response | `Object` | Espress response object.|   | -| path | `String` | Redirect path. |   | -| [options] | Object | Redirection options | *Optional* | -| [options.newResource] | Boolean | If true, changes the path variable on the context token. | *Optional* | -| [options.query] | Object | Query parameters that should be added to the redirection URL. | *Optional* | +| Name | Type | Description | | +| --------------------- | -------------------- | ------------------------------------------------------------- | ---------- | +| response | `Object` | Espress response object. |   | +| path | `String` | Redirect path. |   | +| [options] | Object | Redirection options | _Optional_ | +| [options.newResource] | Boolean | If true, changes the path variable on the context token. | _Optional_ | +| [options.query] | Object | Query parameters that should be added to the redirection URL. | _Optional_ | + +**Example** -**Example** ```js -lti.redirect(res, '/path', { newResource: true, query: { param: 'value' } }) +lti.redirect(res, "/path", { newResource: true, query: { param: "value" } }); // Redirects to /path?param=value ``` @@ -779,22 +666,24 @@ When using Ltijs, the first step must **always** be to call the `lti.setup()` me ```javascript // Require Ltijs package -const lti = require('ltijs').Provider +const lti = require("ltijs").Provider; // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - appRoute: '/app',// Scpecifying main app route - loginRoute: '/login', // Specifying login route - cookies: { - secure: true, // Cookies will only be passed through https. - sameSite: 'None' // Cookies can be set across domains. - } - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + appRoute: "/app", // Scpecifying main app route + loginRoute: "/login", // Specifying login route + cookies: { + secure: true, // Cookies will only be passed through https. + sameSite: "None", // Cookies can be set across domains. + }, + } +); ``` This method receives three arguments **encryptionkey**, **database** and **options**: @@ -805,7 +694,7 @@ The **encryptionkey** parameter receives a string that will be used as a secret ### Database configuration: -The second parameter of the setup method, **database**, is an object with: +The second parameter of the setup method, **database**, is an object with: - An `url` field, that should be the **database connection url**; @@ -815,13 +704,12 @@ The second parameter of the setup method, **database**, is an object with: - And a `plugin` field used for database plugins, such as [ltijs-sequelize](https://github.com/Cvmcosta/ltijs-sequelize). - ### Options: The third parameter, **options**, is an optional parameter that handles the additional provider configuration: #### Reserved endpoint configuration: - + Through the **options** parameter you can specify the routes for the reserved endpoints used by Ltijs: - **appRoute** - Route used to handle successful launch requests through the `onConnect` callback. **Default: '/'**. @@ -834,17 +722,19 @@ Through the **options** parameter you can specify the routes for the reserved en ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - appRoute: '/app',// Scpecifying main app route - loginRoute: '/loginroute', // Specifying login route - keysetRoute: '/keyset', // Specifying keyset route - dynRegRoute: '/register' // Specifying Dynamic registration route - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + appRoute: "/app", // Scpecifying main app route + loginRoute: "/loginroute", // Specifying login route + keysetRoute: "/keyset", // Specifying keyset route + dynRegRoute: "/register", // Specifying Dynamic registration route + } +); ``` #### Cookie configuration: @@ -859,41 +749,46 @@ Ltijs sets session cookies throughout the LTI® validation process, how these co ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - cookies: { // Cookie configuration - secure: true, - sameSite: 'None', - domain: '.domain.com' - } - }) -``` - -***If the platform and tool are in different domains, some browsers will not allow cookies to be set unless they have the `secure: true` and `sameSite: 'None'` flags. If you are in a development environment and cannot set secure cookies (over https), consider using Ltijs in `Development mode`.*** +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + cookies: { + // Cookie configuration + secure: true, + sameSite: "None", + domain: ".domain.com", + }, + } +); +``` + +**_If the platform and tool are in different domains, some browsers will not allow cookies to be set unless they have the `secure: true` and `sameSite: 'None'` flags. If you are in a development environment and cannot set secure cookies (over https), consider using Ltijs in `Development mode`._** #### Development mode: -Ltijs relies on cookies for part of the validation process, but in some development environments, cookies might not be able to be set, for instance if you are trying to set cross domain cookies over an insecure http connection. +Ltijs relies on cookies for part of the validation process, but in some development environments, cookies might not be able to be set, for instance if you are trying to set cross domain cookies over an insecure http connection. In situations like this you can set the `devMode` field as true and Ltijs will stop trying to validate the cookies and will instead use the information obtained through the `ltik` token to retrieve the correct context information. ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - devMode: true // Using development mode - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + devMode: true, // Using development mode + } +); ``` -***DevMode should never be used in a production environment, and it should not be necessary, since most of the cookie issues can be solved by using the `secure: true` and `sameSite: None` flags.*** +**_DevMode should never be used in a production environment, and it should not be necessary, since most of the cookie issues can be solved by using the `secure: true` and `sameSite: None` flags._** #### Ltiaas mode: @@ -901,22 +796,23 @@ Ltijs can remove the session cookie request authentication step by using `ltiaas ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - ltiaas: true // Using ltiaas mode - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + ltiaas: true, // Using ltiaas mode + } +); ``` [See more about request authentication.](#request-authentication) - #### Token max age allowed: -As part of the LTI® 1.3 protocol validation steps, Ltijs checks the idtoken's `iat` claim and flags the token as invalid if it is older than **10 seconds**. +As part of the LTI® 1.3 protocol validation steps, Ltijs checks the idtoken's `iat` claim and flags the token as invalid if it is older than **10 seconds**. This limit can be configured (or removed) through the `tokenMaxAge` field: @@ -924,67 +820,66 @@ This limit can be configured (or removed) through the `tokenMaxAge` field: ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - tokenMaxAge: 60 // Setting maximum token age as 60 seconds - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + tokenMaxAge: 60, // Setting maximum token age as 60 seconds + } +); ``` #### Server addon: Through the `serverAddon` field you can setup a method that will be executed on the moment of the server creation. This method will receive the `Express` app as a parameter and so it can be used to register middlewares or change server configuration: - ```javascript // Creating middleware registration const middleware = (app) => { - app.use(async (req, res, next) => { - console.log('Middleware works!') - next() // Passing to next handler - }) -} + app.use(async (req, res, next) => { + console.log("Middleware works!"); + next(); // Passing to next handler + }); +}; //Configure provider -lti.setup('EXAMPLEKEY', - { url: 'mongodb://localhost/database', - connection:{ user:'user', - pass: 'pass'} - }, - { - serverAddon: middleware // Setting addon method - }) +lti.setup( + "EXAMPLEKEY", + { url: "mongodb://localhost/database", connection: { user: "user", pass: "pass" } }, + { + serverAddon: middleware, // Setting addon method + } +); ``` -***Registered middlewares need to call `next()`, otherwise no other handler will be reached.*** - +**_Registered middlewares need to call `next()`, otherwise no other handler will be reached._** #### Serving static files: `Express` allows us to specify a path from where static files will be served. -Ltijs can use this functionality by setting the staticPath parameter of the constructor's additional options. +Ltijs can use this functionality by setting the staticPath parameter of the constructor's additional options. ```javascript //Configure provider -lti.setup('EXAMPLEKEY', - { url: 'mongodb://localhost/database', - connection:{ user:'user', - pass: 'pass'} - }, - { - staticPath: path.join(__dirname, 'public') // Setting static path - }) +lti.setup( + "EXAMPLEKEY", + { url: "mongodb://localhost/database", connection: { user: "user", pass: "pass" } }, + { + staticPath: path.join(__dirname, "public"), // Setting static path + } +); ``` The specified path is internally bound to the root route: ```javascript -app.use('/', express.static(SPECIFIED_PATH, { index: '_' })) +app.use("/", express.static(SPECIFIED_PATH, { index: "_" })); ``` + Accessing the files: ``` @@ -997,23 +892,22 @@ http://localhost:3000/hello.html This can also be achieved and further customized by using [server addons](#server-addon): - ```javascript // Creating middleware registration const middleware = (app) => { - app.use('/static', express.static(__dirname + '/public')); -} + app.use("/static", express.static(__dirname + "/public")); +}; //Configure provider -lti.setup('EXAMPLEKEY', - { url: 'mongodb://localhost/database', - connection:{ user:'user', - pass: 'pass'} - }, - { - serverAddon: middleware // Setting addon method - }) +lti.setup( + "EXAMPLEKEY", + { url: "mongodb://localhost/database", connection: { user: "user", pass: "pass" } }, + { + serverAddon: middleware, // Setting addon method + } +); ``` + And then accessing the files through the specified `/static` route: ``` @@ -1024,73 +918,100 @@ http://localhost:3000/static/images/bg.png http://localhost:3000/static/hello.html ``` - #### Cors: Ltijs `Express` instance is configured to accept cross origin requests by default, this can be disabled by setting the `cors` field to **false**: ```javascript //Configure provider -lti.setup('EXAMPLEKEY', - { url: 'mongodb://localhost/database', - connection:{ user:'user', - pass: 'pass'} - }, - { - cors: false // Disabling cors - }) +lti.setup( + "EXAMPLEKEY", + { url: "mongodb://localhost/database", connection: { user: "user", pass: "pass" } }, + { + cors: false, // Disabling cors + } +); +``` + +#### BodyParser configuration: + +Ltijs `Express` instance is configured to use the [body-parser](https://www.npmjs.com/package/body-parser) middleware. +The defaults for each parser are: + +```javascript +{ + urlencoded: { extended: false }, + json: { }, + raw: { }, + text: { }, +} +``` + +The body-parser middleware configuration can be individually configured by setting the `bodyParserOpt` field: + +```javascript +lti.setup( + "EXAMPLEKEY", + { url: "mongodb://localhost/database", connection: { user: "user", pass: "pass" } }, + { + bodyParserOpt: { + urlencoded: { limit: "1mb", extended: true }, // see https://github.com/expressjs/body-parser#bodyparserurlencodedoptions for all options + json: { strict: true }, // see https://github.com/expressjs/body-parser#bodyparserjsonoptions for all options + raw: { inflate: true }, // see https://github.com/expressjs/body-parser#bodyparserrawoptions for all options + text: { defaultCharset: "utf-8" }, // see https://github.com/expressjs/body-parser#bodyparsertextoptions for all options + }, + } +); ``` --- ## Using Ltijs -After the `lti.setup()` method is called, the `lti` object gives you access to various functionalities to help you create your LTI® Provider. +After the `lti.setup()` method is called, the `lti` object gives you access to various functionalities to help you create your LTI® Provider. The `lti` object is a singleton that can be accessed across multiple files while calling `lti.setup()` only once: You can setup Ltijs in file `a.js`: ```javascript -// a.js -// Require Provider -const lti = require('ltijs').Provider +// a.js +// Require Provider +const lti = require("ltijs").Provider; // Require b.js -const routes = require('./b') +const routes = require("./b"); // Setup method can be called only once -lti.setup('LTIKEY', - { url: 'mongodb://localhost/database' }, - { appRoute: '/', loginRoute: '/login' }) +lti.setup("LTIKEY", { url: "mongodb://localhost/database" }, { appRoute: "/", loginRoute: "/login" }); // Setting up routes -lti.app.use(routes) +lti.app.use(routes); -lti.deploy() +lti.deploy(); ``` And access the same object in a second file `b.js`: ```javascript -// b.js -// Require Provider -const lti = require('ltijs').Provider +// b.js +// Require Provider +const lti = require("ltijs").Provider; // Require express router -const router = require('express').Router() +const router = require("express").Router(); -router.post('/grade', async (req, res) => { - let grade = { - scoreGiven: 50, - activityProgress: 'Completed', - gradingProgress: 'FullyGraded' - } +router.post("/grade", async (req, res) => { + let grade = { + scoreGiven: 50, + activityProgress: "Completed", + gradingProgress: "FullyGraded", + }; - // Using lti object to access Grade Service in another file - await lti.Grade.scorePublish(res.locals.token, grade) - return res.sendStatus(201) -}) + // Using lti object to access Grade Service in another file + await lti.Grade.scorePublish(res.locals.token, grade); + return res.sendStatus(201); +}); -module.exports = router +module.exports = router; ``` ### App @@ -1098,9 +1019,9 @@ module.exports = router The `lti.app` object is an instance of the underlying `Express` server, through this object you can create routes just like you would when using regular [Express](https://expressjs.com/). ```javascript -lti.app.get('/route', async (req,res,next) => { - return res.send('It works!') -}) +lti.app.get("/route", async (req, res, next) => { + return res.send("It works!"); +}); ``` ### Reserved endpoint routes @@ -1110,34 +1031,34 @@ Ltijs reserved endpoint routes can be retrieved by using the following methods: - **lti.appRoute** ```javascript - const appRoute = lti.appRoute() // returns '/' by default + const appRoute = lti.appRoute(); // returns '/' by default ``` - **lti.loginRoute** ```javascript - const loginRoute = lti.loginRoute() // returns '/login' by default + const loginRoute = lti.loginRoute(); // returns '/login' by default ``` + - **lti.keysetRoute** ```javascript - const keysetRoute = lti.keysetRoute() // returns '/keys' by default + const keysetRoute = lti.keysetRoute(); // returns '/keys' by default ``` - **lti.dynRegRoute** ```javascript - const dynRegRoute = lti.dynRegRoute() // returns '/register' by default + const dynRegRoute = lti.dynRegRoute(); // returns '/register' by default ``` - ### Callbacks Ltijs allows you to configure it's main behaviours through callbacks: #### onConnect -The `onConnect` callback is called whenever a successful launch request arrives at the main app url. This callback can be set through the `lti.onConnect()` method. +The `onConnect` callback is called whenever a successful launch request arrives at the main app url. This callback can be set through the `lti.onConnect()` method. The callback route will be given a first parameter `token`, that is the user's validated [idtoken](#idtoken), and the three Express route parameters (request, response and next). @@ -1145,69 +1066,62 @@ The callback route will be given a first parameter `token`, that is the user's v ```javascript lti.onConnect(async (token, req, res, next) => { - console.log(token) - return res.send('User connected!') - } -) + console.log(token); + return res.send("User connected!"); +}); ``` -*The default method set to this callback simply fowards the request to the next handler, so the usage of `lti.onConnect()` is optional, you can simply create a route receiving requests at the `appRoute`:* +_The default method set to this callback simply fowards the request to the next handler, so the usage of `lti.onConnect()` is optional, you can simply create a route receiving requests at the `appRoute`:_ ```javascript // Equivalent to onConnect usage above lti.app.get(lti.appRoute(), async (req, res, next) => { - console.log(res.locals.token) - return res.send('User connected!') - } -) + console.log(res.locals.token); + return res.send("User connected!"); +}); ``` Launches directed at other endpoints are also valid but **are not handled by the `onConnect` callback**, instead they must be handled by their own `Express` route: ```javascript // This route can handle launches to /endpoint -lti.app.get('/endpoint', async (req, res, next) => { - console.log(res.locals.token) - return res.send('User connected!') - } -) +lti.app.get("/endpoint", async (req, res, next) => { + console.log(res.locals.token); + return res.send("User connected!"); +}); ``` #### onDeepLinking -The `onDeepLinking` callback is called whenever a successfull deep linking request arrives at the main app url. This callback can be set through the `lti.onDeepLinking()` method. +The `onDeepLinking` callback is called whenever a successfull deep linking request arrives at the main app url. This callback can be set through the `lti.onDeepLinking()` method. The callback route will be given a first parameter `token`, that is the user's validated [idtoken](#idtoken), and the three Express route parameters (request, response and next). -> *This callback should be used to display your **LTI® provider's deep linking UI**.* +> _This callback should be used to display your **LTI® provider's deep linking UI**._ ```javascript lti.onDeepLinking(async (token, req, res, next) => { - return res.send('Deep Linking is working!') - } -) + return res.send("Deep Linking is working!"); +}); ``` > [See more about the Deep Linking Service](https://cvmcosta.me/ltijs/#/deeplinking) - #### onInvalidToken The `onInvalidToken` callback is called whenever the idtoken received fails the LTI® validation process. This callback can be set through the `lti.onInvalidToken()` method. The callback route will be given the three Express route parameters (request, response and next). And will also have **access to a `res.locals.err` object**, containing information about the error. -> *This callback should be used to display your **invalid token error screen**.* +> _This callback should be used to display your **invalid token error screen**._ ```javascript -lti.onInvalidToken(async (req, res, next) => { - return res.status(401).send(res.locals.err) - } -) +lti.onInvalidToken(async (req, res, next) => { + return res.status(401).send(res.locals.err); +}); ``` - -*Ltijs provides a default method for this callback that returns a 401 error code with the `res.locals.err` object:* +_Ltijs provides a default method for this callback that returns a 401 error code with the `res.locals.err` object:_ ``` { status: 401, error: 'Unauthorized', details: { message: 'ERROR_MESSAGE' } } @@ -1219,36 +1133,36 @@ The `onSessionTimeout` callback is called whenever no valid session is found dur The callback route will be given the three Express route parameters (request, response and next). And will also have **access to a `res.locals.err` object**, containing information about the error. -> *This callback should be used to display your **session timeout error screen**.* +> _This callback should be used to display your **session timeout error screen**._ ```javascript -lti.onSessionTimeout(async (req, res, next) => { - return res.status(401).send(res.locals.err) - } -) +lti.onSessionTimeout(async (req, res, next) => { + return res.status(401).send(res.locals.err); +}); ``` -*Ltijs provides a default method for this callback that returns a 401 error code with the `res.locals.err` object:* +_Ltijs provides a default method for this callback that returns a 401 error code with the `res.locals.err` object:_ ``` { status: 401, error: 'Unauthorized', details: { message: 'ERROR_MESSAGE' } } ``` - #### onUnregisteredPlatform The `onUnregisteredPlatform` callback is called whenever the Platform attempting to start a LTI launch is not registered. The callback route will be given the two Express route parameters (request, response). -> *This callback should be used to display your **Unregistered Platform error screen**.* +> _This callback should be used to display your **Unregistered Platform error screen**._ ```javascript -lti.onUnregisteredPlatform((req, res) => { - return res.status(400).send({ status: 400, error: 'Bad Request', details: { message: 'Unregistered Platform!' } }) -}) +lti.onUnregisteredPlatform((req, res) => { + return res.status(400).send({ status: 400, error: "Bad Request", details: { message: "Unregistered Platform!" } }); +}); ``` -*Ltijs provides a default method for this callback that returns a 400 error code with the default error object:* + +_Ltijs provides a default method for this callback that returns a 400 error code with the default error object:_ + ``` { status: 400, error: 'Bad Request', details: { message: 'UNREGISTERED_PLATFORM' } } ``` @@ -1259,29 +1173,28 @@ The `onInactivePlatform` callback is called whenever the Platform attempting to The callback route will be given the two Express route parameters (request, response). -> *This callback should be used to display your **Inactive Platform error screen**.* +> _This callback should be used to display your **Inactive Platform error screen**._ ```javascript -lti.onInactivePlatform((req, res) => { - return res.status(401).send({ status: 401, error: 'Unauthorized', details: { message: 'Platform not active!' } }) -}) +lti.onInactivePlatform((req, res) => { + return res.status(401).send({ status: 401, error: "Unauthorized", details: { message: "Platform not active!" } }); +}); ``` -*Ltijs provides a default method for this callback that returns a 401 error code with the default error object:* + +_Ltijs provides a default method for this callback that returns a 401 error code with the default error object:_ + ``` { status: 401, error: 'Unauthorized', details: { message: 'PLATFORM_NOT_ACTIVATED' } } ``` - - ### Deploy Deploying the application opens a connection to the configured database and starts the Express server. ```javascript -await lti.deploy() +await lti.deploy(); ``` - The `lti.deploy()` method accepts an `options` object with the following fields: - **port** - Determines the port used by the Express server. **Default: 3000**. @@ -1290,9 +1203,8 @@ The `lti.deploy()` method accepts an `options` object with the following fields: - **serverless** - If set to true, Ltijs does not start the Express server. **Default: false**. - ```javascript -await lti.deploy({ port: 3030, silent: false }) +await lti.deploy({ port: 3030, silent: false }); ```
@@ -1302,26 +1214,24 @@ await lti.deploy({ port: 3030, silent: false })
- #### Deploying Ltijs as part of another server -You can use Ltijs as a middleware by calling the deploy method with the serverless flag set to true. *Theoretically this also allows you to use Ltijs with AWS or other similar services.* +You can use Ltijs as a middleware by calling the deploy method with the serverless flag set to true. _Theoretically this also allows you to use Ltijs with AWS or other similar services._ ```javascript -const app = express() -lti.setup('EXAMPLEKEY', { url: 'mongodb://localhost/database' }) +const app = express(); +lti.setup("EXAMPLEKEY", { url: "mongodb://localhost/database" }); // Start LTI provider in serverless mode -await lti.deploy({ serverless: true }) +await lti.deploy({ serverless: true }); // Mount Ltijs express app into preexisting express app with /lti prefix -app.use('/lti', lti.app) +app.use("/lti", lti.app); ``` - ### Platform -*Platform manipulation methods require a connection to the database, so they can only be used after the `lti.deploy()` method.* +_Platform manipulation methods require a connection to the database, so they can only be used after the `lti.deploy()` method._ > [Check the Platform Class Documentation](platform.md) @@ -1332,24 +1242,22 @@ A LTI® tool works in conjunction with an LTI® ready platform, so in order for The`lti.registerPlatform()` method returns a Promise that resolves the created [Platform](platform.md) object. ```javascript -let plat = await lti.registerPlatform({ - url: 'https://platform.url', - clientId: 'TOOLCLIENTID', - name: 'Platform Name', - authenticationEndpoint: 'https://platform.url/auth', - accesstokenEndpoint: 'https://platform.url/token', - authConfig: { method: 'JWK_SET', key: 'https://platform.url/keyset' } -}) +let plat = await lti.registerPlatform({ + url: "https://platform.url", + clientId: "TOOLCLIENTID", + name: "Platform Name", + authenticationEndpoint: "https://platform.url/auth", + accesstokenEndpoint: "https://platform.url/token", + authConfig: { method: "JWK_SET", key: "https://platform.url/keyset" }, +}); ``` **url:** Platform url. **clientId:** Tool client Id generated by the Platform. - **name:** Platform name. - **authenticationEndpoint:** Platform authentication endpoint. **accesstokenEndpoint:** Platform access token request endpoint. @@ -1362,12 +1270,11 @@ let plat = await lti.registerPlatform({ authConfig: { method: 'JWK_SET', key: 'https://platform.url/keyset' } ``` - - If the platform uses a JWK key ```javascript -authConfig: { method: 'JWK_KEY', - key: +authConfig: { method: 'JWK_KEY', + key: '{"kty":"EC", "crv":"P-256", "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", @@ -1376,11 +1283,10 @@ authConfig: { method: 'JWK_KEY', }'} ``` - - If the platform uses a RSA key ```javascript -authConfig: { method: 'RSA_KEY', +authConfig: { method: 'RSA_KEY', key: '-----BEGIN PUBLIC KEY-----\n'+ 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0\n'+ 'FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/\n'+ @@ -1398,22 +1304,22 @@ Registered platforms can be retrieved using the following methods: The `lti.getPlatform()` method receives two arguments, `platformUrl` and `clientId`, and returns a Promise that resolves a [Platform](platform.md) object. -``` javascript -const plat = await lti.getPlatform('http://plat.com', 'CLIENTID') // Returns Platform object +```javascript +const plat = await lti.getPlatform("http://plat.com", "CLIENTID"); // Returns Platform object ``` -*If no **clientId** is passed, this method will return an array containing every [Platform](platform.md) object that shares the given **platformUrl**.* +_If no **clientId** is passed, this method will return an array containing every [Platform](platform.md) object that shares the given **platformUrl**._ -``` javascript -const plats = await lti.getPlatform('http://plat.com') // Returns Platform Array +```javascript +const plats = await lti.getPlatform("http://plat.com"); // Returns Platform Array ``` **lti.getPlatformById:** The `lti.getPlatformById()` method receives the `platformId` and returns a Promise that resolves a [Platform](platform.md) object. -``` javascript -const plat = await lti.getPlatformById('60b1fce753c875193d71') // Returns Platform object +```javascript +const plat = await lti.getPlatformById("60b1fce753c875193d71"); // Returns Platform object ``` The platform Id can be found through the `Platform.platformId()` method or in the platformId field of the `idtoken` object after a successful launch. @@ -1423,7 +1329,7 @@ The platform Id can be found through the `Platform.platformId()` method or in th The `lti.getAllPlatforms()` method returns a Promise that resolves an Array containing every registered [Platform](platform.md). ```javascript -const platforms = await lti.getAllPlatforms() // Returns every registered platform +const platforms = await lti.getAllPlatforms(); // Returns every registered platform ``` #### Modifying a Platform @@ -1436,38 +1342,36 @@ The Platform object gives you methods to retrieve and modify platform configurat > [Check the Platform Class Documentation](platform.md) - **Registration method:** If the platform is already registered and you pass different values for the parameters when calling the `lti.registerPlatform()` method, the configuration of the registered platform will be updated. -*platformUrl and cliendId have to be passed and cannot be changed.* +_platformUrl and cliendId have to be passed and cannot be changed._ ```javascript -let plat = await lti.registerPlatform({ - url: 'https://platform.url', - clientId: 'TOOLCLIENTID', - name: 'Platform Name 2', // Changing the name of already registered platform -}) +let plat = await lti.registerPlatform({ + url: "https://platform.url", + clientId: "TOOLCLIENTID", + name: "Platform Name 2", // Changing the name of already registered platform +}); ``` #### Deleting a Platform -Registered platforms can be deleted using the `lti.deletePlatform()` and `lti.deletePlatformById()` methods. +Registered platforms can be deleted using the `lti.deletePlatform()` and `lti.deletePlatformById()` methods. The `lti.deletePlatform()` method receives two arguments, `platformUrl` and `clientId`: -``` javascript -await lti.deletePlatform('http://plat.com', 'CLIENTID') // Deletes a platform +```javascript +await lti.deletePlatform("http://plat.com", "CLIENTID"); // Deletes a platform ``` The `lti.deletePlatformById()` method receives the argument `platformId`: -``` javascript -await lti.deletePlatformById('60b1fce753c875193d71b') // Deletes a platform +```javascript +await lti.deletePlatformById("60b1fce753c875193d71b"); // Deletes a platform ``` - --- ## Authentication and Routing @@ -1484,15 +1388,14 @@ The `idtoken` will contain the platform and user information that is context ind ```javascript onConnect(async (token, req, res) => { - // Retrieving idtoken through response object - console.log(res.locals.token) - // Retrieving idtoken through onConnect token parameter - console.log(token) -}) + // Retrieving idtoken through response object + console.log(res.locals.token); + // Retrieving idtoken through onConnect token parameter + console.log(token); +}); ``` - -The `idtoken` object consists of: +The `idtoken` object consists of: ```javascript // Example idtoken for a Moodle platform @@ -1518,22 +1421,20 @@ The `idtoken` object consists of: } ``` - ### ContextToken The `contexttoken` will contain the context specific information, and will be stored in the `res.locals.context` object and as a part of the `idtoken` object as the `platformContext` field: ```javascript onConnect(async (token, req, res) => { - // Retrieving contexttoken through response object - console.log(res.locals.context) - // Retrieving contexttoken through idtoken object - console.log(token.platformContext) -}) + // Retrieving contexttoken through response object + console.log(res.locals.context); + // Retrieving contexttoken through idtoken object + console.log(token.platformContext); +}); ``` - -The `contexttoken` object consists of: +The `contexttoken` object consists of: ```javascript // Example contexttoken for a Moodle platform @@ -1608,12 +1509,11 @@ Ltijs need as way to retrieve the correct `idtoken` and `contexttoken` informati
- A platform can launch to **any of the tool's endpoints**, but only launches targeting the specified `appRoute` will be sent to the [onConnect callback](#onconnect). **Launches to other endpoints must be handled by their specific `Express` routes.** At the end of a successful launch, Ltijs redirects the request to the desired endpoint, but it also does two other things: - -- Sets a **signed session cookie** containing the `platformCode` and `userId` information; + +- Sets a **signed session cookie** containing the `platformCode` and `userId` information; - Sends a **ltik** JWT token containing the same platform and user information, with additional context information as a query parameter to the endpoint. @@ -1658,7 +1558,7 @@ Example: ```javascript { - ltik: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + ltik: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; } ``` @@ -1668,7 +1568,6 @@ The `ltik` parameter can be passed through a Bearer Authorization header: > Authorization: Bearer \ - Example: > Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c @@ -1692,18 +1591,18 @@ When using the `LTIK-AUTH-V1` authorization schema, `req.headers.authorization` **Ltijs** will look for the `ltik` in the following order: -- LTIK-AUTH-V1 Authorization -- query -- body +- LTIK-AUTH-V1 Authorization +- query +- body - Bearer Authorization ##### Cookies -*In the case of requests coming from different subdomains, usually it is necessary to set `mode: cors` and `credentials: 'include'` flags to include the cookies in the request.* +_In the case of requests coming from different subdomains, usually it is necessary to set `mode: cors` and `credentials: 'include'` flags to include the cookies in the request._ -*If for some reason the cookies could not be set in your development environment, the usage of the **devMode** flag eliminates the validation step that matches the cookie information, instead using only the information contained in the **ltik** token.* +_If for some reason the cookies could not be set in your development environment, the usage of the **devMode** flag eliminates the validation step that matches the cookie information, instead using only the information contained in the **ltik** token._ -> [See more about development mode]((#development-mode)) +> [See more about development mode](<(#development-mode)>) If the validation fails, the request is handled by the **invalidTokenCallback** or the **sessionTimeoutCallback**. @@ -1711,36 +1610,36 @@ If the validation fails, the request is handled by the **invalidTokenCallback** ```javascript // Setup provider example -lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - ltiaas: true // Using ltiaas mode - }) +lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + ltiaas: true, // Using ltiaas mode + } +); ``` Request validation will be done only through the `ltik` token similarly to the development mode, but maintaining the creation and validation of login state cookies. This allows Ltijs to be used as a service, receiving requests from other backend servers. - #### Whitelisting routes - Routes can be whitelisted to bypass the Ltijs authentication protocol **in case of validation failure**, this means that these routes work normally, but if the request sent to them fails validation they are still reached but don't have access to a `idtoken` or `contexttoken`. A good way to exemplify this behaviour is by using it to create a landing page, that will be accessed if a request to the whitelisted route fails validation: ```javascript // Whitelisting the main app route and /landingpage to create a landing page -lti.whitelist(lti.appRoute(), { route: '/landingpage', method: 'get' }) +lti.whitelist(lti.appRoute(), { route: "/landingpage", method: "get" }); // When receiving successful LTI® launch redirects to app, otherwise redirects to landing page lti.onConnect(async (token, req, res, next) => { - // Checking if received idtoken - if (token) return res.sendFile(path.join(__dirname, './public/index.html')) - else lti.redirect(res, '/landingpage') // Redirects to landing page -}) + // Checking if received idtoken + if (token) return res.sendFile(path.join(__dirname, "./public/index.html")); + else lti.redirect(res, "/landingpage"); // Redirects to landing page +}); ``` Whitelisted routes are created using the `lti.whitelist()` method that can receive two types of arguments: @@ -1750,10 +1649,10 @@ Whitelisted routes are created using the `lti.whitelist()` method that can recei Route strings will be whitelisted for **every method**: ```javascript -lti.whitelist('/route1') +lti.whitelist("/route1"); // The lti.whitelist() method can receive multiple arguments -lti.whitelist('/route1', '/route2', '/route3') +lti.whitelist("/route1", "/route2", "/route3"); ``` - **Route object** @@ -1761,41 +1660,40 @@ lti.whitelist('/route1', '/route2', '/route3') Route objects let you specify **whitelisted methods**: ```javascript -lti.whitelist({ route: '/route1', method: 'get' }) +lti.whitelist({ route: "/route1", method: "get" }); // Route objects can also be whitelisted for every method -lti.whitelist({ route: '/route1', method: 'all' }) +lti.whitelist({ route: "/route1", method: "all" }); // The lti.whitelist() method can receive multiple arguments of different trypes -lti.whitelist({ route: '/route1', method: 'get' }, { route: '/route2', method: 'post' }, '/route3') +lti.whitelist({ route: "/route1", method: "get" }, { route: "/route2", method: "post" }, "/route3"); ``` Routes can also be set using Regex which means that you can whitelist a big range of routes: ```javascript // Using Regex -lti.whitelist(new RegExp(/^\/route1/), { route: new RegExp(/^\/route2/), method: 'get' }) +lti.whitelist(new RegExp(/^\/route1/), { route: new RegExp(/^\/route2/), method: "get" }); ``` The `new RegExp(/^\/route1/)` regex will whitelistd every route that starts with `/route`. **Be careful when using regex to whitelist routes, you could whitelist routes accidentally and that can have a big impact on your application. It is recommended to use the start-of-string (^) and end-of-string ($) anchors to avoid accidental matches.** - #### Redirecting with Ltijs -The Ltijs authentication protocol relies on the `ltik` token being passed to endpoints as query parameters. +The Ltijs authentication protocol relies on the `ltik` token being passed to endpoints as query parameters. To make this process seamless, the `lti.redirect()` method can be used to redirect to an endpoint passing the `ltik` token automatically: ```javascript lti.onConnect(async (token, req, res) => { - return lti.redirect(res, '/route') // Redirects to /route with the ltik token -}) + return lti.redirect(res, "/route"); // Redirects to /route with the ltik token +}); -lti.get('/route', async (req, res) => { - return lti.redirect(res, '/route/b?test=123') // Redirects to /route/b with the ltik token and additional query parameters -}) +lti.get("/route", async (req, res) => { + return lti.redirect(res, "/route/b?test=123"); // Redirects to /route/b with the ltik token and additional query parameters +}); ``` The `lti.redirect()` method requires two parameter: @@ -1812,64 +1710,62 @@ The `url` parameter can be an internal route ('/route') or a complete URL ('http ```javascript // Setup provider example - lti.setup('EXAMPLEKEY', - { - url: 'mongodb://localhost/database',// Database url - connection:{ user:'user', pass: 'pass'}// Database configuration - }, - { - cookies: { // Cookie configuration - secure: true, - sameSite: 'None', - domain: '.domain.com' - } - } - ) + lti.setup( + "EXAMPLEKEY", + { + url: "mongodb://localhost/database", // Database url + connection: { user: "user", pass: "pass" }, // Database configuration + }, + { + cookies: { + // Cookie configuration + secure: true, + sameSite: "None", + domain: ".domain.com", + }, + } + ); ``` + Setting the domain to `.domain.com` allows the `session cookie` to be accessed on every domain.com subdomain (a.domain.com, b.domain.com). - If the complete URL is on a different domain, it will have access to the `ltik`, but **it will not have access to a `session cookie`**, and will only be able to make successful requests to whitelisted routes. -- If the route originating the resource does not have access to an `idtoken` (whitelisted route), `lti.redirect()` method will still perform the redirection, but the target will not have access to the `ltik` nor the `session cookie`. - +- If the route originating the resource does not have access to an `idtoken` (whitelisted route), `lti.redirect()` method will still perform the redirection, but the target will not have access to the `ltik` nor the `session cookie`. The `lti.redirect()` method also has an `options` parameter that accepts two fields: -- ***newResource***: If this field is set to true, the `contexttoken` object has it's `path` field changed to reflect the target route. The `path` field can be used to keep track of the main resource route even after several redirections. +- **_newResource_**: If this field is set to true, the `contexttoken` object has it's `path` field changed to reflect the target route. The `path` field can be used to keep track of the main resource route even after several redirections. ```javascript lti.onConnect(async (token, req, res) => { - return lti.redirect(res, '/route', { newResource: true }) -}) + return lti.redirect(res, "/route", { newResource: true }); +}); ``` -- ***query***: This field can be used to easely add query parameter to target URL. +- **_query_**: This field can be used to easely add query parameter to target URL. + ```javascript lti.onConnect(async (token, req, res) => { - return lti.redirect(res, '/path', { newResource: true, query: { param: 'value' } }) - // Redirects to /path?param=value -}) + return lti.redirect(res, "/path", { newResource: true, query: { param: "value" } }); + // Redirects to /path?param=value +}); ``` +_If for some reason you want to redirect manually, the `ltik` token can be retrieved, **after a valid request**, through the `res.locals.ltik` variable._ -*If for some reason you want to redirect manually, the `ltik` token can be retrieved, **after a valid request**, through the `res.locals.ltik` variable.* - -___ +--- ## LTI® Advantage Services - ### Deep Linking Service with Ltijs The Deep Linking Service class documentation can be accessed [here](https://cvmcosta.me/ltijs/#/deeplinking). - ### Assignment and Grades Service with Ltijs The Assignment and Grades Service class documentation can be accessed [here](https://cvmcosta.me/ltijs/#/grading). - - ### Names and Roles Provisioning Service with Ltijs The Names and Roles Provisioning Service class documentation can be accessed [here](https://cvmcosta.me/ltijs/#/namesandroles). @@ -1882,7 +1778,6 @@ The Dynamic Registration Service documentation can be accessed [here](https://cv ## Debugging - **Ltijs** uses [debug](https://www.npmjs.com/package/debug) to log various events to the console. Just append `DEBUG='provider:*'` before your node or npm command and it should work. ```shell @@ -1891,20 +1786,16 @@ DEBUG='provider:*' npm start --- - ## Contributing -Please ⭐️ the repo, it always helps! +Please ⭐️ the repo, it always helps! If you find a bug or think that something is hard to understand feel free to open an issue or contact me on twitter [@cvmcosta](https://twitter.com/cvmcosta), pull requests are also welcome :) - And if you feel like it, you can donate any amount through paypal, it helps a lot. Buy Me A Coffee - - --- ## Special thanks @@ -1916,9 +1807,6 @@ And if you feel like it, you can donate any amount through paypal, it helps a lo > I would like to thank the Federal University of Maranhão and UNA-SUS/UFMA for the support throughout the entire development process. - - -

@@ -1926,7 +1814,6 @@ And if you feel like it, you can donate any amount through paypal, it helps a lo > I would like to thank CourseKey for making the Certification process possible and allowing me to be an IMS Member through them, which will contribute immensely to the future of the project. -

@@ -1934,11 +1821,10 @@ And if you feel like it, you can donate any amount through paypal, it helps a lo > I would like to thank Examind for the amazing work on the Firestore database plugin. As well as the continuous help and support in the development of this project. - --- ## License [![APACHE2 License](https://img.shields.io/github/license/cvmcosta/ltijs)](LICENSE) -> *Learning Tools Interoperability® (LTI®) is a trademark of the IMS Global Learning Consortium, Inc. (https://www.imsglobal.org)* +> _Learning Tools Interoperability® (LTI®) is a trademark of the IMS Global Learning Consortium, Inc. (https://www.imsglobal.org)_ diff --git a/src/Provider/Provider.js b/src/Provider/Provider.js index c792838..0ffc880 100644 --- a/src/Provider/Provider.js +++ b/src/Provider/Provider.js @@ -51,6 +51,13 @@ class Provider { signed: true } + #bodyParserOptions = { + json: {}, + raw: {}, + text: {}, + urlencoded: { extended: false } + } + // Setup flag #setup = false @@ -136,6 +143,10 @@ class Provider { * @param {Array} [options.dynReg.redirectUris] - Additional redirect URIs. (Ex: ['https://tool.example.com/launch']) * @param {Object} [options.dynReg.customParameters] - Custom parameters object. (Ex: { key: 'value' }) * @param {Boolean} [options.dynReg.autoActivate = false] - Platform auto activation flag. If true, every Platform registered dynamically is immediately activated. Defaults to false. + * @param {Object} [options.bodyParserOpt.json = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserjsonoptions) + * @param {Object} [options.bodyParserOpt.raw = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserrawoptions) + * @param {Object} [options.bodyParserOpt.text = {}] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparsertextoptions) + * @param {Object} [options.bodyParserOpt.urlencoded = { extended: false }] - Parameters object to configure bodyParserOpt.json. (See documentation @ https://github.com/expressjs/body-parser#bodyparserurlencodedoptions) */ setup (encryptionkey, database, options) { if (this.#setup) throw new Error('PROVIDER_ALREADY_SETUP') @@ -167,9 +178,17 @@ class Provider { if (options.cookies.domain) this.#cookieOptions.domain = options.cookies.domain } + // BodyParser options + if (options && options.bodyParserOpt) { + if (options.bodyParserOpt.json) this.#bodyParserOptions.json = options.bodyParserOpt.json; + if (options.bodyParserOpt.raw) this.#bodyParserOptions.raw = options.bodyParserOpt.raw; + if (options.bodyParserOpt.text) this.#bodyParserOptions.text = options.bodyParserOpt.text; + if (options.bodyParserOpt.urlencoded) this.#bodyParserOptions.urlencoded = options.bodyParserOpt.urlencoded; + } + this.#ENCRYPTIONKEY = encryptionkey - this.#server = new Server(options ? options.https : false, options ? options.ssl : false, this.#ENCRYPTIONKEY, options ? options.cors : true, options ? options.serverAddon : false) + this.#server = new Server(options ? options.https : false, options ? options.ssl : false, this.#ENCRYPTIONKEY, options ? options.cors : true, options ? options.serverAddon : false, options ? options.bodyParserOpt : this.#bodyParserOptions) /** * @description Express server object. diff --git a/src/Utils/Server.js b/src/Utils/Server.js index cb9c4f8..e6bc7e0 100644 --- a/src/Utils/Server.js +++ b/src/Utils/Server.js @@ -8,7 +8,7 @@ const cors = require('cors') const provAuthDebug = require('debug')('provider:auth') class Server { - constructor (https, ssl, ENCRYPTIONKEY, corsOpt, serverAddon) { + constructor (https, ssl, ENCRYPTIONKEY, corsOpt, serverAddon, bodyParserOpt) { this.app = express() this.server = false @@ -43,10 +43,12 @@ class Server { })) 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()) + + // Ingest body parser options for each parsertype + this.app.use(bodyParser.urlencoded(bodyParserOpt && bodyParserOpt.urlencoded ? bodyParserOpt.urlencoded : { extended: false })) + this.app.use(bodyParser.json(bodyParserOpt && bodyParserOpt.json ? bodyParserOpt.json : { })) + this.app.use(bodyParser.raw(bodyParserOpt && bodyParserOpt.raw ? bodyParserOpt.raw : { })) + this.app.use(bodyParser.text(bodyParserOpt && bodyParserOpt.text ? bodyParserOpt.text : { })) this.app.use(cookieParser(ENCRYPTIONKEY)) this.app.use(async (req, res, next) => { // Creating Authorization schema LTIK-AUTH-V1