UNPKG

ltijs

Version:

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

167 lines (164 loc) 9.54 kB
"use strict"; function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } /* Provider Dynamic Registration Service */ const got = require('../../Utils/Http'); const crypto = require('crypto'); const _url = require('fast-url-parser'); const provDynamicRegistrationDebug = require('debug')('provider:dynamicRegistrationService'); const Objects = require('../../Utils/Objects'); var _name = /*#__PURE__*/new WeakMap(); var _redirectUris = /*#__PURE__*/new WeakMap(); var _customParameters = /*#__PURE__*/new WeakMap(); var _autoActivate = /*#__PURE__*/new WeakMap(); var _useDeepLinking = /*#__PURE__*/new WeakMap(); var _logo = /*#__PURE__*/new WeakMap(); var _description = /*#__PURE__*/new WeakMap(); var _hostname = /*#__PURE__*/new WeakMap(); var _appUrl = /*#__PURE__*/new WeakMap(); var _loginUrl = /*#__PURE__*/new WeakMap(); var _keysetUrl = /*#__PURE__*/new WeakMap(); var _getPlatform = /*#__PURE__*/new WeakMap(); var _registerPlatform = /*#__PURE__*/new WeakMap(); var _ENCRYPTIONKEY = /*#__PURE__*/new WeakMap(); var _Database = /*#__PURE__*/new WeakMap(); var _DynamicRegistration_brand = /*#__PURE__*/new WeakSet(); class DynamicRegistration { constructor(options, routes, registerPlatform, getPlatform, ENCRYPTIONKEY, Database) { // Helper method to build URLs _classPrivateMethodInitSpec(this, _DynamicRegistration_brand); _classPrivateFieldInitSpec(this, _name, void 0); _classPrivateFieldInitSpec(this, _redirectUris, void 0); _classPrivateFieldInitSpec(this, _customParameters, void 0); _classPrivateFieldInitSpec(this, _autoActivate, void 0); _classPrivateFieldInitSpec(this, _useDeepLinking, void 0); _classPrivateFieldInitSpec(this, _logo, void 0); _classPrivateFieldInitSpec(this, _description, void 0); _classPrivateFieldInitSpec(this, _hostname, void 0); _classPrivateFieldInitSpec(this, _appUrl, void 0); _classPrivateFieldInitSpec(this, _loginUrl, void 0); _classPrivateFieldInitSpec(this, _keysetUrl, void 0); _classPrivateFieldInitSpec(this, _getPlatform, void 0); _classPrivateFieldInitSpec(this, _registerPlatform, void 0); _classPrivateFieldInitSpec(this, _ENCRYPTIONKEY, ''); _classPrivateFieldInitSpec(this, _Database, void 0); _classPrivateFieldSet(_name, this, options.name); _classPrivateFieldSet(_redirectUris, this, options.redirectUris || []); _classPrivateFieldSet(_customParameters, this, options.customParameters || {}); _classPrivateFieldSet(_autoActivate, this, options.autoActivate); _classPrivateFieldSet(_useDeepLinking, this, options.useDeepLinking === undefined ? true : options.useDeepLinking); _classPrivateFieldSet(_logo, this, options.logo); _classPrivateFieldSet(_description, this, options.description); _classPrivateFieldSet(_hostname, this, _assertClassBrand(_DynamicRegistration_brand, this, _getHostname).call(this, options.url)); _classPrivateFieldSet(_appUrl, this, _assertClassBrand(_DynamicRegistration_brand, this, _buildUrl).call(this, options.url, routes.appRoute)); _classPrivateFieldSet(_loginUrl, this, _assertClassBrand(_DynamicRegistration_brand, this, _buildUrl).call(this, options.url, routes.loginRoute)); _classPrivateFieldSet(_keysetUrl, this, _assertClassBrand(_DynamicRegistration_brand, this, _buildUrl).call(this, options.url, routes.keysetRoute)); _classPrivateFieldSet(_getPlatform, this, getPlatform); _classPrivateFieldSet(_registerPlatform, this, registerPlatform); _classPrivateFieldSet(_ENCRYPTIONKEY, this, ENCRYPTIONKEY); _classPrivateFieldSet(_Database, this, Database); } /** * @description Performs dynamic registration flow. * @param {String} openidConfiguration - OpenID configuration URL. Retrieved from req.query.openid_configuration. * @param {String} [registrationToken] - Registration Token. Retrieved from req.query.registration_token. * @param {Object} [options] - Replacements or extensions to default registration options. */ async register(openidConfiguration, registrationToken, options = {}) { if (!openidConfiguration) throw new Error('MISSING_OPENID_CONFIGURATION'); provDynamicRegistrationDebug('Starting dynamic registration process'); // Get Platform registration configurations const configuration = await got.get(openidConfiguration).json(); provDynamicRegistrationDebug('Attempting to register Platform with issuer: ', configuration.issuer); // Building registration object const messages = [{ type: 'LtiResourceLinkRequest' }]; if (_classPrivateFieldGet(_useDeepLinking, this)) messages.push({ type: 'LtiDeepLinkingRequest' }); const registration = Objects.deepMergeObjects({ application_type: 'web', response_types: ['id_token'], grant_types: ['implicit', 'client_credentials'], initiate_login_uri: _classPrivateFieldGet(_loginUrl, this), redirect_uris: [..._classPrivateFieldGet(_redirectUris, this), _classPrivateFieldGet(_appUrl, this)], client_name: _classPrivateFieldGet(_name, this), jwks_uri: _classPrivateFieldGet(_keysetUrl, this), logo_uri: _classPrivateFieldGet(_logo, this), token_endpoint_auth_method: 'private_key_jwt', scope: 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly', 'https://purl.imsglobal.org/spec/lti-tool-configuration': { domain: _classPrivateFieldGet(_hostname, this), description: _classPrivateFieldGet(_description, this), target_link_uri: _classPrivateFieldGet(_appUrl, this), custom_parameters: _classPrivateFieldGet(_customParameters, this), claims: configuration.claims_supported, messages } }, options); provDynamicRegistrationDebug('Tool registration request:'); provDynamicRegistrationDebug(registration); provDynamicRegistrationDebug('Sending Tool registration request'); const registrationResponse = await got.post(configuration.registration_endpoint, { json: registration, headers: registrationToken ? { Authorization: 'Bearer ' + registrationToken } : undefined }).json(); // Registering Platform const platformName = (configuration['https://purl.imsglobal.org/spec/lti-platform-configuration'] ? configuration['https://purl.imsglobal.org/spec/lti-platform-configuration'].product_family_code : 'Platform') + '_DynReg_' + crypto.randomBytes(16).toString('hex'); if (await _classPrivateFieldGet(_getPlatform, this).call(this, configuration.issuer, registrationResponse.client_id, _classPrivateFieldGet(_ENCRYPTIONKEY, this), _classPrivateFieldGet(_Database, this))) throw new Error('PLATFORM_ALREADY_REGISTERED'); provDynamicRegistrationDebug('Registering Platform'); const platform = { url: configuration.issuer, name: platformName, clientId: registrationResponse.client_id, authenticationEndpoint: configuration.authorization_endpoint, accesstokenEndpoint: configuration.token_endpoint, authorizationServer: configuration.authorization_server || configuration.token_endpoint, authConfig: { method: 'JWK_SET', key: configuration.jwks_uri } }; const registered = await _classPrivateFieldGet(_registerPlatform, this).call(this, platform, _classPrivateFieldGet(_getPlatform, this), _classPrivateFieldGet(_ENCRYPTIONKEY, this), _classPrivateFieldGet(_Database, this)); await _classPrivateFieldGet(_Database, this).Insert(false, 'platformStatus', { id: await registered.platformId(), active: _classPrivateFieldGet(_autoActivate, this) }); // Returing message indicating the end of registration flow return '<script>(window.opener || window.parent).postMessage({subject:"org.imsglobal.lti.close"}, "*");</script>'; } } function _buildUrl(url, path) { if (path === '/') return url; const pathParts = _url.parse(url); const portMatch = pathParts.pathname.match(/:[0-9]*/); if (portMatch) { pathParts.port = portMatch[0].split(':')[1]; pathParts.pathname = pathParts.pathname.split(portMatch[0]).join(''); } const formattedUrl = _url.format({ protocol: pathParts.protocol, hostname: pathParts.hostname, pathname: (pathParts.pathname + path).replace('//', '/'), port: pathParts.port, auth: pathParts.auth, hash: pathParts.hash, search: pathParts.search }); return formattedUrl; } // Helper method to get the url hostname function _getHostname(url) { const pathParts = _url.parse(url); let hostname = pathParts.hostname; if (pathParts.port) hostname += ':' + pathParts.port; return hostname; } module.exports = DynamicRegistration;