diff --git a/src/routes/internal-sync.route.ts b/src/routes/internal-sync.route.ts index e63a107..97d76d6 100644 --- a/src/routes/internal-sync.route.ts +++ b/src/routes/internal-sync.route.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import { IdToken, Provider as lti } from 'ltijs'; import { Config } from '../config'; -import { LtiLaunchPayload } from '../types'; +import UnitLink from '../schema/unitLink.model'; export const INTERNAL_SYNC_ROUTE_PATH = '/lti/api/internal/test-members'; export const InternalSyncRoute = express.Router(); @@ -15,41 +15,40 @@ InternalSyncRoute.post('/internal/test-members', async (req: Request, res: Respo return res.status(401).json({ error: 'Unauthorized' }); } - const { issuer, clientId, contextId, user } = req.body as Record; - if ( - typeof issuer !== 'string' || - typeof clientId !== 'string' || - typeof contextId !== 'string' || - typeof user !== 'string' - ) { + const { unitId, contextId } = req.body as Record; + const hasContextId = typeof contextId === 'string' && contextId.length > 0; + const hasUnitId = (typeof unitId === 'string' && unitId.length > 0) || typeof unitId === 'number'; + if (!hasUnitId && !hasContextId) { return res.status(400).json({ - error: 'issuer, clientId, contextId and user must be strings', + error: 'unitId or contextId must be provided', }); } try { - const storedContexts = await lti.Database.Get(false, 'contexttoken', { - contextId, - user, - }); - - if (!Array.isArray(storedContexts) || !storedContexts[0]) { + const link = await UnitLink.findOne(hasContextId ? { contextId } : { unitId: String(unitId) }); + if (!link) { return res.status(404).json({ - error: 'Stored LTI context not found; perform a new LMS launch', + error: 'Linked LMS context not found', }); } - const platformContext = storedContexts[0] as LtiLaunchPayload['platformContext']; - if (!platformContext?.namesRoles?.context_memberships_url) { + if (!link.issuer || !link.clientId || !link.deploymentId || !link.membershipsUrl) { return res.status(422).json({ - error: 'Stored LTI context does not include an NRPS memberships URL', + error: 'Link does not include NRPS service details; launch and link the unit again', }); } const serviceToken = { - iss: issuer, - clientId, - platformContext, + iss: link.issuer, + clientId: link.clientId, + deploymentId: link.deploymentId, + platformContext: { + context: { id: link.contextId }, + namesRoles: { + context_memberships_url: link.membershipsUrl, + service_versions: ['2.0'], + }, + }, } as unknown as IdToken; // Ltijs supports `pages: false` to retrieve every page, although its bundled diff --git a/src/routes/unit-link.route.ts b/src/routes/unit-link.route.ts index 76d78fc..074a924 100644 --- a/src/routes/unit-link.route.ts +++ b/src/routes/unit-link.route.ts @@ -28,6 +28,14 @@ UnitLinkRouter.post('/link', async (req: Request, res: Response) => { const _token = res.locals.token; const token = _token as unknown as LtiLaunchPayload; const contextId = token.platformContext?.context?.id; + const issuer = token.iss; + const clientId = token.clientId; + const deploymentId = token.deploymentId; + const membershipsUrl = token.platformContext?.namesRoles?.context_memberships_url; + + if (!contextId || !issuer || !clientId || !deploymentId || !membershipsUrl) { + return sendError(res, 'LTI launch does not include the required NRPS context', 422); + } const newToken = { unit_id: unitId, @@ -58,8 +66,14 @@ UnitLinkRouter.post('/link', async (req: Request, res: Response) => { // Current OnTrack user has permissions to enrol students into requested unit_id const result = await UnitLink.findOneAndUpdate( { contextId }, - { unitId }, - { upsert: true, new: true }, + { + unitId, + issuer, + clientId, + deploymentId, + membershipsUrl, + }, + { upsert: true, new: true, runValidators: true }, ); res.json(result); }); diff --git a/src/schema/unitLink.model.ts b/src/schema/unitLink.model.ts index 8c1e4d8..fe98e03 100644 --- a/src/schema/unitLink.model.ts +++ b/src/schema/unitLink.model.ts @@ -3,6 +3,10 @@ import mongoose from 'mongoose'; const UnitLinkSchema = new mongoose.Schema({ contextId: { type: String, required: true, unique: true }, unitId: { type: String, required: true }, + issuer: { type: String, required: true }, + clientId: { type: String, required: true }, + deploymentId: { type: String, required: true }, + membershipsUrl: { type: String, required: true }, }); const UnitLink = mongoose.model('UnitLink', UnitLinkSchema);