@lti-tool/core 0.11.1 → 0.12.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 (59) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/interfaces/index.d.ts +1 -0
  3. package/dist/interfaces/index.d.ts.map +1 -1
  4. package/dist/interfaces/ltiConfig.d.ts +23 -0
  5. package/dist/interfaces/ltiConfig.d.ts.map +1 -1
  6. package/dist/interfaces/ltiDynamicRegistrationSession.d.ts +14 -0
  7. package/dist/interfaces/ltiDynamicRegistrationSession.d.ts.map +1 -0
  8. package/dist/interfaces/ltiDynamicRegistrationSession.js +1 -0
  9. package/dist/interfaces/ltiStorage.d.ts +22 -0
  10. package/dist/interfaces/ltiStorage.d.ts.map +1 -1
  11. package/dist/ltiTool.d.ts +64 -1
  12. package/dist/ltiTool.d.ts.map +1 -1
  13. package/dist/ltiTool.js +87 -1
  14. package/dist/schemas/index.d.ts +5 -0
  15. package/dist/schemas/index.d.ts.map +1 -1
  16. package/dist/schemas/index.js +4 -0
  17. package/dist/schemas/lti13/dynamicRegistration/ltiDynamicRegistration.schema.d.ts +34 -0
  18. package/dist/schemas/lti13/dynamicRegistration/ltiDynamicRegistration.schema.d.ts.map +1 -0
  19. package/dist/schemas/lti13/dynamicRegistration/ltiDynamicRegistration.schema.js +28 -0
  20. package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.d.ts +101 -0
  21. package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.d.ts.map +1 -0
  22. package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.js +51 -0
  23. package/dist/schemas/lti13/dynamicRegistration/openIDConfiguration.schema.d.ts +59 -0
  24. package/dist/schemas/lti13/dynamicRegistration/openIDConfiguration.schema.d.ts.map +1 -0
  25. package/dist/schemas/lti13/dynamicRegistration/openIDConfiguration.schema.js +53 -0
  26. package/dist/schemas/lti13/dynamicRegistration/registrationRequest.schema.d.ts +7 -0
  27. package/dist/schemas/lti13/dynamicRegistration/registrationRequest.schema.d.ts.map +1 -0
  28. package/dist/schemas/lti13/dynamicRegistration/registrationRequest.schema.js +5 -0
  29. package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.d.ts +73 -0
  30. package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.d.ts.map +1 -0
  31. package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.js +61 -0
  32. package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.d.ts +103 -0
  33. package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.d.ts.map +1 -0
  34. package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.js +49 -0
  35. package/dist/services/dynamicRegistration.service.d.ts +121 -0
  36. package/dist/services/dynamicRegistration.service.d.ts.map +1 -0
  37. package/dist/services/dynamicRegistration.service.js +312 -0
  38. package/dist/services/dynamicRegistrationHandlers/moodle.d.ts +48 -0
  39. package/dist/services/dynamicRegistrationHandlers/moodle.d.ts.map +1 -0
  40. package/dist/services/dynamicRegistrationHandlers/moodle.js +152 -0
  41. package/dist/utils/ltiPlatformCapabilities.d.ts +65 -0
  42. package/dist/utils/ltiPlatformCapabilities.d.ts.map +1 -0
  43. package/dist/utils/ltiPlatformCapabilities.js +73 -0
  44. package/package.json +1 -1
  45. package/src/interfaces/index.ts +1 -0
  46. package/src/interfaces/ltiConfig.ts +25 -0
  47. package/src/interfaces/ltiDynamicRegistrationSession.ts +14 -0
  48. package/src/interfaces/ltiStorage.ts +32 -0
  49. package/src/ltiTool.ts +122 -1
  50. package/src/schemas/index.ts +17 -0
  51. package/src/schemas/lti13/dynamicRegistration/ltiDynamicRegistration.schema.ts +31 -0
  52. package/src/schemas/lti13/dynamicRegistration/ltiMessages.schema.ts +59 -0
  53. package/src/schemas/lti13/dynamicRegistration/openIDConfiguration.schema.ts +60 -0
  54. package/src/schemas/lti13/dynamicRegistration/registrationRequest.schema.ts +8 -0
  55. package/src/schemas/lti13/dynamicRegistration/registrationResponse.schema.ts +67 -0
  56. package/src/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.ts +55 -0
  57. package/src/services/dynamicRegistration.service.ts +406 -0
  58. package/src/services/dynamicRegistrationHandlers/moodle.ts +184 -0
  59. package/src/utils/ltiPlatformCapabilities.ts +86 -0
@@ -1,6 +1,7 @@
1
1
  export type { LTIClient } from './ltiClient.js';
2
2
  export type { LTIConfig } from './ltiConfig.js';
3
3
  export type { LTIDeployment } from './ltiDeployment.js';
4
+ export type { LTIDynamicRegistrationSession } from './ltiDynamicRegistrationSession.js';
4
5
  export type { LTILaunchConfig } from './ltiLaunchConfig.js';
5
6
  export type { LTISession } from './ltiSession.js';
6
7
  export type { LTIStorage } from './ltiStorage.js';
@@ -2,6 +2,28 @@ import type { Logger } from 'pino';
2
2
 
3
3
  import type { LTIStorage } from './ltiStorage.js';
4
4
 
5
+ /** Dynamic registration configuration for LTI 1.3 tool registration */
6
+ export interface DynamicRegistrationConfig {
7
+ /** Base URL of the LTI tool (e.g., 'https://my-tool.com') */
8
+ url: string;
9
+ /** Display name shown to users in the LMS (e.g., 'My Learning Tool') */
10
+ name: string;
11
+ /** Optional description of the tool's functionality */
12
+ description?: string;
13
+ /** Optional URL to tool logo image for LMS display */
14
+ logo?: string;
15
+ /** Additional redirect URIs beyond the default /lti/launch endpoint */
16
+ redirectUris?: string[];
17
+ /** Optional custom deep linking content selection endpoint (defaults to {url}/lti/deep-linking) */
18
+ deepLinkingUri?: string;
19
+ /** Optional custom login endpoint (defaults to {url}/lti/login) */
20
+ loginUri?: string;
21
+ /** Optional custom launch endpoint (defaults to {url}/lti/launch) */
22
+ launchUri?: string;
23
+ /** Optional custom JWKS endpoint (defaults to {url}/lti/jwks) */
24
+ jwksUri?: string;
25
+ }
26
+
5
27
  /**
6
28
  * Configuration object for initializing an LTI Tool instance.
7
29
  * Contains cryptographic keys, secrets, and storage adapter.
@@ -28,4 +50,7 @@ export interface LTIConfig {
28
50
  /** Nonce expiration time in seconds (defaults to 600 = 10 minutes) */
29
51
  nonceExpirationSeconds?: number;
30
52
  };
53
+
54
+ /** Dynamic registration configuration for LTI 1.3 tool registration */
55
+ dynamicRegistration?: DynamicRegistrationConfig;
31
56
  }
@@ -0,0 +1,14 @@
1
+ import type { OpenIDConfiguration } from '../schemas/lti13/dynamicRegistration/openIDConfiguration.schema';
2
+
3
+ /**
4
+ * Temporary session data stored during LTI 1.3 dynamic registration flow.
5
+ * Used to maintain state between the registration initiation and completion steps.
6
+ */
7
+ export interface LTIDynamicRegistrationSession {
8
+ /** Platform's OpenID Connect configuration retrieved during registration initiation */
9
+ openIdConfiguration: OpenIDConfiguration;
10
+ /** Registration token provided by the platform for this registration attempt */
11
+ registrationToken?: string;
12
+ /** Unix timestamp (milliseconds) when this session expires and should be cleaned up */
13
+ expiresAt: number;
14
+ }
@@ -1,5 +1,6 @@
1
1
  import type { LTIClient } from './ltiClient.js';
2
2
  import type { LTIDeployment } from './ltiDeployment.js';
3
+ import type { LTIDynamicRegistrationSession } from './ltiDynamicRegistrationSession.js';
3
4
  import type { LTILaunchConfig } from './ltiLaunchConfig.js';
4
5
  import type { LTISession } from './ltiSession.js';
5
6
 
@@ -158,4 +159,35 @@ export interface LTIStorage {
158
159
  * @param launchConfig - Complete launch configuration with auth URLs and keys
159
160
  */
160
161
  saveLaunchConfig(launchConfig: LTILaunchConfig): Promise<void>;
162
+
163
+ // Dynamic Registration
164
+
165
+ /**
166
+ * Stores a temporary registration session during LTI 1.3 dynamic registration flow.
167
+ * Sessions have a TTL and are automatically cleaned up when expired.
168
+ *
169
+ * @param sessionId - Unique session identifier (typically a UUID)
170
+ * @param session - Registration session data including platform config and tokens
171
+ */
172
+ setRegistrationSession(
173
+ sessionId: string,
174
+ session: LTIDynamicRegistrationSession,
175
+ ): Promise<void>;
176
+
177
+ /**
178
+ * Retrieves a registration session by its ID for validation during completion.
179
+ *
180
+ * @param sessionId - Unique session identifier
181
+ * @returns Registration session if found and not expired, undefined otherwise
182
+ */
183
+ getRegistrationSession(
184
+ sessionId: string,
185
+ ): Promise<LTIDynamicRegistrationSession | undefined>;
186
+
187
+ /**
188
+ * Removes a registration session from storage (cleanup after completion or expiration).
189
+ *
190
+ * @param sessionId - Unique session identifier to delete
191
+ */
192
+ deleteRegistrationSession(sessionId: string): Promise<void>;
161
193
  }
package/src/ltiTool.ts CHANGED
@@ -5,12 +5,15 @@ import type { JWKS } from './interfaces/jwks.js';
5
5
  import type { LTIClient } from './interfaces/ltiClient.js';
6
6
  import type { LTIConfig } from './interfaces/ltiConfig.js';
7
7
  import type { LTIDeployment } from './interfaces/ltiDeployment.js';
8
+ import type { LTIDynamicRegistrationSession } from './interfaces/ltiDynamicRegistrationSession.js';
8
9
  import type { LTISession } from './interfaces/ltiSession.js';
9
10
  import { AddClientSchema, UpdateClientSchema } from './schemas/client.schema.js';
10
11
  import {
12
+ type DynamicRegistrationForm,
11
13
  HandleLoginParamsSchema,
12
14
  type LTI13JwtPayload,
13
15
  LTI13JwtPayloadSchema,
16
+ type RegistrationRequest,
14
17
  SessionIdSchema,
15
18
  VerifyLaunchParamsSchema,
16
19
  } from './schemas/index.js';
@@ -24,11 +27,13 @@ import {
24
27
  } from './schemas/lti13/ags/lineItem.schema.js';
25
28
  import { type Results, ResultsSchema } from './schemas/lti13/ags/result.schema.js';
26
29
  import { type ScoreSubmission } from './schemas/lti13/ags/scoreSubmission.schema.js';
30
+ import { type OpenIDConfiguration } from './schemas/lti13/dynamicRegistration/openIDConfiguration.schema.js';
27
31
  import {
28
32
  type Member,
29
33
  NRPSContextMembershipResponseSchema,
30
34
  } from './schemas/lti13/nrps/contextMembership.schema.js';
31
35
  import { AGSService } from './services/ags.service.js';
36
+ import { DynamicRegistrationService } from './services/dynamicRegistration.service.js';
32
37
  import { NRPSService } from './services/nrps.service.js';
33
38
  import { createSession } from './services/session.service.js';
34
39
  import { TokenService } from './services/token.service.js';
@@ -64,6 +69,7 @@ export class LTITool {
64
69
  private tokenService: TokenService;
65
70
  private agsService: AGSService;
66
71
  private nrpsService: NRPSService;
72
+ private dynamicRegistrationService?: DynamicRegistrationService;
67
73
 
68
74
  /**
69
75
  * Creates a new LTI Tool instance.
@@ -90,6 +96,13 @@ export class LTITool {
90
96
  this.config.storage,
91
97
  this.logger,
92
98
  );
99
+ if (this.config.dynamicRegistration) {
100
+ this.dynamicRegistrationService = new DynamicRegistrationService(
101
+ this.config.storage,
102
+ this.config.dynamicRegistration,
103
+ this.logger,
104
+ );
105
+ }
93
106
  }
94
107
 
95
108
  /**
@@ -444,7 +457,6 @@ export class LTITool {
444
457
 
445
458
  const response = await this.nrpsService.getMembers(session);
446
459
  const data = await response.json();
447
- console.log(data);
448
460
  const validated = NRPSContextMembershipResponseSchema.parse(data);
449
461
 
450
462
  // Transform to clean camelCase format
@@ -462,6 +474,76 @@ export class LTITool {
462
474
  }));
463
475
  }
464
476
 
477
+ /**
478
+ * Fetches and validates the OpenID Connect configuration from an LTI platform during dynamic registration.
479
+ * Validates that the OIDC endpoint and issuer have matching hostnames for security.
480
+ *
481
+ * @param registrationRequest - Registration request containing openid_configuration URL and optional registration_token
482
+ * @returns Validated OpenID configuration with platform endpoints and supported features
483
+ * @throws {Error} When the configuration fetch fails, validation fails, or hostname mismatch detected
484
+ *
485
+ * @example
486
+ * ```typescript
487
+ * const config = await ltiTool.fetchPlatformConfiguration({
488
+ * openid_configuration: 'https://platform.edu/.well-known/openid_configuration',
489
+ * registration_token: 'optional-bearer-token'
490
+ * });
491
+ * console.log('Platform issuer:', config.issuer);
492
+ * ```
493
+ */
494
+ async fetchPlatformConfiguration(
495
+ registrationRequest: RegistrationRequest,
496
+ ): Promise<OpenIDConfiguration> {
497
+ if (!this.dynamicRegistrationService) {
498
+ throw new Error('Dynamic registration service is not configured');
499
+ }
500
+ return await this.dynamicRegistrationService.fetchPlatformConfiguration(
501
+ registrationRequest,
502
+ );
503
+ }
504
+
505
+ /**
506
+ * Initiates LTI 1.3 dynamic registration by fetching platform configuration and generating registration form.
507
+ * Creates a temporary session and returns vendor-specific HTML form for service selection.
508
+ *
509
+ * @param registrationRequest - Registration request containing openid_configuration URL and optional registration_token
510
+ * @param requestPath - Current request path used to build form action URLs
511
+ * @returns HTML form for service selection and registration completion
512
+ * @throws {Error} When dynamic registration service is not configured or platform configuration fails
513
+ */
514
+ async initiateDynamicRegistration(
515
+ registrationRequest: RegistrationRequest,
516
+ requestPath: string,
517
+ ): Promise<string> {
518
+ if (!this.dynamicRegistrationService) {
519
+ throw new Error('Dynamic registration service is not configured');
520
+ }
521
+ return await this.dynamicRegistrationService.initiateDynamicRegistration(
522
+ registrationRequest,
523
+ requestPath,
524
+ );
525
+ }
526
+
527
+ /**
528
+ * Completes LTI 1.3 dynamic registration by processing form submission and storing client configuration.
529
+ * Validates session, registers with platform, stores client/deployment data, and returns success page.
530
+ *
531
+ * @param dynamicRegistrationForm - Validated form data containing selected services and session token
532
+ * @returns HTML success page with registration details and close button
533
+ * @throws {Error} When dynamic registration service is not configured or registration process fails
534
+ */
535
+ async completeDynamicRegistration(
536
+ dynamicRegistrationForm: DynamicRegistrationForm,
537
+ ): Promise<string> {
538
+ if (!this.dynamicRegistrationService) {
539
+ throw new Error('Dynmaic registration service is not configured');
540
+ }
541
+
542
+ return await this.dynamicRegistrationService.completeDynamicRegistration(
543
+ dynamicRegistrationForm,
544
+ );
545
+ }
546
+
465
547
  // Client management
466
548
 
467
549
  /**
@@ -581,4 +663,43 @@ export class LTITool {
581
663
  async deleteDeployment(clientId: string, deploymentId: string): Promise<void> {
582
664
  return await this.config.storage.deleteDeployment(clientId, deploymentId);
583
665
  }
666
+
667
+ // Dynamic Registration Session Management
668
+
669
+ /**
670
+ * Stores a temporary registration session during LTI 1.3 dynamic registration flow.
671
+ * Sessions automatically expire after the configured TTL period.
672
+ *
673
+ * @param sessionId - Unique session identifier (typically a UUID)
674
+ * @param session - Registration session data including platform config and tokens
675
+ */
676
+ async setRegistrationSession(
677
+ sessionId: string,
678
+ session: LTIDynamicRegistrationSession,
679
+ ): Promise<void> {
680
+ return await this.config.storage.setRegistrationSession(sessionId, session);
681
+ }
682
+
683
+ /**
684
+ * Retrieves a registration session by its ID for validation during completion.
685
+ * Returns undefined if the session is not found or has expired.
686
+ *
687
+ * @param sessionId - Unique session identifier
688
+ * @returns Registration session if found and not expired, undefined otherwise
689
+ */
690
+ async getRegistrationSession(
691
+ sessionId: string,
692
+ ): Promise<LTIDynamicRegistrationSession | undefined> {
693
+ return await this.config.storage.getRegistrationSession(sessionId);
694
+ }
695
+
696
+ /**
697
+ * Removes a registration session from storage after completion or expiration.
698
+ * Used for cleanup to prevent session accumulation.
699
+ *
700
+ * @param sessionId - Unique session identifier to delete
701
+ */
702
+ async deleteRegistrationSession(sessionId: string): Promise<void> {
703
+ return await this.config.storage.deleteRegistrationSession(sessionId);
704
+ }
584
705
  }
@@ -1,4 +1,21 @@
1
1
  export { SessionIdSchema } from './common.schema.js';
2
+ export {
3
+ DynamicRegistrationFormSchema,
4
+ type DynamicRegistrationForm,
5
+ } from './lti13/dynamicRegistration/ltiDynamicRegistration.schema.js';
6
+ export {
7
+ LTIMessagesArraySchema,
8
+ type LTIMessage,
9
+ } from './lti13/dynamicRegistration/ltiMessages.schema.js';
10
+ export { type OpenIDConfiguration } from './lti13/dynamicRegistration/openIDConfiguration.schema.js';
11
+ export {
12
+ RegistrationRequestSchema,
13
+ type RegistrationRequest,
14
+ } from './lti13/dynamicRegistration/registrationRequest.schema.js';
15
+ export {
16
+ RegistrationResponseSchema,
17
+ type RegistrationResponse,
18
+ } from './lti13/dynamicRegistration/registrationResponse.schema.js';
2
19
  export {
3
20
  LTI13JwtPayloadSchema,
4
21
  type LTI13JwtPayload,
@@ -0,0 +1,31 @@
1
+ import * as z from 'zod';
2
+
3
+ /**
4
+ * Zod schema for validating LTI 1.3 dynamic registration form submissions.
5
+ * Represents the service selections and session data submitted by an administrator during tool registration.
6
+ *
7
+ * @property services - Optional array of LTI Advantage services the admin chooses to enable:
8
+ * - 'ags': Assignment and Grade Services for grade passback
9
+ * - 'nrps': Names and Role Provisioning Services for roster access
10
+ * - 'deep_linking': Deep Linking for content selection
11
+ * - 'tool_settings': Tool Settings service for configuration storage
12
+ * - 'basic_outcome': Basic Outcome service for simple grade reporting
13
+ * @property sessionToken - Security token to validate the registration session and prevent CSRF attacks
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const formData = {
18
+ * services: ['ags', 'nrps', 'deep_linking'],
19
+ * sessionToken: 'uuid-session-token'
20
+ * };
21
+ * const validated = DynamicRegistrationFormSchema.parse(formData);
22
+ * ```
23
+ */
24
+ export const DynamicRegistrationFormSchema = z.object({
25
+ services: z
26
+ .array(z.enum(['ags', 'nrps', 'deep_linking', 'tool_settings', 'basic_outcome']))
27
+ .optional(),
28
+ sessionToken: z.string(),
29
+ });
30
+
31
+ export type DynamicRegistrationForm = z.infer<typeof DynamicRegistrationFormSchema>;
@@ -0,0 +1,59 @@
1
+ import * as z from 'zod';
2
+
3
+ /**
4
+ * Zod schema for LTI 1.3 Resource Link Request message configuration.
5
+ * Represents the standard tool launch message type for accessing tool content.
6
+ * This is the most common LTI message type for regular tool launches.
7
+ */
8
+ const LTIResourceLinkMessageSchema = z.object({
9
+ type: z.literal('LtiResourceLinkRequest'),
10
+ });
11
+
12
+ /**
13
+ * Zod schema for LTI 1.3 Deep Linking Request message configuration.
14
+ * Represents the message type used when the tool supports content selection/authoring.
15
+ * Allows instructors to select or create content that will be linked back to the LMS.
16
+ *
17
+ * @property type - Always 'LtiDeepLinkingRequest' for deep linking messages
18
+ * @property target_link_uri - Optional URL where deep linking requests should be sent
19
+ * @property label - Optional human-readable label for the deep linking option
20
+ * @property placements - Optional array of where the tool can be placed in the LMS UI
21
+ * @property supported_types - Optional array of content types the tool can create/select
22
+ * @property supported_media_types - Optional array of MIME types the tool supports
23
+ * @property roles - Optional array of LIS role URIs that can access deep linking
24
+ * @property custom_parameters - Optional custom parameters for deep linking configuration
25
+ */
26
+ const LTIDeepLinkingMessageSchema = z.object({
27
+ type: z.literal('LtiDeepLinkingRequest'),
28
+ target_link_uri: z.url().optional(),
29
+ label: z.string().optional(),
30
+ placements: z
31
+ .array(z.enum(['ContentArea', 'RichTextEditor', 'CourseNavigation']))
32
+ .optional(),
33
+ supported_types: z
34
+ .array(z.enum(['ltiResourceLink', 'file', 'html', 'link', 'image']))
35
+ .optional(),
36
+ supported_media_types: z.array(z.string()).optional(), // e.g., ['image/*', 'video/*']
37
+ roles: z.array(z.string()).optional(), // LIS role URIs
38
+ custom_parameters: z.record(z.string(), z.string()).optional(),
39
+ });
40
+
41
+ /**
42
+ * Discriminated union schema for all supported LTI 1.3 message types.
43
+ * Used during dynamic registration to declare which message types the tool supports.
44
+ * The platform uses this to determine what launch options to provide to users.
45
+ */
46
+ export const LTIMessageSchema = z.discriminatedUnion('type', [
47
+ LTIResourceLinkMessageSchema,
48
+ LTIDeepLinkingMessageSchema,
49
+ ]);
50
+
51
+ /**
52
+ * Schema for validating arrays of LTI message configurations.
53
+ * Used in tool registration payloads to declare multiple supported message types.
54
+ */
55
+ export const LTIMessagesArraySchema = z.array(LTIMessageSchema);
56
+
57
+ export type LTIMessage = z.infer<typeof LTIMessageSchema>;
58
+ export type LTIResourceLinkMessage = z.infer<typeof LTIResourceLinkMessageSchema>;
59
+ export type LTIDeepLinkingMessage = z.infer<typeof LTIDeepLinkingMessageSchema>;
@@ -0,0 +1,60 @@
1
+ import * as z from 'zod';
2
+
3
+ /**
4
+ * Zod schema for LTI platform-specific configuration within OpenID Connect Discovery.
5
+ * Contains LTI-specific metadata about the platform's capabilities and supported features.
6
+ *
7
+ * @property product_family_code - Platform identifier (e.g., 'moodle', 'canvas', 'sakai')
8
+ * @property version - Platform version string
9
+ * @property messages_supported - Array of LTI message types the platform supports
10
+ * @property variables - Optional array of custom variable names the platform supports
11
+ */
12
+ export const ltiPlatformConfigurationSchema = z.object({
13
+ product_family_code: z.string(),
14
+ version: z.string(),
15
+ messages_supported: z.array(
16
+ z
17
+ .object({
18
+ type: z.string(),
19
+ placements: z.array(z.string()).optional(),
20
+ })
21
+ .loose(),
22
+ ),
23
+ variables: z.array(z.string()).optional(),
24
+ });
25
+
26
+ /**
27
+ * Zod schema for validating OpenID Connect Discovery configuration from LTI 1.3 platforms.
28
+ * This configuration is fetched during dynamic registration to discover platform endpoints,
29
+ * supported features, and security requirements. Used to validate the response from the
30
+ * platform's /.well-known/openid_configuration endpoint.
31
+ *
32
+ * @property issuer - Platform's issuer URL (must match hostname of configuration endpoint)
33
+ * @property authorization_endpoint - OAuth 2.0 authorization endpoint for LTI launches
34
+ * @property registration_endpoint - Dynamic registration endpoint for tool registration
35
+ * @property jwks_uri - JSON Web Key Set endpoint for signature verification
36
+ * @property token_endpoint - OAuth 2.0 token endpoint for service access tokens
37
+ * @property scopes_supported - Array of OAuth scopes the platform supports (AGS, NRPS, etc.)
38
+ * @property https://purl.imsglobal.org/spec/lti-platform-configuration - LTI-specific platform metadata
39
+ */
40
+ export const openIDConfigurationSchema = z
41
+ .object({
42
+ issuer: z.url(),
43
+ authorization_endpoint: z.url(),
44
+ registration_endpoint: z.url(),
45
+ jwks_uri: z.url(),
46
+ token_endpoint: z.url(),
47
+ token_endpoint_auth_methods_supported: z.array(z.string()),
48
+ token_endpoint_auth_signing_alg_values_supported: z.array(z.string()),
49
+ scopes_supported: z.array(z.string()),
50
+ response_types_supported: z.array(z.string()),
51
+ id_token_signing_alg_values_supported: z.array(z.string()),
52
+ claims_supported: z.array(z.string()),
53
+ subject_types_supported: z.array(z.string()),
54
+ authorization_server: z.string().optional(),
55
+ 'https://purl.imsglobal.org/spec/lti-platform-configuration':
56
+ ltiPlatformConfigurationSchema,
57
+ })
58
+ .loose();
59
+
60
+ export type OpenIDConfiguration = z.infer<typeof openIDConfigurationSchema>;
@@ -0,0 +1,8 @@
1
+ import * as z from 'zod';
2
+
3
+ export const RegistrationRequestSchema = z.object({
4
+ openid_configuration: z.url(),
5
+ registration_token: z.string().optional(),
6
+ });
7
+
8
+ export type RegistrationRequest = z.infer<typeof RegistrationRequestSchema>;
@@ -0,0 +1,67 @@
1
+ import * as z from 'zod';
2
+
3
+ import { LTIMessagesArraySchema } from './ltiMessages.schema';
4
+
5
+ /**
6
+ * Zod schema for LTI tool configuration within dynamic registration response.
7
+ * Contains tool-specific metadata returned by the platform after successful registration.
8
+ *
9
+ * @property domain - Tool's domain name for security validation
10
+ * @property target_link_uri - Optional default launch URL for the tool
11
+ * @property custom_parameters - Optional custom parameters passed to the tool
12
+ * @property claims - Array of JWT claims the tool requires (e.g., 'iss', 'sub', 'name', 'email')
13
+ * @property messages - Array of LTI message types the tool supports
14
+ * @property version - Optional tool version string
15
+ * @property deployment_id - Optional deployment identifier assigned by the platform
16
+ */
17
+ const LTIToolConfigurationResponseSchema = z.object({
18
+ domain: z.string(),
19
+ target_link_uri: z.url().optional(),
20
+ custom_parameters: z.record(z.string(), z.string()).optional(),
21
+ claims: z.array(z.string()),
22
+ messages: LTIMessagesArraySchema,
23
+ version: z.string().optional(),
24
+ deployment_id: z.string().optional(),
25
+ });
26
+
27
+ /**
28
+ * Zod schema for validating LTI 1.3 dynamic registration response from platforms.
29
+ * This response is returned after successfully registering a tool with an LTI platform.
30
+ * Contains the registered client credentials and configuration that the tool needs to store.
31
+ *
32
+ * @property client_id - Unique client identifier assigned by the platform
33
+ * @property registration_client_uri - Optional URI for managing this registration
34
+ * @property registration_access_token - Optional token for registration management
35
+ * @property application_type - Always 'web' for LTI tools
36
+ * @property response_types - OAuth response types, always ['id_token'] for LTI
37
+ * @property grant_types - OAuth grant types supported ('implicit', 'client_credentials')
38
+ * @property initiate_login_uri - Tool's login initiation endpoint
39
+ * @property redirect_uris - Array of valid redirect URIs for the tool
40
+ * @property client_name - Human-readable name of the registered tool
41
+ * @property jwks_uri - Tool's JSON Web Key Set endpoint for signature verification
42
+ * @property logo_uri - Optional URL to the tool's logo image
43
+ * @property token_endpoint_auth_method - Always 'private_key_jwt' for LTI 1.3
44
+ * @property contacts - Optional array of contact email addresses
45
+ * @property scope - Optional OAuth scopes granted to the tool
46
+ * @property https://purl.imsglobal.org/spec/lti-tool-configuration - LTI-specific tool configuration
47
+ */
48
+ export const RegistrationResponseSchema = z.object({
49
+ client_id: z.string(),
50
+ registration_client_uri: z.url().optional(),
51
+ registration_access_token: z.string().optional(),
52
+ application_type: z.literal('web'),
53
+ response_types: z.array(z.literal('id_token')),
54
+ grant_types: z.array(z.enum(['implicit', 'client_credentials'])), // Note: "implicit" not "implict"
55
+ initiate_login_uri: z.url(),
56
+ redirect_uris: z.array(z.url()),
57
+ client_name: z.string(),
58
+ jwks_uri: z.url(),
59
+ logo_uri: z.url().optional().or(z.literal('')),
60
+ token_endpoint_auth_method: z.literal('private_key_jwt'),
61
+ contacts: z.array(z.email()).optional(),
62
+ scope: z.string().optional().or(z.literal('')),
63
+ 'https://purl.imsglobal.org/spec/lti-tool-configuration':
64
+ LTIToolConfigurationResponseSchema,
65
+ });
66
+
67
+ export type RegistrationResponse = z.infer<typeof RegistrationResponseSchema>;
@@ -0,0 +1,55 @@
1
+ import * as z from 'zod';
2
+
3
+ import { LTIMessagesArraySchema } from './ltiMessages.schema';
4
+
5
+ /**
6
+ * Zod schema for LTI tool configuration section within tool registration payload.
7
+ * Contains LTI-specific metadata about the tool being registered with the platform.
8
+ *
9
+ * @property domain - Tool's domain name for security validation and CORS policies
10
+ * @property description - Optional human-readable description of the tool's purpose
11
+ * @property target_link_uri - Default launch URL where the platform should send LTI requests
12
+ * @property claims - Array of JWT claims the tool requires from launch requests (e.g., 'iss', 'sub', 'name', 'email')
13
+ * @property messages - Array of LTI message types the tool supports (ResourceLink, DeepLinking, etc.)
14
+ */
15
+ const LTIToolConfigurationSchema = z.object({
16
+ domain: z.string(),
17
+ description: z.string().optional(),
18
+ target_link_uri: z.url(),
19
+ claims: z.array(z.string()),
20
+ messages: LTIMessagesArraySchema,
21
+ });
22
+
23
+ /**
24
+ * Zod schema for validating LTI 1.3 dynamic registration payload sent to platforms.
25
+ * This payload is constructed by the tool and sent to the platform's registration endpoint
26
+ * to register the tool and request specific OAuth scopes and LTI services.
27
+ *
28
+ * @property application_type - Always 'web' for LTI tools
29
+ * @property response_types - OAuth response types, always ['id_token'] for LTI 1.3
30
+ * @property grant_types - OAuth grant types requested ('implicit' for launches, 'client_credentials' for services)
31
+ * @property initiate_login_uri - Tool's login initiation endpoint for OIDC flow
32
+ * @property redirect_uris - Array of valid redirect URIs where the platform can send responses
33
+ * @property client_name - Human-readable name of the tool being registered
34
+ * @property jwks_uri - Tool's JSON Web Key Set endpoint for signature verification
35
+ * @property logo_uri - Optional URL to the tool's logo image
36
+ * @property scope - Optional OAuth scopes being requested (AGS, NRPS, etc.)
37
+ * @property token_endpoint_auth_method - Always 'private_key_jwt' for LTI 1.3 security
38
+ * @property https://purl.imsglobal.org/spec/lti-tool-configuration - LTI-specific tool configuration
39
+ */
40
+ export const ToolRegistrationPayloadSchema = z.object({
41
+ application_type: z.literal('web'),
42
+ response_types: z.array(z.literal('id_token')),
43
+ grant_types: z.array(z.enum(['implicit', 'client_credentials'])),
44
+ initiate_login_uri: z.url(),
45
+ redirect_uris: z.array(z.url()),
46
+ client_name: z.string(),
47
+ jwks_uri: z.url(),
48
+ logo_uri: z.url().optional(),
49
+ scope: z.string().optional(),
50
+ token_endpoint_auth_method: z.literal('private_key_jwt'),
51
+ 'https://purl.imsglobal.org/spec/lti-tool-configuration': LTIToolConfigurationSchema,
52
+ });
53
+
54
+ export type ToolRegistrationPayload = z.infer<typeof ToolRegistrationPayloadSchema>;
55
+ export type LTIToolConfiguration = z.infer<typeof LTIToolConfigurationSchema>;