@flink-app/oidc-plugin 1.0.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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +846 -0
- package/dist/OidcInternalContext.d.ts +15 -0
- package/dist/OidcInternalContext.d.ts.map +1 -0
- package/dist/OidcInternalContext.js +2 -0
- package/dist/OidcPlugin.d.ts +77 -0
- package/dist/OidcPlugin.d.ts.map +1 -0
- package/dist/OidcPlugin.js +274 -0
- package/dist/OidcPluginContext.d.ts +73 -0
- package/dist/OidcPluginContext.d.ts.map +1 -0
- package/dist/OidcPluginContext.js +2 -0
- package/dist/OidcPluginOptions.d.ts +267 -0
- package/dist/OidcPluginOptions.d.ts.map +1 -0
- package/dist/OidcPluginOptions.js +2 -0
- package/dist/OidcProviderConfig.d.ts +77 -0
- package/dist/OidcProviderConfig.d.ts.map +1 -0
- package/dist/OidcProviderConfig.js +2 -0
- package/dist/handlers/CallbackOidc.d.ts +38 -0
- package/dist/handlers/CallbackOidc.d.ts.map +1 -0
- package/dist/handlers/CallbackOidc.js +219 -0
- package/dist/handlers/InitiateOidc.d.ts +35 -0
- package/dist/handlers/InitiateOidc.d.ts.map +1 -0
- package/dist/handlers/InitiateOidc.js +91 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/providers/OidcProvider.d.ts +90 -0
- package/dist/providers/OidcProvider.d.ts.map +1 -0
- package/dist/providers/OidcProvider.js +208 -0
- package/dist/providers/ProviderRegistry.d.ts +55 -0
- package/dist/providers/ProviderRegistry.d.ts.map +1 -0
- package/dist/providers/ProviderRegistry.js +94 -0
- package/dist/repos/OidcConnectionRepo.d.ts +75 -0
- package/dist/repos/OidcConnectionRepo.d.ts.map +1 -0
- package/dist/repos/OidcConnectionRepo.js +122 -0
- package/dist/repos/OidcSessionRepo.d.ts +57 -0
- package/dist/repos/OidcSessionRepo.d.ts.map +1 -0
- package/dist/repos/OidcSessionRepo.js +91 -0
- package/dist/schemas/CallbackRequest.d.ts +37 -0
- package/dist/schemas/CallbackRequest.d.ts.map +1 -0
- package/dist/schemas/CallbackRequest.js +2 -0
- package/dist/schemas/InitiateRequest.d.ts +17 -0
- package/dist/schemas/InitiateRequest.d.ts.map +1 -0
- package/dist/schemas/InitiateRequest.js +2 -0
- package/dist/schemas/OidcConnection.d.ts +69 -0
- package/dist/schemas/OidcConnection.d.ts.map +1 -0
- package/dist/schemas/OidcConnection.js +2 -0
- package/dist/schemas/OidcProfile.d.ts +69 -0
- package/dist/schemas/OidcProfile.d.ts.map +1 -0
- package/dist/schemas/OidcProfile.js +2 -0
- package/dist/schemas/OidcSession.d.ts +46 -0
- package/dist/schemas/OidcSession.d.ts.map +1 -0
- package/dist/schemas/OidcSession.js +2 -0
- package/dist/schemas/OidcTokenSet.d.ts +42 -0
- package/dist/schemas/OidcTokenSet.d.ts.map +1 -0
- package/dist/schemas/OidcTokenSet.js +2 -0
- package/dist/utils/claims-mapper.d.ts +46 -0
- package/dist/utils/claims-mapper.d.ts.map +1 -0
- package/dist/utils/claims-mapper.js +104 -0
- package/dist/utils/encryption-utils.d.ts +32 -0
- package/dist/utils/encryption-utils.d.ts.map +1 -0
- package/dist/utils/encryption-utils.js +82 -0
- package/dist/utils/error-utils.d.ts +65 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +150 -0
- package/dist/utils/response-utils.d.ts +18 -0
- package/dist/utils/response-utils.d.ts.map +1 -0
- package/dist/utils/response-utils.js +42 -0
- package/dist/utils/state-utils.d.ts +36 -0
- package/dist/utils/state-utils.d.ts.map +1 -0
- package/dist/utils/state-utils.js +66 -0
- package/examples/basic-oidc.ts +151 -0
- package/examples/multi-provider.ts +146 -0
- package/package.json +44 -0
- package/spec/handlers/InitiateOidc.spec.ts +62 -0
- package/spec/helpers/reporter.ts +34 -0
- package/spec/helpers/test-helpers.ts +108 -0
- package/spec/plugin/OidcPlugin.spec.ts +126 -0
- package/spec/providers/ProviderRegistry.spec.ts +197 -0
- package/spec/repos/OidcConnectionRepo.spec.ts +257 -0
- package/spec/repos/OidcSessionRepo.spec.ts +196 -0
- package/spec/support/jasmine.json +7 -0
- package/spec/utils/claims-mapper.spec.ts +257 -0
- package/spec/utils/encryption-utils.spec.ts +126 -0
- package/spec/utils/error-utils.spec.ts +107 -0
- package/spec/utils/state-utils.spec.ts +102 -0
- package/src/OidcInternalContext.ts +15 -0
- package/src/OidcPlugin.ts +290 -0
- package/src/OidcPluginContext.ts +76 -0
- package/src/OidcPluginOptions.ts +286 -0
- package/src/OidcProviderConfig.ts +87 -0
- package/src/handlers/CallbackOidc.ts +257 -0
- package/src/handlers/InitiateOidc.ts +110 -0
- package/src/index.ts +38 -0
- package/src/providers/OidcProvider.ts +237 -0
- package/src/providers/ProviderRegistry.ts +107 -0
- package/src/repos/OidcConnectionRepo.ts +132 -0
- package/src/repos/OidcSessionRepo.ts +99 -0
- package/src/schemas/CallbackRequest.ts +41 -0
- package/src/schemas/InitiateRequest.ts +17 -0
- package/src/schemas/OidcConnection.ts +80 -0
- package/src/schemas/OidcProfile.ts +79 -0
- package/src/schemas/OidcSession.ts +52 -0
- package/src/schemas/OidcTokenSet.ts +47 -0
- package/src/utils/claims-mapper.ts +114 -0
- package/src/utils/encryption-utils.ts +92 -0
- package/src/utils/error-utils.ts +167 -0
- package/src/utils/response-utils.ts +41 -0
- package/src/utils/state-utils.ts +66 -0
- package/tsconfig.dist.json +9 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FlinkContext } from "@flink-app/flink";
|
|
2
|
+
import { OidcPluginContext } from "./OidcPluginContext";
|
|
3
|
+
import OidcSessionRepo from "./repos/OidcSessionRepo";
|
|
4
|
+
import OidcConnectionRepo from "./repos/OidcConnectionRepo";
|
|
5
|
+
/**
|
|
6
|
+
* Internal context type for OIDC plugin
|
|
7
|
+
* Extends the app's context with OIDC-specific repositories
|
|
8
|
+
*/
|
|
9
|
+
export interface OidcInternalContext extends FlinkContext, OidcPluginContext {
|
|
10
|
+
repos: FlinkContext["repos"] & {
|
|
11
|
+
oidcSessionRepo: OidcSessionRepo;
|
|
12
|
+
oidcConnectionRepo: OidcConnectionRepo;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=OidcInternalContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OidcInternalContext.d.ts","sourceRoot":"","sources":["../src/OidcInternalContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,eAAe,MAAM,yBAAyB,CAAC;AACtD,OAAO,kBAAkB,MAAM,4BAA4B,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY,EAAE,iBAAiB;IACxE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG;QAC3B,eAAe,EAAE,eAAe,CAAC;QACjC,kBAAkB,EAAE,kBAAkB,CAAC;KAC1C,CAAC;CACL"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { FlinkPlugin } from "@flink-app/flink";
|
|
2
|
+
import { OidcPluginOptions } from "./OidcPluginOptions";
|
|
3
|
+
/**
|
|
4
|
+
* OIDC Plugin Factory Function
|
|
5
|
+
*
|
|
6
|
+
* Creates a Flink plugin for OIDC authentication with generic IdP support.
|
|
7
|
+
* Integrates with JWT Auth Plugin for token generation.
|
|
8
|
+
*
|
|
9
|
+
* @param options - OIDC plugin configuration options
|
|
10
|
+
* @returns FlinkPlugin instance
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
|
|
15
|
+
* import { oidcPlugin } from '@flink-app/oidc-plugin';
|
|
16
|
+
*
|
|
17
|
+
* const app = new FlinkApp({
|
|
18
|
+
* auth: jwtAuthPlugin({
|
|
19
|
+
* secret: process.env.JWT_SECRET!,
|
|
20
|
+
* getUser: async (tokenData) => {
|
|
21
|
+
* return ctx.repos.userRepo.getById(tokenData.userId);
|
|
22
|
+
* },
|
|
23
|
+
* rolePermissions: {
|
|
24
|
+
* user: ['read:own'],
|
|
25
|
+
* admin: ['read:all', 'write:all']
|
|
26
|
+
* }
|
|
27
|
+
* }),
|
|
28
|
+
*
|
|
29
|
+
* plugins: [
|
|
30
|
+
* oidcPlugin({
|
|
31
|
+
* providers: {
|
|
32
|
+
* acme: {
|
|
33
|
+
* issuer: 'https://idp.acme.com',
|
|
34
|
+
* clientId: process.env.OIDC_CLIENT_ID!,
|
|
35
|
+
* clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
36
|
+
* callbackUrl: 'https://myapp.com/oidc/acme/callback',
|
|
37
|
+
* discoveryUrl: 'https://idp.acme.com/.well-known/openid-configuration'
|
|
38
|
+
* }
|
|
39
|
+
* },
|
|
40
|
+
* onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
|
|
41
|
+
* // Find or create user (JIT provisioning)
|
|
42
|
+
* let user = await ctx.repos.userRepo.getOne({
|
|
43
|
+
* 'oidcConnections.subject': claims.sub,
|
|
44
|
+
* 'oidcConnections.issuer': claims.iss
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* if (!user) {
|
|
48
|
+
* user = await ctx.repos.userRepo.create({
|
|
49
|
+
* email: claims.email,
|
|
50
|
+
* name: claims.name,
|
|
51
|
+
* oidcConnections: [{
|
|
52
|
+
* issuer: claims.iss,
|
|
53
|
+
* subject: claims.sub,
|
|
54
|
+
* provider
|
|
55
|
+
* }]
|
|
56
|
+
* });
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* // Generate JWT token
|
|
60
|
+
* const token = await ctx.plugins.jwtAuth.createToken(
|
|
61
|
+
* { userId: user._id, email: user.email },
|
|
62
|
+
* ['user']
|
|
63
|
+
* );
|
|
64
|
+
*
|
|
65
|
+
* return {
|
|
66
|
+
* user,
|
|
67
|
+
* token,
|
|
68
|
+
* redirectUrl: '/dashboard'
|
|
69
|
+
* };
|
|
70
|
+
* }
|
|
71
|
+
* })
|
|
72
|
+
* ]
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function oidcPlugin(options: OidcPluginOptions): FlinkPlugin;
|
|
77
|
+
//# sourceMappingURL=OidcPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OidcPlugin.d.ts","sourceRoot":"","sources":["../src/OidcPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,WAAW,EAAO,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAWxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,CA2MlE"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.oidcPlugin = oidcPlugin;
|
|
30
|
+
const flink_1 = require("@flink-app/flink");
|
|
31
|
+
const OidcSessionRepo_1 = __importDefault(require("./repos/OidcSessionRepo"));
|
|
32
|
+
const OidcConnectionRepo_1 = __importDefault(require("./repos/OidcConnectionRepo"));
|
|
33
|
+
const encryption_utils_1 = require("./utils/encryption-utils");
|
|
34
|
+
const ProviderRegistry_1 = require("./providers/ProviderRegistry");
|
|
35
|
+
const InitiateOidc = __importStar(require("./handlers/InitiateOidc"));
|
|
36
|
+
const CallbackOidc = __importStar(require("./handlers/CallbackOidc"));
|
|
37
|
+
/**
|
|
38
|
+
* OIDC Plugin Factory Function
|
|
39
|
+
*
|
|
40
|
+
* Creates a Flink plugin for OIDC authentication with generic IdP support.
|
|
41
|
+
* Integrates with JWT Auth Plugin for token generation.
|
|
42
|
+
*
|
|
43
|
+
* @param options - OIDC plugin configuration options
|
|
44
|
+
* @returns FlinkPlugin instance
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
|
|
49
|
+
* import { oidcPlugin } from '@flink-app/oidc-plugin';
|
|
50
|
+
*
|
|
51
|
+
* const app = new FlinkApp({
|
|
52
|
+
* auth: jwtAuthPlugin({
|
|
53
|
+
* secret: process.env.JWT_SECRET!,
|
|
54
|
+
* getUser: async (tokenData) => {
|
|
55
|
+
* return ctx.repos.userRepo.getById(tokenData.userId);
|
|
56
|
+
* },
|
|
57
|
+
* rolePermissions: {
|
|
58
|
+
* user: ['read:own'],
|
|
59
|
+
* admin: ['read:all', 'write:all']
|
|
60
|
+
* }
|
|
61
|
+
* }),
|
|
62
|
+
*
|
|
63
|
+
* plugins: [
|
|
64
|
+
* oidcPlugin({
|
|
65
|
+
* providers: {
|
|
66
|
+
* acme: {
|
|
67
|
+
* issuer: 'https://idp.acme.com',
|
|
68
|
+
* clientId: process.env.OIDC_CLIENT_ID!,
|
|
69
|
+
* clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
70
|
+
* callbackUrl: 'https://myapp.com/oidc/acme/callback',
|
|
71
|
+
* discoveryUrl: 'https://idp.acme.com/.well-known/openid-configuration'
|
|
72
|
+
* }
|
|
73
|
+
* },
|
|
74
|
+
* onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
|
|
75
|
+
* // Find or create user (JIT provisioning)
|
|
76
|
+
* let user = await ctx.repos.userRepo.getOne({
|
|
77
|
+
* 'oidcConnections.subject': claims.sub,
|
|
78
|
+
* 'oidcConnections.issuer': claims.iss
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* if (!user) {
|
|
82
|
+
* user = await ctx.repos.userRepo.create({
|
|
83
|
+
* email: claims.email,
|
|
84
|
+
* name: claims.name,
|
|
85
|
+
* oidcConnections: [{
|
|
86
|
+
* issuer: claims.iss,
|
|
87
|
+
* subject: claims.sub,
|
|
88
|
+
* provider
|
|
89
|
+
* }]
|
|
90
|
+
* });
|
|
91
|
+
* }
|
|
92
|
+
*
|
|
93
|
+
* // Generate JWT token
|
|
94
|
+
* const token = await ctx.plugins.jwtAuth.createToken(
|
|
95
|
+
* { userId: user._id, email: user.email },
|
|
96
|
+
* ['user']
|
|
97
|
+
* );
|
|
98
|
+
*
|
|
99
|
+
* return {
|
|
100
|
+
* user,
|
|
101
|
+
* token,
|
|
102
|
+
* redirectUrl: '/dashboard'
|
|
103
|
+
* };
|
|
104
|
+
* }
|
|
105
|
+
* })
|
|
106
|
+
* ]
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
function oidcPlugin(options) {
|
|
111
|
+
// Validation
|
|
112
|
+
if (!options.providers || Object.keys(options.providers).length === 0) {
|
|
113
|
+
throw new Error("OIDC Plugin: At least one provider must be configured");
|
|
114
|
+
}
|
|
115
|
+
// Validate provider configurations
|
|
116
|
+
const configuredProviders = Object.keys(options.providers);
|
|
117
|
+
for (const providerName of configuredProviders) {
|
|
118
|
+
const providerConfig = options.providers[providerName];
|
|
119
|
+
if (!providerConfig)
|
|
120
|
+
continue;
|
|
121
|
+
if (!providerConfig.issuer) {
|
|
122
|
+
throw new Error(`OIDC Plugin: ${providerName} issuer is required`);
|
|
123
|
+
}
|
|
124
|
+
if (!providerConfig.clientId) {
|
|
125
|
+
throw new Error(`OIDC Plugin: ${providerName} clientId is required`);
|
|
126
|
+
}
|
|
127
|
+
if (!providerConfig.clientSecret) {
|
|
128
|
+
throw new Error(`OIDC Plugin: ${providerName} clientSecret is required`);
|
|
129
|
+
}
|
|
130
|
+
if (!providerConfig.callbackUrl) {
|
|
131
|
+
throw new Error(`OIDC Plugin: ${providerName} callbackUrl is required`);
|
|
132
|
+
}
|
|
133
|
+
// Validate that either discoveryUrl or manual endpoints are provided
|
|
134
|
+
if (!providerConfig.discoveryUrl) {
|
|
135
|
+
if (!providerConfig.authorizationEndpoint || !providerConfig.tokenEndpoint || !providerConfig.jwksUri) {
|
|
136
|
+
throw new Error(`OIDC Plugin: ${providerName} must have either discoveryUrl or manual endpoints ` +
|
|
137
|
+
`(authorizationEndpoint, tokenEndpoint, jwksUri)`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!options.onAuthSuccess) {
|
|
142
|
+
throw new Error("OIDC Plugin: onAuthSuccess callback is required");
|
|
143
|
+
}
|
|
144
|
+
// Determine encryption key
|
|
145
|
+
let encryptionKey = options.encryptionKey;
|
|
146
|
+
if (!encryptionKey) {
|
|
147
|
+
// Derive from first configured provider's client secret
|
|
148
|
+
const firstProvider = configuredProviders[0];
|
|
149
|
+
const firstProviderConfig = options.providers[firstProvider];
|
|
150
|
+
if (firstProviderConfig) {
|
|
151
|
+
encryptionKey = firstProviderConfig.clientSecret;
|
|
152
|
+
flink_1.log.warn("OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options.");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!encryptionKey || encryptionKey.length < 32) {
|
|
156
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters");
|
|
157
|
+
}
|
|
158
|
+
// Validate encryption key
|
|
159
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
160
|
+
let flinkApp;
|
|
161
|
+
let sessionRepo;
|
|
162
|
+
let connectionRepo;
|
|
163
|
+
let providerRegistry;
|
|
164
|
+
/**
|
|
165
|
+
* Plugin initialization
|
|
166
|
+
*/
|
|
167
|
+
async function init(app, db) {
|
|
168
|
+
flink_1.log.info("Initializing OIDC Plugin...");
|
|
169
|
+
flinkApp = app;
|
|
170
|
+
try {
|
|
171
|
+
if (!db) {
|
|
172
|
+
throw new Error("OIDC Plugin: Database connection is required");
|
|
173
|
+
}
|
|
174
|
+
// Initialize repositories
|
|
175
|
+
const sessionsCollectionName = options.sessionsCollectionName || "oidc_sessions";
|
|
176
|
+
const connectionsCollectionName = options.connectionsCollectionName || "oidc_connections";
|
|
177
|
+
sessionRepo = new OidcSessionRepo_1.default(sessionsCollectionName, db);
|
|
178
|
+
connectionRepo = new OidcConnectionRepo_1.default(connectionsCollectionName, db);
|
|
179
|
+
flinkApp.addRepo("oidcSessionRepo", sessionRepo);
|
|
180
|
+
flinkApp.addRepo("oidcConnectionRepo", connectionRepo);
|
|
181
|
+
// Create TTL index for session expiration
|
|
182
|
+
const sessionTTL = options.sessionTTL || 600; // Default 10 minutes
|
|
183
|
+
await db.collection(sessionsCollectionName).createIndex({ createdAt: 1 }, { expireAfterSeconds: sessionTTL });
|
|
184
|
+
flink_1.log.info(`OIDC Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
185
|
+
// Initialize provider registry
|
|
186
|
+
providerRegistry = new ProviderRegistry_1.ProviderRegistry(options.providers, options.providerLoader);
|
|
187
|
+
// Store provider registry in app context for handlers to access
|
|
188
|
+
flinkApp.ctx.oidcProviderRegistry = providerRegistry;
|
|
189
|
+
// Register OIDC handlers
|
|
190
|
+
// Only register handlers if registerRoutes is enabled (default: true)
|
|
191
|
+
if (options.registerRoutes !== false) {
|
|
192
|
+
flinkApp.addHandler(InitiateOidc);
|
|
193
|
+
flinkApp.addHandler(CallbackOidc);
|
|
194
|
+
}
|
|
195
|
+
flink_1.log.info(`OIDC Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
flink_1.log.error("Failed to initialize OIDC Plugin:", error);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get OIDC connection for a user
|
|
204
|
+
*/
|
|
205
|
+
async function getConnection(userId, provider) {
|
|
206
|
+
if (!connectionRepo) {
|
|
207
|
+
throw new Error("OIDC Plugin: Plugin not initialized");
|
|
208
|
+
}
|
|
209
|
+
const connection = await connectionRepo.findByUserAndProvider(userId, provider);
|
|
210
|
+
if (!connection) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
// Decrypt tokens before returning
|
|
214
|
+
if (connection.accessToken && encryptionKey) {
|
|
215
|
+
connection.accessToken = (0, encryption_utils_1.decryptToken)(connection.accessToken, encryptionKey);
|
|
216
|
+
}
|
|
217
|
+
if (connection.idToken && encryptionKey) {
|
|
218
|
+
connection.idToken = (0, encryption_utils_1.decryptToken)(connection.idToken, encryptionKey);
|
|
219
|
+
}
|
|
220
|
+
if (connection.refreshToken && encryptionKey) {
|
|
221
|
+
connection.refreshToken = (0, encryption_utils_1.decryptToken)(connection.refreshToken, encryptionKey);
|
|
222
|
+
}
|
|
223
|
+
return connection;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get all OIDC connections for a user
|
|
227
|
+
*/
|
|
228
|
+
async function getConnections(userId) {
|
|
229
|
+
if (!connectionRepo) {
|
|
230
|
+
throw new Error("OIDC Plugin: Plugin not initialized");
|
|
231
|
+
}
|
|
232
|
+
const connections = await connectionRepo.findByUserId(userId);
|
|
233
|
+
// Decrypt tokens in each connection
|
|
234
|
+
return connections.map((connection) => {
|
|
235
|
+
if (connection.accessToken && encryptionKey) {
|
|
236
|
+
connection.accessToken = (0, encryption_utils_1.decryptToken)(connection.accessToken, encryptionKey);
|
|
237
|
+
}
|
|
238
|
+
if (connection.idToken && encryptionKey) {
|
|
239
|
+
connection.idToken = (0, encryption_utils_1.decryptToken)(connection.idToken, encryptionKey);
|
|
240
|
+
}
|
|
241
|
+
if (connection.refreshToken && encryptionKey) {
|
|
242
|
+
connection.refreshToken = (0, encryption_utils_1.decryptToken)(connection.refreshToken, encryptionKey);
|
|
243
|
+
}
|
|
244
|
+
return connection;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Delete OIDC connection for a user
|
|
249
|
+
*/
|
|
250
|
+
async function deleteConnection(userId, provider) {
|
|
251
|
+
if (!connectionRepo) {
|
|
252
|
+
throw new Error("OIDC Plugin: Plugin not initialized");
|
|
253
|
+
}
|
|
254
|
+
await connectionRepo.deleteByUserAndProvider(userId, provider);
|
|
255
|
+
flink_1.log.info(`OIDC Plugin: Deleted ${provider} connection for user ${userId}`);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Plugin context exposed via ctx.plugins.oidc
|
|
259
|
+
*/
|
|
260
|
+
const pluginCtx = {
|
|
261
|
+
getConnection,
|
|
262
|
+
getConnections,
|
|
263
|
+
deleteConnection,
|
|
264
|
+
options: Object.freeze({ ...options }),
|
|
265
|
+
};
|
|
266
|
+
return {
|
|
267
|
+
id: "oidc",
|
|
268
|
+
db: {
|
|
269
|
+
useHostDb: true,
|
|
270
|
+
},
|
|
271
|
+
ctx: pluginCtx,
|
|
272
|
+
init,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import OidcConnection from "./schemas/OidcConnection";
|
|
2
|
+
import { OidcPluginOptions } from "./OidcPluginOptions";
|
|
3
|
+
/**
|
|
4
|
+
* OIDC Plugin context API exposed via ctx.plugins.oidc
|
|
5
|
+
*
|
|
6
|
+
* Provides methods to manage OIDC connections for users.
|
|
7
|
+
*/
|
|
8
|
+
export interface OidcPluginContext {
|
|
9
|
+
oidc: {
|
|
10
|
+
/**
|
|
11
|
+
* Get OIDC connection for a user and provider
|
|
12
|
+
*
|
|
13
|
+
* Returns the stored OIDC connection with decrypted tokens (if storeTokens enabled).
|
|
14
|
+
*
|
|
15
|
+
* @param userId - Application user ID
|
|
16
|
+
* @param provider - Provider name (e.g., "acme")
|
|
17
|
+
* @returns OIDC connection or null if not found
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const connection = await ctx.plugins.oidc.getConnection(user._id, 'acme');
|
|
22
|
+
* if (connection) {
|
|
23
|
+
* console.log('User connected to:', connection.issuer);
|
|
24
|
+
* console.log('Subject:', connection.subject);
|
|
25
|
+
* if (connection.accessToken) {
|
|
26
|
+
* // Use access token to call IdP APIs
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
getConnection: (userId: string, provider: string) => Promise<OidcConnection | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Get all OIDC connections for a user
|
|
34
|
+
*
|
|
35
|
+
* Returns all IdP connections for the user with decrypted tokens.
|
|
36
|
+
*
|
|
37
|
+
* @param userId - Application user ID
|
|
38
|
+
* @returns Array of OIDC connections
|
|
39
|
+
*
|
|
40
|
+
* Example:
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const connections = await ctx.plugins.oidc.getConnections(user._id);
|
|
43
|
+
* console.log('User has', connections.length, 'IdP connections');
|
|
44
|
+
* connections.forEach(conn => {
|
|
45
|
+
* console.log('- Provider:', conn.provider, 'Email:', conn.email);
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
getConnections: (userId: string) => Promise<OidcConnection[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Delete/unlink OIDC connection for a user
|
|
52
|
+
*
|
|
53
|
+
* Removes the connection between the user and the IdP.
|
|
54
|
+
* The user will need to re-authenticate with the IdP.
|
|
55
|
+
*
|
|
56
|
+
* @param userId - Application user ID
|
|
57
|
+
* @param provider - Provider name (e.g., "acme")
|
|
58
|
+
*
|
|
59
|
+
* Example:
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // User wants to disconnect their Acme account
|
|
62
|
+
* await ctx.plugins.oidc.deleteConnection(user._id, 'acme');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
deleteConnection: (userId: string, provider: string) => Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Plugin configuration options (read-only)
|
|
68
|
+
* Useful for checking plugin settings at runtime
|
|
69
|
+
*/
|
|
70
|
+
options: Readonly<OidcPluginOptions>;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=OidcPluginContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OidcPluginContext.d.ts","sourceRoot":"","sources":["../src/OidcPluginContext.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE;QACF;;;;;;;;;;;;;;;;;;;;WAoBG;QACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;QAEpF;;;;;;;;;;;;;;;;WAgBG;QACH,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;QAE9D;;;;;;;;;;;;;;WAcG;QACH,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAEtE;;;WAGG;QACH,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;KACxC,CAAC;CACL"}
|