Skip to content

Commit

Permalink
wip: add code from serlo-editor-for-edusharing and reorganize
Browse files Browse the repository at this point in the history
  • Loading branch information
LarsTheGlidingSquirrel committed Oct 7, 2024
1 parent 5c6ff65 commit 2d802e1
Show file tree
Hide file tree
Showing 20 changed files with 2,536 additions and 120 deletions.
25 changes: 0 additions & 25 deletions .env-template

This file was deleted.

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# .env - Should not be added because it contains private keys used for encryption
.env
*.env

# Logs
logs
Expand Down
2 changes: 0 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
FROM node:20.17.0
WORKDIR /usr/src/app

RUN apt update && apt install neovim nano -y

COPY . .
RUN yarn install --immutable

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Serlo editor as LTI tool Status: Early prototype
Prototype: Serlo editor as LTI tool

# Local dev setup

Requirements:

- Docker & Docker Compose

1. Create file `.env` and copy content from `.env-template`
1. Create local `.env` file and paste values from 1password
2. `yarn` to install dependencies
3. `yarn dev` to start docker containers (hot reload)
4. Go to https://saltire.lti.app/platform, sign in, navigate to "Advanced
Expand Down
31 changes: 23 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Shared network 'express-mongo-mysql' makes service 'mongo' & 'mysql' available to service 'express'. You can connect using the service name in MONGODB_CONNECTION_URI like this: mongodb://[SERVICE_NAME]:27017/
# Shared network 'express-mongo-mysql' makes service 'mongo' & 'mysql' available to service 'express'. You can connect using the service name in MONGODB_URI like this: mongodb://[SERVICE_NAME]:27017/

services:
express:
Expand All @@ -7,8 +7,10 @@ services:
dockerfile: Dockerfile.dev
env_file: '.env'
depends_on:
- mongo
- mysql
mongo:
condition: service_started
mysql:
condition: service_healthy
networks:
- express-mongo-mysql
expose:
Expand All @@ -32,11 +34,24 @@ services:
expose:
- 27017
mysql:
image: ghcr.io/serlo/serlo-mysql-database:latest
platform: linux/x86_64
pull_policy: always
ports:
- '3306:3306'
image: mariadb:lts
environment:
- MARIADB_DATABASE=serlo
- MARIADB_USER=root
- MARIADB_PASSWORD=secret
- MARIADB_ROOT_PASSWORD=secret
# https://mariadb.com/kb/en/using-healthcheck-sh/
healthcheck:
test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
start_period: 10s
interval: 10s
timeout: 5s
retries: 3
# image: ghcr.io/serlo/serlo-mysql-database:latest
# platform: linux/x86_64
# pull_policy: always
# ports:
# - '3306:3306'
networks:
- express-mongo-mysql
networks:
Expand Down
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@
"push-image:dev": "yarn push-image dev",
"push-image:latest": "yarn push-image latest",
"start:dev": "tsx --watch src/backend/index.ts",
"start": "node dist/backend/index.cjs"
"start": "node dist/backend/index.cjs",
"dev:edusharing": "dotenvx run --env-file=dev.env --env-file=.env -- tsx --watch src/edusharing-server/start-server.ts"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.14.2",
"@serlo/editor": "0.14.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"fp-ts": "2.16.9",
"io-ts": "^2.2.21",
"jsonwebtoken": "9.0.2",
"jwt-decode": "4.0.0",
"lodash": "^4.17.21",
"ltijs": "^5.9.5",
"mongodb": "^6.9.0",
"mysql2": "^3.11.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -45,7 +48,9 @@
"@eslint/compat": "^1.1.1",
"@eslint/js": "^9.11.1",
"@types/jsonwebtoken": "9.0.6",
"@types/lodash": "^4",
"@types/ltijs": "4.0.11",
"@types/multer": "^1",
"@types/node": "^22.7.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
Expand All @@ -57,6 +62,9 @@
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"json-web-key": "^0.4.0",
"jwks-rsa": "^3.1.0",
"multer": "^1.4.5-lts.1",
"npm-run-all": "^4.1.5",
"prettier": "3.3.3",
"prettier-plugin-packagejson": "^2.5.2",
Expand Down
4 changes: 2 additions & 2 deletions scripts/setup_uberspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi
uberspace web header suppress / X-Frame-Options

# Create MySQL table
mysql -e 'USE '$USER'; CREATE TABLE IF NOT EXISTS `lti_entity` ( `id` bigint NOT NULL AUTO_INCREMENT, `resource_link_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, `custom_claim_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, `id_token_on_creation` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, PRIMARY KEY (`id`), KEY `idx_lti_entity_custom_claim_id` (`custom_claim_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;'
mysql -e 'USE '$USER'; CREATE TABLE IF NOT EXISTS `lti_entity` ( `id` bigint NOT NULL AUTO_INCREMENT, `resource_link_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, `custom_claim_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,`edusharing_node_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, `id_token_on_creation` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL, PRIMARY KEY (`id`), KEY `idx_lti_entity_custom_claim_id` (`custom_claim_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;'
echo 'MySQL table created successfully (or existed already)'

# Generate mongodb password
Expand Down Expand Up @@ -47,7 +47,7 @@ echo 'MongoDB set up successfully'
cp .env-template .env
mysql_pw=$(grep -oP -m 1 "^password=(.*)" ~/.my.cnf | cut -d '=' -f 2-)
echo "MYSQL_URI=mysql://${USER}:${mysql_pw}@localhost:3306/${USER}" >> .env
echo "MONGODB_CONNECTION_URI=mongodb://${USER}_mongoroot:${MONGODB_PASSWORD}@127.0.0.1:27017/" >> .env
echo "MONGODB_URI=mongodb://${USER}_mongoroot:${MONGODB_PASSWORD}@127.0.0.1:27017/" >> .env
echo 'Updated environment variables'

# Install dependencies
Expand Down
16 changes: 16 additions & 0 deletions src/backend/create-acccess-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import jwt from 'jsonwebtoken'

export function createAccessToken(
editorMode: 'read' | 'write',
entityId: number,
signingKey: string
) {
return jwt.sign(
{
entityId: entityId,
accessRight: editorMode,
},
signingKey, // Reuse the symmetric HS256 key used by ltijs to sign ltik and database entries
{ expiresIn: '3 days' }
)
}
85 changes: 85 additions & 0 deletions src/backend/edu-sharing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { Response } from 'express'
import * as t from 'io-ts'

export function createAutoFromResponse({
res,
method = 'GET',
targetUrl,
params,
}: {
res: Response
method?: 'GET' | 'POST'
targetUrl: string
params: Record<string, string>
}) {
const escapedTargetUrl = escapeHTML(targetUrl)
const formDataHtml = Object.entries(params)
.map(([name, value]) => {
const escapedValue = escapeHTML(value)
return `<input type="hidden" name="${name}" value="${escapedValue}" />`
})
.join('\n')

res.setHeader('Content-Type', 'text/html')
res.send(
`<!DOCTYPE html>
<html>
<head><title>Redirect to ${escapedTargetUrl}</title></head>
<body>
<form id="form" action="${escapedTargetUrl}" method="${method}">
${formDataHtml}
</form>
<script type="text/javascript">
document.getElementById("form").submit();
</script>
</body>
</html>
`.trim()
)
res.end()
}

function escapeHTML(text: string): string {
return text
.replaceAll('&', '&amp;')
.replaceAll('"', '&quot;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
}

export const EdusharingAssetDecoder = t.type({
nodeId: t.string,
repositoryId: t.string,
})

export const JwtDeepflowResponseDecoder = t.type({
'https://purl.imsglobal.org/spec/lti-dl/claim/content_items': t.array(
t.type({
custom: EdusharingAssetDecoder,
})
),
})

export const DeeplinkNonce = t.type({ nonce: t.string })
export const DeeplinkLoginData = t.type({
dataToken: t.string,
nodeId: t.string,
user: t.string,
})

// Define type for the LTI claim https://purl.imsglobal.org/spec/lti/claim/custom
// Partial contains optional properties.
// TODO: rename to not confuse it with other custom types
export const LtiCustomType = t.intersection([
t.type({
getContentApiUrl: t.string,
appId: t.string,
}),
DeeplinkLoginData,
t.partial({
fileName: t.string,
/** Is set when editor was opened in edit mode */
postContentApiUrl: t.string,
version: t.string,
}),
])
Loading

0 comments on commit 2d802e1

Please sign in to comment.