From 19e1642a8a4d553ea31353a3b8aae363fa63b3dd Mon Sep 17 00:00:00 2001 From: Karen Dolan Date: Wed, 22 Jul 2026 14:46:28 -0400 Subject: [PATCH 1/2] UP-166 dce-expresskit sessioned user multiple CourseContextToken support to allow users to make validatable API calls when different course contexts are open (i.e. multiple tabs) in the same browser session --- examples/courseContext.example.ts | 99 +++++++++ lib/constants/COURSE_CONTEXT_HEADER.d.ts | 15 ++ lib/constants/COURSE_CONTEXT_HEADER.js | 18 ++ lib/constants/COURSE_CONTEXT_HEADER.js.map | 1 + lib/helpers/courseContext.d.ts | 35 +++ lib/helpers/courseContext.js | 168 ++++++++++++++ lib/helpers/courseContext.js.map | 1 + lib/helpers/genRouteHandler.js | 121 +++++++++-- lib/helpers/genRouteHandler.js.map | 2 +- lib/index.d.ts | 6 +- lib/index.js | 10 +- lib/index.js.map | 2 +- lib/types/CourseContextTokenPayload.d.ts | 25 +++ lib/types/CourseContextTokenPayload.js | 3 + lib/types/CourseContextTokenPayload.js.map | 1 + lib/types/ExpressKitErrorCode.d.ts | 4 + lib/types/ExpressKitErrorCode.js | 7 +- lib/types/ExpressKitErrorCode.js.map | 2 +- lib/types/VerifiedCourseAuth.d.ts | 46 ++++ lib/types/VerifiedCourseAuth.js | 3 + lib/types/VerifiedCourseAuth.js.map | 1 + src/constants/COURSE_CONTEXT_HEADER.ts | 16 ++ src/helpers/addCourseContextEndpoint.ts | 84 +++++++ src/helpers/courseContext.ts | 242 +++++++++++++++++++++ src/helpers/genRouteHandler.ts | 119 +++++++++- src/index.ts | 14 ++ src/types/CourseContextTokenPayload.ts | 35 +++ src/types/ExpressKitErrorCode.ts | 8 +- src/types/VerifiedCourseAuth.ts | 58 +++++ 29 files changed, 1108 insertions(+), 38 deletions(-) create mode 100644 examples/courseContext.example.ts create mode 100644 lib/constants/COURSE_CONTEXT_HEADER.d.ts create mode 100644 lib/constants/COURSE_CONTEXT_HEADER.js create mode 100644 lib/constants/COURSE_CONTEXT_HEADER.js.map create mode 100644 lib/helpers/courseContext.d.ts create mode 100644 lib/helpers/courseContext.js create mode 100644 lib/helpers/courseContext.js.map create mode 100644 lib/types/CourseContextTokenPayload.d.ts create mode 100644 lib/types/CourseContextTokenPayload.js create mode 100644 lib/types/CourseContextTokenPayload.js.map create mode 100644 lib/types/VerifiedCourseAuth.d.ts create mode 100644 lib/types/VerifiedCourseAuth.js create mode 100644 lib/types/VerifiedCourseAuth.js.map create mode 100644 src/constants/COURSE_CONTEXT_HEADER.ts create mode 100644 src/helpers/addCourseContextEndpoint.ts create mode 100644 src/helpers/courseContext.ts create mode 100644 src/types/CourseContextTokenPayload.ts create mode 100644 src/types/VerifiedCourseAuth.ts diff --git a/examples/courseContext.example.ts b/examples/courseContext.example.ts new file mode 100644 index 0000000..1470d62 --- /dev/null +++ b/examples/courseContext.example.ts @@ -0,0 +1,99 @@ +/** + * Example: end-to-end per-tab course context with dce-expresskit. + * + * This file is illustrative teaching material — it is intentionally kept out of + * /src so it is not compiled into the published library. It shows the full loop: + * + * 1. (Server) Mint a course-context token for the current launch. + * 2. (Client) Store the token per browser tab and send it on every request. + * 3. (Server) genRouteHandler verifies the token automatically. + * + * Prerequisite: set DCEKIT_COURSE_CONTEXT_SECRET in the server environment. This + * secret signs and verifies tokens, so it must be stable and kept private. + * + * @author Karen Dolan + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +// Import express +import express from 'express'; + +// Import expresskit +import { + addCourseContextEndpoint, + genRouteHandler, + COURSE_CONTEXT_HEADER, +} from 'dce-expresskit'; + +/*------------------------------------------------------------------------*/ +/* ------------------------- 1. Server: mint it ------------------------- */ +/*------------------------------------------------------------------------*/ + +const app = express(); + +// Option A (recommended): use the ready-made endpoint helper. This mounts +// GET /api/course-context, which returns a signed token for the current launch. +addCourseContextEndpoint({ app }); + +// Option B: mint inside your own endpoint if you need custom behavior. +// import { genCourseContext } from 'dce-expresskit'; +// app.get('/api/my-course-context', genRouteHandler({ +// handler: async ({ req }) => { +// return genCourseContext({ req }); +// }, +// })); + +// Every normal endpoint stays exactly as it is today. When a verified course +// context is present on the request, genRouteHandler trusts it for this +// request's course + roles; when it is absent, behavior is unchanged. +app.get( + '/api/assignments', + genRouteHandler({ + handler: async ( + handlerOpts: { + params: { [k: string]: any }, + }, + ) => { + // params.courseId / params.isTTM / params.isAdmin now reflect the verified + // course context for THIS tab, not just the shared session launch. + const { + courseId, + isTTM, + } = handlerOpts.params; + return { courseId, isTTM }; + }, + }), +); + +/*------------------------------------------------------------------------*/ +/* ------------------- 2. Client: store + attach it --------------------- */ +/*------------------------------------------------------------------------*/ + +/** + * The snippet below is pseudocode for the client (for example, in a dce-reactkit + * app). Once dce-reactkit ships built-in support (Phase 2), visitServerEndpoint + * will do this automatically and app code will not need any of it. Until then, + * this shows the contract a client must honor. + * + * IMPORTANT: use sessionStorage, NOT localStorage. sessionStorage is scoped to a + * single browser tab, which is exactly what lets two tabs hold two different + * course contexts at once. localStorage is shared across tabs and would defeat + * the purpose. + * + * // On app launch, fetch and store the token for THIS tab: + * const { token } = await visitServerEndpoint({ + * path: '/api/course-context', + * method: 'GET', + * }); + * window.sessionStorage.setItem('courseContextToken', token); + * + * // On every request, attach the stored token as a header: + * const token = window.sessionStorage.getItem('courseContextToken'); + * const headers = token ? { [COURSE_CONTEXT_HEADER]: token } : {}; + * + * // If a request fails with the "expired" code (DEK39), re-mint once and retry: + * // 1) GET /api/course-context again, 2) store the new token, 3) retry. + */ + +export default app; diff --git a/lib/constants/COURSE_CONTEXT_HEADER.d.ts b/lib/constants/COURSE_CONTEXT_HEADER.d.ts new file mode 100644 index 0000000..495fc6a --- /dev/null +++ b/lib/constants/COURSE_CONTEXT_HEADER.d.ts @@ -0,0 +1,15 @@ +/** + * Name of the HTTP header that carries a signed course-context token. + * + * The client (for example, dce-reactkit's visitServerEndpoint) sends the token + * in this header, and genRouteHandler reads it to verify the per-request course + * context. Kept in a shared constant so the client and server never drift apart + * on the exact header name. + * + * Note: Express lower-cases incoming header names, so read it as + * req.headers['x-course-context']. + * + * @author Karen Dolan + */ +declare const COURSE_CONTEXT_HEADER = "X-Course-Context"; +export default COURSE_CONTEXT_HEADER; diff --git a/lib/constants/COURSE_CONTEXT_HEADER.js b/lib/constants/COURSE_CONTEXT_HEADER.js new file mode 100644 index 0000000..14e5f58 --- /dev/null +++ b/lib/constants/COURSE_CONTEXT_HEADER.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Name of the HTTP header that carries a signed course-context token. + * + * The client (for example, dce-reactkit's visitServerEndpoint) sends the token + * in this header, and genRouteHandler reads it to verify the per-request course + * context. Kept in a shared constant so the client and server never drift apart + * on the exact header name. + * + * Note: Express lower-cases incoming header names, so read it as + * req.headers['x-course-context']. + * + * @author Karen Dolan + */ +var COURSE_CONTEXT_HEADER = 'X-Course-Context'; +exports.default = COURSE_CONTEXT_HEADER; +//# sourceMappingURL=COURSE_CONTEXT_HEADER.js.map \ No newline at end of file diff --git a/lib/constants/COURSE_CONTEXT_HEADER.js.map b/lib/constants/COURSE_CONTEXT_HEADER.js.map new file mode 100644 index 0000000..bcbf558 --- /dev/null +++ b/lib/constants/COURSE_CONTEXT_HEADER.js.map @@ -0,0 +1 @@ +{"version":3,"file":"COURSE_CONTEXT_HEADER.js","sourceRoot":"","sources":["../../src/constants/COURSE_CONTEXT_HEADER.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;GAYG;AACH,IAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAEjD,kBAAe,qBAAqB,CAAC"} \ No newline at end of file diff --git a/lib/helpers/courseContext.d.ts b/lib/helpers/courseContext.d.ts new file mode 100644 index 0000000..f7bfb5e --- /dev/null +++ b/lib/helpers/courseContext.d.ts @@ -0,0 +1,35 @@ +import VerifiedCourseAuth from '../types/VerifiedCourseAuth'; +/** + * Mint a signed course-context token for the caller's current, Canvas-verified + * launch. The consumer app calls this (typically from a small endpoint) right + * after a launch and hands the token to the client, which stores it per browser + * tab and sends it back on each request (see COURSE_CONTEXT_HEADER). + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.req the express request (must have a valid CACCL launch) + * @param [opts.ttlMs=1 hour] how long the token should remain valid, in ms + * @returns a signed course-context token string + */ +declare const genCourseContext: (opts: { + req: any; + ttlMs?: number; +}) => string; +/** + * Verify a signed course-context token and return the course + roles it proves. + * Throws an ErrorWithCode if the token is malformed, has a bad signature, has + * expired, or was minted for a different user than the current session user. + * + * genRouteHandler calls this internally, so most consumers never need to. It is + * exported for apps that build their own middleware. + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.token the raw token string from the request header + * @param [opts.expectedUserId] if provided, the token's user must match this + * (bind the token to the logged-in session user to prevent replay by others) + * @returns the verified course authorization + */ +declare const verifyCourseContextToken: (opts: { + token: string; + expectedUserId?: number; +}) => VerifiedCourseAuth; +export { genCourseContext, verifyCourseContextToken, }; diff --git a/lib/helpers/courseContext.js b/lib/helpers/courseContext.js new file mode 100644 index 0000000..885aa06 --- /dev/null +++ b/lib/helpers/courseContext.js @@ -0,0 +1,168 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.verifyCourseContextToken = exports.genCourseContext = void 0; +// Import dce-commonkit +var dce_commonkit_1 = require("dce-commonkit"); +// Import caccl +var server_1 = require("caccl/server"); +// Import node libs +var crypto_1 = __importDefault(require("crypto")); +// Import shared types +var ExpressKitErrorCode_1 = __importDefault(require("../types/ExpressKitErrorCode")); +/*------------------------------------------------------------------------*/ +/* ------------------------------ Constants ----------------------------- */ +/*------------------------------------------------------------------------*/ +// How long a freshly minted course-context token is valid for, in ms. +// Tokens are short-lived: the client re-mints when one expires. Kept modest so +// a stolen token has a small window, but long enough to cover a work session. +var DEFAULT_COURSE_CONTEXT_TTL_MS = dce_commonkit_1.HOUR_IN_MS; +/*------------------------------------------------------------------------*/ +/* ------------------------------- Helpers ------------------------------ */ +/*------------------------------------------------------------------------*/ +/** + * Read the server's course-context signing secret from the environment. + * This secret never leaves the server: it signs tokens on mint and verifies + * them on each request, so the same server is the only party that can issue a + * token the same server will trust. + * @author Karen Dolan + * @returns the signing secret + */ +var getCourseContextSecret = function () { + var DCEKIT_COURSE_CONTEXT_SECRET = process.env.DCEKIT_COURSE_CONTEXT_SECRET; + if (!DCEKIT_COURSE_CONTEXT_SECRET) { + throw new dce_commonkit_1.ErrorWithCode('We could not process the course context for this request because the server is missing its course-context signing secret. Please contact support.', ExpressKitErrorCode_1.default.CourseContextNoSecret); + } + return DCEKIT_COURSE_CONTEXT_SECRET; +}; +/** + * Compute the base64url HMAC-SHA256 signature of an encoded payload. + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.encodedPayload the base64url-encoded payload to sign + * @param opts.secret the signing secret + * @returns the base64url signature + */ +var signEncodedPayload = function (opts) { + return (crypto_1.default + .createHmac('sha256', opts.secret) + .update(opts.encodedPayload) + .digest('base64url')); +}; +/*------------------------------------------------------------------------*/ +/* -------------------------------- Mint -------------------------------- */ +/*------------------------------------------------------------------------*/ +/** + * Mint a signed course-context token for the caller's current, Canvas-verified + * launch. The consumer app calls this (typically from a small endpoint) right + * after a launch and hands the token to the client, which stores it per browser + * tab and sends it back on each request (see COURSE_CONTEXT_HEADER). + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.req the express request (must have a valid CACCL launch) + * @param [opts.ttlMs=1 hour] how long the token should remain valid, in ms + * @returns a signed course-context token string + */ +var genCourseContext = function (opts) { + var _a; + // Read the launch info: the launch is our root of trust for who the user is + // and which course + roles they actually have right now. + var _b = (0, server_1.getLaunchInfo)(opts.req), launched = _b.launched, launchInfo = _b.launchInfo; + if (!launched || !launchInfo) { + throw new dce_commonkit_1.ErrorWithCode('We could not create a course context because your session has expired. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextInvalid); + } + // Build the token payload from the verified launch + var now = Date.now(); + var payload = { + courseId: launchInfo.courseId, + courseName: launchInfo.contextLabel, + userId: launchInfo.userId, + isLearner: !!launchInfo.isLearner, + isTTM: !!launchInfo.isTTM, + isAdmin: !!launchInfo.isAdmin, + iat: now, + exp: now + ((_a = opts.ttlMs) !== null && _a !== void 0 ? _a : DEFAULT_COURSE_CONTEXT_TTL_MS), + }; + // Encode and sign + var encodedPayload = (Buffer + .from(JSON.stringify(payload), 'utf8') + .toString('base64url')); + var secret = getCourseContextSecret(); + var signature = signEncodedPayload({ + encodedPayload: encodedPayload, + secret: secret, + }); + // Token is "." + return "".concat(encodedPayload, ".").concat(signature); +}; +exports.genCourseContext = genCourseContext; +/*------------------------------------------------------------------------*/ +/* ------------------------------- Verify ------------------------------- */ +/*------------------------------------------------------------------------*/ +/** + * Verify a signed course-context token and return the course + roles it proves. + * Throws an ErrorWithCode if the token is malformed, has a bad signature, has + * expired, or was minted for a different user than the current session user. + * + * genRouteHandler calls this internally, so most consumers never need to. It is + * exported for apps that build their own middleware. + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.token the raw token string from the request header + * @param [opts.expectedUserId] if provided, the token's user must match this + * (bind the token to the logged-in session user to prevent replay by others) + * @returns the verified course authorization + */ +var verifyCourseContextToken = function (opts) { + var secret = getCourseContextSecret(); + // Split into payload + signature + var parts = opts.token.split('.'); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new dce_commonkit_1.ErrorWithCode('We could not verify your course context because the token was malformed. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextInvalid); + } + var encodedPayload = parts[0], signature = parts[1]; + // Verify the signature with a constant-time comparison + var expectedSignature = signEncodedPayload({ + encodedPayload: encodedPayload, + secret: secret, + }); + var signatureBuffer = Buffer.from(signature); + var expectedBuffer = Buffer.from(expectedSignature); + if (signatureBuffer.length !== expectedBuffer.length + || !crypto_1.default.timingSafeEqual(signatureBuffer, expectedBuffer)) { + throw new dce_commonkit_1.ErrorWithCode('We could not verify your course context because its signature was invalid. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextInvalid); + } + // Decode the payload (signature already proves it was not tampered with) + var payload; + try { + var decoded = (Buffer + .from(encodedPayload, 'base64url') + .toString('utf8')); + payload = JSON.parse(decoded); + } + catch (err) { + throw new dce_commonkit_1.ErrorWithCode('We could not verify your course context because its contents could not be read. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextInvalid); + } + // Reject expired tokens + if (typeof payload.exp !== 'number' + || Date.now() > payload.exp) { + throw new dce_commonkit_1.ErrorWithCode('Your course context has expired. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextExpired); + } + // Bind the token to the current session user, if one was provided + if (opts.expectedUserId !== undefined + && payload.userId !== opts.expectedUserId) { + throw new dce_commonkit_1.ErrorWithCode('We could not verify your course context because it does not match the signed-in user. Please refresh the page and try again.', ExpressKitErrorCode_1.default.CourseContextUserMismatch); + } + // Hand back only the narrow, verified course authorization + return { + courseId: payload.courseId, + courseName: payload.courseName, + isLearner: !!payload.isLearner, + isTTM: !!payload.isTTM, + isAdmin: !!payload.isAdmin, + }; +}; +exports.verifyCourseContextToken = verifyCourseContextToken; +//# sourceMappingURL=courseContext.js.map \ No newline at end of file diff --git a/lib/helpers/courseContext.js.map b/lib/helpers/courseContext.js.map new file mode 100644 index 0000000..a9e0ea5 --- /dev/null +++ b/lib/helpers/courseContext.js.map @@ -0,0 +1 @@ +{"version":3,"file":"courseContext.js","sourceRoot":"","sources":["../../src/helpers/courseContext.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/helpers/genRouteHandler.js b/lib/helpers/genRouteHandler.js index 59f9b69..98529b7 100644 --- a/lib/helpers/genRouteHandler.js +++ b/lib/helpers/genRouteHandler.js @@ -58,6 +58,8 @@ var server_1 = require("caccl/server"); var initExpressKitCollections_1 = require("./initExpressKitCollections"); // Import shared types var ExpressKitErrorCode_1 = __importDefault(require("../types/ExpressKitErrorCode")); +// Import shared constants +var COURSE_CONTEXT_HEADER_1 = __importDefault(require("../constants/COURSE_CONTEXT_HEADER")); // Import helpers var handleError_1 = __importDefault(require("./handleError")); var handleSuccess_1 = __importDefault(require("./handleSuccess")); @@ -65,6 +67,7 @@ var genErrorPage_1 = __importDefault(require("../html/genErrorPage")); var genInfoPage_1 = __importDefault(require("../html/genInfoPage")); var parseUserAgent_1 = __importDefault(require("./parseUserAgent")); var dataSigner_1 = require("./dataSigner"); +var courseContext_1 = require("./courseContext"); /** * Generate an express API route handler * @author Gabe Abrams @@ -108,10 +111,10 @@ var dataSigner_1 = require("./dataSigner"); var genRouteHandler = function (opts) { // Return a route handler return function (req, res, next) { return __awaiter(void 0, void 0, void 0, function () { - var output, crossServerScope, skipSessionCheck, requestBody, paramsToSign, err_1, paramList, i, _a, name_1, type, value, simpleVal, _b, launched, launchInfo, selectAdminCollection, id, match, logServerEvent, responseSent, redirect, send, renderErrorPage, renderInfoPage, renderCustomHTML, results, err_2; - var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; - return __generator(this, function (_w) { - switch (_w.label) { + var output, crossServerScope, skipSessionCheck, requestBody, paramsToSign, err_1, paramList, i, _a, name_1, type, value, simpleVal, _b, launched, launchInfo, courseContextToken, verifiedCourseAuth, selectAdminCollection, id, match, logServerEvent, responseSent, redirect, send, renderErrorPage, renderInfoPage, renderCustomHTML, results, err_2; + var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w; + return __generator(this, function (_x) { + switch (_x.label) { case 0: output = {}; crossServerScope = null; @@ -122,9 +125,9 @@ var genRouteHandler = function (opts) { || crossServerScope); requestBody = __assign(__assign(__assign({}, req.body), req.query), req.params); if (!crossServerScope) return [3 /*break*/, 4]; - _w.label = 1; + _x.label = 1; case 1: - _w.trys.push([1, 3, , 4]); + _x.trys.push([1, 3, , 4]); paramsToSign = __assign(__assign({}, req.body), req.query); // Validate the request body return [4 /*yield*/, (0, dataSigner_1.validateSignedRequest)({ @@ -135,7 +138,7 @@ var genRouteHandler = function (opts) { })]; case 2: // Validate the request body - _w.sent(); + _x.sent(); // Valid! Remove oauth values because they're no longer needed, and shouldn't be passed to the handler Object.keys(requestBody).forEach(function (key) { if (key.startsWith('oauth_')) { @@ -144,7 +147,7 @@ var genRouteHandler = function (opts) { }); return [3 /*break*/, 4]; case 3: - err_1 = _w.sent(); + err_1 = _x.sent(); return [2 /*return*/, (0, handleError_1.default)(res, { message: "The authenticity of a cross-server request could not be validated because an error occurred: ".concat((_e = err_1.message) !== null && _e !== void 0 ? _e : 'unknown error'), code: ((_f = err_1.code) !== null && _f !== void 0 ? _f : ExpressKitErrorCode_1.default.UnknownCrossServerError), @@ -398,10 +401,67 @@ var genRouteHandler = function (opts) { } }); /*----------------------------------------*/ + /* -------- Verified Course Auth -------- */ + /*----------------------------------------*/ + // A consumer app may attach an already-verified, per-request course + // authorization to req.verifiedCourseAuth. This supports having multiple + // browser tabs open on different courses at once: the single shared CACCL + // session can only represent one launched course, but each request can carry + // its own verified course context. + // + // When present, we trust it over the shared session launch for this + // request's course + role fields. When absent, every branch below behaves + // exactly as it did before, so this is fully backward compatible. + // + // There are two ways this field gets populated: + // 1. Library-owned (preferred): the client sends a signed course-context + // token in the COURSE_CONTEXT_HEADER header. We verify it here (see + // below) and populate req.verifiedCourseAuth ourselves, so the app + // writes no crypto and the library owns the trust boundary. + // 2. App-set (interim): the app verifies something itself and sets + // req.verifiedCourseAuth before this handler runs. Still honored for + // backward compatibility during migration onto the token path. + // Library-owned path: verify a signed course-context token, if one was sent + // and the app has not already attached a verified auth. We require a launch + // so we can bind the token to the current session user. + if (!req.verifiedCourseAuth && launchInfo) { + courseContextToken = (_w = req.headers) === null || _w === void 0 ? void 0 : _w[COURSE_CONTEXT_HEADER_1.default.toLowerCase()]; + if (typeof courseContextToken === 'string' && courseContextToken.length > 0) { + try { + req.verifiedCourseAuth = (0, courseContext_1.verifyCourseContextToken)({ + token: courseContextToken, + expectedUserId: launchInfo.userId, + }); + } + catch (err) { + // A present-but-invalid token is an auth failure (401), not a 500 + return [2 /*return*/, (0, handleError_1.default)(res, { + message: err.message, + code: err.code, + status: 401, + })]; + } + } + } + verifiedCourseAuth = (req.verifiedCourseAuth); + if (verifiedCourseAuth) { + output.courseId = verifiedCourseAuth.courseId; + output.isLearner = verifiedCourseAuth.isLearner; + output.isTTM = verifiedCourseAuth.isTTM; + output.isAdmin = verifiedCourseAuth.isAdmin; + // Only override the course name if the verified auth carries one + if (verifiedCourseAuth.courseName !== undefined) { + output.courseName = verifiedCourseAuth.courseName; + } + } + /*----------------------------------------*/ /* ----- Require Course Consistency ----- */ /*----------------------------------------*/ - // Make sure the user actually launched from the appropriate course - if (output.courseId + // Make sure the user actually launched from the appropriate course. + // If a verified per-request course auth is present, it already proves which + // course this request is about, so we skip this session-based check. + if (!verifiedCourseAuth + && output.courseId && launchInfo && launchInfo.courseId && output.courseId !== launchInfo.courseId @@ -454,11 +514,11 @@ var genRouteHandler = function (opts) { return [3 /*break*/, 7]; return [4 /*yield*/, (0, initExpressKitCollections_1.internalGetSelectAdminCollection)()]; case 5: - selectAdminCollection = _w.sent(); + selectAdminCollection = _x.sent(); id = output.userId; return [4 /*yield*/, selectAdminCollection.find({ id: id })]; case 6: - match = (_w.sent())[0]; + match = (_x.sent())[0]; // Check that user exists in select admin collection if (!match) { // User does not have access @@ -468,10 +528,10 @@ var genRouteHandler = function (opts) { status: 401, })]; } - _w.label = 7; + _x.label = 7; case 7: logServerEvent = function (logOpts) { return __awaiter(void 0, void 0, void 0, function () { - var _a, browser, device, _b, timestamp, year, month, day, hour, minute, mainLogInfo, typeSpecificInfo, sourceSpecificInfo, log, logCollection, err_3, dummyMainInfo, dummyTypeSpecificInfo, dummySourceSpecificInfo, log; + var _a, browser, device, _b, timestamp, year, month, day, hour, minute, logIsLearner, logIsTTM, logIsAdmin, logCourseId, logCourseName, mainLogInfo, typeSpecificInfo, sourceSpecificInfo, log, logCollection, err_3, dummyMainInfo, dummyTypeSpecificInfo, dummySourceSpecificInfo, log; var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; return __generator(this, function (_r) { switch (_r.label) { @@ -479,17 +539,32 @@ var genRouteHandler = function (opts) { _r.trys.push([0, 5, , 6]); _a = (0, parseUserAgent_1.default)(req.headers['user-agent']), browser = _a.browser, device = _a.device; _b = (0, dce_commonkit_1.getTimeInfoInET)(), timestamp = _b.timestamp, year = _b.year, month = _b.month, day = _b.day, hour = _b.hour, minute = _b.minute; + logIsLearner = !!(verifiedCourseAuth + ? verifiedCourseAuth.isLearner + : (launchInfo && launchInfo.isLearner)); + logIsTTM = !!(verifiedCourseAuth + ? verifiedCourseAuth.isTTM + : (launchInfo && launchInfo.isTTM)); + logIsAdmin = !!(verifiedCourseAuth + ? verifiedCourseAuth.isAdmin + : (launchInfo && launchInfo.isAdmin)); + logCourseId = (verifiedCourseAuth + ? verifiedCourseAuth.courseId + : (launchInfo ? launchInfo.courseId : -1)); + logCourseName = ((verifiedCourseAuth && verifiedCourseAuth.courseName !== undefined) + ? verifiedCourseAuth.courseName + : (launchInfo ? launchInfo.contextLabel : 'unknown')); mainLogInfo = { id: "".concat(launchInfo ? launchInfo.userId : 'unknown', "-").concat(Date.now(), "-").concat(Math.floor(Math.random() * 100000), "-").concat(Math.floor(Math.random() * 100000)), userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'), userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'), userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'), userId: (launchInfo ? launchInfo.userId : -1), - isLearner: (launchInfo && !!launchInfo.isLearner), - isAdmin: (launchInfo && !!launchInfo.isAdmin), - isTTM: (launchInfo && !!launchInfo.isTTM), - courseId: (launchInfo ? launchInfo.courseId : -1), - courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'), + isLearner: logIsLearner, + isAdmin: logIsAdmin, + isTTM: logIsTTM, + courseId: logCourseId, + courseName: logCourseName, browser: browser, device: device, year: year, @@ -650,9 +725,9 @@ var genRouteHandler = function (opts) { var _a; send(htmlOpts.html, (_a = htmlOpts.status) !== null && _a !== void 0 ? _a : 200); }; - _w.label = 8; + _x.label = 8; case 8: - _w.trys.push([8, 10, , 11]); + _x.trys.push([8, 10, , 11]); return [4 /*yield*/, opts.handler({ params: output, req: req, @@ -668,14 +743,14 @@ var genRouteHandler = function (opts) { logServerEvent: logServerEvent, })]; case 9: - results = _w.sent(); + results = _x.sent(); // Send results to client (only if next wasn't called) if (!responseSent) { return [2 /*return*/, (0, handleSuccess_1.default)(res, results !== null && results !== void 0 ? results : undefined)]; } return [3 /*break*/, 11]; case 10: - err_2 = _w.sent(); + err_2 = _x.sent(); // Prefix error message if needed if (opts.unhandledErrorMessagePrefix && err_2 instanceof Error diff --git a/lib/helpers/genRouteHandler.js.map b/lib/helpers/genRouteHandler.js.map index 466036b..98b0f16 100644 --- a/lib/helpers/genRouteHandler.js.map +++ b/lib/helpers/genRouteHandler.js.map @@ -1 +1 @@ -{"version":3,"file":"genRouteHandler.js","sourceRoot":"","sources":["../../src/helpers/genRouteHandler.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"genRouteHandler.js","sourceRoot":"","sources":["../../src/helpers/genRouteHandler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/index.d.ts b/lib/index.d.ts index 35daf8a..66cc18a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -6,5 +6,9 @@ import handleSuccess from './helpers/handleSuccess'; import addDBEditorEndpoints from './helpers/addDBEditorEndpoints'; import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer'; import initExpressKitCollections, { getLogCollection } from './helpers/initExpressKitCollections'; +import { genCourseContext, verifyCourseContextToken } from './helpers/courseContext'; +import COURSE_CONTEXT_HEADER from './constants/COURSE_CONTEXT_HEADER'; import CrossServerCredential from './types/CrossServerCredential'; -export { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, getTimestampFromTimeInfoInET, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, genCommaList, getLocalTimeInfo, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, spaceAtCapitals, initServer, genRouteHandler, handleError, handleSuccess, initExpressKitCollections, getLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, CrossServerCredential, ParamType, }; +import VerifiedCourseAuth from './types/VerifiedCourseAuth'; +import CourseContextTokenPayload from './types/CourseContextTokenPayload'; +export { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, getTimestampFromTimeInfoInET, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, genCommaList, getLocalTimeInfo, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, spaceAtCapitals, initServer, genRouteHandler, handleError, handleSuccess, initExpressKitCollections, getLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, genCourseContext, verifyCourseContextToken, COURSE_CONTEXT_HEADER, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, CrossServerCredential, VerifiedCourseAuth, CourseContextTokenPayload, ParamType, }; diff --git a/lib/index.js b/lib/index.js index 4ccb6af..12d9586 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,8 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.LogSource = exports.LogType = exports.DayOfWeek = exports.visitEndpointOnAnotherServer = exports.addDBEditorEndpoints = exports.getLogCollection = exports.initExpressKitCollections = exports.handleSuccess = exports.handleError = exports.genRouteHandler = exports.initServer = exports.spaceAtCapitals = exports.shuffleArray = exports.capitalize = exports.someAsync = exports.mapAsync = exports.forEachAsync = exports.filterAsync = exports.everyAsync = exports.prefixWithAOrAn = exports.getLocalTimeInfo = exports.genCommaList = exports.compareArraysByProp = exports.extractProp = exports.genCSV = exports.getMonthName = exports.parallelLimit = exports.onlyKeepLetters = exports.stringsToHumanReadableList = exports.getPartOfDay = exports.getHumanReadableDate = exports.startMinWait = exports.getTimestampFromTimeInfoInET = exports.getMondayOfTimestamp = exports.getTimeInfoInET = exports.getOrdinal = exports.waitMs = exports.sum = exports.roundToNumDecimals = exports.padZerosLeft = exports.padDecimalZeros = exports.forceNumIntoBounds = exports.floorToNumDecimals = exports.ceilToNumDecimals = exports.avg = exports.abbreviate = exports.DAY_IN_MS = exports.HOUR_IN_MS = exports.MINUTE_IN_MS = exports.ErrorWithCode = void 0; -exports.ParamType = exports.LogBuiltInMetadata = exports.LogAction = void 0; +exports.COURSE_CONTEXT_HEADER = exports.verifyCourseContextToken = exports.genCourseContext = exports.visitEndpointOnAnotherServer = exports.addDBEditorEndpoints = exports.getLogCollection = exports.initExpressKitCollections = exports.handleSuccess = exports.handleError = exports.genRouteHandler = exports.initServer = exports.spaceAtCapitals = exports.shuffleArray = exports.capitalize = exports.someAsync = exports.mapAsync = exports.forEachAsync = exports.filterAsync = exports.everyAsync = exports.prefixWithAOrAn = exports.getLocalTimeInfo = exports.genCommaList = exports.compareArraysByProp = exports.extractProp = exports.genCSV = exports.getMonthName = exports.parallelLimit = exports.onlyKeepLetters = exports.stringsToHumanReadableList = exports.getPartOfDay = exports.getHumanReadableDate = exports.startMinWait = exports.getTimestampFromTimeInfoInET = exports.getMondayOfTimestamp = exports.getTimeInfoInET = exports.getOrdinal = exports.waitMs = exports.sum = exports.roundToNumDecimals = exports.padZerosLeft = exports.padDecimalZeros = exports.forceNumIntoBounds = exports.floorToNumDecimals = exports.ceilToNumDecimals = exports.avg = exports.abbreviate = exports.DAY_IN_MS = exports.HOUR_IN_MS = exports.MINUTE_IN_MS = exports.ErrorWithCode = void 0; +exports.ParamType = exports.LogBuiltInMetadata = exports.LogAction = exports.LogSource = exports.LogType = exports.DayOfWeek = void 0; // Import dce-commonkit var dce_commonkit_1 = require("dce-commonkit"); Object.defineProperty(exports, "abbreviate", { enumerable: true, get: function () { return dce_commonkit_1.abbreviate; } }); @@ -101,4 +101,10 @@ exports.visitEndpointOnAnotherServer = visitEndpointOnAnotherServer_1.default; var initExpressKitCollections_1 = __importStar(require("./helpers/initExpressKitCollections")); exports.initExpressKitCollections = initExpressKitCollections_1.default; Object.defineProperty(exports, "getLogCollection", { enumerable: true, get: function () { return initExpressKitCollections_1.getLogCollection; } }); +var courseContext_1 = require("./helpers/courseContext"); +Object.defineProperty(exports, "genCourseContext", { enumerable: true, get: function () { return courseContext_1.genCourseContext; } }); +Object.defineProperty(exports, "verifyCourseContextToken", { enumerable: true, get: function () { return courseContext_1.verifyCourseContextToken; } }); +// Import constants +var COURSE_CONTEXT_HEADER_1 = __importDefault(require("./constants/COURSE_CONTEXT_HEADER")); +exports.COURSE_CONTEXT_HEADER = COURSE_CONTEXT_HEADER_1.default; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib/index.js.map b/lib/index.js.map index c61e5f9..f382b27 100644 --- a/lib/index.js.map +++ b/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAuB;AACvB,+CAiDuB;AAuBrB,2FAvEA,0BAAU,OAuEA;AACV,oFAvEA,mBAAG,OAuEA;AACH,kGAvEA,iCAAiB,OAuEA;AACjB,mGAvEA,kCAAkB,OAuEA;AAClB,mGAvEA,kCAAkB,OAuEA;AAClB,gGAvEA,+BAAe,OAuEA;AACf,6FAvEA,4BAAY,OAuEA;AACZ,mGAvEA,kCAAkB,OAuEA;AAClB,oFAvEA,mBAAG,OAuEA;AACH,uFAvEA,sBAAM,OAuEA;AACN,2FAvEA,0BAAU,OAuEA;AACV,gGAvEA,+BAAe,OAuEA;AACf,qGAvEA,oCAAoB,OAuEA;AACpB,6GAvEA,4CAA4B,OAuEA;AAC5B,6FAvEA,4BAAY,OAuEA;AACZ,qGAvEA,oCAAoB,OAuEA;AACpB,6FAvEA,4BAAY,OAuEA;AACZ,2GAvEA,0CAA0B,OAuEA;AAC1B,gGAvEA,+BAAe,OAuEA;AACf,8FAvEA,6BAAa,OAuEA;AACb,6FAvEA,4BAAY,OAuEA;AACZ,uFAvEA,sBAAM,OAuEA;AACN,4FAvEA,2BAAW,OAuEA;AACX,oGAvEA,mCAAmB,OAuEA;AAEnB,iGAxEA,gCAAgB,OAwEA;AADhB,6FAtEA,4BAAY,OAsEA;AAEZ,gGAvEA,+BAAe,OAuEA;AACf,2FAvEA,0BAAU,OAuEA;AACV,4FAvEA,2BAAW,OAuEA;AACX,6FAvEA,4BAAY,OAuEA;AACZ,yFAvEA,wBAAQ,OAuEA;AACR,0FAvEA,yBAAS,OAuEA;AACT,2FAvEA,0BAAU,OAuEA;AACV,6FAvEA,4BAAY,OAuEA;AAYZ,0FAlFA,yBAAS,OAkFA;AAET,wFAlFA,uBAAO,OAkFA;AACP,0FAlFA,yBAAS,OAkFA;AACT,0FAlFA,yBAAS,OAkFA;AACT,mGAlFA,kCAAkB,OAkFA;AAtDlB,6FAzBA,4BAAY,OAyBA;AACZ,2FAzBA,0BAAU,OAyBA;AACV,0FAzBA,yBAAS,OAyBA;AAJT,8FApBA,6BAAa,OAoBA;AA6Db,0FAhFA,yBAAS,OAgFA;AArBT,gGA1DA,+BAAe,OA0DA;AAvDjB,iBAAiB;AACjB,oEAA8C;AAwD5C,qBAxDK,oBAAU,CAwDL;AAvDZ,8EAAwD;AAwDtD,0BAxDK,yBAAe,CAwDL;AAvDjB,sEAAgD;AAwD9C,sBAxDK,qBAAW,CAwDL;AAvDb,0EAAoD;AAwDlD,wBAxDK,uBAAa,CAwDL;AAvDf,wFAAkE;AA0DhE,+BA1DK,8BAAoB,CA0DL;AAzDtB,wGAAkF;AA0DhF,uCA1DK,sCAA4B,CA0DL;AAzD9B,+FAAkG;AAsDhG,oCAtDK,mCAAyB,CAsDL;AACzB,iGAvDkC,4CAAgB,OAuDlC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAuB;AACvB,+CAiDuB;AA6BrB,2FA7EA,0BAAU,OA6EA;AACV,oFA7EA,mBAAG,OA6EA;AACH,kGA7EA,iCAAiB,OA6EA;AACjB,mGA7EA,kCAAkB,OA6EA;AAClB,mGA7EA,kCAAkB,OA6EA;AAClB,gGA7EA,+BAAe,OA6EA;AACf,6FA7EA,4BAAY,OA6EA;AACZ,mGA7EA,kCAAkB,OA6EA;AAClB,oFA7EA,mBAAG,OA6EA;AACH,uFA7EA,sBAAM,OA6EA;AACN,2FA7EA,0BAAU,OA6EA;AACV,gGA7EA,+BAAe,OA6EA;AACf,qGA7EA,oCAAoB,OA6EA;AACpB,6GA7EA,4CAA4B,OA6EA;AAC5B,6FA7EA,4BAAY,OA6EA;AACZ,qGA7EA,oCAAoB,OA6EA;AACpB,6FA7EA,4BAAY,OA6EA;AACZ,2GA7EA,0CAA0B,OA6EA;AAC1B,gGA7EA,+BAAe,OA6EA;AACf,8FA7EA,6BAAa,OA6EA;AACb,6FA7EA,4BAAY,OA6EA;AACZ,uFA7EA,sBAAM,OA6EA;AACN,4FA7EA,2BAAW,OA6EA;AACX,oGA7EA,mCAAmB,OA6EA;AAEnB,iGA9EA,gCAAgB,OA8EA;AADhB,6FA5EA,4BAAY,OA4EA;AAEZ,gGA7EA,+BAAe,OA6EA;AACf,2FA7EA,0BAAU,OA6EA;AACV,4FA7EA,2BAAW,OA6EA;AACX,6FA7EA,4BAAY,OA6EA;AACZ,yFA7EA,wBAAQ,OA6EA;AACR,0FA7EA,yBAAS,OA6EA;AACT,2FA7EA,0BAAU,OA6EA;AACV,6FA7EA,4BAAY,OA6EA;AAgBZ,0FA5FA,yBAAS,OA4FA;AAET,wFA5FA,uBAAO,OA4FA;AACP,0FA5FA,yBAAS,OA4FA;AACT,0FA5FA,yBAAS,OA4FA;AACT,mGA5FA,kCAAkB,OA4FA;AA1DlB,6FA/BA,4BAAY,OA+BA;AACZ,2FA/BA,0BAAU,OA+BA;AACV,0FA/BA,yBAAS,OA+BA;AAJT,8FA1BA,6BAAa,OA0BA;AAmEb,0FA5FA,yBAAS,OA4FA;AA3BT,gGAhEA,+BAAe,OAgEA;AA7DjB,iBAAiB;AACjB,oEAA8C;AA8D5C,qBA9DK,oBAAU,CA8DL;AA7DZ,8EAAwD;AA8DtD,0BA9DK,yBAAe,CA8DL;AA7DjB,sEAAgD;AA8D9C,sBA9DK,qBAAW,CA8DL;AA7Db,0EAAoD;AA8DlD,wBA9DK,uBAAa,CA8DL;AA7Df,wFAAkE;AAgEhE,+BAhEK,8BAAoB,CAgEL;AA/DtB,wGAAkF;AAgEhF,uCAhEK,sCAA4B,CAgEL;AA/D9B,+FAAkG;AA4DhG,oCA5DK,mCAAyB,CA4DL;AACzB,iGA7DkC,4CAAgB,OA6DlC;AA5DlB,yDAAqF;AAgEnF,iGAhEO,gCAAgB,OAgEP;AAChB,yGAjEyB,wCAAwB,OAiEzB;AA/D1B,mBAAmB;AACnB,4FAAsE;AA+DpE,gCA/DK,+BAAqB,CA+DL"} \ No newline at end of file diff --git a/lib/types/CourseContextTokenPayload.d.ts b/lib/types/CourseContextTokenPayload.d.ts new file mode 100644 index 0000000..49b7cb9 --- /dev/null +++ b/lib/types/CourseContextTokenPayload.d.ts @@ -0,0 +1,25 @@ +/** + * The decoded payload of a signed course-context token. + * + * A course-context token is a small, self-signed (HMAC) assertion that this + * server minted at a Canvas-verified launch. It captures which course a user + * launched, who they are, and what roles they hold in that course, so that a + * single request can prove its own course context independently of the one + * course the shared CACCL session happens to represent. + * + * This is the internal, over-the-wire shape. The verified result handed to + * route handlers is the narrower VerifiedCourseAuth (no user id, no timestamps). + * + * @author Karen Dolan + */ +type CourseContextTokenPayload = { + courseId: number; + courseName: string; + userId: number; + isLearner: boolean; + isTTM: boolean; + isAdmin: boolean; + iat: number; + exp: number; +}; +export default CourseContextTokenPayload; diff --git a/lib/types/CourseContextTokenPayload.js b/lib/types/CourseContextTokenPayload.js new file mode 100644 index 0000000..66e7ebd --- /dev/null +++ b/lib/types/CourseContextTokenPayload.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=CourseContextTokenPayload.js.map \ No newline at end of file diff --git a/lib/types/CourseContextTokenPayload.js.map b/lib/types/CourseContextTokenPayload.js.map new file mode 100644 index 0000000..a19fc6d --- /dev/null +++ b/lib/types/CourseContextTokenPayload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CourseContextTokenPayload.js","sourceRoot":"","sources":["../../src/types/CourseContextTokenPayload.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/lib/types/ExpressKitErrorCode.d.ts b/lib/types/ExpressKitErrorCode.d.ts index ee3d72a..659a7a9 100644 --- a/lib/types/ExpressKitErrorCode.d.ts +++ b/lib/types/ExpressKitErrorCode.d.ts @@ -11,6 +11,10 @@ declare enum ExpressKitErrorCode { InvalidParameter = "DEK5", MissingParameter = "DEK4", StudentIdMismatch = "DEK36", + CourseContextNoSecret = "DEK37", + CourseContextInvalid = "DEK38", + CourseContextExpired = "DEK39", + CourseContextUserMismatch = "DEK40", NotConnected = "DEK14", SelfSigned = "DEK15", ResponseParseError = "DEK16", diff --git a/lib/types/ExpressKitErrorCode.js b/lib/types/ExpressKitErrorCode.js index df91dc7..6816d54 100644 --- a/lib/types/ExpressKitErrorCode.js +++ b/lib/types/ExpressKitErrorCode.js @@ -1,5 +1,5 @@ "use strict"; -// Highest error code = DEK36 +// Highest error code = DEK40 Object.defineProperty(exports, "__esModule", { value: true }); /** * List of error codes built into the express kit @@ -16,6 +16,11 @@ var ExpressKitErrorCode; ExpressKitErrorCode["InvalidParameter"] = "DEK5"; ExpressKitErrorCode["MissingParameter"] = "DEK4"; ExpressKitErrorCode["StudentIdMismatch"] = "DEK36"; + // Course context tokens (per-tab course authorization) + ExpressKitErrorCode["CourseContextNoSecret"] = "DEK37"; + ExpressKitErrorCode["CourseContextInvalid"] = "DEK38"; + ExpressKitErrorCode["CourseContextExpired"] = "DEK39"; + ExpressKitErrorCode["CourseContextUserMismatch"] = "DEK40"; // Server-to-server requests ExpressKitErrorCode["NotConnected"] = "DEK14"; ExpressKitErrorCode["SelfSigned"] = "DEK15"; diff --git a/lib/types/ExpressKitErrorCode.js.map b/lib/types/ExpressKitErrorCode.js.map index 672ae7d..98d5d94 100644 --- a/lib/types/ExpressKitErrorCode.js.map +++ b/lib/types/ExpressKitErrorCode.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpressKitErrorCode.js","sourceRoot":"","sources":["../../src/types/ExpressKitErrorCode.ts"],"names":[],"mappings":";AAAA,6BAA6B;;AAE7B;;;GAGG;AACH,IAAK,mBA8BJ;AA9BD,WAAK,mBAAmB;IACtB,iBAAiB;IACjB,2CAAoB,CAAA;IACpB,sCAAe,CAAA;IACf,yCAAkB,CAAA;IAClB,uDAAgC,CAAA;IAChC,gEAAyC,CAAA;IACzC,gDAAyB,CAAA;IACzB,gDAAyB,CAAA;IACzB,kDAA2B,CAAA;IAE3B,4BAA4B;IAC5B,6CAAsB,CAAA;IACtB,2CAAoB,CAAA;IACpB,mDAA4B,CAAA;IAC5B,yDAAkC,CAAA;IAClC,+DAAwC,CAAA;IACxC,+DAAwC,CAAA;IACxC,0DAAmC,CAAA;IACnC,8DAAuC,CAAA;IACvC,8DAAuC,CAAA;IACvC,yDAAkC,CAAA;IAClC,mEAA4C,CAAA;IAC5C,oEAA6C,CAAA;IAC7C,oEAA6C,CAAA;IAC7C,2CAAoB,CAAA;IACpB,4CAAqB,CAAA;IACrB,oEAA6C,CAAA;IAC7C,wDAAiC,CAAA;IACjC,+CAAwB,CAAA;AAC1B,CAAC,EA9BI,mBAAmB,KAAnB,mBAAmB,QA8BvB;AAED,kBAAe,mBAAmB,CAAC"} \ No newline at end of file +{"version":3,"file":"ExpressKitErrorCode.js","sourceRoot":"","sources":["../../src/types/ExpressKitErrorCode.ts"],"names":[],"mappings":";AAAA,6BAA6B;;AAE7B;;;GAGG;AACH,IAAK,mBAoCJ;AApCD,WAAK,mBAAmB;IACtB,iBAAiB;IACjB,2CAAoB,CAAA;IACpB,sCAAe,CAAA;IACf,yCAAkB,CAAA;IAClB,uDAAgC,CAAA;IAChC,gEAAyC,CAAA;IACzC,gDAAyB,CAAA;IACzB,gDAAyB,CAAA;IACzB,kDAA2B,CAAA;IAE3B,uDAAuD;IACvD,sDAA+B,CAAA;IAC/B,qDAA8B,CAAA;IAC9B,qDAA8B,CAAA;IAC9B,0DAAmC,CAAA;IAEnC,4BAA4B;IAC5B,6CAAsB,CAAA;IACtB,2CAAoB,CAAA;IACpB,mDAA4B,CAAA;IAC5B,yDAAkC,CAAA;IAClC,+DAAwC,CAAA;IACxC,+DAAwC,CAAA;IACxC,0DAAmC,CAAA;IACnC,8DAAuC,CAAA;IACvC,8DAAuC,CAAA;IACvC,yDAAkC,CAAA;IAClC,mEAA4C,CAAA;IAC5C,oEAA6C,CAAA;IAC7C,oEAA6C,CAAA;IAC7C,2CAAoB,CAAA;IACpB,4CAAqB,CAAA;IACrB,oEAA6C,CAAA;IAC7C,wDAAiC,CAAA;IACjC,+CAAwB,CAAA;AAC1B,CAAC,EApCI,mBAAmB,KAAnB,mBAAmB,QAoCvB;AAED,kBAAe,mBAAmB,CAAC"} \ No newline at end of file diff --git a/lib/types/VerifiedCourseAuth.d.ts b/lib/types/VerifiedCourseAuth.d.ts new file mode 100644 index 0000000..c8e562b --- /dev/null +++ b/lib/types/VerifiedCourseAuth.d.ts @@ -0,0 +1,46 @@ +/** + * An already-verified, per-request course authorization. + * + * This lets a consumer app tell genRouteHandler which course (and which roles) + * a single request is really about, independently of the one course the shared + * CACCL session happened to launch. That is what makes it possible to have + * multiple browser tabs open on *different* courses at the same time: each + * request carries its own verified course context instead of relying on the + * single, shared session launch. + * + * PHASE 0 TRUST CONTRACT (interim): + * Right now the library trusts this value exactly as the app sets it, so the + * consumer app is responsible for cryptographically verifying it before + * attaching it to the request (for example, by validating a token that this + * same server signed at a Canvas-verified launch, and confirming that the + * token's user matches the current session user). + * + * A later release will move that verification into the library itself + * (minting and verifying a signed "course context" token). At that point this + * same field becomes an output the library populates, rather than an input the + * app supplies — the shape below does not change. + * + * @author Karen Dolan + */ +type VerifiedCourseAuth = { + courseId: number; + courseName?: string; + isLearner: boolean; + isTTM: boolean; + isAdmin: boolean; +}; +/** + * Augment the Express Request type so consumer apps get type safety when they + * attach a verified course auth (for example, from their own auth middleware). + * Inside the library, req is treated as `any`, so this augmentation exists purely + * for the benefit of consumers of the package. + * @author Karen Dolan + */ +declare global { + namespace Express { + interface Request { + verifiedCourseAuth?: VerifiedCourseAuth; + } + } +} +export default VerifiedCourseAuth; diff --git a/lib/types/VerifiedCourseAuth.js b/lib/types/VerifiedCourseAuth.js new file mode 100644 index 0000000..c20d2d8 --- /dev/null +++ b/lib/types/VerifiedCourseAuth.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=VerifiedCourseAuth.js.map \ No newline at end of file diff --git a/lib/types/VerifiedCourseAuth.js.map b/lib/types/VerifiedCourseAuth.js.map new file mode 100644 index 0000000..bebb9ed --- /dev/null +++ b/lib/types/VerifiedCourseAuth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"VerifiedCourseAuth.js","sourceRoot":"","sources":["../../src/types/VerifiedCourseAuth.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/src/constants/COURSE_CONTEXT_HEADER.ts b/src/constants/COURSE_CONTEXT_HEADER.ts new file mode 100644 index 0000000..f61bbd6 --- /dev/null +++ b/src/constants/COURSE_CONTEXT_HEADER.ts @@ -0,0 +1,16 @@ +/** + * Name of the HTTP header that carries a signed course-context token. + * + * The client (for example, dce-reactkit's visitServerEndpoint) sends the token + * in this header, and genRouteHandler reads it to verify the per-request course + * context. Kept in a shared constant so the client and server never drift apart + * on the exact header name. + * + * Note: Express lower-cases incoming header names, so read it as + * req.headers['x-course-context']. + * + * @author Karen Dolan + */ +const COURSE_CONTEXT_HEADER = 'X-Course-Context'; + +export default COURSE_CONTEXT_HEADER; diff --git a/src/helpers/addCourseContextEndpoint.ts b/src/helpers/addCourseContextEndpoint.ts new file mode 100644 index 0000000..6f59f74 --- /dev/null +++ b/src/helpers/addCourseContextEndpoint.ts @@ -0,0 +1,84 @@ +// Import express +import express from 'express'; + +// Import shared helpers +import genRouteHandler from './genRouteHandler'; +import { genCourseContext } from './courseContext'; + +// Import shared constants +import COURSE_CONTEXT_HEADER from '../constants/COURSE_CONTEXT_HEADER'; + +/** + * Add an endpoint that mints a signed course-context token for the caller's + * current, Canvas-verified launch. + * + * This is the bootstrap for per-tab course contexts: the client calls this + * endpoint once right after a launch, stores the returned token per browser tab, + * and then sends it back on every subsequent request in the COURSE_CONTEXT_HEADER + * header (genRouteHandler verifies it). Because the token is derived from the + * session launch, this endpoint runs behind the normal session check — a user + * can only mint a context for a course they actually launched. + * + * The endpoint responds with the token as a plain string (wrapped in the + * standard success envelope), so the client can read it directly. + * + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.app the express app to add the endpoint to + * @param [opts.path=/api/course-context] the path to mount the endpoint at. + * Keep it under /api (all authorized users) — never /api/ttm or /api/admin, + * because every launched user legitimately needs to mint their own context. + * @param [opts.ttlMs] how long each minted token should remain valid, in ms + * (defaults to the library default inside genCourseContext) + * @example + * // Server: wire the endpoint once while setting up routes + * import express from 'express'; + * import { addCourseContextEndpoint } from 'dce-expresskit'; + * + * const app = express(); + * addCourseContextEndpoint({ app }); + * // → GET /api/course-context now returns a signed token for the launch + */ +const addCourseContextEndpoint = ( + opts: { + app: express.Application, + path?: string, + ttlMs?: number, + }, +) => { + // Destructure opts with a sensible default path + const { + app, + path = '/api/course-context', + ttlMs, + } = opts; + + /** + * Mint a course-context token for the current launch + * @author Karen Dolan + * @returns a signed course-context token string + */ + app.get( + path, + genRouteHandler({ + handler: async ( + handlerOpts: { + req: any, + }, + ) => { + // genRouteHandler has already enforced a valid session/launch, so the + // request is safe to mint a token from. + return genCourseContext({ + req: handlerOpts.req, + ttlMs, + }); + }, + }), + ); +}; + +// Re-export the header name here too, so a consumer wiring up this endpoint has +// the client-side header contract close at hand. +export { COURSE_CONTEXT_HEADER }; + +export default addCourseContextEndpoint; diff --git a/src/helpers/courseContext.ts b/src/helpers/courseContext.ts new file mode 100644 index 0000000..f187b48 --- /dev/null +++ b/src/helpers/courseContext.ts @@ -0,0 +1,242 @@ +// Import dce-commonkit +import { + ErrorWithCode, + HOUR_IN_MS, +} from 'dce-commonkit'; + +// Import caccl +import { getLaunchInfo } from 'caccl/server'; + +// Import node libs +import crypto from 'crypto'; + +// Import shared types +import ExpressKitErrorCode from '../types/ExpressKitErrorCode'; +import CourseContextTokenPayload from '../types/CourseContextTokenPayload'; +import VerifiedCourseAuth from '../types/VerifiedCourseAuth'; + +/*------------------------------------------------------------------------*/ +/* ------------------------------ Constants ----------------------------- */ +/*------------------------------------------------------------------------*/ + +// How long a freshly minted course-context token is valid for, in ms. +// Tokens are short-lived: the client re-mints when one expires. Kept modest so +// a stolen token has a small window, but long enough to cover a work session. +const DEFAULT_COURSE_CONTEXT_TTL_MS = HOUR_IN_MS; + +/*------------------------------------------------------------------------*/ +/* ------------------------------- Helpers ------------------------------ */ +/*------------------------------------------------------------------------*/ + +/** + * Read the server's course-context signing secret from the environment. + * This secret never leaves the server: it signs tokens on mint and verifies + * them on each request, so the same server is the only party that can issue a + * token the same server will trust. + * @author Karen Dolan + * @returns the signing secret + */ +const getCourseContextSecret = (): string => { + const { DCEKIT_COURSE_CONTEXT_SECRET } = process.env; + if (!DCEKIT_COURSE_CONTEXT_SECRET) { + throw new ErrorWithCode( + 'We could not process the course context for this request because the server is missing its course-context signing secret. Please contact support.', + ExpressKitErrorCode.CourseContextNoSecret, + ); + } + return DCEKIT_COURSE_CONTEXT_SECRET; +}; + +/** + * Compute the base64url HMAC-SHA256 signature of an encoded payload. + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.encodedPayload the base64url-encoded payload to sign + * @param opts.secret the signing secret + * @returns the base64url signature + */ +const signEncodedPayload = ( + opts: { + encodedPayload: string, + secret: string, + }, +): string => { + return ( + crypto + .createHmac('sha256', opts.secret) + .update(opts.encodedPayload) + .digest('base64url') + ); +}; + +/*------------------------------------------------------------------------*/ +/* -------------------------------- Mint -------------------------------- */ +/*------------------------------------------------------------------------*/ + +/** + * Mint a signed course-context token for the caller's current, Canvas-verified + * launch. The consumer app calls this (typically from a small endpoint) right + * after a launch and hands the token to the client, which stores it per browser + * tab and sends it back on each request (see COURSE_CONTEXT_HEADER). + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.req the express request (must have a valid CACCL launch) + * @param [opts.ttlMs=1 hour] how long the token should remain valid, in ms + * @returns a signed course-context token string + */ +const genCourseContext = ( + opts: { + req: any, + ttlMs?: number, + }, +): string => { + // Read the launch info: the launch is our root of trust for who the user is + // and which course + roles they actually have right now. + const { + launched, + launchInfo, + } = getLaunchInfo(opts.req); + if (!launched || !launchInfo) { + throw new ErrorWithCode( + 'We could not create a course context because your session has expired. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextInvalid, + ); + } + + // Build the token payload from the verified launch + const now = Date.now(); + const payload: CourseContextTokenPayload = { + courseId: launchInfo.courseId, + courseName: launchInfo.contextLabel, + userId: launchInfo.userId, + isLearner: !!launchInfo.isLearner, + isTTM: !!launchInfo.isTTM, + isAdmin: !!launchInfo.isAdmin, + iat: now, + exp: now + (opts.ttlMs ?? DEFAULT_COURSE_CONTEXT_TTL_MS), + }; + + // Encode and sign + const encodedPayload = ( + Buffer + .from(JSON.stringify(payload), 'utf8') + .toString('base64url') + ); + const secret = getCourseContextSecret(); + const signature = signEncodedPayload({ + encodedPayload, + secret, + }); + + // Token is "." + return `${encodedPayload}.${signature}`; +}; + +/*------------------------------------------------------------------------*/ +/* ------------------------------- Verify ------------------------------- */ +/*------------------------------------------------------------------------*/ + +/** + * Verify a signed course-context token and return the course + roles it proves. + * Throws an ErrorWithCode if the token is malformed, has a bad signature, has + * expired, or was minted for a different user than the current session user. + * + * genRouteHandler calls this internally, so most consumers never need to. It is + * exported for apps that build their own middleware. + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.token the raw token string from the request header + * @param [opts.expectedUserId] if provided, the token's user must match this + * (bind the token to the logged-in session user to prevent replay by others) + * @returns the verified course authorization + */ +const verifyCourseContextToken = ( + opts: { + token: string, + expectedUserId?: number, + }, +): VerifiedCourseAuth => { + const secret = getCourseContextSecret(); + + // Split into payload + signature + const parts = opts.token.split('.'); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new ErrorWithCode( + 'We could not verify your course context because the token was malformed. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextInvalid, + ); + } + const [ + encodedPayload, + signature, + ] = parts; + + // Verify the signature with a constant-time comparison + const expectedSignature = signEncodedPayload({ + encodedPayload, + secret, + }); + const signatureBuffer = Buffer.from(signature); + const expectedBuffer = Buffer.from(expectedSignature); + if ( + signatureBuffer.length !== expectedBuffer.length + || !crypto.timingSafeEqual(signatureBuffer, expectedBuffer) + ) { + throw new ErrorWithCode( + 'We could not verify your course context because its signature was invalid. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextInvalid, + ); + } + + // Decode the payload (signature already proves it was not tampered with) + let payload: CourseContextTokenPayload; + try { + const decoded = ( + Buffer + .from(encodedPayload, 'base64url') + .toString('utf8') + ); + payload = JSON.parse(decoded); + } catch (err) { + throw new ErrorWithCode( + 'We could not verify your course context because its contents could not be read. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextInvalid, + ); + } + + // Reject expired tokens + if ( + typeof payload.exp !== 'number' + || Date.now() > payload.exp + ) { + throw new ErrorWithCode( + 'Your course context has expired. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextExpired, + ); + } + + // Bind the token to the current session user, if one was provided + if ( + opts.expectedUserId !== undefined + && payload.userId !== opts.expectedUserId + ) { + throw new ErrorWithCode( + 'We could not verify your course context because it does not match the signed-in user. Please refresh the page and try again.', + ExpressKitErrorCode.CourseContextUserMismatch, + ); + } + + // Hand back only the narrow, verified course authorization + return { + courseId: payload.courseId, + courseName: payload.courseName, + isLearner: !!payload.isLearner, + isTTM: !!payload.isTTM, + isAdmin: !!payload.isAdmin, + }; +}; + +export { + genCourseContext, + verifyCourseContextToken, +}; diff --git a/src/helpers/genRouteHandler.ts b/src/helpers/genRouteHandler.ts index ec32b8f..83e8285 100644 --- a/src/helpers/genRouteHandler.ts +++ b/src/helpers/genRouteHandler.ts @@ -23,6 +23,10 @@ import initExpressKitCollections, { internalGetLogCollection, internalGetSelectA // Import shared types import ExpressKitErrorCode from '../types/ExpressKitErrorCode'; +import VerifiedCourseAuth from '../types/VerifiedCourseAuth'; + +// Import shared constants +import COURSE_CONTEXT_HEADER from '../constants/COURSE_CONTEXT_HEADER'; // Import helpers import handleError from './handleError'; @@ -31,6 +35,7 @@ import genErrorPage from '../html/genErrorPage'; import genInfoPage from '../html/genInfoPage'; import parseUserAgent from './parseUserAgent'; import { validateSignedRequest } from './dataSigner'; +import { verifyCourseContextToken } from './courseContext'; /** * Generate an express API route handler @@ -516,13 +521,82 @@ const genRouteHandler = ( } }); + /*----------------------------------------*/ + /* -------- Verified Course Auth -------- */ + /*----------------------------------------*/ + + // A consumer app may attach an already-verified, per-request course + // authorization to req.verifiedCourseAuth. This supports having multiple + // browser tabs open on different courses at once: the single shared CACCL + // session can only represent one launched course, but each request can carry + // its own verified course context. + // + // When present, we trust it over the shared session launch for this + // request's course + role fields. When absent, every branch below behaves + // exactly as it did before, so this is fully backward compatible. + // + // There are two ways this field gets populated: + // 1. Library-owned (preferred): the client sends a signed course-context + // token in the COURSE_CONTEXT_HEADER header. We verify it here (see + // below) and populate req.verifiedCourseAuth ourselves, so the app + // writes no crypto and the library owns the trust boundary. + // 2. App-set (interim): the app verifies something itself and sets + // req.verifiedCourseAuth before this handler runs. Still honored for + // backward compatibility during migration onto the token path. + + // Library-owned path: verify a signed course-context token, if one was sent + // and the app has not already attached a verified auth. We require a launch + // so we can bind the token to the current session user. + if (!req.verifiedCourseAuth && launchInfo) { + const courseContextToken = req.headers?.[COURSE_CONTEXT_HEADER.toLowerCase()]; + if (typeof courseContextToken === 'string' && courseContextToken.length > 0) { + try { + req.verifiedCourseAuth = verifyCourseContextToken({ + token: courseContextToken, + expectedUserId: launchInfo.userId, + }); + } catch (err) { + // A present-but-invalid token is an auth failure (401), not a 500 + return handleError( + res, + { + message: (err as any).message, + code: (err as any).code, + status: 401, + }, + ); + } + } + } + + // PHASE 0 TRUST CONTRACT (app-set path only): when the app sets this field + // directly, the app is responsible for verifying it first (see + // VerifiedCourseAuth for details). The library-owned token path above does + // this verification for you. + const verifiedCourseAuth: VerifiedCourseAuth | undefined = ( + req.verifiedCourseAuth + ); + if (verifiedCourseAuth) { + output.courseId = verifiedCourseAuth.courseId; + output.isLearner = verifiedCourseAuth.isLearner; + output.isTTM = verifiedCourseAuth.isTTM; + output.isAdmin = verifiedCourseAuth.isAdmin; + // Only override the course name if the verified auth carries one + if (verifiedCourseAuth.courseName !== undefined) { + output.courseName = verifiedCourseAuth.courseName; + } + } + /*----------------------------------------*/ /* ----- Require Course Consistency ----- */ /*----------------------------------------*/ - // Make sure the user actually launched from the appropriate course + // Make sure the user actually launched from the appropriate course. + // If a verified per-request course auth is present, it already proves which + // course this request is about, so we skip this session-based check. if ( - output.courseId + !verifiedCourseAuth + && output.courseId && launchInfo && launchInfo.courseId && output.courseId !== launchInfo.courseId @@ -643,6 +717,37 @@ const genRouteHandler = ( minute, } = getTimeInfoInET(); + // Determine the course + role values to log. When a verified per-request + // course auth is present, it (not the shared session launch) reflects the + // course and roles this request actually acted on, so we log those for an + // accurate audit trail. The course name is logged from the verified auth + // when it carries one, otherwise it falls back to the session launch. + const logIsLearner = !!( + verifiedCourseAuth + ? verifiedCourseAuth.isLearner + : (launchInfo && launchInfo.isLearner) + ); + const logIsTTM = !!( + verifiedCourseAuth + ? verifiedCourseAuth.isTTM + : (launchInfo && launchInfo.isTTM) + ); + const logIsAdmin = !!( + verifiedCourseAuth + ? verifiedCourseAuth.isAdmin + : (launchInfo && launchInfo.isAdmin) + ); + const logCourseId = ( + verifiedCourseAuth + ? verifiedCourseAuth.courseId + : (launchInfo ? launchInfo.courseId : -1) + ); + const logCourseName = ( + (verifiedCourseAuth && verifiedCourseAuth.courseName !== undefined) + ? verifiedCourseAuth.courseName + : (launchInfo ? launchInfo.contextLabel : 'unknown') + ); + // Main log info const mainLogInfo: LogMainInfo = { id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`, @@ -650,11 +755,11 @@ const genRouteHandler = ( userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'), userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'), userId: (launchInfo ? launchInfo.userId : -1), - isLearner: (launchInfo && !!launchInfo.isLearner), - isAdmin: (launchInfo && !!launchInfo.isAdmin), - isTTM: (launchInfo && !!launchInfo.isTTM), - courseId: (launchInfo ? launchInfo.courseId : -1), - courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'), + isLearner: logIsLearner, + isAdmin: logIsAdmin, + isTTM: logIsTTM, + courseId: logCourseId, + courseName: logCourseName, browser, device, year, diff --git a/src/index.ts b/src/index.ts index f0575c2..908a2c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,9 +58,16 @@ import handleSuccess from './helpers/handleSuccess'; import addDBEditorEndpoints from './helpers/addDBEditorEndpoints'; import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer'; import initExpressKitCollections, { getLogCollection } from './helpers/initExpressKitCollections'; +import { genCourseContext, verifyCourseContextToken } from './helpers/courseContext'; +import addCourseContextEndpoint from './helpers/addCourseContextEndpoint'; + +// Import constants +import COURSE_CONTEXT_HEADER from './constants/COURSE_CONTEXT_HEADER'; // Import types import CrossServerCredential from './types/CrossServerCredential'; +import VerifiedCourseAuth from './types/VerifiedCourseAuth'; +import CourseContextTokenPayload from './types/CourseContextTokenPayload'; // Export each item export { @@ -115,6 +122,11 @@ export { getLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, + // Course context (per-tab course authorization) + genCourseContext, + verifyCourseContextToken, + addCourseContextEndpoint, + COURSE_CONTEXT_HEADER, // Types DayOfWeek, Log, @@ -125,6 +137,8 @@ export { LogMetadataType, LogFunction, CrossServerCredential, + VerifiedCourseAuth, + CourseContextTokenPayload, // Server types ParamType, }; diff --git a/src/types/CourseContextTokenPayload.ts b/src/types/CourseContextTokenPayload.ts new file mode 100644 index 0000000..abe12f8 --- /dev/null +++ b/src/types/CourseContextTokenPayload.ts @@ -0,0 +1,35 @@ +/** + * The decoded payload of a signed course-context token. + * + * A course-context token is a small, self-signed (HMAC) assertion that this + * server minted at a Canvas-verified launch. It captures which course a user + * launched, who they are, and what roles they hold in that course, so that a + * single request can prove its own course context independently of the one + * course the shared CACCL session happens to represent. + * + * This is the internal, over-the-wire shape. The verified result handed to + * route handlers is the narrower VerifiedCourseAuth (no user id, no timestamps). + * + * @author Karen Dolan + */ +type CourseContextTokenPayload = { + // The Canvas ID of the course this token authorizes + courseId: number; + // Human-readable name of the course (Canvas context label) + courseName: string; + // The Canvas user ID this token was minted for. Verified against the current + // session user so a token cannot be replayed by a different user. + userId: number; + // True if the user is a learner in this course + isLearner: boolean; + // True if the user is a teaching team member in this course + isTTM: boolean; + // True if the user is an admin in this course + isAdmin: boolean; + // Issued-at time (ms since epoch) + iat: number; + // Expiry time (ms since epoch); the token is rejected after this moment + exp: number; +}; + +export default CourseContextTokenPayload; diff --git a/src/types/ExpressKitErrorCode.ts b/src/types/ExpressKitErrorCode.ts index 30804f8..e703ba1 100644 --- a/src/types/ExpressKitErrorCode.ts +++ b/src/types/ExpressKitErrorCode.ts @@ -1,4 +1,4 @@ -// Highest error code = DEK36 +// Highest error code = DEK40 /** * List of error codes built into the express kit @@ -15,6 +15,12 @@ enum ExpressKitErrorCode { MissingParameter = 'DEK4', StudentIdMismatch = 'DEK36', + // Course context tokens (per-tab course authorization) + CourseContextNoSecret = 'DEK37', + CourseContextInvalid = 'DEK38', + CourseContextExpired = 'DEK39', + CourseContextUserMismatch = 'DEK40', + // Server-to-server requests NotConnected = 'DEK14', SelfSigned = 'DEK15', diff --git a/src/types/VerifiedCourseAuth.ts b/src/types/VerifiedCourseAuth.ts new file mode 100644 index 0000000..f982d3f --- /dev/null +++ b/src/types/VerifiedCourseAuth.ts @@ -0,0 +1,58 @@ +/** + * An already-verified, per-request course authorization. + * + * This lets a consumer app tell genRouteHandler which course (and which roles) + * a single request is really about, independently of the one course the shared + * CACCL session happened to launch. That is what makes it possible to have + * multiple browser tabs open on *different* courses at the same time: each + * request carries its own verified course context instead of relying on the + * single, shared session launch. + * + * PHASE 0 TRUST CONTRACT (interim): + * Right now the library trusts this value exactly as the app sets it, so the + * consumer app is responsible for cryptographically verifying it before + * attaching it to the request (for example, by validating a token that this + * same server signed at a Canvas-verified launch, and confirming that the + * token's user matches the current session user). + * + * A later release will move that verification into the library itself + * (minting and verifying a signed "course context" token). At that point this + * same field becomes an output the library populates, rather than an input the + * app supplies — the shape below does not change. + * + * @author Karen Dolan + */ +type VerifiedCourseAuth = { + // The Canvas ID of the verified course this request is about + courseId: number; + // Human-readable name of the verified course, if known. When present, it is + // used for the request's audit log so the log reflects the verified course + // (not the shared session launch). Optional because a caller may only have + // verified the course ID and roles. + courseName?: string; + // True if the user is a learner in the verified course + isLearner: boolean; + // True if the user is a teaching team member in the verified course + isTTM: boolean; + // True if the user is an admin in the verified course + isAdmin: boolean; +}; + +/** + * Augment the Express Request type so consumer apps get type safety when they + * attach a verified course auth (for example, from their own auth middleware). + * Inside the library, req is treated as `any`, so this augmentation exists purely + * for the benefit of consumers of the package. + * @author Karen Dolan + */ +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + // An already-verified, per-request course authorization (see above) + verifiedCourseAuth?: VerifiedCourseAuth; + } + } +} + +export default VerifiedCourseAuth; From 8fa1db3ac5fb3c5f8495792d7d61262a094c8cd1 Mon Sep 17 00:00:00 2001 From: Karen Dolan Date: Thu, 23 Jul 2026 08:57:19 -0400 Subject: [PATCH 2/2] Build lib for the helper endpoint for client to create a Course Context token for the current session --- lib/helpers/addCourseContextEndpoint.d.ts | 40 ++++++++ lib/helpers/addCourseContextEndpoint.js | 102 ++++++++++++++++++++ lib/helpers/addCourseContextEndpoint.js.map | 1 + lib/index.d.ts | 3 +- lib/index.js | 6 +- lib/index.js.map | 2 +- 6 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 lib/helpers/addCourseContextEndpoint.d.ts create mode 100644 lib/helpers/addCourseContextEndpoint.js create mode 100644 lib/helpers/addCourseContextEndpoint.js.map diff --git a/lib/helpers/addCourseContextEndpoint.d.ts b/lib/helpers/addCourseContextEndpoint.d.ts new file mode 100644 index 0000000..4250b60 --- /dev/null +++ b/lib/helpers/addCourseContextEndpoint.d.ts @@ -0,0 +1,40 @@ +import express from 'express'; +import COURSE_CONTEXT_HEADER from '../constants/COURSE_CONTEXT_HEADER'; +/** + * Add an endpoint that mints a signed course-context token for the caller's + * current, Canvas-verified launch. + * + * This is the bootstrap for per-tab course contexts: the client calls this + * endpoint once right after a launch, stores the returned token per browser tab, + * and then sends it back on every subsequent request in the COURSE_CONTEXT_HEADER + * header (genRouteHandler verifies it). Because the token is derived from the + * session launch, this endpoint runs behind the normal session check — a user + * can only mint a context for a course they actually launched. + * + * The endpoint responds with the token as a plain string (wrapped in the + * standard success envelope), so the client can read it directly. + * + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.app the express app to add the endpoint to + * @param [opts.path=/api/course-context] the path to mount the endpoint at. + * Keep it under /api (all authorized users) — never /api/ttm or /api/admin, + * because every launched user legitimately needs to mint their own context. + * @param [opts.ttlMs] how long each minted token should remain valid, in ms + * (defaults to the library default inside genCourseContext) + * @example + * // Server: wire the endpoint once while setting up routes + * import express from 'express'; + * import { addCourseContextEndpoint } from 'dce-expresskit'; + * + * const app = express(); + * addCourseContextEndpoint({ app }); + * // → GET /api/course-context now returns a signed token for the launch + */ +declare const addCourseContextEndpoint: (opts: { + app: express.Application; + path?: string; + ttlMs?: number; +}) => void; +export { COURSE_CONTEXT_HEADER }; +export default addCourseContextEndpoint; diff --git a/lib/helpers/addCourseContextEndpoint.js b/lib/helpers/addCourseContextEndpoint.js new file mode 100644 index 0000000..b7b6ec5 --- /dev/null +++ b/lib/helpers/addCourseContextEndpoint.js @@ -0,0 +1,102 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.COURSE_CONTEXT_HEADER = void 0; +// Import shared helpers +var genRouteHandler_1 = __importDefault(require("./genRouteHandler")); +var courseContext_1 = require("./courseContext"); +// Import shared constants +var COURSE_CONTEXT_HEADER_1 = __importDefault(require("../constants/COURSE_CONTEXT_HEADER")); +exports.COURSE_CONTEXT_HEADER = COURSE_CONTEXT_HEADER_1.default; +/** + * Add an endpoint that mints a signed course-context token for the caller's + * current, Canvas-verified launch. + * + * This is the bootstrap for per-tab course contexts: the client calls this + * endpoint once right after a launch, stores the returned token per browser tab, + * and then sends it back on every subsequent request in the COURSE_CONTEXT_HEADER + * header (genRouteHandler verifies it). Because the token is derived from the + * session launch, this endpoint runs behind the normal session check — a user + * can only mint a context for a course they actually launched. + * + * The endpoint responds with the token as a plain string (wrapped in the + * standard success envelope), so the client can read it directly. + * + * @author Karen Dolan + * @param opts object containing all arguments + * @param opts.app the express app to add the endpoint to + * @param [opts.path=/api/course-context] the path to mount the endpoint at. + * Keep it under /api (all authorized users) — never /api/ttm or /api/admin, + * because every launched user legitimately needs to mint their own context. + * @param [opts.ttlMs] how long each minted token should remain valid, in ms + * (defaults to the library default inside genCourseContext) + * @example + * // Server: wire the endpoint once while setting up routes + * import express from 'express'; + * import { addCourseContextEndpoint } from 'dce-expresskit'; + * + * const app = express(); + * addCourseContextEndpoint({ app }); + * // → GET /api/course-context now returns a signed token for the launch + */ +var addCourseContextEndpoint = function (opts) { + // Destructure opts with a sensible default path + var app = opts.app, _a = opts.path, path = _a === void 0 ? '/api/course-context' : _a, ttlMs = opts.ttlMs; + /** + * Mint a course-context token for the current launch + * @author Karen Dolan + * @returns a signed course-context token string + */ + app.get(path, (0, genRouteHandler_1.default)({ + handler: function (handlerOpts) { return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + // genRouteHandler has already enforced a valid session/launch, so the + // request is safe to mint a token from. + return [2 /*return*/, (0, courseContext_1.genCourseContext)({ + req: handlerOpts.req, + ttlMs: ttlMs, + })]; + }); + }); }, + })); +}; +exports.default = addCourseContextEndpoint; +//# sourceMappingURL=addCourseContextEndpoint.js.map \ No newline at end of file diff --git a/lib/helpers/addCourseContextEndpoint.js.map b/lib/helpers/addCourseContextEndpoint.js.map new file mode 100644 index 0000000..1e46aeb --- /dev/null +++ b/lib/helpers/addCourseContextEndpoint.js.map @@ -0,0 +1 @@ +{"version":3,"file":"addCourseContextEndpoint.js","sourceRoot":"","sources":["../../src/helpers/addCourseContextEndpoint.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,wBAAwB;AACxB,sEAAgD;AAChD,iDAAmD;AAEnD,0BAA0B;AAC1B,6FAAuE;AAyE9D,gCAzEF,+BAAqB,CAyEE;AAvE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,IAAM,wBAAwB,GAAG,UAC/B,IAIC;IAED,gDAAgD;IAE9C,IAAA,GAAG,GAGD,IAAI,IAHH,EACH,KAEE,IAAI,KAFsB,EAA5B,IAAI,mBAAG,qBAAqB,KAAA,EAC5B,KAAK,GACH,IAAI,MADD,CACE;IAET;;;;OAIG;IACH,GAAG,CAAC,GAAG,CACL,IAAI,EACJ,IAAA,yBAAe,EAAC;QACd,OAAO,EAAE,UACP,WAEC;;gBAED,sEAAsE;gBACtE,wCAAwC;gBACxC,sBAAO,IAAA,gCAAgB,EAAC;wBACtB,GAAG,EAAE,WAAW,CAAC,GAAG;wBACpB,KAAK,OAAA;qBACN,CAAC,EAAC;;aACJ;KACF,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAMF,kBAAe,wBAAwB,CAAC"} \ No newline at end of file diff --git a/lib/index.d.ts b/lib/index.d.ts index 66cc18a..0cc1ba0 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -7,8 +7,9 @@ import addDBEditorEndpoints from './helpers/addDBEditorEndpoints'; import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer'; import initExpressKitCollections, { getLogCollection } from './helpers/initExpressKitCollections'; import { genCourseContext, verifyCourseContextToken } from './helpers/courseContext'; +import addCourseContextEndpoint from './helpers/addCourseContextEndpoint'; import COURSE_CONTEXT_HEADER from './constants/COURSE_CONTEXT_HEADER'; import CrossServerCredential from './types/CrossServerCredential'; import VerifiedCourseAuth from './types/VerifiedCourseAuth'; import CourseContextTokenPayload from './types/CourseContextTokenPayload'; -export { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, getTimestampFromTimeInfoInET, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, genCommaList, getLocalTimeInfo, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, spaceAtCapitals, initServer, genRouteHandler, handleError, handleSuccess, initExpressKitCollections, getLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, genCourseContext, verifyCourseContextToken, COURSE_CONTEXT_HEADER, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, CrossServerCredential, VerifiedCourseAuth, CourseContextTokenPayload, ParamType, }; +export { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, getTimestampFromTimeInfoInET, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, genCommaList, getLocalTimeInfo, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, spaceAtCapitals, initServer, genRouteHandler, handleError, handleSuccess, initExpressKitCollections, getLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, genCourseContext, verifyCourseContextToken, addCourseContextEndpoint, COURSE_CONTEXT_HEADER, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, CrossServerCredential, VerifiedCourseAuth, CourseContextTokenPayload, ParamType, }; diff --git a/lib/index.js b/lib/index.js index 12d9586..538f209 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,8 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.COURSE_CONTEXT_HEADER = exports.verifyCourseContextToken = exports.genCourseContext = exports.visitEndpointOnAnotherServer = exports.addDBEditorEndpoints = exports.getLogCollection = exports.initExpressKitCollections = exports.handleSuccess = exports.handleError = exports.genRouteHandler = exports.initServer = exports.spaceAtCapitals = exports.shuffleArray = exports.capitalize = exports.someAsync = exports.mapAsync = exports.forEachAsync = exports.filterAsync = exports.everyAsync = exports.prefixWithAOrAn = exports.getLocalTimeInfo = exports.genCommaList = exports.compareArraysByProp = exports.extractProp = exports.genCSV = exports.getMonthName = exports.parallelLimit = exports.onlyKeepLetters = exports.stringsToHumanReadableList = exports.getPartOfDay = exports.getHumanReadableDate = exports.startMinWait = exports.getTimestampFromTimeInfoInET = exports.getMondayOfTimestamp = exports.getTimeInfoInET = exports.getOrdinal = exports.waitMs = exports.sum = exports.roundToNumDecimals = exports.padZerosLeft = exports.padDecimalZeros = exports.forceNumIntoBounds = exports.floorToNumDecimals = exports.ceilToNumDecimals = exports.avg = exports.abbreviate = exports.DAY_IN_MS = exports.HOUR_IN_MS = exports.MINUTE_IN_MS = exports.ErrorWithCode = void 0; -exports.ParamType = exports.LogBuiltInMetadata = exports.LogAction = exports.LogSource = exports.LogType = exports.DayOfWeek = void 0; +exports.addCourseContextEndpoint = exports.verifyCourseContextToken = exports.genCourseContext = exports.visitEndpointOnAnotherServer = exports.addDBEditorEndpoints = exports.getLogCollection = exports.initExpressKitCollections = exports.handleSuccess = exports.handleError = exports.genRouteHandler = exports.initServer = exports.spaceAtCapitals = exports.shuffleArray = exports.capitalize = exports.someAsync = exports.mapAsync = exports.forEachAsync = exports.filterAsync = exports.everyAsync = exports.prefixWithAOrAn = exports.getLocalTimeInfo = exports.genCommaList = exports.compareArraysByProp = exports.extractProp = exports.genCSV = exports.getMonthName = exports.parallelLimit = exports.onlyKeepLetters = exports.stringsToHumanReadableList = exports.getPartOfDay = exports.getHumanReadableDate = exports.startMinWait = exports.getTimestampFromTimeInfoInET = exports.getMondayOfTimestamp = exports.getTimeInfoInET = exports.getOrdinal = exports.waitMs = exports.sum = exports.roundToNumDecimals = exports.padZerosLeft = exports.padDecimalZeros = exports.forceNumIntoBounds = exports.floorToNumDecimals = exports.ceilToNumDecimals = exports.avg = exports.abbreviate = exports.DAY_IN_MS = exports.HOUR_IN_MS = exports.MINUTE_IN_MS = exports.ErrorWithCode = void 0; +exports.ParamType = exports.LogBuiltInMetadata = exports.LogAction = exports.LogSource = exports.LogType = exports.DayOfWeek = exports.COURSE_CONTEXT_HEADER = void 0; // Import dce-commonkit var dce_commonkit_1 = require("dce-commonkit"); Object.defineProperty(exports, "abbreviate", { enumerable: true, get: function () { return dce_commonkit_1.abbreviate; } }); @@ -104,6 +104,8 @@ Object.defineProperty(exports, "getLogCollection", { enumerable: true, get: func var courseContext_1 = require("./helpers/courseContext"); Object.defineProperty(exports, "genCourseContext", { enumerable: true, get: function () { return courseContext_1.genCourseContext; } }); Object.defineProperty(exports, "verifyCourseContextToken", { enumerable: true, get: function () { return courseContext_1.verifyCourseContextToken; } }); +var addCourseContextEndpoint_1 = __importDefault(require("./helpers/addCourseContextEndpoint")); +exports.addCourseContextEndpoint = addCourseContextEndpoint_1.default; // Import constants var COURSE_CONTEXT_HEADER_1 = __importDefault(require("./constants/COURSE_CONTEXT_HEADER")); exports.COURSE_CONTEXT_HEADER = COURSE_CONTEXT_HEADER_1.default; diff --git a/lib/index.js.map b/lib/index.js.map index f382b27..961067d 100644 --- a/lib/index.js.map +++ b/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAuB;AACvB,+CAiDuB;AA6BrB,2FA7EA,0BAAU,OA6EA;AACV,oFA7EA,mBAAG,OA6EA;AACH,kGA7EA,iCAAiB,OA6EA;AACjB,mGA7EA,kCAAkB,OA6EA;AAClB,mGA7EA,kCAAkB,OA6EA;AAClB,gGA7EA,+BAAe,OA6EA;AACf,6FA7EA,4BAAY,OA6EA;AACZ,mGA7EA,kCAAkB,OA6EA;AAClB,oFA7EA,mBAAG,OA6EA;AACH,uFA7EA,sBAAM,OA6EA;AACN,2FA7EA,0BAAU,OA6EA;AACV,gGA7EA,+BAAe,OA6EA;AACf,qGA7EA,oCAAoB,OA6EA;AACpB,6GA7EA,4CAA4B,OA6EA;AAC5B,6FA7EA,4BAAY,OA6EA;AACZ,qGA7EA,oCAAoB,OA6EA;AACpB,6FA7EA,4BAAY,OA6EA;AACZ,2GA7EA,0CAA0B,OA6EA;AAC1B,gGA7EA,+BAAe,OA6EA;AACf,8FA7EA,6BAAa,OA6EA;AACb,6FA7EA,4BAAY,OA6EA;AACZ,uFA7EA,sBAAM,OA6EA;AACN,4FA7EA,2BAAW,OA6EA;AACX,oGA7EA,mCAAmB,OA6EA;AAEnB,iGA9EA,gCAAgB,OA8EA;AADhB,6FA5EA,4BAAY,OA4EA;AAEZ,gGA7EA,+BAAe,OA6EA;AACf,2FA7EA,0BAAU,OA6EA;AACV,4FA7EA,2BAAW,OA6EA;AACX,6FA7EA,4BAAY,OA6EA;AACZ,yFA7EA,wBAAQ,OA6EA;AACR,0FA7EA,yBAAS,OA6EA;AACT,2FA7EA,0BAAU,OA6EA;AACV,6FA7EA,4BAAY,OA6EA;AAgBZ,0FA5FA,yBAAS,OA4FA;AAET,wFA5FA,uBAAO,OA4FA;AACP,0FA5FA,yBAAS,OA4FA;AACT,0FA5FA,yBAAS,OA4FA;AACT,mGA5FA,kCAAkB,OA4FA;AA1DlB,6FA/BA,4BAAY,OA+BA;AACZ,2FA/BA,0BAAU,OA+BA;AACV,0FA/BA,yBAAS,OA+BA;AAJT,8FA1BA,6BAAa,OA0BA;AAmEb,0FA5FA,yBAAS,OA4FA;AA3BT,gGAhEA,+BAAe,OAgEA;AA7DjB,iBAAiB;AACjB,oEAA8C;AA8D5C,qBA9DK,oBAAU,CA8DL;AA7DZ,8EAAwD;AA8DtD,0BA9DK,yBAAe,CA8DL;AA7DjB,sEAAgD;AA8D9C,sBA9DK,qBAAW,CA8DL;AA7Db,0EAAoD;AA8DlD,wBA9DK,uBAAa,CA8DL;AA7Df,wFAAkE;AAgEhE,+BAhEK,8BAAoB,CAgEL;AA/DtB,wGAAkF;AAgEhF,uCAhEK,sCAA4B,CAgEL;AA/D9B,+FAAkG;AA4DhG,oCA5DK,mCAAyB,CA4DL;AACzB,iGA7DkC,4CAAgB,OA6DlC;AA5DlB,yDAAqF;AAgEnF,iGAhEO,gCAAgB,OAgEP;AAChB,yGAjEyB,wCAAwB,OAiEzB;AA/D1B,mBAAmB;AACnB,4FAAsE;AA+DpE,gCA/DK,+BAAqB,CA+DL"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAuB;AACvB,+CAiDuB;AA8BrB,2FA9EA,0BAAU,OA8EA;AACV,oFA9EA,mBAAG,OA8EA;AACH,kGA9EA,iCAAiB,OA8EA;AACjB,mGA9EA,kCAAkB,OA8EA;AAClB,mGA9EA,kCAAkB,OA8EA;AAClB,gGA9EA,+BAAe,OA8EA;AACf,6FA9EA,4BAAY,OA8EA;AACZ,mGA9EA,kCAAkB,OA8EA;AAClB,oFA9EA,mBAAG,OA8EA;AACH,uFA9EA,sBAAM,OA8EA;AACN,2FA9EA,0BAAU,OA8EA;AACV,gGA9EA,+BAAe,OA8EA;AACf,qGA9EA,oCAAoB,OA8EA;AACpB,6GA9EA,4CAA4B,OA8EA;AAC5B,6FA9EA,4BAAY,OA8EA;AACZ,qGA9EA,oCAAoB,OA8EA;AACpB,6FA9EA,4BAAY,OA8EA;AACZ,2GA9EA,0CAA0B,OA8EA;AAC1B,gGA9EA,+BAAe,OA8EA;AACf,8FA9EA,6BAAa,OA8EA;AACb,6FA9EA,4BAAY,OA8EA;AACZ,uFA9EA,sBAAM,OA8EA;AACN,4FA9EA,2BAAW,OA8EA;AACX,oGA9EA,mCAAmB,OA8EA;AAEnB,iGA/EA,gCAAgB,OA+EA;AADhB,6FA7EA,4BAAY,OA6EA;AAEZ,gGA9EA,+BAAe,OA8EA;AACf,2FA9EA,0BAAU,OA8EA;AACV,4FA9EA,2BAAW,OA8EA;AACX,6FA9EA,4BAAY,OA8EA;AACZ,yFA9EA,wBAAQ,OA8EA;AACR,0FA9EA,yBAAS,OA8EA;AACT,2FA9EA,0BAAU,OA8EA;AACV,6FA9EA,4BAAY,OA8EA;AAiBZ,0FA9FA,yBAAS,OA8FA;AAET,wFA9FA,uBAAO,OA8FA;AACP,0FA9FA,yBAAS,OA8FA;AACT,0FA9FA,yBAAS,OA8FA;AACT,mGA9FA,kCAAkB,OA8FA;AA3DlB,6FAhCA,4BAAY,OAgCA;AACZ,2FAhCA,0BAAU,OAgCA;AACV,0FAhCA,yBAAS,OAgCA;AAJT,8FA3BA,6BAAa,OA2BA;AAoEb,0FA9FA,yBAAS,OA8FA;AA5BT,gGAjEA,+BAAe,OAiEA;AA9DjB,iBAAiB;AACjB,oEAA8C;AA+D5C,qBA/DK,oBAAU,CA+DL;AA9DZ,8EAAwD;AA+DtD,0BA/DK,yBAAe,CA+DL;AA9DjB,sEAAgD;AA+D9C,sBA/DK,qBAAW,CA+DL;AA9Db,0EAAoD;AA+DlD,wBA/DK,uBAAa,CA+DL;AA9Df,wFAAkE;AAiEhE,+BAjEK,8BAAoB,CAiEL;AAhEtB,wGAAkF;AAiEhF,uCAjEK,sCAA4B,CAiEL;AAhE9B,+FAAkG;AA6DhG,oCA7DK,mCAAyB,CA6DL;AACzB,iGA9DkC,4CAAgB,OA8DlC;AA7DlB,yDAAqF;AAiEnF,iGAjEO,gCAAgB,OAiEP;AAChB,yGAlEyB,wCAAwB,OAkEzB;AAjE1B,gGAA0E;AAkExE,mCAlEK,kCAAwB,CAkEL;AAhE1B,mBAAmB;AACnB,4FAAsE;AAgEpE,gCAhEK,+BAAqB,CAgEL"} \ No newline at end of file