@flink-app/oidc-plugin 0.13.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 (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +846 -0
  4. package/dist/OidcInternalContext.d.ts +15 -0
  5. package/dist/OidcInternalContext.d.ts.map +1 -0
  6. package/dist/OidcInternalContext.js +2 -0
  7. package/dist/OidcPlugin.d.ts +77 -0
  8. package/dist/OidcPlugin.d.ts.map +1 -0
  9. package/dist/OidcPlugin.js +274 -0
  10. package/dist/OidcPluginContext.d.ts +73 -0
  11. package/dist/OidcPluginContext.d.ts.map +1 -0
  12. package/dist/OidcPluginContext.js +2 -0
  13. package/dist/OidcPluginOptions.d.ts +267 -0
  14. package/dist/OidcPluginOptions.d.ts.map +1 -0
  15. package/dist/OidcPluginOptions.js +2 -0
  16. package/dist/OidcProviderConfig.d.ts +77 -0
  17. package/dist/OidcProviderConfig.d.ts.map +1 -0
  18. package/dist/OidcProviderConfig.js +2 -0
  19. package/dist/handlers/CallbackOidc.d.ts +38 -0
  20. package/dist/handlers/CallbackOidc.d.ts.map +1 -0
  21. package/dist/handlers/CallbackOidc.js +219 -0
  22. package/dist/handlers/InitiateOidc.d.ts +35 -0
  23. package/dist/handlers/InitiateOidc.d.ts.map +1 -0
  24. package/dist/handlers/InitiateOidc.js +91 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +40 -0
  28. package/dist/providers/OidcProvider.d.ts +90 -0
  29. package/dist/providers/OidcProvider.d.ts.map +1 -0
  30. package/dist/providers/OidcProvider.js +208 -0
  31. package/dist/providers/ProviderRegistry.d.ts +55 -0
  32. package/dist/providers/ProviderRegistry.d.ts.map +1 -0
  33. package/dist/providers/ProviderRegistry.js +94 -0
  34. package/dist/repos/OidcConnectionRepo.d.ts +75 -0
  35. package/dist/repos/OidcConnectionRepo.d.ts.map +1 -0
  36. package/dist/repos/OidcConnectionRepo.js +122 -0
  37. package/dist/repos/OidcSessionRepo.d.ts +57 -0
  38. package/dist/repos/OidcSessionRepo.d.ts.map +1 -0
  39. package/dist/repos/OidcSessionRepo.js +91 -0
  40. package/dist/schemas/CallbackRequest.d.ts +37 -0
  41. package/dist/schemas/CallbackRequest.d.ts.map +1 -0
  42. package/dist/schemas/CallbackRequest.js +2 -0
  43. package/dist/schemas/InitiateRequest.d.ts +17 -0
  44. package/dist/schemas/InitiateRequest.d.ts.map +1 -0
  45. package/dist/schemas/InitiateRequest.js +2 -0
  46. package/dist/schemas/OidcConnection.d.ts +69 -0
  47. package/dist/schemas/OidcConnection.d.ts.map +1 -0
  48. package/dist/schemas/OidcConnection.js +2 -0
  49. package/dist/schemas/OidcProfile.d.ts +69 -0
  50. package/dist/schemas/OidcProfile.d.ts.map +1 -0
  51. package/dist/schemas/OidcProfile.js +2 -0
  52. package/dist/schemas/OidcSession.d.ts +46 -0
  53. package/dist/schemas/OidcSession.d.ts.map +1 -0
  54. package/dist/schemas/OidcSession.js +2 -0
  55. package/dist/schemas/OidcTokenSet.d.ts +42 -0
  56. package/dist/schemas/OidcTokenSet.d.ts.map +1 -0
  57. package/dist/schemas/OidcTokenSet.js +2 -0
  58. package/dist/utils/claims-mapper.d.ts +46 -0
  59. package/dist/utils/claims-mapper.d.ts.map +1 -0
  60. package/dist/utils/claims-mapper.js +104 -0
  61. package/dist/utils/encryption-utils.d.ts +32 -0
  62. package/dist/utils/encryption-utils.d.ts.map +1 -0
  63. package/dist/utils/encryption-utils.js +82 -0
  64. package/dist/utils/error-utils.d.ts +65 -0
  65. package/dist/utils/error-utils.d.ts.map +1 -0
  66. package/dist/utils/error-utils.js +150 -0
  67. package/dist/utils/response-utils.d.ts +18 -0
  68. package/dist/utils/response-utils.d.ts.map +1 -0
  69. package/dist/utils/response-utils.js +42 -0
  70. package/dist/utils/state-utils.d.ts +36 -0
  71. package/dist/utils/state-utils.d.ts.map +1 -0
  72. package/dist/utils/state-utils.js +66 -0
  73. package/examples/basic-oidc.ts +151 -0
  74. package/examples/multi-provider.ts +146 -0
  75. package/package.json +44 -0
  76. package/spec/handlers/InitiateOidc.spec.ts +62 -0
  77. package/spec/helpers/reporter.ts +34 -0
  78. package/spec/helpers/test-helpers.ts +108 -0
  79. package/spec/plugin/OidcPlugin.spec.ts +126 -0
  80. package/spec/providers/ProviderRegistry.spec.ts +197 -0
  81. package/spec/repos/OidcConnectionRepo.spec.ts +257 -0
  82. package/spec/repos/OidcSessionRepo.spec.ts +196 -0
  83. package/spec/support/jasmine.json +7 -0
  84. package/spec/utils/claims-mapper.spec.ts +257 -0
  85. package/spec/utils/encryption-utils.spec.ts +126 -0
  86. package/spec/utils/error-utils.spec.ts +107 -0
  87. package/spec/utils/state-utils.spec.ts +102 -0
  88. package/src/OidcInternalContext.ts +15 -0
  89. package/src/OidcPlugin.ts +290 -0
  90. package/src/OidcPluginContext.ts +76 -0
  91. package/src/OidcPluginOptions.ts +286 -0
  92. package/src/OidcProviderConfig.ts +87 -0
  93. package/src/handlers/CallbackOidc.ts +257 -0
  94. package/src/handlers/InitiateOidc.ts +110 -0
  95. package/src/index.ts +38 -0
  96. package/src/providers/OidcProvider.ts +237 -0
  97. package/src/providers/ProviderRegistry.ts +107 -0
  98. package/src/repos/OidcConnectionRepo.ts +132 -0
  99. package/src/repos/OidcSessionRepo.ts +99 -0
  100. package/src/schemas/CallbackRequest.ts +41 -0
  101. package/src/schemas/InitiateRequest.ts +17 -0
  102. package/src/schemas/OidcConnection.ts +80 -0
  103. package/src/schemas/OidcProfile.ts +79 -0
  104. package/src/schemas/OidcSession.ts +52 -0
  105. package/src/schemas/OidcTokenSet.ts +47 -0
  106. package/src/utils/claims-mapper.ts +114 -0
  107. package/src/utils/encryption-utils.ts +92 -0
  108. package/src/utils/error-utils.ts +167 -0
  109. package/src/utils/response-utils.ts +41 -0
  110. package/src/utils/state-utils.ts +66 -0
  111. package/tsconfig.dist.json +9 -0
  112. package/tsconfig.json +20 -0
@@ -0,0 +1,237 @@
1
+ import { Issuer, Client, generators, TokenSet, UserinfoResponse } from "openid-client";
2
+ import { OidcProviderConfig } from "../OidcProviderConfig";
3
+ import OidcProfile from "../schemas/OidcProfile";
4
+ import OidcTokenSet from "../schemas/OidcTokenSet";
5
+ import { mapClaimsToProfile, extractCustomClaims } from "../utils/claims-mapper";
6
+ import { createOidcError, OidcErrorCodes } from "../utils/error-utils";
7
+
8
+ /**
9
+ * Generic OIDC Provider implementation using openid-client
10
+ *
11
+ * Supports both OIDC discovery and manual configuration.
12
+ * Handles the complete OIDC flow including:
13
+ * - Authorization URL generation with PKCE
14
+ * - Token exchange (code → tokens)
15
+ * - ID token validation
16
+ * - UserInfo endpoint fetching
17
+ * - Claims mapping to profile
18
+ */
19
+ export class OidcProvider {
20
+ private config: OidcProviderConfig;
21
+ private issuer: Issuer<Client> | null = null;
22
+ private client: Client | null = null;
23
+ private initialized: boolean = false;
24
+
25
+ constructor(config: OidcProviderConfig) {
26
+ this.config = config;
27
+ }
28
+
29
+ /**
30
+ * Initialize the provider by discovering or creating the OIDC client
31
+ *
32
+ * Uses OIDC discovery if discoveryUrl is provided, otherwise uses
33
+ * manual endpoint configuration.
34
+ *
35
+ * This is async and should be called before using the provider.
36
+ */
37
+ async initialize(): Promise<void> {
38
+ if (this.initialized) {
39
+ return;
40
+ }
41
+
42
+ try {
43
+ // Option 1: OIDC Discovery
44
+ if (this.config.discoveryUrl) {
45
+ this.issuer = await Issuer.discover(this.config.discoveryUrl);
46
+ }
47
+ // Option 2: Manual configuration
48
+ else {
49
+ // Validate required endpoints for manual config
50
+ if (!this.config.authorizationEndpoint || !this.config.tokenEndpoint || !this.config.jwksUri) {
51
+ throw createOidcError(
52
+ OidcErrorCodes.PROVIDER_NOT_CONFIGURED,
53
+ "Provider must have either discoveryUrl or manual endpoints (authorizationEndpoint, tokenEndpoint, jwksUri)",
54
+ { provider: this.config.issuer }
55
+ );
56
+ }
57
+
58
+ this.issuer = new Issuer({
59
+ issuer: this.config.issuer,
60
+ authorization_endpoint: this.config.authorizationEndpoint,
61
+ token_endpoint: this.config.tokenEndpoint,
62
+ userinfo_endpoint: this.config.userinfoEndpoint,
63
+ jwks_uri: this.config.jwksUri,
64
+ });
65
+ }
66
+
67
+ // Create OIDC client
68
+ this.client = new this.issuer.Client({
69
+ client_id: this.config.clientId,
70
+ client_secret: this.config.clientSecret,
71
+ redirect_uris: [this.config.callbackUrl],
72
+ response_types: ["code"],
73
+ });
74
+
75
+ this.initialized = true;
76
+ } catch (error: any) {
77
+ throw createOidcError(OidcErrorCodes.DISCOVERY_FAILED, `Failed to initialize OIDC provider: ${error.message}`, {
78
+ issuer: this.config.issuer,
79
+ originalError: error.message,
80
+ });
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Generate authorization URL with PKCE and nonce
86
+ *
87
+ * @param params - Authorization parameters
88
+ * @returns Authorization URL to redirect user to
89
+ */
90
+ async getAuthorizationUrl(params: { state: string; codeVerifier: string; nonce: string }): Promise<string> {
91
+ await this.ensureInitialized();
92
+
93
+ const codeChallenge = generators.codeChallenge(params.codeVerifier);
94
+ const scope = this.config.scope?.join(" ") || "openid email profile";
95
+
96
+ const authUrl = this.client!.authorizationUrl({
97
+ scope,
98
+ state: params.state,
99
+ code_challenge: codeChallenge,
100
+ code_challenge_method: "S256",
101
+ nonce: params.nonce,
102
+ });
103
+
104
+ return authUrl;
105
+ }
106
+
107
+ /**
108
+ * Exchange authorization code for tokens
109
+ *
110
+ * Performs the OAuth 2.0 token exchange and validates the ID token.
111
+ *
112
+ * @param params - Token exchange parameters
113
+ * @returns Token set with access token, ID token, and claims
114
+ */
115
+ async exchangeCodeForToken(params: { code: string; codeVerifier: string; state: string; nonce: string }): Promise<OidcTokenSet> {
116
+ await this.ensureInitialized();
117
+
118
+ try {
119
+ const tokenSet: TokenSet = await this.client!.callback(
120
+ this.config.callbackUrl,
121
+ {
122
+ code: params.code,
123
+ state: params.state,
124
+ },
125
+ {
126
+ code_verifier: params.codeVerifier,
127
+ state: params.state,
128
+ nonce: params.nonce,
129
+ }
130
+ );
131
+
132
+ // Extract claims from ID token (already validated by openid-client)
133
+ const claims = tokenSet.claims();
134
+
135
+ return {
136
+ accessToken: tokenSet.access_token!,
137
+ idToken: tokenSet.id_token!,
138
+ refreshToken: tokenSet.refresh_token,
139
+ tokenType: tokenSet.token_type || "Bearer",
140
+ expiresIn: tokenSet.expires_in,
141
+ scope: tokenSet.scope,
142
+ claims,
143
+ };
144
+ } catch (error: any) {
145
+ throw createOidcError(OidcErrorCodes.TOKEN_EXCHANGE_FAILED, `Token exchange failed: ${error.message}`, {
146
+ originalError: error.message,
147
+ errorCode: error.error,
148
+ });
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get user profile from UserInfo endpoint
154
+ *
155
+ * Fetches additional user claims from the UserInfo endpoint.
156
+ * Merges with claims from ID token.
157
+ *
158
+ * @param accessToken - Access token from token exchange
159
+ * @returns UserInfo response
160
+ */
161
+ async getUserInfo(accessToken: string): Promise<UserinfoResponse> {
162
+ await this.ensureInitialized();
163
+
164
+ try {
165
+ const userinfo = await this.client!.userinfo(accessToken);
166
+ return userinfo;
167
+ } catch (error: any) {
168
+ throw createOidcError(OidcErrorCodes.USERINFO_FAILED, `UserInfo request failed: ${error.message}`, {
169
+ originalError: error.message,
170
+ });
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Build complete user profile from tokens
176
+ *
177
+ * Combines claims from ID token and UserInfo endpoint,
178
+ * applies custom claim mapping, and returns normalized profile.
179
+ *
180
+ * @param tokenSet - Token set from exchange
181
+ * @param includeUserInfo - Whether to fetch UserInfo endpoint
182
+ * @returns Normalized user profile
183
+ */
184
+ async buildProfile(tokenSet: OidcTokenSet, includeUserInfo: boolean = true): Promise<OidcProfile> {
185
+ let claims = { ...tokenSet.claims };
186
+
187
+ // Optionally fetch additional claims from UserInfo endpoint
188
+ if (includeUserInfo && this.config.userinfoEndpoint) {
189
+ try {
190
+ const userinfo = await this.getUserInfo(tokenSet.accessToken);
191
+ // Merge UserInfo claims with ID token claims
192
+ claims = { ...claims, ...userinfo };
193
+ } catch (error) {
194
+ // UserInfo is optional - continue with ID token claims only
195
+ console.warn("Failed to fetch UserInfo, using ID token claims only:", error);
196
+ }
197
+ }
198
+
199
+ // Apply custom claim mapping if configured
200
+ if (this.config.claimMapping) {
201
+ const customClaims = extractCustomClaims(claims, this.config.claimMapping);
202
+ claims = { ...claims, ...customClaims };
203
+ }
204
+
205
+ // Map to normalized profile
206
+ const profile = mapClaimsToProfile(claims);
207
+
208
+ return profile;
209
+ }
210
+
211
+ /**
212
+ * Ensure provider is initialized before use
213
+ *
214
+ * @throws Error if not initialized
215
+ */
216
+ private async ensureInitialized(): Promise<void> {
217
+ if (!this.initialized) {
218
+ await this.initialize();
219
+ }
220
+
221
+ if (!this.client) {
222
+ throw createOidcError(OidcErrorCodes.PROVIDER_NOT_CONFIGURED, "OIDC client not initialized");
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Get issuer metadata (after initialization)
228
+ *
229
+ * @returns Issuer metadata
230
+ */
231
+ getIssuerMetadata(): any {
232
+ if (!this.issuer) {
233
+ throw createOidcError(OidcErrorCodes.PROVIDER_NOT_CONFIGURED, "Provider not initialized");
234
+ }
235
+ return this.issuer.metadata;
236
+ }
237
+ }
@@ -0,0 +1,107 @@
1
+ import { OidcProvider } from "./OidcProvider";
2
+ import { OidcProviderConfig } from "../OidcProviderConfig";
3
+ import { createOidcError, OidcErrorCodes } from "../utils/error-utils";
4
+
5
+ /**
6
+ * Provider registry for managing OIDC provider instances
7
+ *
8
+ * Handles:
9
+ * - Static provider configuration
10
+ * - Dynamic provider loading from database
11
+ * - Provider instance caching
12
+ * - Lazy initialization
13
+ */
14
+ export class ProviderRegistry {
15
+ private staticProviders: Record<string, OidcProviderConfig>;
16
+ private providerInstances: Map<string, OidcProvider> = new Map();
17
+ private providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
18
+
19
+ constructor(
20
+ staticProviders: Record<string, OidcProviderConfig>,
21
+ providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>
22
+ ) {
23
+ this.staticProviders = staticProviders;
24
+ this.providerLoader = providerLoader;
25
+ }
26
+
27
+ /**
28
+ * Get provider instance by name
29
+ *
30
+ * Looks up provider in the following order:
31
+ * 1. Cached instance
32
+ * 2. Static configuration
33
+ * 3. Dynamic loader (if configured)
34
+ *
35
+ * Providers are lazy-initialized on first use and cached.
36
+ *
37
+ * @param providerName - Provider identifier
38
+ * @returns Initialized OIDC provider instance
39
+ * @throws Error if provider not found or initialization fails
40
+ */
41
+ async getProvider(providerName: string): Promise<OidcProvider> {
42
+ // Check cache first
43
+ const cachedProvider = this.providerInstances.get(providerName);
44
+ if (cachedProvider) {
45
+ return cachedProvider;
46
+ }
47
+
48
+ // Try static configuration
49
+ let config: OidcProviderConfig | null = this.staticProviders[providerName] || null;
50
+
51
+ // Try dynamic loader if not in static config
52
+ if (!config && this.providerLoader) {
53
+ config = await this.providerLoader(providerName);
54
+ }
55
+
56
+ if (!config) {
57
+ throw createOidcError(OidcErrorCodes.PROVIDER_NOT_CONFIGURED, `OIDC provider '${providerName}' is not configured`, {
58
+ providerName,
59
+ availableProviders: Object.keys(this.staticProviders),
60
+ });
61
+ }
62
+
63
+ // Create and initialize provider
64
+ const provider = new OidcProvider(config);
65
+ await provider.initialize();
66
+
67
+ // Cache the instance
68
+ this.providerInstances.set(providerName, provider);
69
+
70
+ return provider;
71
+ }
72
+
73
+ /**
74
+ * Check if provider exists in static configuration or can be loaded dynamically
75
+ *
76
+ * @param providerName - Provider identifier
77
+ * @returns true if provider exists, false otherwise
78
+ */
79
+ hasProvider(providerName: string): boolean {
80
+ return providerName in this.staticProviders || !!this.providerLoader;
81
+ }
82
+
83
+ /**
84
+ * Clear provider cache
85
+ *
86
+ * Forces re-initialization of providers on next access.
87
+ * Useful when provider configurations change.
88
+ *
89
+ * @param providerName - Optional provider to clear (clears all if not specified)
90
+ */
91
+ clearCache(providerName?: string): void {
92
+ if (providerName) {
93
+ this.providerInstances.delete(providerName);
94
+ } else {
95
+ this.providerInstances.clear();
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get list of configured provider names
101
+ *
102
+ * @returns Array of provider names from static configuration
103
+ */
104
+ getProviderNames(): string[] {
105
+ return Object.keys(this.staticProviders);
106
+ }
107
+ }
@@ -0,0 +1,132 @@
1
+ import { Collection, Db } from "mongodb";
2
+ import OidcConnection from "../schemas/OidcConnection";
3
+
4
+ /**
5
+ * Repository for OIDC connections
6
+ *
7
+ * Manages persistent connections between users and OIDC providers.
8
+ * Stores the mapping of app users to IdP subjects, and optionally
9
+ * stores encrypted tokens for API access.
10
+ */
11
+ export default class OidcConnectionRepo {
12
+ private collection: Collection<OidcConnection>;
13
+
14
+ constructor(collectionName: string, db: Db) {
15
+ this.collection = db.collection<OidcConnection>(collectionName);
16
+ }
17
+
18
+ /**
19
+ * Create a new OIDC connection
20
+ *
21
+ * @param connection - Connection data
22
+ * @returns Created connection with _id
23
+ */
24
+ async create(connection: Omit<OidcConnection, "_id">): Promise<OidcConnection> {
25
+ const result = await this.collection.insertOne(connection as any);
26
+ return {
27
+ ...connection,
28
+ _id: result.insertedId.toString(),
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Find connection by user ID and provider
34
+ *
35
+ * @param userId - Application user ID
36
+ * @param provider - Provider name
37
+ * @returns Connection or null if not found
38
+ */
39
+ async findByUserAndProvider(userId: string, provider: string): Promise<OidcConnection | null> {
40
+ const connection = await this.collection.findOne({ userId, provider });
41
+ if (!connection) {
42
+ return null;
43
+ }
44
+ return {
45
+ ...connection,
46
+ _id: connection._id?.toString(),
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Find connection by subject and issuer
52
+ *
53
+ * Used to look up users by their IdP identity.
54
+ *
55
+ * @param subject - OIDC subject (sub claim)
56
+ * @param issuer - OIDC issuer (iss claim)
57
+ * @returns Connection or null if not found
58
+ */
59
+ async findBySubjectAndIssuer(subject: string, issuer: string): Promise<OidcConnection | null> {
60
+ const connection = await this.collection.findOne({ subject, issuer });
61
+ if (!connection) {
62
+ return null;
63
+ }
64
+ return {
65
+ ...connection,
66
+ _id: connection._id?.toString(),
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Find all connections for a user
72
+ *
73
+ * @param userId - Application user ID
74
+ * @returns Array of connections
75
+ */
76
+ async findByUserId(userId: string): Promise<OidcConnection[]> {
77
+ const connections = await this.collection.find({ userId }).toArray();
78
+ return connections.map((conn) => ({
79
+ ...conn,
80
+ _id: conn._id?.toString(),
81
+ }));
82
+ }
83
+
84
+ /**
85
+ * Update connection
86
+ *
87
+ * Typically used to update tokens when they're refreshed.
88
+ *
89
+ * @param connectionId - Connection _id
90
+ * @param updates - Fields to update
91
+ */
92
+ async updateOne(connectionId: string, updates: Partial<OidcConnection>): Promise<void> {
93
+ await this.collection.updateOne({ _id: connectionId as any }, { $set: updates });
94
+ }
95
+
96
+ /**
97
+ * Delete connection by user and provider
98
+ *
99
+ * @param userId - Application user ID
100
+ * @param provider - Provider name
101
+ */
102
+ async deleteByUserAndProvider(userId: string, provider: string): Promise<void> {
103
+ await this.collection.deleteOne({ userId, provider });
104
+ }
105
+
106
+ /**
107
+ * Delete all connections for a user
108
+ *
109
+ * @param userId - Application user ID
110
+ */
111
+ async deleteByUserId(userId: string): Promise<number> {
112
+ const result = await this.collection.deleteMany({ userId });
113
+ return result.deletedCount;
114
+ }
115
+
116
+ /**
117
+ * Find one connection by query
118
+ *
119
+ * @param query - MongoDB query
120
+ * @returns Connection or null if not found
121
+ */
122
+ async getOne(query: Partial<OidcConnection>): Promise<OidcConnection | null> {
123
+ const connection = await this.collection.findOne(query as any);
124
+ if (!connection) {
125
+ return null;
126
+ }
127
+ return {
128
+ ...connection,
129
+ _id: connection._id?.toString(),
130
+ };
131
+ }
132
+ }
@@ -0,0 +1,99 @@
1
+ import { Collection, Db } from "mongodb";
2
+ import OidcSession from "../schemas/OidcSession";
3
+
4
+ /**
5
+ * Repository for OIDC sessions
6
+ *
7
+ * Manages temporary sessions during the OIDC authorization flow.
8
+ * Sessions are automatically deleted by MongoDB TTL index after expiration.
9
+ */
10
+ export default class OidcSessionRepo {
11
+ private collection: Collection<OidcSession>;
12
+
13
+ constructor(collectionName: string, db: Db) {
14
+ this.collection = db.collection<OidcSession>(collectionName);
15
+ }
16
+
17
+ /**
18
+ * Create a new OIDC session
19
+ *
20
+ * @param session - Session data
21
+ * @returns Created session with _id
22
+ */
23
+ async create(session: Omit<OidcSession, "_id">): Promise<OidcSession> {
24
+ const result = await this.collection.insertOne(session as any);
25
+ return {
26
+ ...session,
27
+ _id: result.insertedId.toString(),
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Find session by state parameter
33
+ *
34
+ * Used during callback to validate the state and retrieve session data.
35
+ *
36
+ * @param state - State parameter from callback
37
+ * @returns Session or null if not found
38
+ */
39
+ async getByState(state: string): Promise<OidcSession | null> {
40
+ const session = await this.collection.findOne({ state });
41
+ if (!session) {
42
+ return null;
43
+ }
44
+ return {
45
+ ...session,
46
+ _id: session._id?.toString(),
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Find one session by query
52
+ *
53
+ * @param query - MongoDB query
54
+ * @returns Session or null if not found
55
+ */
56
+ async getOne(query: Partial<OidcSession>): Promise<OidcSession | null> {
57
+ const session = await this.collection.findOne(query as any);
58
+ if (!session) {
59
+ return null;
60
+ }
61
+ return {
62
+ ...session,
63
+ _id: session._id?.toString(),
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Delete session by session ID
69
+ *
70
+ * Sessions are one-time use - delete after successful validation.
71
+ *
72
+ * @param sessionId - Session identifier
73
+ */
74
+ async deleteBySessionId(sessionId: string): Promise<void> {
75
+ await this.collection.deleteOne({ sessionId });
76
+ }
77
+
78
+ /**
79
+ * Delete session by state
80
+ *
81
+ * @param state - State parameter
82
+ */
83
+ async deleteByState(state: string): Promise<void> {
84
+ await this.collection.deleteOne({ state });
85
+ }
86
+
87
+ /**
88
+ * Delete all expired sessions
89
+ *
90
+ * This is handled automatically by MongoDB TTL index,
91
+ * but can be called manually for testing or cleanup.
92
+ */
93
+ async deleteExpired(): Promise<number> {
94
+ const result = await this.collection.deleteMany({
95
+ createdAt: { $lt: new Date(Date.now() - 600000) }, // 10 minutes
96
+ });
97
+ return result.deletedCount;
98
+ }
99
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Query parameters for the OIDC callback endpoint
3
+ *
4
+ * GET /oidc/:provider/callback?code=...&state=...&response_type=json
5
+ */
6
+ export default interface CallbackRequest {
7
+ /**
8
+ * Authorization code from the IdP
9
+ * Required for successful authentication
10
+ */
11
+ code?: string;
12
+
13
+ /**
14
+ * State parameter for CSRF protection
15
+ * Must match the state stored in the session
16
+ */
17
+ state?: string;
18
+
19
+ /**
20
+ * Error code from the IdP (if authorization failed)
21
+ * e.g., "access_denied" if user cancelled
22
+ */
23
+ error?: string;
24
+
25
+ /**
26
+ * Human-readable error description from the IdP
27
+ */
28
+ error_description?: string;
29
+
30
+ /**
31
+ * Response format for the callback
32
+ * - "json": Return JSON response with user and token
33
+ * - undefined: Redirect to redirectUri with token in URL fragment
34
+ */
35
+ response_type?: "json";
36
+
37
+ /**
38
+ * Index signature for Flink Query type compatibility
39
+ */
40
+ [key: string]: string | string[] | undefined;
41
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Query parameters for the OIDC initiate endpoint
3
+ *
4
+ * GET /oidc/:provider/initiate?redirectUri=...
5
+ */
6
+ export default interface InitiateRequest {
7
+ /**
8
+ * Optional redirect URI after successful authentication
9
+ * If not provided, uses the default callbackUrl from provider config
10
+ */
11
+ redirectUri?: string;
12
+
13
+ /**
14
+ * Index signature for Flink Query type compatibility
15
+ */
16
+ [key: string]: string | string[] | undefined;
17
+ }