@lti-tool/core 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/ltiTool.d.ts +2 -0
- package/dist/ltiTool.d.ts.map +1 -1
- package/dist/ltiTool.js +26 -6
- package/dist/schemas/lti13/nrps/contextMembership.schema.d.ts +4 -4
- package/dist/schemas/lti13/nrps/contextMembership.schema.js +2 -2
- package/package.json +2 -2
- package/src/ltiTool.ts +35 -6
- package/src/schemas/lti13/nrps/contextMembership.schema.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @lti-tool/core
|
|
2
2
|
|
|
3
|
+
## 1.0.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b0a98e0: Resiliency -- retry launch JWT verification on JWKS kid miss -- @ottenhoff 🎉 #94
|
|
8
|
+
|
|
9
|
+
## 1.0.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a5404db: Sakai compatibility — @ottenhoff 🎉 #83
|
|
14
|
+
|
|
3
15
|
## 1.0.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/ltiTool.d.ts
CHANGED
|
@@ -89,6 +89,8 @@ export declare class LTITool {
|
|
|
89
89
|
* @throws {Error} When verification fails for security reasons
|
|
90
90
|
*/
|
|
91
91
|
verifyLaunch(idToken: string, state: string): Promise<LTI13JwtPayload>;
|
|
92
|
+
private getOrCreateJwks;
|
|
93
|
+
private verifyLaunchJwtWithCachedJwks;
|
|
92
94
|
/**
|
|
93
95
|
* Generates JSON Web Key Set (JWKS) containing the tool's public key for platform verification.
|
|
94
96
|
*
|
package/dist/ltiTool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,+CAA+C,CAAC;AACnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EACL,KAAK,uBAAuB,EAE5B,KAAK,eAAe,EAEpB,KAAK,mBAAmB,EAGzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,SAAS,EAGd,KAAK,cAAc,EACpB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,+CAA+C,CAAC;AACrF,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,mDAAmD,CAAC;AAChG,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,mEAAmE,CAAC;AAC7G,OAAO,EACL,KAAK,MAAM,EAEZ,MAAM,kDAAkD,CAAC;AAU1D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,OAAO;IAeN,OAAO,CAAC,MAAM;IAd1B,4DAA4D;IAC5D,OAAO,CAAC,SAAS,CAA4D;IAC7E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,0BAA0B,CAAC,CAA6B;IAEhE;;;;OAIG;gBACiB,MAAM,EAAE,SAAS;IAkCrC;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,GAAG,GAAG,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,MAAM,CAAC;IAsCnB;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"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"}
|
package/dist/ltiTool.js
CHANGED
|
@@ -133,12 +133,7 @@ export class LTITool {
|
|
|
133
133
|
// 2. get the launchConfig so we can get the remote JWKS from our data store
|
|
134
134
|
const launchConfig = await getValidLaunchConfig(this.config.storage, unverified.iss, unverified.aud, unverified['https://purl.imsglobal.org/spec/lti/claim/deployment_id']);
|
|
135
135
|
// 3. Verify LMS JWT
|
|
136
|
-
|
|
137
|
-
if (!jwks) {
|
|
138
|
-
jwks = createRemoteJWKSet(new URL(launchConfig.jwksUrl));
|
|
139
|
-
this.jwksCache.set(launchConfig.jwksUrl, jwks);
|
|
140
|
-
}
|
|
141
|
-
const { payload } = await jwtVerify(validatedParams.idToken, jwks);
|
|
136
|
+
const payload = await this.verifyLaunchJwtWithCachedJwks(validatedParams.idToken, launchConfig.jwksUrl);
|
|
142
137
|
// 4. Verify our state JWT
|
|
143
138
|
const { payload: stateData } = await jwtVerify(validatedParams.state, this.config.stateSecret);
|
|
144
139
|
// 5. Parse and validate LMS JWT
|
|
@@ -162,6 +157,31 @@ export class LTITool {
|
|
|
162
157
|
throw new Error(`[LTI] Launch verification failed: ${formatError(error)}`);
|
|
163
158
|
}
|
|
164
159
|
}
|
|
160
|
+
getOrCreateJwks(jwksUrl) {
|
|
161
|
+
let jwks = this.jwksCache.get(jwksUrl);
|
|
162
|
+
if (!jwks) {
|
|
163
|
+
jwks = createRemoteJWKSet(new URL(jwksUrl));
|
|
164
|
+
this.jwksCache.set(jwksUrl, jwks);
|
|
165
|
+
}
|
|
166
|
+
return jwks;
|
|
167
|
+
}
|
|
168
|
+
async verifyLaunchJwtWithCachedJwks(idToken, jwksUrl) {
|
|
169
|
+
const jwks = this.getOrCreateJwks(jwksUrl);
|
|
170
|
+
try {
|
|
171
|
+
const { payload } = await jwtVerify(idToken, jwks);
|
|
172
|
+
return payload;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error.code !== 'ERR_JWKS_NO_MATCHING_KEY') {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
// Key rotation fallback: evict cached JWKS and retry verification once.
|
|
179
|
+
this.jwksCache.delete(jwksUrl);
|
|
180
|
+
const refreshedJwks = this.getOrCreateJwks(jwksUrl);
|
|
181
|
+
const { payload } = await jwtVerify(idToken, refreshedJwks);
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
165
185
|
/**
|
|
166
186
|
* Generates JSON Web Key Set (JWKS) containing the tool's public key for platform verification.
|
|
167
187
|
*
|
|
@@ -19,8 +19,8 @@ export declare const NRPSMemberResponseSchema: z.ZodObject<{
|
|
|
19
19
|
*/
|
|
20
20
|
export declare const NRPSContextResponseSchema: z.ZodObject<{
|
|
21
21
|
id: z.ZodString;
|
|
22
|
-
label: z.ZodString
|
|
23
|
-
title: z.ZodString
|
|
22
|
+
label: z.ZodOptional<z.ZodString>;
|
|
23
|
+
title: z.ZodOptional<z.ZodString>;
|
|
24
24
|
}, z.core.$strip>;
|
|
25
25
|
/**
|
|
26
26
|
* Schema for full NRPS context membership response
|
|
@@ -29,8 +29,8 @@ export declare const NRPSContextMembershipResponseSchema: z.ZodObject<{
|
|
|
29
29
|
id: z.ZodURL;
|
|
30
30
|
context: z.ZodObject<{
|
|
31
31
|
id: z.ZodString;
|
|
32
|
-
label: z.ZodString
|
|
33
|
-
title: z.ZodString
|
|
32
|
+
label: z.ZodOptional<z.ZodString>;
|
|
33
|
+
title: z.ZodOptional<z.ZodString>;
|
|
34
34
|
}, z.core.$strip>;
|
|
35
35
|
members: z.ZodArray<z.ZodObject<{
|
|
36
36
|
status: z.ZodString;
|
|
@@ -19,8 +19,8 @@ export const NRPSMemberResponseSchema = z.object({
|
|
|
19
19
|
*/
|
|
20
20
|
export const NRPSContextResponseSchema = z.object({
|
|
21
21
|
id: z.string(),
|
|
22
|
-
label: z.string(),
|
|
23
|
-
title: z.string(),
|
|
22
|
+
label: z.string().optional(),
|
|
23
|
+
title: z.string().optional(),
|
|
24
24
|
});
|
|
25
25
|
/**
|
|
26
26
|
* Schema for full NRPS context membership response
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lti-tool/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "LTI 1.3 implementation for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lti",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"test": "vitest"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"jose": "^6.1
|
|
39
|
+
"jose": "^6.2.1",
|
|
40
40
|
"zod": "^4.3.6"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
package/src/ltiTool.ts
CHANGED
|
@@ -208,12 +208,10 @@ export class LTITool {
|
|
|
208
208
|
);
|
|
209
209
|
|
|
210
210
|
// 3. Verify LMS JWT
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
const { payload } = await jwtVerify(validatedParams.idToken, jwks);
|
|
211
|
+
const payload = await this.verifyLaunchJwtWithCachedJwks(
|
|
212
|
+
validatedParams.idToken,
|
|
213
|
+
launchConfig.jwksUrl,
|
|
214
|
+
);
|
|
217
215
|
|
|
218
216
|
// 4. Verify our state JWT
|
|
219
217
|
const { payload: stateData } = await jwtVerify(
|
|
@@ -248,6 +246,37 @@ export class LTITool {
|
|
|
248
246
|
}
|
|
249
247
|
}
|
|
250
248
|
|
|
249
|
+
private getOrCreateJwks(jwksUrl: string): ReturnType<typeof createRemoteJWKSet> {
|
|
250
|
+
let jwks = this.jwksCache.get(jwksUrl);
|
|
251
|
+
if (!jwks) {
|
|
252
|
+
jwks = createRemoteJWKSet(new URL(jwksUrl));
|
|
253
|
+
this.jwksCache.set(jwksUrl, jwks);
|
|
254
|
+
}
|
|
255
|
+
return jwks;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async verifyLaunchJwtWithCachedJwks(
|
|
259
|
+
idToken: string,
|
|
260
|
+
jwksUrl: string,
|
|
261
|
+
): Promise<LTI13JwtPayload> {
|
|
262
|
+
const jwks = this.getOrCreateJwks(jwksUrl);
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const { payload } = await jwtVerify(idToken, jwks);
|
|
266
|
+
return payload as LTI13JwtPayload;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if ((error as { code?: string }).code !== 'ERR_JWKS_NO_MATCHING_KEY') {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Key rotation fallback: evict cached JWKS and retry verification once.
|
|
273
|
+
this.jwksCache.delete(jwksUrl);
|
|
274
|
+
const refreshedJwks = this.getOrCreateJwks(jwksUrl);
|
|
275
|
+
const { payload } = await jwtVerify(idToken, refreshedJwks);
|
|
276
|
+
return payload as LTI13JwtPayload;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
251
280
|
/**
|
|
252
281
|
* Generates JSON Web Key Set (JWKS) containing the tool's public key for platform verification.
|
|
253
282
|
*
|
|
@@ -21,8 +21,8 @@ export const NRPSMemberResponseSchema = z.object({
|
|
|
21
21
|
*/
|
|
22
22
|
export const NRPSContextResponseSchema = z.object({
|
|
23
23
|
id: z.string(),
|
|
24
|
-
label: z.string(),
|
|
25
|
-
title: z.string(),
|
|
24
|
+
label: z.string().optional(),
|
|
25
|
+
title: z.string().optional(),
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
/**
|