@flink-app/oauth-plugin 0.12.1-alpha.33
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/LICENSE +21 -0
- package/README.md +783 -0
- package/SECURITY.md +433 -0
- package/dist/OAuthInternalContext.d.ts +45 -0
- package/dist/OAuthInternalContext.js +2 -0
- package/dist/OAuthPlugin.d.ts +70 -0
- package/dist/OAuthPlugin.js +220 -0
- package/dist/OAuthPluginContext.d.ts +49 -0
- package/dist/OAuthPluginContext.js +2 -0
- package/dist/OAuthPluginOptions.d.ts +111 -0
- package/dist/OAuthPluginOptions.js +2 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +66 -0
- package/dist/providers/GitHubProvider.d.ts +32 -0
- package/dist/providers/GitHubProvider.js +82 -0
- package/dist/providers/GoogleProvider.d.ts +32 -0
- package/dist/providers/GoogleProvider.js +83 -0
- package/dist/providers/OAuthProvider.d.ts +69 -0
- package/dist/providers/OAuthProvider.js +2 -0
- package/dist/providers/OAuthProviderBase.d.ts +32 -0
- package/dist/providers/OAuthProviderBase.js +86 -0
- package/dist/providers/ProviderRegistry.d.ts +14 -0
- package/dist/providers/ProviderRegistry.js +24 -0
- package/dist/repos/OAuthConnectionRepo.d.ts +30 -0
- package/dist/repos/OAuthConnectionRepo.js +38 -0
- package/dist/repos/OAuthSessionRepo.d.ts +22 -0
- package/dist/repos/OAuthSessionRepo.js +28 -0
- package/dist/schemas/OAuthConnection.d.ts +12 -0
- package/dist/schemas/OAuthConnection.js +2 -0
- package/dist/schemas/OAuthSession.d.ts +9 -0
- package/dist/schemas/OAuthSession.js +2 -0
- package/dist/utils/encryption-utils.d.ts +34 -0
- package/dist/utils/encryption-utils.js +134 -0
- package/dist/utils/error-utils.d.ts +68 -0
- package/dist/utils/error-utils.js +120 -0
- package/dist/utils/state-utils.d.ts +36 -0
- package/dist/utils/state-utils.js +72 -0
- package/examples/api-client-auth.ts +550 -0
- package/examples/basic-auth.ts +288 -0
- package/examples/multi-provider.ts +409 -0
- package/examples/token-storage.ts +490 -0
- package/package.json +38 -0
- package/spec/OAuthHandlers.spec.ts +146 -0
- package/spec/OAuthPluginSpec.ts +31 -0
- package/spec/ProvidersSpec.ts +178 -0
- package/spec/README.md +365 -0
- package/spec/helpers/mockJwtAuthPlugin.ts +104 -0
- package/spec/helpers/mockOAuthProviders.ts +189 -0
- package/spec/helpers/reporter.ts +41 -0
- package/spec/helpers/testDatabase.ts +107 -0
- package/spec/helpers/testHelpers.ts +192 -0
- package/spec/integration-critical.spec.ts +857 -0
- package/spec/integration.spec.ts +301 -0
- package/spec/repositories.spec.ts +181 -0
- package/spec/support/jasmine.json +7 -0
- package/spec/utils/security.spec.ts +243 -0
- package/src/OAuthInternalContext.ts +46 -0
- package/src/OAuthPlugin.ts +251 -0
- package/src/OAuthPluginContext.ts +53 -0
- package/src/OAuthPluginOptions.ts +122 -0
- package/src/handlers/CallbackOAuth.ts +238 -0
- package/src/handlers/InitiateOAuth.ts +99 -0
- package/src/index.ts +62 -0
- package/src/providers/GitHubProvider.ts +90 -0
- package/src/providers/GoogleProvider.ts +91 -0
- package/src/providers/OAuthProvider.ts +77 -0
- package/src/providers/OAuthProviderBase.ts +98 -0
- package/src/providers/ProviderRegistry.ts +27 -0
- package/src/repos/OAuthConnectionRepo.ts +41 -0
- package/src/repos/OAuthSessionRepo.ts +30 -0
- package/src/repos/TTL_INDEX_NOTE.md +28 -0
- package/src/schemas/CallbackRequest.ts +64 -0
- package/src/schemas/InitiateRequest.ts +10 -0
- package/src/schemas/OAuthConnection.ts +12 -0
- package/src/schemas/OAuthSession.ts +9 -0
- package/src/utils/encryption-utils.ts +148 -0
- package/src/utils/error-utils.ts +139 -0
- package/src/utils/state-utils.ts +70 -0
- package/src/utils/token-response-utils.ts +49 -0
- package/src/utils/validation-utils.ts +120 -0
- package/tsconfig.dist.json +4 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.oauthPlugin = void 0;
|
|
7
|
+
const flink_1 = require("@flink-app/flink");
|
|
8
|
+
const OAuthSessionRepo_1 = __importDefault(require("./repos/OAuthSessionRepo"));
|
|
9
|
+
const OAuthConnectionRepo_1 = __importDefault(require("./repos/OAuthConnectionRepo"));
|
|
10
|
+
const encryption_utils_1 = require("./utils/encryption-utils");
|
|
11
|
+
/**
|
|
12
|
+
* OAuth Plugin Factory Function
|
|
13
|
+
*
|
|
14
|
+
* Creates a Flink plugin for OAuth 2.0 authentication with GitHub and Google providers.
|
|
15
|
+
* Integrates with JWT Auth Plugin for token generation.
|
|
16
|
+
*
|
|
17
|
+
* @param options - OAuth plugin configuration options
|
|
18
|
+
* @returns FlinkPlugin instance
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
|
|
23
|
+
* import { oauthPlugin } from '@flink-app/oauth-plugin';
|
|
24
|
+
*
|
|
25
|
+
* const app = new FlinkApp({
|
|
26
|
+
* plugins: [
|
|
27
|
+
* // JWT Auth Plugin must be registered first
|
|
28
|
+
* jwtAuthPlugin({
|
|
29
|
+
* secret: process.env.JWT_SECRET!,
|
|
30
|
+
* getUser: async (tokenData) => {
|
|
31
|
+
* return ctx.repos.userRepo.getById(tokenData.userId);
|
|
32
|
+
* },
|
|
33
|
+
* rolePermissions: {
|
|
34
|
+
* user: ['read:own'],
|
|
35
|
+
* admin: ['read:all', 'write:all']
|
|
36
|
+
* }
|
|
37
|
+
* }),
|
|
38
|
+
*
|
|
39
|
+
* // OAuth Plugin uses JWT Auth Plugin in callbacks
|
|
40
|
+
* oauthPlugin({
|
|
41
|
+
* providers: {
|
|
42
|
+
* github: {
|
|
43
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
44
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
45
|
+
* callbackUrl: 'https://myapp.com/oauth/github/callback'
|
|
46
|
+
* }
|
|
47
|
+
* },
|
|
48
|
+
* onAuthSuccess: async ({ profile, provider }, ctx) => {
|
|
49
|
+
* // Find or create user
|
|
50
|
+
* let user = await ctx.repos.userRepo.getOne({ email: profile.email });
|
|
51
|
+
*
|
|
52
|
+
* if (!user) {
|
|
53
|
+
* user = await ctx.repos.userRepo.create({
|
|
54
|
+
* email: profile.email,
|
|
55
|
+
* name: profile.name,
|
|
56
|
+
* oauthProvider: provider,
|
|
57
|
+
* oauthProviderId: profile.id
|
|
58
|
+
* });
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* // Generate JWT token using JWT Auth Plugin
|
|
62
|
+
* const token = await ctx.plugins.jwtAuth.createToken(
|
|
63
|
+
* { userId: user._id, email: user.email },
|
|
64
|
+
* ['user']
|
|
65
|
+
* );
|
|
66
|
+
*
|
|
67
|
+
* return {
|
|
68
|
+
* user,
|
|
69
|
+
* token,
|
|
70
|
+
* redirectUrl: 'https://myapp.com/dashboard'
|
|
71
|
+
* };
|
|
72
|
+
* }
|
|
73
|
+
* })
|
|
74
|
+
* ]
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
function oauthPlugin(options) {
|
|
79
|
+
// Validation
|
|
80
|
+
if (!options.providers || Object.keys(options.providers).length === 0) {
|
|
81
|
+
throw new Error("OAuth Plugin: At least one provider must be configured");
|
|
82
|
+
}
|
|
83
|
+
// Validate provider configurations
|
|
84
|
+
const configuredProviders = Object.keys(options.providers);
|
|
85
|
+
for (const providerName of configuredProviders) {
|
|
86
|
+
const providerConfig = options.providers[providerName];
|
|
87
|
+
if (!providerConfig)
|
|
88
|
+
continue;
|
|
89
|
+
if (!providerConfig.clientId) {
|
|
90
|
+
throw new Error(`OAuth Plugin: ${providerName} clientId is required`);
|
|
91
|
+
}
|
|
92
|
+
if (!providerConfig.clientSecret) {
|
|
93
|
+
throw new Error(`OAuth Plugin: ${providerName} clientSecret is required`);
|
|
94
|
+
}
|
|
95
|
+
if (!providerConfig.callbackUrl) {
|
|
96
|
+
throw new Error(`OAuth Plugin: ${providerName} callbackUrl is required`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!options.onAuthSuccess) {
|
|
100
|
+
throw new Error("OAuth Plugin: onAuthSuccess callback is required");
|
|
101
|
+
}
|
|
102
|
+
// Determine encryption key
|
|
103
|
+
let encryptionKey = options.encryptionKey;
|
|
104
|
+
if (!encryptionKey) {
|
|
105
|
+
// Derive from first configured provider's client secret
|
|
106
|
+
const firstProvider = configuredProviders[0];
|
|
107
|
+
const firstProviderConfig = options.providers[firstProvider];
|
|
108
|
+
if (firstProviderConfig) {
|
|
109
|
+
encryptionKey = firstProviderConfig.clientSecret;
|
|
110
|
+
flink_1.log.warn("OAuth Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!encryptionKey || encryptionKey.length < 32) {
|
|
114
|
+
throw new Error("OAuth Plugin: Encryption key must be at least 32 characters");
|
|
115
|
+
}
|
|
116
|
+
// Validate encryption key
|
|
117
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
118
|
+
let flinkApp;
|
|
119
|
+
let sessionRepo;
|
|
120
|
+
let connectionRepo;
|
|
121
|
+
/**
|
|
122
|
+
* Plugin initialization
|
|
123
|
+
*/
|
|
124
|
+
async function init(app, db) {
|
|
125
|
+
flink_1.log.info("Initializing OAuth Plugin...");
|
|
126
|
+
flinkApp = app;
|
|
127
|
+
try {
|
|
128
|
+
if (!db) {
|
|
129
|
+
throw new Error("OAuth Plugin: Database connection is required");
|
|
130
|
+
}
|
|
131
|
+
// Initialize repositories
|
|
132
|
+
const sessionsCollectionName = options.sessionsCollectionName || "oauth_sessions";
|
|
133
|
+
const connectionsCollectionName = options.connectionsCollectionName || "oauth_connections";
|
|
134
|
+
sessionRepo = new OAuthSessionRepo_1.default(sessionsCollectionName, db);
|
|
135
|
+
connectionRepo = new OAuthConnectionRepo_1.default(connectionsCollectionName, db);
|
|
136
|
+
flinkApp.addRepo("oauthSessionRepo", sessionRepo);
|
|
137
|
+
flinkApp.addRepo("oauthConnectionRepo", connectionRepo);
|
|
138
|
+
// Create TTL index for session expiration
|
|
139
|
+
const sessionTTL = options.sessionTTL || 600; // Default 10 minutes
|
|
140
|
+
await db.collection(sessionsCollectionName).createIndex({ createdAt: 1 }, { expireAfterSeconds: sessionTTL });
|
|
141
|
+
flink_1.log.info(`OAuth Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
142
|
+
// Register handlers for each configured provider
|
|
143
|
+
// Note: Handlers will be registered dynamically
|
|
144
|
+
// This requires handlers to be imported, but we'll handle that in the handler files
|
|
145
|
+
// For now, we'll skip handler registration and implement it when handlers are ready
|
|
146
|
+
flink_1.log.info(`OAuth Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
flink_1.log.error("Failed to initialize OAuth Plugin:", error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get OAuth connection for a user
|
|
155
|
+
*/
|
|
156
|
+
async function getConnection(userId, provider) {
|
|
157
|
+
if (!connectionRepo) {
|
|
158
|
+
throw new Error("OAuth Plugin: Plugin not initialized");
|
|
159
|
+
}
|
|
160
|
+
const connection = await connectionRepo.findByUserAndProvider(userId, provider);
|
|
161
|
+
if (!connection) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
// Decrypt tokens before returning
|
|
165
|
+
if (connection.accessToken && encryptionKey) {
|
|
166
|
+
connection.accessToken = (0, encryption_utils_1.decryptToken)(connection.accessToken, encryptionKey);
|
|
167
|
+
}
|
|
168
|
+
if (connection.refreshToken && encryptionKey) {
|
|
169
|
+
connection.refreshToken = (0, encryption_utils_1.decryptToken)(connection.refreshToken, encryptionKey);
|
|
170
|
+
}
|
|
171
|
+
return connection;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get all OAuth connections for a user
|
|
175
|
+
*/
|
|
176
|
+
async function getConnections(userId) {
|
|
177
|
+
if (!connectionRepo) {
|
|
178
|
+
throw new Error("OAuth Plugin: Plugin not initialized");
|
|
179
|
+
}
|
|
180
|
+
const connections = await connectionRepo.findByUserId(userId);
|
|
181
|
+
// Decrypt tokens in each connection
|
|
182
|
+
return connections.map((connection) => {
|
|
183
|
+
if (connection.accessToken && encryptionKey) {
|
|
184
|
+
connection.accessToken = (0, encryption_utils_1.decryptToken)(connection.accessToken, encryptionKey);
|
|
185
|
+
}
|
|
186
|
+
if (connection.refreshToken && encryptionKey) {
|
|
187
|
+
connection.refreshToken = (0, encryption_utils_1.decryptToken)(connection.refreshToken, encryptionKey);
|
|
188
|
+
}
|
|
189
|
+
return connection;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Delete OAuth connection for a user
|
|
194
|
+
*/
|
|
195
|
+
async function deleteConnection(userId, provider) {
|
|
196
|
+
if (!connectionRepo) {
|
|
197
|
+
throw new Error("OAuth Plugin: Plugin not initialized");
|
|
198
|
+
}
|
|
199
|
+
await connectionRepo.deleteByUserAndProvider(userId, provider);
|
|
200
|
+
flink_1.log.info(`OAuth Plugin: Deleted ${provider} connection for user ${userId}`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Plugin context exposed via ctx.plugins.oauth
|
|
204
|
+
*/
|
|
205
|
+
const pluginCtx = {
|
|
206
|
+
getConnection,
|
|
207
|
+
getConnections,
|
|
208
|
+
deleteConnection,
|
|
209
|
+
options: Object.freeze({ ...options }),
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
id: "oauth",
|
|
213
|
+
db: {
|
|
214
|
+
useHostDb: true,
|
|
215
|
+
},
|
|
216
|
+
ctx: pluginCtx,
|
|
217
|
+
init,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
exports.oauthPlugin = oauthPlugin;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import OAuthConnection from "./schemas/OAuthConnection";
|
|
2
|
+
import { OAuthPluginOptions } from "./OAuthPluginOptions";
|
|
3
|
+
/**
|
|
4
|
+
* OAuth Plugin context API exposed via ctx.plugins.oauth
|
|
5
|
+
*
|
|
6
|
+
* Provides methods for managing OAuth connections (stored tokens) for users.
|
|
7
|
+
* Only applicable when storeTokens option is enabled.
|
|
8
|
+
*/
|
|
9
|
+
export interface OAuthPluginContext {
|
|
10
|
+
oauth: {
|
|
11
|
+
/**
|
|
12
|
+
* Get stored OAuth connection for a specific user and provider
|
|
13
|
+
*
|
|
14
|
+
* Tokens are automatically decrypted before being returned.
|
|
15
|
+
* Returns null if no connection exists.
|
|
16
|
+
*
|
|
17
|
+
* @param userId - The user's unique identifier
|
|
18
|
+
* @param provider - The OAuth provider (github or google)
|
|
19
|
+
* @returns The OAuth connection with decrypted tokens, or null
|
|
20
|
+
*/
|
|
21
|
+
getConnection: (userId: string, provider: "github" | "google") => Promise<OAuthConnection | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Get all stored OAuth connections for a specific user
|
|
24
|
+
*
|
|
25
|
+
* Tokens are automatically decrypted before being returned.
|
|
26
|
+
* Returns empty array if no connections exist.
|
|
27
|
+
*
|
|
28
|
+
* @param userId - The user's unique identifier
|
|
29
|
+
* @returns Array of OAuth connections with decrypted tokens
|
|
30
|
+
*/
|
|
31
|
+
getConnections: (userId: string) => Promise<OAuthConnection[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Delete OAuth connection for a specific user and provider
|
|
34
|
+
*
|
|
35
|
+
* Used when unlinking an OAuth provider from a user account.
|
|
36
|
+
* Also deletes the encrypted tokens from storage.
|
|
37
|
+
*
|
|
38
|
+
* @param userId - The user's unique identifier
|
|
39
|
+
* @param provider - The OAuth provider (github or google)
|
|
40
|
+
* @returns void
|
|
41
|
+
*/
|
|
42
|
+
deleteConnection: (userId: string, provider: "github" | "google") => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Plugin configuration (read-only)
|
|
45
|
+
* Provides access to plugin options for custom logic
|
|
46
|
+
*/
|
|
47
|
+
options: Readonly<OAuthPluginOptions>;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { OAuthProfile, OAuthTokens } from "./providers/OAuthProvider";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth error information
|
|
4
|
+
*/
|
|
5
|
+
export interface OAuthError {
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
details?: any;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Response from onAuthSuccess callback
|
|
12
|
+
* Must include JWT token generated by the application
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthSuccessCallbackResponse {
|
|
15
|
+
user: any;
|
|
16
|
+
token: string;
|
|
17
|
+
redirectUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Response from onAuthError callback
|
|
21
|
+
*/
|
|
22
|
+
export interface AuthErrorCallbackResponse {
|
|
23
|
+
redirectUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for OAuth Plugin with JWT integration
|
|
27
|
+
*
|
|
28
|
+
* The plugin depends on @flink-app/jwt-auth-plugin being installed and configured.
|
|
29
|
+
* The onAuthSuccess callback receives the Flink context as a second parameter,
|
|
30
|
+
* allowing the application to generate JWT tokens using ctx.plugins.jwtAuth.createToken().
|
|
31
|
+
*/
|
|
32
|
+
export interface OAuthPluginOptions {
|
|
33
|
+
/**
|
|
34
|
+
* OAuth provider configurations
|
|
35
|
+
* At least one provider must be configured
|
|
36
|
+
*/
|
|
37
|
+
providers: {
|
|
38
|
+
github?: {
|
|
39
|
+
clientId: string;
|
|
40
|
+
clientSecret: string;
|
|
41
|
+
callbackUrl: string;
|
|
42
|
+
scope?: string[];
|
|
43
|
+
};
|
|
44
|
+
google?: {
|
|
45
|
+
clientId: string;
|
|
46
|
+
clientSecret: string;
|
|
47
|
+
callbackUrl: string;
|
|
48
|
+
scope?: string[];
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Whether to store OAuth tokens for future API access
|
|
53
|
+
* If false, tokens are discarded after authentication
|
|
54
|
+
* Default: false
|
|
55
|
+
*/
|
|
56
|
+
storeTokens?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Callback invoked after successful OAuth authentication
|
|
59
|
+
*
|
|
60
|
+
* Application responsibilities:
|
|
61
|
+
* 1. Find or create user based on OAuth profile
|
|
62
|
+
* 2. Link OAuth provider to user account
|
|
63
|
+
* 3. Generate JWT token using ctx.plugins.jwtAuth.createToken(payload, roles)
|
|
64
|
+
* 4. Return user object, JWT token, and optional redirect URL
|
|
65
|
+
*
|
|
66
|
+
* @param params - OAuth profile, provider name, and tokens (if storeTokens enabled)
|
|
67
|
+
* @param ctx - Flink context with access to repos and plugins (including jwtAuth)
|
|
68
|
+
* @returns User object, JWT token, and optional redirect URL
|
|
69
|
+
*/
|
|
70
|
+
onAuthSuccess: (params: {
|
|
71
|
+
profile: OAuthProfile;
|
|
72
|
+
provider: "github" | "google";
|
|
73
|
+
tokens?: OAuthTokens;
|
|
74
|
+
}, ctx: any) => Promise<AuthSuccessCallbackResponse>;
|
|
75
|
+
/**
|
|
76
|
+
* Callback invoked on OAuth errors
|
|
77
|
+
*
|
|
78
|
+
* Application responsibilities:
|
|
79
|
+
* - Log error for debugging
|
|
80
|
+
* - Optionally provide redirect URL for error page
|
|
81
|
+
*
|
|
82
|
+
* @param params - Error information and provider name
|
|
83
|
+
* @returns Optional redirect URL for error page
|
|
84
|
+
*/
|
|
85
|
+
onAuthError?: (params: {
|
|
86
|
+
error: OAuthError;
|
|
87
|
+
provider: "github" | "google";
|
|
88
|
+
}) => Promise<AuthErrorCallbackResponse>;
|
|
89
|
+
/**
|
|
90
|
+
* Custom collection name for OAuth sessions
|
|
91
|
+
* Default: 'oauth_sessions'
|
|
92
|
+
*/
|
|
93
|
+
sessionsCollectionName?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Custom collection name for OAuth connections (if storeTokens enabled)
|
|
96
|
+
* Default: 'oauth_connections'
|
|
97
|
+
*/
|
|
98
|
+
connectionsCollectionName?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Session TTL in seconds
|
|
101
|
+
* Sessions are automatically cleaned up after this duration
|
|
102
|
+
* Default: 600 (10 minutes)
|
|
103
|
+
*/
|
|
104
|
+
sessionTTL?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Encryption key for encrypting stored OAuth tokens
|
|
107
|
+
* If not provided, will be derived from first configured provider's client secret
|
|
108
|
+
* Recommended: Use a dedicated encryption key from environment variables
|
|
109
|
+
*/
|
|
110
|
+
encryptionKey?: string;
|
|
111
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flink-app/oauth-plugin
|
|
3
|
+
*
|
|
4
|
+
* OAuth 2.0 plugin for Flink supporting GitHub and Google providers
|
|
5
|
+
* with JWT token integration and flexible user management.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
|
|
10
|
+
* import { oauthPlugin } from '@flink-app/oauth-plugin';
|
|
11
|
+
*
|
|
12
|
+
* const app = new FlinkApp({
|
|
13
|
+
* plugins: [
|
|
14
|
+
* jwtAuthPlugin({ ... }),
|
|
15
|
+
* oauthPlugin({
|
|
16
|
+
* providers: {
|
|
17
|
+
* github: {
|
|
18
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
19
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
20
|
+
* callbackUrl: 'https://myapp.com/oauth/github/callback'
|
|
21
|
+
* }
|
|
22
|
+
* },
|
|
23
|
+
* onAuthSuccess: async ({ profile }, ctx) => {
|
|
24
|
+
* const user = await ctx.repos.userRepo.getOne({ email: profile.email });
|
|
25
|
+
* const token = await ctx.plugins.jwtAuth.createToken({ userId: user._id }, ['user']);
|
|
26
|
+
* return { user, token };
|
|
27
|
+
* }
|
|
28
|
+
* })
|
|
29
|
+
* ]
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export { oauthPlugin } from "./OAuthPlugin";
|
|
34
|
+
export type { OAuthPluginOptions, OAuthError, AuthSuccessCallbackResponse, AuthErrorCallbackResponse } from "./OAuthPluginOptions";
|
|
35
|
+
export type { OAuthPluginContext } from "./OAuthPluginContext";
|
|
36
|
+
export type { OAuthInternalContext } from "./OAuthInternalContext";
|
|
37
|
+
export type { OAuthProvider, OAuthTokens, OAuthProfile, AuthorizationUrlParams, TokenExchangeParams, ProviderConfig } from "./providers/OAuthProvider";
|
|
38
|
+
export { GitHubProvider } from "./providers/GitHubProvider";
|
|
39
|
+
export { GoogleProvider } from "./providers/GoogleProvider";
|
|
40
|
+
export { OAuthProviderBase } from "./providers/OAuthProviderBase";
|
|
41
|
+
export { getProvider, type ProviderName } from "./providers/ProviderRegistry";
|
|
42
|
+
export type { default as OAuthSession } from "./schemas/OAuthSession";
|
|
43
|
+
export type { default as OAuthConnection } from "./schemas/OAuthConnection";
|
|
44
|
+
export { default as OAuthSessionRepo } from "./repos/OAuthSessionRepo";
|
|
45
|
+
export { default as OAuthConnectionRepo } from "./repos/OAuthConnectionRepo";
|
|
46
|
+
export { encryptToken, decryptToken, validateEncryptionSecret } from "./utils/encryption-utils";
|
|
47
|
+
export { generateState, validateState } from "./utils/state-utils";
|
|
48
|
+
export { createOAuthError, handleProviderError } from "./utils/error-utils";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @flink-app/oauth-plugin
|
|
4
|
+
*
|
|
5
|
+
* OAuth 2.0 plugin for Flink supporting GitHub and Google providers
|
|
6
|
+
* with JWT token integration and flexible user management.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
|
|
11
|
+
* import { oauthPlugin } from '@flink-app/oauth-plugin';
|
|
12
|
+
*
|
|
13
|
+
* const app = new FlinkApp({
|
|
14
|
+
* plugins: [
|
|
15
|
+
* jwtAuthPlugin({ ... }),
|
|
16
|
+
* oauthPlugin({
|
|
17
|
+
* providers: {
|
|
18
|
+
* github: {
|
|
19
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
20
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
21
|
+
* callbackUrl: 'https://myapp.com/oauth/github/callback'
|
|
22
|
+
* }
|
|
23
|
+
* },
|
|
24
|
+
* onAuthSuccess: async ({ profile }, ctx) => {
|
|
25
|
+
* const user = await ctx.repos.userRepo.getOne({ email: profile.email });
|
|
26
|
+
* const token = await ctx.plugins.jwtAuth.createToken({ userId: user._id }, ['user']);
|
|
27
|
+
* return { user, token };
|
|
28
|
+
* }
|
|
29
|
+
* })
|
|
30
|
+
* ]
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.handleProviderError = exports.createOAuthError = exports.validateState = exports.generateState = exports.validateEncryptionSecret = exports.decryptToken = exports.encryptToken = exports.OAuthConnectionRepo = exports.OAuthSessionRepo = exports.getProvider = exports.OAuthProviderBase = exports.GoogleProvider = exports.GitHubProvider = exports.oauthPlugin = void 0;
|
|
39
|
+
// Plugin factory function
|
|
40
|
+
var OAuthPlugin_1 = require("./OAuthPlugin");
|
|
41
|
+
Object.defineProperty(exports, "oauthPlugin", { enumerable: true, get: function () { return OAuthPlugin_1.oauthPlugin; } });
|
|
42
|
+
// Provider implementations
|
|
43
|
+
var GitHubProvider_1 = require("./providers/GitHubProvider");
|
|
44
|
+
Object.defineProperty(exports, "GitHubProvider", { enumerable: true, get: function () { return GitHubProvider_1.GitHubProvider; } });
|
|
45
|
+
var GoogleProvider_1 = require("./providers/GoogleProvider");
|
|
46
|
+
Object.defineProperty(exports, "GoogleProvider", { enumerable: true, get: function () { return GoogleProvider_1.GoogleProvider; } });
|
|
47
|
+
var OAuthProviderBase_1 = require("./providers/OAuthProviderBase");
|
|
48
|
+
Object.defineProperty(exports, "OAuthProviderBase", { enumerable: true, get: function () { return OAuthProviderBase_1.OAuthProviderBase; } });
|
|
49
|
+
var ProviderRegistry_1 = require("./providers/ProviderRegistry");
|
|
50
|
+
Object.defineProperty(exports, "getProvider", { enumerable: true, get: function () { return ProviderRegistry_1.getProvider; } });
|
|
51
|
+
// Repositories
|
|
52
|
+
var OAuthSessionRepo_1 = require("./repos/OAuthSessionRepo");
|
|
53
|
+
Object.defineProperty(exports, "OAuthSessionRepo", { enumerable: true, get: function () { return __importDefault(OAuthSessionRepo_1).default; } });
|
|
54
|
+
var OAuthConnectionRepo_1 = require("./repos/OAuthConnectionRepo");
|
|
55
|
+
Object.defineProperty(exports, "OAuthConnectionRepo", { enumerable: true, get: function () { return __importDefault(OAuthConnectionRepo_1).default; } });
|
|
56
|
+
// Utilities (for advanced use cases)
|
|
57
|
+
var encryption_utils_1 = require("./utils/encryption-utils");
|
|
58
|
+
Object.defineProperty(exports, "encryptToken", { enumerable: true, get: function () { return encryption_utils_1.encryptToken; } });
|
|
59
|
+
Object.defineProperty(exports, "decryptToken", { enumerable: true, get: function () { return encryption_utils_1.decryptToken; } });
|
|
60
|
+
Object.defineProperty(exports, "validateEncryptionSecret", { enumerable: true, get: function () { return encryption_utils_1.validateEncryptionSecret; } });
|
|
61
|
+
var state_utils_1 = require("./utils/state-utils");
|
|
62
|
+
Object.defineProperty(exports, "generateState", { enumerable: true, get: function () { return state_utils_1.generateState; } });
|
|
63
|
+
Object.defineProperty(exports, "validateState", { enumerable: true, get: function () { return state_utils_1.validateState; } });
|
|
64
|
+
var error_utils_1 = require("./utils/error-utils");
|
|
65
|
+
Object.defineProperty(exports, "createOAuthError", { enumerable: true, get: function () { return error_utils_1.createOAuthError; } });
|
|
66
|
+
Object.defineProperty(exports, "handleProviderError", { enumerable: true, get: function () { return error_utils_1.handleProviderError; } });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { OAuthProvider, OAuthTokens, OAuthProfile, AuthorizationUrlParams, TokenExchangeParams, ProviderConfig } from "./OAuthProvider";
|
|
2
|
+
import { OAuthProviderBase } from "./OAuthProviderBase";
|
|
3
|
+
/**
|
|
4
|
+
* GitHub OAuth 2.0 provider implementation
|
|
5
|
+
*
|
|
6
|
+
* Implements OAuth flow for GitHub:
|
|
7
|
+
* - Authorization URL: https://github.com/login/oauth/authorize
|
|
8
|
+
* - Token exchange: POST https://github.com/login/oauth/access_token
|
|
9
|
+
* - User profile: GET https://api.github.com/user
|
|
10
|
+
*
|
|
11
|
+
* Default scopes: ['user:email']
|
|
12
|
+
*/
|
|
13
|
+
export declare class GitHubProvider extends OAuthProviderBase implements OAuthProvider {
|
|
14
|
+
readonly name = "github";
|
|
15
|
+
private static readonly AUTHORIZATION_URL;
|
|
16
|
+
private static readonly TOKEN_URL;
|
|
17
|
+
private static readonly USER_PROFILE_URL;
|
|
18
|
+
private static readonly DEFAULT_SCOPES;
|
|
19
|
+
constructor(config: ProviderConfig);
|
|
20
|
+
/**
|
|
21
|
+
* Generate GitHub authorization URL
|
|
22
|
+
*/
|
|
23
|
+
getAuthorizationUrl(params: AuthorizationUrlParams): string;
|
|
24
|
+
/**
|
|
25
|
+
* Exchange authorization code for access token
|
|
26
|
+
*/
|
|
27
|
+
exchangeCodeForToken(params: TokenExchangeParams): Promise<OAuthTokens>;
|
|
28
|
+
/**
|
|
29
|
+
* Fetch user profile from GitHub
|
|
30
|
+
*/
|
|
31
|
+
getUserProfile(accessToken: string): Promise<OAuthProfile>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubProvider = void 0;
|
|
4
|
+
const OAuthProviderBase_1 = require("./OAuthProviderBase");
|
|
5
|
+
/**
|
|
6
|
+
* GitHub OAuth 2.0 provider implementation
|
|
7
|
+
*
|
|
8
|
+
* Implements OAuth flow for GitHub:
|
|
9
|
+
* - Authorization URL: https://github.com/login/oauth/authorize
|
|
10
|
+
* - Token exchange: POST https://github.com/login/oauth/access_token
|
|
11
|
+
* - User profile: GET https://api.github.com/user
|
|
12
|
+
*
|
|
13
|
+
* Default scopes: ['user:email']
|
|
14
|
+
*/
|
|
15
|
+
class GitHubProvider extends OAuthProviderBase_1.OAuthProviderBase {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super(config);
|
|
18
|
+
this.name = "github";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate GitHub authorization URL
|
|
22
|
+
*/
|
|
23
|
+
getAuthorizationUrl(params) {
|
|
24
|
+
const scopes = params.scope.length > 0 ? params.scope : GitHubProvider.DEFAULT_SCOPES;
|
|
25
|
+
const queryParams = {
|
|
26
|
+
client_id: this.config.clientId,
|
|
27
|
+
redirect_uri: params.redirectUri,
|
|
28
|
+
state: params.state,
|
|
29
|
+
scope: scopes.join(" "),
|
|
30
|
+
};
|
|
31
|
+
return `${GitHubProvider.AUTHORIZATION_URL}?${this.buildQueryString(queryParams)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Exchange authorization code for access token
|
|
35
|
+
*/
|
|
36
|
+
async exchangeCodeForToken(params) {
|
|
37
|
+
const body = this.buildQueryString({
|
|
38
|
+
client_id: this.config.clientId,
|
|
39
|
+
client_secret: this.config.clientSecret,
|
|
40
|
+
code: params.code,
|
|
41
|
+
redirect_uri: params.redirectUri,
|
|
42
|
+
});
|
|
43
|
+
const response = await this.makeHttpRequest(GitHubProvider.TOKEN_URL, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
47
|
+
Accept: "application/json",
|
|
48
|
+
},
|
|
49
|
+
body,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
accessToken: response.access_token,
|
|
53
|
+
refreshToken: response.refresh_token,
|
|
54
|
+
scope: response.scope,
|
|
55
|
+
expiresIn: response.expires_in,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Fetch user profile from GitHub
|
|
60
|
+
*/
|
|
61
|
+
async getUserProfile(accessToken) {
|
|
62
|
+
const response = await this.makeHttpRequest(GitHubProvider.USER_PROFILE_URL, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${accessToken}`,
|
|
66
|
+
Accept: "application/json",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
id: String(response.id),
|
|
71
|
+
email: response.email,
|
|
72
|
+
name: response.name,
|
|
73
|
+
avatarUrl: response.avatar_url,
|
|
74
|
+
raw: response,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.GitHubProvider = GitHubProvider;
|
|
79
|
+
GitHubProvider.AUTHORIZATION_URL = "https://github.com/login/oauth/authorize";
|
|
80
|
+
GitHubProvider.TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
81
|
+
GitHubProvider.USER_PROFILE_URL = "https://api.github.com/user";
|
|
82
|
+
GitHubProvider.DEFAULT_SCOPES = ["user:email"];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { OAuthProvider, OAuthTokens, OAuthProfile, AuthorizationUrlParams, TokenExchangeParams, ProviderConfig } from "./OAuthProvider";
|
|
2
|
+
import { OAuthProviderBase } from "./OAuthProviderBase";
|
|
3
|
+
/**
|
|
4
|
+
* Google OAuth 2.0 provider implementation
|
|
5
|
+
*
|
|
6
|
+
* Implements OAuth flow for Google:
|
|
7
|
+
* - Authorization URL: https://accounts.google.com/o/oauth2/v2/auth
|
|
8
|
+
* - Token exchange: POST https://oauth2.googleapis.com/token
|
|
9
|
+
* - User profile: GET https://www.googleapis.com/oauth2/v2/userinfo
|
|
10
|
+
*
|
|
11
|
+
* Default scopes: ['openid', 'email', 'profile']
|
|
12
|
+
*/
|
|
13
|
+
export declare class GoogleProvider extends OAuthProviderBase implements OAuthProvider {
|
|
14
|
+
readonly name = "google";
|
|
15
|
+
private static readonly AUTHORIZATION_URL;
|
|
16
|
+
private static readonly TOKEN_URL;
|
|
17
|
+
private static readonly USER_PROFILE_URL;
|
|
18
|
+
private static readonly DEFAULT_SCOPES;
|
|
19
|
+
constructor(config: ProviderConfig);
|
|
20
|
+
/**
|
|
21
|
+
* Generate Google authorization URL
|
|
22
|
+
*/
|
|
23
|
+
getAuthorizationUrl(params: AuthorizationUrlParams): string;
|
|
24
|
+
/**
|
|
25
|
+
* Exchange authorization code for access token
|
|
26
|
+
*/
|
|
27
|
+
exchangeCodeForToken(params: TokenExchangeParams): Promise<OAuthTokens>;
|
|
28
|
+
/**
|
|
29
|
+
* Fetch user profile from Google
|
|
30
|
+
*/
|
|
31
|
+
getUserProfile(accessToken: string): Promise<OAuthProfile>;
|
|
32
|
+
}
|