Skip to content

Commit

Permalink
Change editor mode depending on LTI roles claim.
Browse files Browse the repository at this point in the history
  • Loading branch information
LarsTheGlidingSquirrel committed Jun 11, 2024
1 parent d8eda00 commit 64744ad
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 13 deletions.
2 changes: 1 addition & 1 deletion saltire-platform.config

Large diffs are not rendered by default.

37 changes: 25 additions & 12 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Provider as ltijs } from "ltijs";
import "dotenv/config";
import path from "path";
import * as t from "io-ts";
import * as jwt from "jsonwebtoken";

// Requires Node.js 20.11 or higher
Expand All @@ -20,10 +19,6 @@ const ltiPlatform = {
keysetEndpoint: readEnvVariable("LTI_PLATFORM_KEYSET_ENDPOINT"),
};

export const LtiCustomType = t.type({
editor_mode: t.union([t.literal("read"), t.literal("write")]),
});

// Setup
ltijs.setup(
ltijsKey,
Expand Down Expand Up @@ -106,19 +101,37 @@ ltijs.app.put("/entity", async (req, res) => {
});

// Successful LTI launch
ltijs.onConnect((_, req, res) => {
ltijs.onConnect((idToken, req, res) => {
// Using search query params is suggested by ltijs, see: https://github.com/Cvmcosta/ltijs/issues/100#issuecomment-832284300
const entityId = req.query.entityId;

if (!entityId)
return res.send('Search query parameter "entityId" was missing!');

// This might need to change depending on what type the platform sends us
const custom: unknown = res.locals.context?.custom;
if (!LtiCustomType.is(custom)) {
return res.send("Custom claim malformed!");
}
const editorMode = custom.editor_mode;
// https://www.imsglobal.org/spec/lti/v1p3#lis-vocabulary-for-context-roles
// Example roles claim from itslearning
// "https://purl.imsglobal.org/spec/lti/claim/roles":[
// 0:"http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff"
// 1:"http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"
// ]
const rolesWithWriteAccess = [
"membership#Administrator",
"membership#ContentDeveloper",
"membership#Instructor",
"membership#Mentor",
"membership#Manager",
"membership#Officer",
];
const courseMembershipRole = idToken.roles.find((role) =>
role.includes("membership#")
);
const editorMode =
courseMembershipRole &&
rolesWithWriteAccess.some((roleWithWriteAccess) =>
courseMembershipRole.includes(roleWithWriteAccess)
)
? "write"
: "read";

// Generate access token and send to client
// TODO: Maybe use registered jwt names
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SerloEditor } from "@serlo/editor";
import { useEffect, useState } from "react";
import * as jwt from "jsonwebtoken";

function App() {
// TODO: Make editorState always contain valid value
Expand Down Expand Up @@ -41,6 +42,8 @@ function App() {
const ltik = urlParams.get("ltik");
if (!accessToken || !ltik) return <p>Access token or ltik was missing!</p>;

const decodedAccessToken = jwt.decode(accessToken);

const isDeeplink = urlParams.get("deeplink");

return (
Expand Down Expand Up @@ -78,6 +81,11 @@ function App() {
return <>{editor.element}</>;
}}
</SerloEditor>
<h2>Debug info</h2>
<h3>Access token:</h3>
<div>{JSON.stringify(decodedAccessToken)}</div>
<h3>ltik:</h3>
<div>{ltik}</div>
</>
);
}
Expand Down

0 comments on commit 64744ad

Please sign in to comment.