Skip to content

Commit

Permalink
Merge pull request #113 from atomicjolt/jb_enhance_lti_server
Browse files Browse the repository at this point in the history
Add functionality to lti-server
  • Loading branch information
seanrcollings authored Feb 5, 2025
2 parents 2466f8b + feb5f59 commit 5d694aa
Show file tree
Hide file tree
Showing 54 changed files with 1,676 additions and 119 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ packages/atomic-fuel/libs
.vscode


*.map
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ npm run playground

## Testing

Before running tests, ensure that all packages are built:

```bash
npm run build --workspaces
```

Then, run the tests:

```bash
npm run test --workspaces
```
Expand All @@ -30,13 +38,15 @@ This repo uses [changsets](https://github.com/changesets/changesets/tree/main) f
### Create a new changeset

Each time that you make meanginful changes to a package, you should create a new changeset. To do this, run the following command:

```bash
npx changeset
```

This will guide you through the process of creating a new changeset.

Once you have created a changeset, you can view it by running the following command:

```bash
npx changeset status
```
Expand All @@ -48,10 +58,10 @@ To publish a new version of a package, run the following command:
```bash
npx changeset version
```

This will consume all of the changesets that have been created and bump the versions of the packages accordingly.
Additionally, it will write changelog entries for each package.


### Publish

To publish the new versions of the packages, run the following command:
Expand All @@ -61,4 +71,3 @@ npx changeset publish
```

This will loop through all of the packages and publish any that have a newer version than what is on NPM.

95 changes: 60 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@types/jsdom": "^21.1.7",
"@vitest/ui": "^2.1.4",
"gh-pages": "^6.2.0",
"jsdom": "^25.0.1",
"jsdom": "^26.0.0",
"simple-scaffold": "^2.3.2",
"storybook": "^8.0.10",
"tsx": "^4.19.2",
Expand All @@ -60,4 +60,4 @@
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.26.0"
}
}
}
3 changes: 2 additions & 1 deletion packages/lti-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"typings": "types/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.build.json",
"pretest": "npm run build",
"test": "vitest run",
"prepublishOnly": "npm run build"
},
Expand All @@ -24,4 +25,4 @@
"i18next-browser-languagedetector": ">=7.0.0",
"@atomicjolt/lti-types": "^1.1.7"
}
}
}
14 changes: 9 additions & 5 deletions packages/lti-server/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
# AtomicJolt LTI

This is a collection of Javascript used by Atomic Jolt to assist in handling an LTI launch on the server.

## Installation

`npm i @atomicjolt/lti-server`

## Usage

For an example of how to use this library see https://github.com/atomicjolt/atomic-lti-worker

The application code using this library must implement the LTI Launch in 3 phases, providing the server side code for each phase and returning and html response for each phase. Phases 1 and 3 will include a call to the client side javacript contained in this library. See the 1Edtech working group documentation for more information about the LTI standard: https://www.imsglobal.org/activity/learning-tools-interoperability.

1. Open ID Connect initialization
During this phase respond to the OIDC initialization request, attempt to write a state cookie and return and html page with a call to `InitOIDCLaunch`
During this phase respond to the OIDC initialization request, attempt to write a state cookie and return and html page with a call to `initOIDCLaunch` from @atomicjolt/lti-client

2. Redirect
Server side validate the redirect and then return an HTML page capable of redirecting to the final LTI launch
Server side validate the redirect and then return an HTML page capable of redirecting to the final LTI launch

3. Handle the LTI launch.
Validate the request including checking the nonce server side. Check for a valid state cookie and then return an HTML page with a script that calls `LtiLaunch` from this library.
Validate the request including checking the nonce server side. Check for a valid state cookie and then return an HTML page with a script that calls `ltiLaunch` from @atomicjolt/lti-client.

## Contributing

Report any issues using Github

Build package:
`npm run build`
`npm run build`

Publish package:
`npm publish --access public`
`npm publish --access public`

## License

MIT
This code is released as open source without any support or warranty. It is used by Atomic Jolt internally and is released in case someone finds it useful.
6 changes: 4 additions & 2 deletions packages/lti-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
"dependencies": {
"@atomicjolt/lti-client": "@atomicjolt/lti-client",
"i18next": "^23.2.8",
"i18next-browser-languagedetector": "^7.1.0"
"i18next-browser-languagedetector": "^7.1.0",
"jose": "^5.9.6"
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"pretest": "npm run build",
"test": "vitest run",
"prepublishOnly": "npm run build"
}
}
}
21 changes: 20 additions & 1 deletion packages/lti-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
export * as LtiPlatform from './libs/platforms';
export * as LtiPlatform from './libs/platform_storage';
export * as LtiValidation from './libs/lti_validation';
export * as LtiOidc from './libs/oidc';

export { validateIdTokenContents } from './libs/lti_validation';
export { buildInit, validateNonce } from './libs/oidc';
export { getLtiStorageParams } from './libs/platform_storage';

export {
OPEN_ID_COOKIE_PREFIX,
OPEN_ID_STORAGE_COOKIE,
ALLOWED_LAUNCH_TIME,
} from './libs/constants';
export { ALGORITHM, signJwt, verifyJwt, getKid, getIss } from './libs/jwt';
export { generateKeySet, keySetsToJwks, fetchRemoteJwks, verifyJwtUsingJwks } from './libs/jwks';
export { TEST_ID_TOKEN, genJwt } from './tests/helper';
export { getDefaultToolConfiguration } from './libs/tool_configuration';
export { parseLinkHeader } from './libs/link_header';
export { requestServiceToken, ClientCredentialsError } from './libs/client_credentials';
export { createScore, sendScore } from './libs/scores';
export { listResults, showResult } from './libs/results';
export { listLineItems, showLineItem, createLineItem, updateLineItem } from './libs/line_items';
35 changes: 35 additions & 0 deletions packages/lti-server/src/libs/client_credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ClientAuthorizationRequest, ClientAuthorizationResponse } from '../../types';

export class ClientCredentialsError extends Error { }

// Request a token from the platform that can be used in LTI Advantage requests
export async function requestServiceToken(platformTokenUrl: string, token: string, scopes: string): Promise<ClientAuthorizationResponse> {
const clientAuthorizationRequest: ClientAuthorizationRequest = {
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
scope: scopes,
client_assertion: token,
};

try {
const formBody = new URLSearchParams(clientAuthorizationRequest).toString();
const response = await fetch(platformTokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formBody,
});

if (response.status !== 200) {
const text = await response.text();
if (text?.toLowerCase().indexOf('rate limit') >= 0) {
throw new ClientCredentialsError('RateLimited');
}
throw new ClientCredentialsError(`RequestFailed: ${text}`);
}

let clientAuth = await response.json() as ClientAuthorizationResponse;
return clientAuth;
} catch (error) {
throw new ClientCredentialsError(`RequestFailed: ${error}`);
}
}
Loading

0 comments on commit 5d694aa

Please sign in to comment.