@lti-tool/core 0.9.0

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +60 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/interfaces/index.d.ts +7 -0
  7. package/dist/interfaces/index.d.ts.map +1 -0
  8. package/dist/interfaces/index.js +1 -0
  9. package/dist/interfaces/jwks.d.ts +18 -0
  10. package/dist/interfaces/jwks.d.ts.map +1 -0
  11. package/dist/interfaces/jwks.js +1 -0
  12. package/dist/interfaces/ltiClient.d.ts +24 -0
  13. package/dist/interfaces/ltiClient.d.ts.map +1 -0
  14. package/dist/interfaces/ltiClient.js +1 -0
  15. package/dist/interfaces/ltiConfig.d.ts +26 -0
  16. package/dist/interfaces/ltiConfig.d.ts.map +1 -0
  17. package/dist/interfaces/ltiConfig.js +1 -0
  18. package/dist/interfaces/ltiDeployment.d.ts +15 -0
  19. package/dist/interfaces/ltiDeployment.d.ts.map +1 -0
  20. package/dist/interfaces/ltiDeployment.js +1 -0
  21. package/dist/interfaces/ltiLaunchConfig.d.ts +19 -0
  22. package/dist/interfaces/ltiLaunchConfig.d.ts.map +1 -0
  23. package/dist/interfaces/ltiLaunchConfig.js +1 -0
  24. package/dist/interfaces/ltiSession.d.ts +110 -0
  25. package/dist/interfaces/ltiSession.d.ts.map +1 -0
  26. package/dist/interfaces/ltiSession.js +1 -0
  27. package/dist/interfaces/ltiStorage.d.ts +122 -0
  28. package/dist/interfaces/ltiStorage.d.ts.map +1 -0
  29. package/dist/interfaces/ltiStorage.js +1 -0
  30. package/dist/ltiTool.d.ts +184 -0
  31. package/dist/ltiTool.d.ts.map +1 -0
  32. package/dist/ltiTool.js +305 -0
  33. package/dist/schemas/client.schema.d.ts +33 -0
  34. package/dist/schemas/client.schema.d.ts.map +1 -0
  35. package/dist/schemas/client.schema.js +14 -0
  36. package/dist/schemas/common.schema.d.ts +6 -0
  37. package/dist/schemas/common.schema.d.ts.map +1 -0
  38. package/dist/schemas/common.schema.js +5 -0
  39. package/dist/schemas/deployment.schema.d.ts +8 -0
  40. package/dist/schemas/deployment.schema.d.ts.map +1 -0
  41. package/dist/schemas/deployment.schema.js +11 -0
  42. package/dist/schemas/index.d.ts +5 -0
  43. package/dist/schemas/index.d.ts.map +1 -0
  44. package/dist/schemas/index.js +4 -0
  45. package/dist/schemas/lti13/ags/scoreSubmission.schema.d.ts +34 -0
  46. package/dist/schemas/lti13/ags/scoreSubmission.schema.d.ts.map +1 -0
  47. package/dist/schemas/lti13/ags/scoreSubmission.schema.js +41 -0
  48. package/dist/schemas/lti13/claims/baseJwtClaims.schema.d.ts +11 -0
  49. package/dist/schemas/lti13/claims/baseJwtClaims.schema.d.ts.map +1 -0
  50. package/dist/schemas/lti13/claims/baseJwtClaims.schema.js +10 -0
  51. package/dist/schemas/lti13/claims/contextClaims.schema.d.ts +11 -0
  52. package/dist/schemas/lti13/claims/contextClaims.schema.d.ts.map +1 -0
  53. package/dist/schemas/lti13/claims/contextClaims.schema.js +14 -0
  54. package/dist/schemas/lti13/claims/coreLtiClaims.schema.d.ts +9 -0
  55. package/dist/schemas/lti13/claims/coreLtiClaims.schema.d.ts.map +1 -0
  56. package/dist/schemas/lti13/claims/coreLtiClaims.schema.js +11 -0
  57. package/dist/schemas/lti13/claims/platformClaims.schema.d.ts +19 -0
  58. package/dist/schemas/lti13/claims/platformClaims.schema.d.ts.map +1 -0
  59. package/dist/schemas/lti13/claims/platformClaims.schema.js +24 -0
  60. package/dist/schemas/lti13/claims/privacyClaims.schema.d.ts +8 -0
  61. package/dist/schemas/lti13/claims/privacyClaims.schema.d.ts.map +1 -0
  62. package/dist/schemas/lti13/claims/privacyClaims.schema.js +7 -0
  63. package/dist/schemas/lti13/claims/serviceClaims.schema.d.ts +20 -0
  64. package/dist/schemas/lti13/claims/serviceClaims.schema.d.ts.map +1 -0
  65. package/dist/schemas/lti13/claims/serviceClaims.schema.js +25 -0
  66. package/dist/schemas/lti13/lti13JwtPayload.schema.d.ts +66 -0
  67. package/dist/schemas/lti13/lti13JwtPayload.schema.d.ts.map +1 -0
  68. package/dist/schemas/lti13/lti13JwtPayload.schema.js +22 -0
  69. package/dist/schemas/lti13/lti13Launch.schema.d.ts +14 -0
  70. package/dist/schemas/lti13/lti13Launch.schema.d.ts.map +1 -0
  71. package/dist/schemas/lti13/lti13Launch.schema.js +13 -0
  72. package/dist/schemas/lti13/lti13Login.schema.d.ts +23 -0
  73. package/dist/schemas/lti13/lti13Login.schema.d.ts.map +1 -0
  74. package/dist/schemas/lti13/lti13Login.schema.js +16 -0
  75. package/dist/services/ags.service.d.ts +38 -0
  76. package/dist/services/ags.service.d.ts.map +1 -0
  77. package/dist/services/ags.service.js +69 -0
  78. package/dist/services/session.service.d.ts +11 -0
  79. package/dist/services/session.service.d.ts.map +1 -0
  80. package/dist/services/session.service.js +103 -0
  81. package/dist/services/token.service.d.ts +36 -0
  82. package/dist/services/token.service.d.ts.map +1 -0
  83. package/dist/services/token.service.js +74 -0
  84. package/dist/utils/launchConfigValidation.d.ts +3 -0
  85. package/dist/utils/launchConfigValidation.d.ts.map +1 -0
  86. package/dist/utils/launchConfigValidation.js +7 -0
  87. package/package.json +53 -0
  88. package/src/index.ts +3 -0
  89. package/src/interfaces/index.ts +6 -0
  90. package/src/interfaces/jwks.ts +20 -0
  91. package/src/interfaces/ltiClient.ts +24 -0
  92. package/src/interfaces/ltiConfig.ts +31 -0
  93. package/src/interfaces/ltiDeployment.ts +17 -0
  94. package/src/interfaces/ltiLaunchConfig.ts +23 -0
  95. package/src/interfaces/ltiSession.ts +119 -0
  96. package/src/interfaces/ltiStorage.ts +161 -0
  97. package/src/ltiTool.ts +394 -0
  98. package/src/schemas/client.schema.ts +17 -0
  99. package/src/schemas/common.schema.ts +7 -0
  100. package/src/schemas/deployment.schema.ts +12 -0
  101. package/src/schemas/index.ts +10 -0
  102. package/src/schemas/lti13/ags/scoreSubmission.schema.ts +54 -0
  103. package/src/schemas/lti13/claims/baseJwtClaims.schema.ts +11 -0
  104. package/src/schemas/lti13/claims/contextClaims.schema.ts +16 -0
  105. package/src/schemas/lti13/claims/coreLtiClaims.schema.ts +12 -0
  106. package/src/schemas/lti13/claims/platformClaims.schema.ts +27 -0
  107. package/src/schemas/lti13/claims/privacyClaims.schema.ts +8 -0
  108. package/src/schemas/lti13/claims/serviceClaims.schema.ts +28 -0
  109. package/src/schemas/lti13/lti13JwtPayload.schema.ts +36 -0
  110. package/src/schemas/lti13/lti13Launch.schema.ts +15 -0
  111. package/src/schemas/lti13/lti13Login.schema.ts +18 -0
  112. package/src/services/ags.service.ts +92 -0
  113. package/src/services/session.service.ts +115 -0
  114. package/src/services/token.service.ts +84 -0
  115. package/src/utils/launchConfigValidation.ts +16 -0
  116. package/tsconfig.json +8 -0
@@ -0,0 +1,184 @@
1
+ import type { JWKS } from './interfaces/jwks.js';
2
+ import type { LTIClient } from './interfaces/ltiClient.js';
3
+ import type { LTIConfig } from './interfaces/ltiConfig.js';
4
+ import type { LTIDeployment } from './interfaces/ltiDeployment.js';
5
+ import type { LTISession } from './interfaces/ltiSession.js';
6
+ import { type LTI13JwtPayload } from './schemas/index.js';
7
+ import type { ScoreSubmission } from './schemas/lti13/ags/scoreSubmission.schema.js';
8
+ /**
9
+ * Main LTI 1.3 Tool implementation providing secure authentication, launch verification,
10
+ * and LTI Advantage services integration.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const ltiTool = new LTITool({
15
+ * stateSecret: new TextEncoder().encode('your-secret'),
16
+ * keyPair: await generateKeyPair('RS256'),
17
+ * storage: new MemoryStorage()
18
+ * });
19
+ *
20
+ * // Handle login initiation
21
+ * const authUrl = await ltiTool.handleLogin({
22
+ * client_id: 'your-client-id',
23
+ * iss: 'https://platform.example.com',
24
+ * launchUrl: 'https://yourtool.com/lti/launch',
25
+ * login_hint: 'user123',
26
+ * target_link_uri: 'https://yourtool.com/content',
27
+ * lti_deployment_id: 'deployment123'
28
+ * });
29
+ * ```
30
+ */
31
+ export declare class LTITool {
32
+ private config;
33
+ /** Cache for JWKS remote key sets to improve performance */
34
+ private jwksCache;
35
+ private logger;
36
+ private tokenService;
37
+ private agsService;
38
+ /**
39
+ * Creates a new LTI Tool instance.
40
+ *
41
+ * @param config - Configuration object containing secrets, keys, and storage adapter
42
+ */
43
+ constructor(config: LTIConfig);
44
+ /**
45
+ * Handles LTI 1.3 login initiation by generating state/nonce and redirecting to platform auth.
46
+ *
47
+ * @param params - Login parameters from the platform
48
+ * @param params.client_id - OAuth2 client identifier for this tool
49
+ * @param params.iss - Platform issuer URL (identifies the LMS)
50
+ * @param params.launchUrl - URL where platform will POST the id_token after auth
51
+ * @param params.login_hint - Platform-specific user identifier hint
52
+ * @param params.target_link_uri - Final destination URL after successful launch
53
+ * @param params.lti_deployment_id - Deployment identifier within the platform
54
+ * @param params.lti_message_hint - Optional platform-specific message context
55
+ * @returns Authorization URL to redirect user to for authentication
56
+ * @throws {Error} When platform configuration is not found
57
+ */
58
+ handleLogin(params: {
59
+ client_id: string;
60
+ iss: string;
61
+ launchUrl: URL | string;
62
+ login_hint: string;
63
+ target_link_uri: string;
64
+ lti_deployment_id: string;
65
+ lti_message_hint?: string;
66
+ }): Promise<string>;
67
+ /**
68
+ * Verifies and validates an LTI 1.3 launch by checking JWT signatures, nonces, and claims.
69
+ *
70
+ * Performs comprehensive security validation including:
71
+ * - JWT signature verification using platform's JWKS
72
+ * - State JWT verification to prevent CSRF
73
+ * - Nonce validation to prevent replay attacks
74
+ * - Client ID and deployment ID verification
75
+ * - LTI 1.3 claim structure validation
76
+ *
77
+ * @param idToken - JWT id_token received from platform after authentication
78
+ * @param state - State JWT that was generated during login initiation
79
+ * @returns Validated and parsed LTI 1.3 JWT payload
80
+ * @throws {Error} When verification fails for security reasons
81
+ */
82
+ verifyLaunch(idToken: string, state: string): Promise<LTI13JwtPayload>;
83
+ /**
84
+ * Generates JSON Web Key Set (JWKS) containing the tool's public key for platform verification.
85
+ *
86
+ * @returns JWKS object with the tool's public key for JWT signature verification
87
+ */
88
+ getJWKS(): Promise<JWKS>;
89
+ /**
90
+ * Creates and stores a new LTI session from validated JWT payload.
91
+ *
92
+ * @param lti13JwtPayload - Validated LTI 1.3 JWT payload from successful launch
93
+ * @returns Created session object with user, context, and service information
94
+ */
95
+ createSession(lti13JwtPayload: LTI13JwtPayload): Promise<LTISession>;
96
+ /**
97
+ * Retrieves an existing LTI session by session ID.
98
+ *
99
+ * @param sessionId - Unique session identifier
100
+ * @returns Session object if found, undefined otherwise
101
+ */
102
+ getSession(sessionId: string): Promise<LTISession | undefined>;
103
+ /**
104
+ * Submits a grade score to the platform using Assignment and Grade Services (AGS).
105
+ *
106
+ * @param session - Active LTI session containing AGS service endpoints
107
+ * @param score - Score submission data including grade value and user ID
108
+ * @returns Result of the score submission
109
+ * @throws {Error} When AGS is not available or submission fails
110
+ */
111
+ submitScore(session: LTISession, score: ScoreSubmission): Promise<Response>;
112
+ /**
113
+ * Retrieves all configured LTI client platforms.
114
+ *
115
+ * @returns Array of client configurations (without deployment details)
116
+ */
117
+ listClients(): Promise<Omit<LTIClient, 'deployments'>[]>;
118
+ /**
119
+ * Updates an existing client configuration.
120
+ *
121
+ * @param clientId - Unique client identifier
122
+ * @param client - Partial client object with fields to update
123
+ */
124
+ updateClient(clientId: string, client: Partial<Omit<LTIClient, 'id' | 'deployments'>>): Promise<void>;
125
+ /**
126
+ * Retrieves a specific client configuration by ID.
127
+ *
128
+ * @param clientId - Unique client identifier
129
+ * @returns Client configuration if found, undefined otherwise
130
+ */
131
+ getClientById(clientId: string): Promise<LTIClient | undefined>;
132
+ /**
133
+ * Adds a new LTI client platform configuration.
134
+ *
135
+ * @param client - Client configuration (ID will be auto-generated)
136
+ * @returns The generated client ID
137
+ */
138
+ addClient(client: Omit<LTIClient, 'id' | 'deployments'>): Promise<string>;
139
+ /**
140
+ * Removes a client configuration and all its deployments.
141
+ *
142
+ * @param clientId - Unique client identifier
143
+ */
144
+ deleteClient(clientId: string): Promise<void>;
145
+ /**
146
+ * Lists all deployments for a specific client platform.
147
+ *
148
+ * @param clientId - Client identifier
149
+ * @returns Array of deployment configurations for the client
150
+ */
151
+ listDeployments(clientId: string): Promise<LTIDeployment[]>;
152
+ /**
153
+ * Retrieves a specific deployment configuration.
154
+ *
155
+ * @param clientId - Client identifier
156
+ * @param deploymentId - Deployment identifier
157
+ * @returns Deployment configuration if found, undefined otherwise
158
+ */
159
+ getDeployment(clientId: string, deploymentId: string): Promise<LTIDeployment | undefined>;
160
+ /**
161
+ * Adds a new deployment to an existing client.
162
+ *
163
+ * @param clientId - Client identifier
164
+ * @param deployment - Deployment configuration to add
165
+ * @returns The generated deployment ID
166
+ */
167
+ addDeployment(clientId: string, deployment: Omit<LTIDeployment, 'id'>): Promise<string>;
168
+ /**
169
+ * Updates an existing deployment configuration.
170
+ *
171
+ * @param clientId - Client identifier
172
+ * @param deploymentId - Deployment identifier
173
+ * @param deployment - Partial deployment object with fields to update
174
+ */
175
+ updateDeployment(clientId: string, deploymentId: string, deployment: Partial<LTIDeployment>): Promise<void>;
176
+ /**
177
+ * Removes a deployment from a client.
178
+ *
179
+ * @param clientId - Client identifier
180
+ * @param deploymentId - Deployment identifier to remove
181
+ */
182
+ deleteDeployment(clientId: string, deploymentId: string): Promise<void>;
183
+ }
184
+ //# sourceMappingURL=ltiTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,305 @@
1
+ import { SignJWT, createRemoteJWKSet, decodeJwt, exportJWK, jwtVerify } from 'jose';
2
+ import { AddClientSchema, UpdateClientSchema } from './schemas/client.schema.js';
3
+ import { HandleLoginParamsSchema, LTI13JwtPayloadSchema, SessionIdSchema, VerifyLaunchParamsSchema, } from './schemas/index.js';
4
+ import { AGSService } from './services/ags.service.js';
5
+ import { createSession } from './services/session.service.js';
6
+ import { TokenService } from './services/token.service.js';
7
+ import { getValidLaunchConfig } from './utils/launchConfigValidation.js';
8
+ /**
9
+ * Main LTI 1.3 Tool implementation providing secure authentication, launch verification,
10
+ * and LTI Advantage services integration.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const ltiTool = new LTITool({
15
+ * stateSecret: new TextEncoder().encode('your-secret'),
16
+ * keyPair: await generateKeyPair('RS256'),
17
+ * storage: new MemoryStorage()
18
+ * });
19
+ *
20
+ * // Handle login initiation
21
+ * const authUrl = await ltiTool.handleLogin({
22
+ * client_id: 'your-client-id',
23
+ * iss: 'https://platform.example.com',
24
+ * launchUrl: 'https://yourtool.com/lti/launch',
25
+ * login_hint: 'user123',
26
+ * target_link_uri: 'https://yourtool.com/content',
27
+ * lti_deployment_id: 'deployment123'
28
+ * });
29
+ * ```
30
+ */
31
+ export class LTITool {
32
+ config;
33
+ /** Cache for JWKS remote key sets to improve performance */
34
+ jwksCache = new Map();
35
+ logger;
36
+ tokenService;
37
+ agsService;
38
+ /**
39
+ * Creates a new LTI Tool instance.
40
+ *
41
+ * @param config - Configuration object containing secrets, keys, and storage adapter
42
+ */
43
+ constructor(config) {
44
+ this.config = config;
45
+ this.logger =
46
+ config.logger ??
47
+ {
48
+ debug: () => { },
49
+ info: () => { },
50
+ warn: () => { },
51
+ error: () => { },
52
+ };
53
+ this.tokenService = new TokenService(this.config.keyPair, this.config.security?.keyId ?? 'main');
54
+ this.agsService = new AGSService(this.tokenService, this.config.storage, this.logger);
55
+ }
56
+ /**
57
+ * Handles LTI 1.3 login initiation by generating state/nonce and redirecting to platform auth.
58
+ *
59
+ * @param params - Login parameters from the platform
60
+ * @param params.client_id - OAuth2 client identifier for this tool
61
+ * @param params.iss - Platform issuer URL (identifies the LMS)
62
+ * @param params.launchUrl - URL where platform will POST the id_token after auth
63
+ * @param params.login_hint - Platform-specific user identifier hint
64
+ * @param params.target_link_uri - Final destination URL after successful launch
65
+ * @param params.lti_deployment_id - Deployment identifier within the platform
66
+ * @param params.lti_message_hint - Optional platform-specific message context
67
+ * @returns Authorization URL to redirect user to for authentication
68
+ * @throws {Error} When platform configuration is not found
69
+ */
70
+ async handleLogin(params) {
71
+ const validatedParams = HandleLoginParamsSchema.parse(params);
72
+ const nonce = crypto.randomUUID();
73
+ // Store nonce with expiration for replay attack prevention
74
+ const nonceExpirationSeconds = this.config.security?.nonceExpirationSeconds ?? 600;
75
+ const nonceExpiresAt = new Date(Date.now() + nonceExpirationSeconds * 1000);
76
+ await this.config.storage.storeNonce(nonce, nonceExpiresAt);
77
+ const state = await new SignJWT({
78
+ nonce,
79
+ iss: validatedParams.iss,
80
+ client_id: validatedParams.client_id,
81
+ target_link_uri: validatedParams.target_link_uri,
82
+ exp: Math.floor(Date.now() / 1000) +
83
+ (this.config.security?.stateExpirationSeconds ?? 600),
84
+ })
85
+ .setProtectedHeader({ alg: 'HS256' })
86
+ .sign(this.config.stateSecret);
87
+ const launchConfig = await getValidLaunchConfig(this.config.storage, validatedParams.iss, validatedParams.client_id, validatedParams.lti_deployment_id);
88
+ const authUrl = new URL(launchConfig.authUrl);
89
+ authUrl.searchParams.set('scope', 'openid');
90
+ authUrl.searchParams.set('response_type', 'id_token');
91
+ authUrl.searchParams.set('response_mode', 'form_post');
92
+ authUrl.searchParams.set('prompt', 'none');
93
+ authUrl.searchParams.set('client_id', validatedParams.client_id);
94
+ authUrl.searchParams.set('redirect_uri', validatedParams.launchUrl.toString());
95
+ authUrl.searchParams.set('login_hint', validatedParams.login_hint);
96
+ authUrl.searchParams.set('state', state);
97
+ authUrl.searchParams.set('nonce', nonce);
98
+ authUrl.searchParams.set('lti_deployment_id', validatedParams.lti_deployment_id);
99
+ if (validatedParams.lti_message_hint) {
100
+ authUrl.searchParams.set('lti_message_hint', validatedParams.lti_message_hint);
101
+ }
102
+ return authUrl.toString();
103
+ }
104
+ /**
105
+ * Verifies and validates an LTI 1.3 launch by checking JWT signatures, nonces, and claims.
106
+ *
107
+ * Performs comprehensive security validation including:
108
+ * - JWT signature verification using platform's JWKS
109
+ * - State JWT verification to prevent CSRF
110
+ * - Nonce validation to prevent replay attacks
111
+ * - Client ID and deployment ID verification
112
+ * - LTI 1.3 claim structure validation
113
+ *
114
+ * @param idToken - JWT id_token received from platform after authentication
115
+ * @param state - State JWT that was generated during login initiation
116
+ * @returns Validated and parsed LTI 1.3 JWT payload
117
+ * @throws {Error} When verification fails for security reasons
118
+ */
119
+ async verifyLaunch(idToken, state) {
120
+ const validatedParams = VerifyLaunchParamsSchema.parse({ idToken, state });
121
+ // 1. UNVERIFIED - get issuer
122
+ const unverified = decodeJwt(validatedParams.idToken);
123
+ if (!unverified.iss) {
124
+ throw new Error('No issuer in token');
125
+ }
126
+ // 2. get the launchConfig so we can get the remote JWKS from our data store
127
+ const launchConfig = await getValidLaunchConfig(this.config.storage, unverified.iss, unverified.aud, unverified['https://purl.imsglobal.org/spec/lti/claim/deployment_id']);
128
+ // 3. Verify LMS JWT
129
+ let jwks = this.jwksCache.get(launchConfig.jwksUrl);
130
+ if (!jwks) {
131
+ jwks = createRemoteJWKSet(new URL(launchConfig.jwksUrl));
132
+ this.jwksCache.set(launchConfig.jwksUrl, jwks);
133
+ }
134
+ const { payload } = await jwtVerify(validatedParams.idToken, jwks);
135
+ // 4. Verify our state JWT
136
+ const { payload: stateData } = await jwtVerify(validatedParams.state, this.config.stateSecret);
137
+ // 5. Parse and validate LMS JWT
138
+ const validated = LTI13JwtPayloadSchema.parse(payload);
139
+ // 6. Verify client id matches (audience claim)
140
+ if (validated.aud !== launchConfig.clientId) {
141
+ throw new Error(`Invalid client_id: expected ${launchConfig.clientId}, got ${validated.aud}`);
142
+ }
143
+ // 7. Verify nonce matches
144
+ if (stateData.nonce !== validated.nonce) {
145
+ throw new Error('Nonce mismatch');
146
+ }
147
+ // 8. Check nonce hasn't been used before (prevent replay attacks)
148
+ const isValidNonce = await this.config.storage.validateNonce(validated.nonce);
149
+ if (!isValidNonce) {
150
+ throw new Error('Nonce has already been used or expired');
151
+ }
152
+ return validated;
153
+ }
154
+ /**
155
+ * Generates JSON Web Key Set (JWKS) containing the tool's public key for platform verification.
156
+ *
157
+ * @returns JWKS object with the tool's public key for JWT signature verification
158
+ */
159
+ async getJWKS() {
160
+ const publicJwk = await exportJWK(this.config.keyPair.publicKey);
161
+ return {
162
+ keys: [
163
+ {
164
+ ...publicJwk,
165
+ use: 'sig',
166
+ alg: 'RS256',
167
+ kid: this.config.security?.keyId ?? 'main',
168
+ },
169
+ ],
170
+ };
171
+ }
172
+ /**
173
+ * Creates and stores a new LTI session from validated JWT payload.
174
+ *
175
+ * @param lti13JwtPayload - Validated LTI 1.3 JWT payload from successful launch
176
+ * @returns Created session object with user, context, and service information
177
+ */
178
+ async createSession(lti13JwtPayload) {
179
+ const session = createSession(lti13JwtPayload);
180
+ await this.config.storage.addSession(session);
181
+ return session;
182
+ }
183
+ /**
184
+ * Retrieves an existing LTI session by session ID.
185
+ *
186
+ * @param sessionId - Unique session identifier
187
+ * @returns Session object if found, undefined otherwise
188
+ */
189
+ async getSession(sessionId) {
190
+ const validatedSessionId = SessionIdSchema.parse(sessionId);
191
+ return await this.config.storage.getSession(validatedSessionId);
192
+ }
193
+ /**
194
+ * Submits a grade score to the platform using Assignment and Grade Services (AGS).
195
+ *
196
+ * @param session - Active LTI session containing AGS service endpoints
197
+ * @param score - Score submission data including grade value and user ID
198
+ * @returns Result of the score submission
199
+ * @throws {Error} When AGS is not available or submission fails
200
+ */
201
+ async submitScore(session, score) {
202
+ if (!session) {
203
+ throw new Error('session is required');
204
+ }
205
+ if (!score) {
206
+ throw new Error('score is required');
207
+ }
208
+ return await this.agsService.submitScore(session, score);
209
+ }
210
+ // Client management
211
+ /**
212
+ * Retrieves all configured LTI client platforms.
213
+ *
214
+ * @returns Array of client configurations (without deployment details)
215
+ */
216
+ async listClients() {
217
+ return await this.config.storage.listClients();
218
+ }
219
+ /**
220
+ * Updates an existing client configuration.
221
+ *
222
+ * @param clientId - Unique client identifier
223
+ * @param client - Partial client object with fields to update
224
+ */
225
+ async updateClient(clientId, client) {
226
+ const validated = UpdateClientSchema.parse(client);
227
+ return await this.config.storage.updateClient(clientId, validated);
228
+ }
229
+ /**
230
+ * Retrieves a specific client configuration by ID.
231
+ *
232
+ * @param clientId - Unique client identifier
233
+ * @returns Client configuration if found, undefined otherwise
234
+ */
235
+ async getClientById(clientId) {
236
+ return await this.config.storage.getClientById(clientId);
237
+ }
238
+ /**
239
+ * Adds a new LTI client platform configuration.
240
+ *
241
+ * @param client - Client configuration (ID will be auto-generated)
242
+ * @returns The generated client ID
243
+ */
244
+ async addClient(client) {
245
+ const validated = AddClientSchema.parse(client);
246
+ return await this.config.storage.addClient(validated);
247
+ }
248
+ /**
249
+ * Removes a client configuration and all its deployments.
250
+ *
251
+ * @param clientId - Unique client identifier
252
+ */
253
+ async deleteClient(clientId) {
254
+ return await this.config.storage.deleteClient(clientId);
255
+ }
256
+ // Deployment management
257
+ /**
258
+ * Lists all deployments for a specific client platform.
259
+ *
260
+ * @param clientId - Client identifier
261
+ * @returns Array of deployment configurations for the client
262
+ */
263
+ async listDeployments(clientId) {
264
+ return await this.config.storage.listDeployments(clientId);
265
+ }
266
+ /**
267
+ * Retrieves a specific deployment configuration.
268
+ *
269
+ * @param clientId - Client identifier
270
+ * @param deploymentId - Deployment identifier
271
+ * @returns Deployment configuration if found, undefined otherwise
272
+ */
273
+ async getDeployment(clientId, deploymentId) {
274
+ return await this.config.storage.getDeployment(clientId, deploymentId);
275
+ }
276
+ /**
277
+ * Adds a new deployment to an existing client.
278
+ *
279
+ * @param clientId - Client identifier
280
+ * @param deployment - Deployment configuration to add
281
+ * @returns The generated deployment ID
282
+ */
283
+ async addDeployment(clientId, deployment) {
284
+ return await this.config.storage.addDeployment(clientId, deployment);
285
+ }
286
+ /**
287
+ * Updates an existing deployment configuration.
288
+ *
289
+ * @param clientId - Client identifier
290
+ * @param deploymentId - Deployment identifier
291
+ * @param deployment - Partial deployment object with fields to update
292
+ */
293
+ async updateDeployment(clientId, deploymentId, deployment) {
294
+ return await this.config.storage.updateDeployment(clientId, deploymentId, deployment);
295
+ }
296
+ /**
297
+ * Removes a deployment from a client.
298
+ *
299
+ * @param clientId - Client identifier
300
+ * @param deploymentId - Deployment identifier to remove
301
+ */
302
+ async deleteDeployment(clientId, deploymentId) {
303
+ return await this.config.storage.deleteDeployment(clientId, deploymentId);
304
+ }
305
+ }
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+ export declare const ClientSchema: z.ZodObject<{
3
+ id: z.ZodUUID;
4
+ name: z.ZodString;
5
+ iss: z.ZodURL;
6
+ clientId: z.ZodString;
7
+ authUrl: z.ZodURL;
8
+ tokenUrl: z.ZodURL;
9
+ jwksUrl: z.ZodURL;
10
+ deployments: z.ZodArray<z.ZodObject<{
11
+ id: z.ZodUUID;
12
+ deploymentId: z.ZodString;
13
+ name: z.ZodOptional<z.ZodString>;
14
+ description: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>>;
16
+ }, z.core.$strip>;
17
+ export declare const AddClientSchema: z.ZodObject<{
18
+ name: z.ZodString;
19
+ iss: z.ZodURL;
20
+ clientId: z.ZodString;
21
+ authUrl: z.ZodURL;
22
+ tokenUrl: z.ZodURL;
23
+ jwksUrl: z.ZodURL;
24
+ }, z.core.$strip>;
25
+ export declare const UpdateClientSchema: z.ZodObject<{
26
+ name: z.ZodString;
27
+ iss: z.ZodURL;
28
+ clientId: z.ZodString;
29
+ authUrl: z.ZodURL;
30
+ tokenUrl: z.ZodURL;
31
+ jwksUrl: z.ZodURL;
32
+ }, z.core.$strip>;
33
+ //# sourceMappingURL=client.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/client.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;iBASvB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;iBAAqD,CAAC;AAClF,eAAO,MAAM,kBAAkB;;;;;;;iBAAqD,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ import { DeploymentSchema } from './deployment.schema';
3
+ export const ClientSchema = z.object({
4
+ id: z.uuid().describe('Internal stable UUID for the client'),
5
+ name: z.string().min(1).describe('human-readable name for the platform'),
6
+ iss: z.url().describe('Platform issuer (unique identifier)'),
7
+ clientId: z.string().min(1).describe("Your app's client ID on this platform"),
8
+ authUrl: z.url().describe("Platform's auth endpoint"),
9
+ tokenUrl: z.url().describe("Platform's token endpoint"),
10
+ jwksUrl: z.url().describe("Platform's JWKS endpoint"),
11
+ deployments: z.array(DeploymentSchema),
12
+ });
13
+ export const AddClientSchema = ClientSchema.omit({ id: true, deployments: true });
14
+ export const UpdateClientSchema = ClientSchema.omit({ id: true, deployments: true });
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Common validation schemas used across the LTI tool
4
+ */
5
+ export declare const SessionIdSchema: z.ZodString;
6
+ //# sourceMappingURL=common.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/common.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AAEH,eAAO,MAAM,eAAe,aAA6C,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Common validation schemas used across the LTI tool
4
+ */
5
+ export const SessionIdSchema = z.string().min(1, 'sessionId is required');
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ export declare const DeploymentSchema: z.ZodObject<{
3
+ id: z.ZodUUID;
4
+ deploymentId: z.ZodString;
5
+ name: z.ZodOptional<z.ZodString>;
6
+ description: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ //# sourceMappingURL=deployment.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/deployment.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB;;;;;iBAS3B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ export const DeploymentSchema = z.object({
3
+ id: z.uuid().describe('Internal stable UUID for this deployment configuration'),
4
+ deploymentId: z.string().min(1).describe('LMS-provided deployment identifier'),
5
+ name: z
6
+ .string()
7
+ .min(1)
8
+ .optional()
9
+ .describe('Optional human-readable name for the deployment'),
10
+ description: z.string().optional().describe('Optional description of the deployment'),
11
+ });
@@ -0,0 +1,5 @@
1
+ export { SessionIdSchema } from './common.schema.js';
2
+ export { LTI13JwtPayloadSchema, type LTI13JwtPayload, } from './lti13/lti13JwtPayload.schema.js';
3
+ export { LTI13LaunchSchema, VerifyLaunchParamsSchema, } from './lti13/lti13Launch.schema.js';
4
+ export { HandleLoginParamsSchema, LTI13LoginSchema } from './lti13/lti13Login.schema.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { SessionIdSchema } from './common.schema.js';
2
+ export { LTI13JwtPayloadSchema, } from './lti13/lti13JwtPayload.schema.js';
3
+ export { LTI13LaunchSchema, VerifyLaunchParamsSchema, } from './lti13/lti13Launch.schema.js';
4
+ export { HandleLoginParamsSchema, LTI13LoginSchema } from './lti13/lti13Login.schema.js';
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema for submitting grades via LTI Assignment and Grade Services (AGS).
4
+ * Validates score data according to LTI AGS v2.0 specification.
5
+ *
6
+ * @see https://www.imsglobal.org/spec/lti-ags/v2p0/#score-publish-service
7
+ */
8
+ export declare const ScoreSubmissionSchema: z.ZodObject<{
9
+ scoreGiven: z.ZodNumber;
10
+ scoreMaximum: z.ZodNumber;
11
+ comment: z.ZodOptional<z.ZodString>;
12
+ userId: z.ZodOptional<z.ZodString>;
13
+ timestamp: z.ZodOptional<z.ZodISODateTime>;
14
+ activityProgress: z.ZodDefault<z.ZodEnum<{
15
+ Initialized: "Initialized";
16
+ Started: "Started";
17
+ InProgress: "InProgress";
18
+ Submitted: "Submitted";
19
+ Completed: "Completed";
20
+ }>>;
21
+ gradingProgress: z.ZodDefault<z.ZodEnum<{
22
+ NotReady: "NotReady";
23
+ Failed: "Failed";
24
+ Pending: "Pending";
25
+ PendingManual: "PendingManual";
26
+ FullyGraded: "FullyGraded";
27
+ }>>;
28
+ }, z.core.$strip>;
29
+ /**
30
+ * Type representing a validated score submission for LTI AGS.
31
+ * Contains grade data and metadata to be sent to the platform.
32
+ */
33
+ export type ScoreSubmission = z.infer<typeof ScoreSubmissionSchema>;
34
+ //# sourceMappingURL=scoreSubmission.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoreSubmission.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/ags/scoreSubmission.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;iBAuChC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}