@flink-app/oauth-plugin 0.12.1-alpha.35 → 0.12.1-alpha.37
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/dist/OAuthPlugin.js +31 -4
- package/dist/OAuthPluginOptions.d.ts +6 -0
- package/dist/handlers/CallbackOAuth.d.ts +37 -0
- package/dist/handlers/CallbackOAuth.js +201 -0
- package/dist/handlers/InitiateOAuth.d.ts +35 -0
- package/dist/handlers/InitiateOAuth.js +83 -0
- package/dist/schemas/CallbackRequest.d.ts +53 -0
- package/dist/schemas/CallbackRequest.js +2 -0
- package/dist/schemas/InitiateRequest.d.ts +10 -0
- package/dist/schemas/InitiateRequest.js +2 -0
- package/dist/utils/token-response-utils.d.ts +14 -0
- package/dist/utils/token-response-utils.js +50 -0
- package/package.json +2 -2
- package/spec/OAuthHandlers.spec.ts +118 -119
- package/src/OAuthPlugin.ts +8 -4
- package/src/OAuthPluginOptions.ts +7 -0
- package/src/handlers/CallbackOAuth.ts +2 -1
- package/src/handlers/InitiateOAuth.ts +2 -1
package/dist/OAuthPlugin.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
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
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
@@ -8,6 +31,8 @@ const flink_1 = require("@flink-app/flink");
|
|
|
8
31
|
const OAuthSessionRepo_1 = __importDefault(require("./repos/OAuthSessionRepo"));
|
|
9
32
|
const OAuthConnectionRepo_1 = __importDefault(require("./repos/OAuthConnectionRepo"));
|
|
10
33
|
const encryption_utils_1 = require("./utils/encryption-utils");
|
|
34
|
+
const InitiateOAuth = __importStar(require("./handlers/InitiateOAuth"));
|
|
35
|
+
const CallbackOAuth = __importStar(require("./handlers/CallbackOAuth"));
|
|
11
36
|
/**
|
|
12
37
|
* OAuth Plugin Factory Function
|
|
13
38
|
*
|
|
@@ -139,10 +164,12 @@ function oauthPlugin(options) {
|
|
|
139
164
|
const sessionTTL = options.sessionTTL || 600; // Default 10 minutes
|
|
140
165
|
await db.collection(sessionsCollectionName).createIndex({ createdAt: 1 }, { expireAfterSeconds: sessionTTL });
|
|
141
166
|
flink_1.log.info(`OAuth Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
142
|
-
// Register handlers
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
// Register OAuth handlers
|
|
168
|
+
// Only register handlers if registerRoutes is enabled (default: true)
|
|
169
|
+
if (options.registerRoutes !== false) {
|
|
170
|
+
flinkApp.addHandler(InitiateOAuth);
|
|
171
|
+
flinkApp.addHandler(CallbackOAuth);
|
|
172
|
+
}
|
|
146
173
|
flink_1.log.info(`OAuth Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
147
174
|
}
|
|
148
175
|
catch (error) {
|
|
@@ -108,4 +108,10 @@ export interface OAuthPluginOptions {
|
|
|
108
108
|
* Recommended: Use a dedicated encryption key from environment variables
|
|
109
109
|
*/
|
|
110
110
|
encryptionKey?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Whether to register OAuth routes automatically
|
|
113
|
+
* If false, you must manually handle OAuth flow
|
|
114
|
+
* Default: true
|
|
115
|
+
*/
|
|
116
|
+
registerRoutes?: boolean;
|
|
111
117
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Callback Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles the OAuth 2.0 callback from the provider by:
|
|
5
|
+
* 1. Validating the state parameter to prevent CSRF attacks
|
|
6
|
+
* 2. Exchanging the authorization code for an access token
|
|
7
|
+
* 3. Fetching the user profile from the provider
|
|
8
|
+
* 4. Calling the onAuthSuccess callback to create/link user and generate JWT token
|
|
9
|
+
* 5. Optionally storing the OAuth connection (if storeTokens enabled)
|
|
10
|
+
* 6. Returning the JWT token to the client (via JSON or redirect)
|
|
11
|
+
*
|
|
12
|
+
* Route: GET /oauth/:provider/callback?code=...&state=...&response_type=json
|
|
13
|
+
*/
|
|
14
|
+
import { GetHandler, RouteProps } from "@flink-app/flink";
|
|
15
|
+
import CallbackRequest from "../schemas/CallbackRequest";
|
|
16
|
+
/**
|
|
17
|
+
* Path parameters for the handler
|
|
18
|
+
*/
|
|
19
|
+
interface PathParams {
|
|
20
|
+
provider: string;
|
|
21
|
+
[key: string]: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Route configuration
|
|
25
|
+
* Note: This handler is registered programmatically by the plugin
|
|
26
|
+
* with dynamic provider parameter support
|
|
27
|
+
*/
|
|
28
|
+
export declare const Route: RouteProps;
|
|
29
|
+
/**
|
|
30
|
+
* OAuth Callback Handler
|
|
31
|
+
*
|
|
32
|
+
* Completes the OAuth flow by exchanging the authorization code for tokens,
|
|
33
|
+
* fetching user profile, calling the app's onAuthSuccess callback to generate
|
|
34
|
+
* JWT token, and returning the token to the client.
|
|
35
|
+
*/
|
|
36
|
+
declare const CallbackOAuth: GetHandler<any, any, PathParams, CallbackRequest>;
|
|
37
|
+
export default CallbackOAuth;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth Callback Handler
|
|
4
|
+
*
|
|
5
|
+
* Handles the OAuth 2.0 callback from the provider by:
|
|
6
|
+
* 1. Validating the state parameter to prevent CSRF attacks
|
|
7
|
+
* 2. Exchanging the authorization code for an access token
|
|
8
|
+
* 3. Fetching the user profile from the provider
|
|
9
|
+
* 4. Calling the onAuthSuccess callback to create/link user and generate JWT token
|
|
10
|
+
* 5. Optionally storing the OAuth connection (if storeTokens enabled)
|
|
11
|
+
* 6. Returning the JWT token to the client (via JSON or redirect)
|
|
12
|
+
*
|
|
13
|
+
* Route: GET /oauth/:provider/callback?code=...&state=...&response_type=json
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.Route = void 0;
|
|
17
|
+
const flink_1 = require("@flink-app/flink");
|
|
18
|
+
const state_utils_1 = require("../utils/state-utils");
|
|
19
|
+
const ProviderRegistry_1 = require("../providers/ProviderRegistry");
|
|
20
|
+
const token_response_utils_1 = require("../utils/token-response-utils");
|
|
21
|
+
const encryption_utils_1 = require("../utils/encryption-utils");
|
|
22
|
+
const error_utils_1 = require("../utils/error-utils");
|
|
23
|
+
/**
|
|
24
|
+
* Route configuration
|
|
25
|
+
* Note: This handler is registered programmatically by the plugin
|
|
26
|
+
* with dynamic provider parameter support
|
|
27
|
+
*/
|
|
28
|
+
exports.Route = {
|
|
29
|
+
path: "/oauth/:provider/callback",
|
|
30
|
+
method: flink_1.HttpMethod.get,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* OAuth Callback Handler
|
|
34
|
+
*
|
|
35
|
+
* Completes the OAuth flow by exchanging the authorization code for tokens,
|
|
36
|
+
* fetching user profile, calling the app's onAuthSuccess callback to generate
|
|
37
|
+
* JWT token, and returning the token to the client.
|
|
38
|
+
*/
|
|
39
|
+
const CallbackOAuth = async ({ ctx, req }) => {
|
|
40
|
+
const { provider } = req.params;
|
|
41
|
+
const { code, state, error: oauthError, response_type } = req.query;
|
|
42
|
+
try {
|
|
43
|
+
// Validate provider and response_type
|
|
44
|
+
(0, error_utils_1.validateProvider)(provider);
|
|
45
|
+
(0, error_utils_1.validateResponseType)(response_type);
|
|
46
|
+
// Check for OAuth provider errors (e.g., user denied access)
|
|
47
|
+
if (oauthError) {
|
|
48
|
+
const error = (0, error_utils_1.handleProviderError)({ error: oauthError });
|
|
49
|
+
// Call onAuthError callback if provided
|
|
50
|
+
const { options } = ctx.plugins.oauth;
|
|
51
|
+
if (options.onAuthError) {
|
|
52
|
+
const errorResult = await options.onAuthError({
|
|
53
|
+
error,
|
|
54
|
+
provider: provider,
|
|
55
|
+
});
|
|
56
|
+
if (errorResult.redirectUrl) {
|
|
57
|
+
return {
|
|
58
|
+
status: 302,
|
|
59
|
+
headers: { Location: errorResult.redirectUrl },
|
|
60
|
+
data: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return (0, flink_1.badRequest)(error.message);
|
|
65
|
+
}
|
|
66
|
+
// Validate required parameters
|
|
67
|
+
if (!code || !state) {
|
|
68
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.MISSING_CODE, "Missing authorization code or state parameter", { hasCode: !!code, hasState: !!state });
|
|
69
|
+
}
|
|
70
|
+
// Find OAuth session by state
|
|
71
|
+
const session = await ctx.repos.oauthSessionRepo.getOne({ state });
|
|
72
|
+
if (!session) {
|
|
73
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.SESSION_EXPIRED, "OAuth session not found or expired. Please try logging in again.", { state });
|
|
74
|
+
}
|
|
75
|
+
// Validate state parameter (CSRF protection)
|
|
76
|
+
if (!(0, state_utils_1.validateState)(state, session.state)) {
|
|
77
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.INVALID_STATE, "Invalid state parameter. Possible CSRF attack detected.", {
|
|
78
|
+
providedState: state.substring(0, 10) + "...",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Delete session immediately after validation (one-time use)
|
|
82
|
+
await ctx.repos.oauthSessionRepo.deleteBySessionId(session.sessionId);
|
|
83
|
+
// Get plugin options and provider config
|
|
84
|
+
const { options } = ctx.plugins.oauth;
|
|
85
|
+
const providerConfig = options.providers[provider];
|
|
86
|
+
if (!providerConfig) {
|
|
87
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.INVALID_PROVIDER, `Provider "${provider}" is not configured`, { provider });
|
|
88
|
+
}
|
|
89
|
+
// Exchange authorization code for access token
|
|
90
|
+
const oauthProvider = (0, ProviderRegistry_1.getProvider)(provider, providerConfig);
|
|
91
|
+
const tokens = await oauthProvider.exchangeCodeForToken({
|
|
92
|
+
code,
|
|
93
|
+
redirectUri: providerConfig.callbackUrl,
|
|
94
|
+
});
|
|
95
|
+
// Fetch user profile from provider
|
|
96
|
+
const profile = await oauthProvider.getUserProfile(tokens.accessToken);
|
|
97
|
+
// Call onAuthSuccess callback to create/link user and generate JWT token
|
|
98
|
+
const authSuccessParams = {
|
|
99
|
+
profile,
|
|
100
|
+
provider: provider,
|
|
101
|
+
...(options.storeTokens ? { tokens } : {}),
|
|
102
|
+
};
|
|
103
|
+
let authResult;
|
|
104
|
+
try {
|
|
105
|
+
authResult = await options.onAuthSuccess(authSuccessParams, ctx);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Handle JWT generation or user creation errors
|
|
109
|
+
flink_1.log.error("OAuth onAuthSuccess callback failed:", error);
|
|
110
|
+
const oauthError = (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.JWT_GENERATION_FAILED, "Failed to complete authentication. Please try again.", {
|
|
111
|
+
originalError: error.message,
|
|
112
|
+
});
|
|
113
|
+
// Call onAuthError callback if provided
|
|
114
|
+
if (options.onAuthError) {
|
|
115
|
+
const errorResult = await options.onAuthError({
|
|
116
|
+
error: oauthError,
|
|
117
|
+
provider: provider,
|
|
118
|
+
});
|
|
119
|
+
if (errorResult.redirectUrl) {
|
|
120
|
+
return {
|
|
121
|
+
status: 302,
|
|
122
|
+
headers: { Location: errorResult.redirectUrl },
|
|
123
|
+
data: {},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return (0, flink_1.internalServerError)("Authentication failed. Please try again.");
|
|
128
|
+
}
|
|
129
|
+
// Extract user and JWT token from callback result
|
|
130
|
+
const { user, token, redirectUrl } = authResult;
|
|
131
|
+
if (!token) {
|
|
132
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.JWT_GENERATION_FAILED, "No authentication token returned from callback", { hasUser: !!user });
|
|
133
|
+
}
|
|
134
|
+
// Store OAuth connection if token storage is enabled
|
|
135
|
+
if (options.storeTokens && user && user._id) {
|
|
136
|
+
const encryptionSecret = providerConfig.clientSecret;
|
|
137
|
+
// Encrypt tokens before storing
|
|
138
|
+
const encryptedAccessToken = (0, encryption_utils_1.encryptToken)(tokens.accessToken, encryptionSecret);
|
|
139
|
+
const encryptedRefreshToken = tokens.refreshToken ? (0, encryption_utils_1.encryptToken)(tokens.refreshToken, encryptionSecret) : undefined;
|
|
140
|
+
// Calculate token expiration
|
|
141
|
+
const expiresAt = tokens.expiresIn ? new Date(Date.now() + tokens.expiresIn * 1000) : undefined;
|
|
142
|
+
// Create or update OAuth connection
|
|
143
|
+
const existingConnection = await ctx.repos.oauthConnectionRepo.findByUserAndProvider(user._id, provider);
|
|
144
|
+
if (existingConnection) {
|
|
145
|
+
await ctx.repos.oauthConnectionRepo.updateOne(existingConnection._id, {
|
|
146
|
+
accessToken: encryptedAccessToken,
|
|
147
|
+
refreshToken: encryptedRefreshToken,
|
|
148
|
+
scope: tokens.scope || "",
|
|
149
|
+
expiresAt,
|
|
150
|
+
updatedAt: new Date(),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
await ctx.repos.oauthConnectionRepo.create({
|
|
155
|
+
userId: user._id,
|
|
156
|
+
provider: provider,
|
|
157
|
+
providerId: profile.id,
|
|
158
|
+
accessToken: encryptedAccessToken,
|
|
159
|
+
refreshToken: encryptedRefreshToken,
|
|
160
|
+
scope: tokens.scope || "",
|
|
161
|
+
expiresAt,
|
|
162
|
+
createdAt: new Date(),
|
|
163
|
+
updatedAt: new Date(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Return JWT token in requested format
|
|
168
|
+
return (0, token_response_utils_1.formatTokenResponse)(token, user, redirectUrl || session.redirectUri, response_type);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
flink_1.log.error("OAuth callback error:", error);
|
|
172
|
+
// Handle OAuth-specific errors
|
|
173
|
+
if (error.code && Object.values(error_utils_1.OAuthErrorCodes).includes(error.code)) {
|
|
174
|
+
// Call onAuthError callback if provided
|
|
175
|
+
const { options } = ctx.plugins.oauth;
|
|
176
|
+
if (options.onAuthError) {
|
|
177
|
+
try {
|
|
178
|
+
const errorResult = await options.onAuthError({
|
|
179
|
+
error,
|
|
180
|
+
provider: provider,
|
|
181
|
+
});
|
|
182
|
+
if (errorResult.redirectUrl) {
|
|
183
|
+
return {
|
|
184
|
+
status: 302,
|
|
185
|
+
headers: { Location: errorResult.redirectUrl },
|
|
186
|
+
data: {},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (callbackError) {
|
|
191
|
+
flink_1.log.error("onAuthError callback failed:", callbackError);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return (0, flink_1.badRequest)(error.message);
|
|
195
|
+
}
|
|
196
|
+
// Handle provider errors
|
|
197
|
+
const mappedError = (0, error_utils_1.handleProviderError)(error);
|
|
198
|
+
return (0, flink_1.internalServerError)(mappedError.message);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
exports.default = CallbackOAuth;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Initiate Handler
|
|
3
|
+
*
|
|
4
|
+
* Initiates the OAuth 2.0 authorization code flow by:
|
|
5
|
+
* 1. Validating the provider is supported and configured
|
|
6
|
+
* 2. Generating a cryptographically secure state parameter for CSRF protection
|
|
7
|
+
* 3. Creating an OAuth session to track the flow
|
|
8
|
+
* 4. Building the provider's authorization URL
|
|
9
|
+
* 5. Redirecting the user to the provider for authorization
|
|
10
|
+
*
|
|
11
|
+
* Route: GET /oauth/:provider/initiate?redirectUri={optional_redirect_url}
|
|
12
|
+
*/
|
|
13
|
+
import { GetHandler, RouteProps } from "@flink-app/flink";
|
|
14
|
+
import InitiateRequest from "../schemas/InitiateRequest";
|
|
15
|
+
/**
|
|
16
|
+
* Path parameters for the handler
|
|
17
|
+
*/
|
|
18
|
+
interface PathParams {
|
|
19
|
+
provider: string;
|
|
20
|
+
[key: string]: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Route configuration
|
|
24
|
+
* Note: This handler is registered programmatically by the plugin
|
|
25
|
+
* with dynamic provider parameter support
|
|
26
|
+
*/
|
|
27
|
+
export declare const Route: RouteProps;
|
|
28
|
+
/**
|
|
29
|
+
* OAuth Initiate Handler
|
|
30
|
+
*
|
|
31
|
+
* Starts the OAuth flow by generating state, creating a session,
|
|
32
|
+
* and redirecting to the OAuth provider's authorization URL.
|
|
33
|
+
*/
|
|
34
|
+
declare const InitiateOAuth: GetHandler<any, any, PathParams, InitiateRequest>;
|
|
35
|
+
export default InitiateOAuth;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth Initiate Handler
|
|
4
|
+
*
|
|
5
|
+
* Initiates the OAuth 2.0 authorization code flow by:
|
|
6
|
+
* 1. Validating the provider is supported and configured
|
|
7
|
+
* 2. Generating a cryptographically secure state parameter for CSRF protection
|
|
8
|
+
* 3. Creating an OAuth session to track the flow
|
|
9
|
+
* 4. Building the provider's authorization URL
|
|
10
|
+
* 5. Redirecting the user to the provider for authorization
|
|
11
|
+
*
|
|
12
|
+
* Route: GET /oauth/:provider/initiate?redirectUri={optional_redirect_url}
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Route = void 0;
|
|
16
|
+
const flink_1 = require("@flink-app/flink");
|
|
17
|
+
const state_utils_1 = require("../utils/state-utils");
|
|
18
|
+
const ProviderRegistry_1 = require("../providers/ProviderRegistry");
|
|
19
|
+
const error_utils_1 = require("../utils/error-utils");
|
|
20
|
+
/**
|
|
21
|
+
* Route configuration
|
|
22
|
+
* Note: This handler is registered programmatically by the plugin
|
|
23
|
+
* with dynamic provider parameter support
|
|
24
|
+
*/
|
|
25
|
+
exports.Route = {
|
|
26
|
+
path: "/oauth/:provider/initiate",
|
|
27
|
+
method: flink_1.HttpMethod.get,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* OAuth Initiate Handler
|
|
31
|
+
*
|
|
32
|
+
* Starts the OAuth flow by generating state, creating a session,
|
|
33
|
+
* and redirecting to the OAuth provider's authorization URL.
|
|
34
|
+
*/
|
|
35
|
+
const InitiateOAuth = async ({ ctx, req }) => {
|
|
36
|
+
const { provider } = req.params;
|
|
37
|
+
const { redirectUri } = req.query;
|
|
38
|
+
try {
|
|
39
|
+
// Validate provider is supported
|
|
40
|
+
(0, error_utils_1.validateProvider)(provider);
|
|
41
|
+
// Get plugin options and provider config
|
|
42
|
+
const { options } = ctx.plugins.oauth;
|
|
43
|
+
const providerConfig = options.providers[provider];
|
|
44
|
+
if (!providerConfig) {
|
|
45
|
+
throw (0, error_utils_1.createOAuthError)(error_utils_1.OAuthErrorCodes.INVALID_PROVIDER, `Provider "${provider}" is not configured`, { provider });
|
|
46
|
+
}
|
|
47
|
+
// Generate cryptographically secure state and session ID
|
|
48
|
+
const state = (0, state_utils_1.generateState)();
|
|
49
|
+
const sessionId = (0, state_utils_1.generateSessionId)();
|
|
50
|
+
// Store session for state validation in callback
|
|
51
|
+
await ctx.repos.oauthSessionRepo.create({
|
|
52
|
+
sessionId,
|
|
53
|
+
state,
|
|
54
|
+
provider: provider,
|
|
55
|
+
redirectUri: redirectUri || providerConfig.callbackUrl,
|
|
56
|
+
createdAt: new Date(),
|
|
57
|
+
});
|
|
58
|
+
// Get provider instance and build authorization URL
|
|
59
|
+
const oauthProvider = (0, ProviderRegistry_1.getProvider)(provider, providerConfig);
|
|
60
|
+
const authorizationUrl = oauthProvider.getAuthorizationUrl({
|
|
61
|
+
state,
|
|
62
|
+
redirectUri: providerConfig.callbackUrl,
|
|
63
|
+
scope: providerConfig.scope || [],
|
|
64
|
+
});
|
|
65
|
+
// Redirect user to provider's authorization page
|
|
66
|
+
return {
|
|
67
|
+
status: 302,
|
|
68
|
+
headers: {
|
|
69
|
+
Location: authorizationUrl,
|
|
70
|
+
},
|
|
71
|
+
data: {},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// Handle validation errors
|
|
76
|
+
if (error.code && Object.values(error_utils_1.OAuthErrorCodes).includes(error.code)) {
|
|
77
|
+
return (0, flink_1.badRequest)(error.message);
|
|
78
|
+
}
|
|
79
|
+
// Handle unexpected errors
|
|
80
|
+
return (0, flink_1.internalServerError)(error.message || "Failed to initiate OAuth flow");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
exports.default = InitiateOAuth;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query parameters for OAuth callback request
|
|
3
|
+
*/
|
|
4
|
+
export default interface CallbackRequest {
|
|
5
|
+
/**
|
|
6
|
+
* Authorization code from OAuth provider
|
|
7
|
+
*/
|
|
8
|
+
code: string;
|
|
9
|
+
/**
|
|
10
|
+
* CSRF protection state parameter
|
|
11
|
+
*/
|
|
12
|
+
state: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional error from OAuth provider
|
|
15
|
+
* Common values: access_denied, invalid_request, unauthorized_client, etc.
|
|
16
|
+
*/
|
|
17
|
+
error?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Human-readable error description (OAuth 2.0 standard)
|
|
20
|
+
*/
|
|
21
|
+
error_description?: string;
|
|
22
|
+
/**
|
|
23
|
+
* URI with error information (OAuth 2.0 standard)
|
|
24
|
+
*/
|
|
25
|
+
error_uri?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Response type - 'json' returns JSON instead of redirect
|
|
28
|
+
*/
|
|
29
|
+
response_type?: "json";
|
|
30
|
+
/**
|
|
31
|
+
* Granted scopes (provider-specific)
|
|
32
|
+
* May be sent by GitHub, Google, and other providers
|
|
33
|
+
*/
|
|
34
|
+
scope?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Google-specific: Index of the account selected by the user
|
|
37
|
+
*/
|
|
38
|
+
authuser?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Google-specific: Indicates which prompt was shown to the user
|
|
41
|
+
* Values: none, consent, select_account
|
|
42
|
+
*/
|
|
43
|
+
prompt?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Google-specific: Hosted domain of the user
|
|
46
|
+
*/
|
|
47
|
+
hd?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Session state or other provider-specific parameters
|
|
50
|
+
*/
|
|
51
|
+
session_state?: string;
|
|
52
|
+
[key: string]: string | undefined;
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats the OAuth callback response with JWT token.
|
|
3
|
+
* Supports multiple response formats:
|
|
4
|
+
* - JSON response with user and token
|
|
5
|
+
* - URL fragment redirect with token
|
|
6
|
+
* - Query parameter redirect with token
|
|
7
|
+
*
|
|
8
|
+
* @param token - JWT token to return
|
|
9
|
+
* @param user - User object to return
|
|
10
|
+
* @param redirectUrl - Optional redirect URL
|
|
11
|
+
* @param responseType - Response format ('json' or undefined for redirect)
|
|
12
|
+
* @returns Response object for Flink handler
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatTokenResponse(token: string, user: any, redirectUrl?: string, responseType?: string): any;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTokenResponse = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Formats the OAuth callback response with JWT token.
|
|
6
|
+
* Supports multiple response formats:
|
|
7
|
+
* - JSON response with user and token
|
|
8
|
+
* - URL fragment redirect with token
|
|
9
|
+
* - Query parameter redirect with token
|
|
10
|
+
*
|
|
11
|
+
* @param token - JWT token to return
|
|
12
|
+
* @param user - User object to return
|
|
13
|
+
* @param redirectUrl - Optional redirect URL
|
|
14
|
+
* @param responseType - Response format ('json' or undefined for redirect)
|
|
15
|
+
* @returns Response object for Flink handler
|
|
16
|
+
*/
|
|
17
|
+
function formatTokenResponse(token, user, redirectUrl, responseType) {
|
|
18
|
+
// JSON response format
|
|
19
|
+
if (responseType === "json") {
|
|
20
|
+
return {
|
|
21
|
+
status: 200,
|
|
22
|
+
data: {
|
|
23
|
+
user,
|
|
24
|
+
token,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Redirect format
|
|
29
|
+
if (redirectUrl) {
|
|
30
|
+
// Use URL fragment for better security (token not sent to server)
|
|
31
|
+
const separator = redirectUrl.includes("#") ? "&" : "#";
|
|
32
|
+
const fullRedirectUrl = `${redirectUrl}${separator}token=${encodeURIComponent(token)}`;
|
|
33
|
+
return {
|
|
34
|
+
status: 302,
|
|
35
|
+
headers: {
|
|
36
|
+
Location: fullRedirectUrl,
|
|
37
|
+
},
|
|
38
|
+
data: {},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Default: return JSON if no redirect URL provided
|
|
42
|
+
return {
|
|
43
|
+
status: 200,
|
|
44
|
+
data: {
|
|
45
|
+
user,
|
|
46
|
+
token,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
exports.formatTokenResponse = formatTokenResponse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/oauth-plugin",
|
|
3
|
-
"version": "0.12.1-alpha.
|
|
3
|
+
"version": "0.12.1-alpha.37",
|
|
4
4
|
"description": "Flink plugin for OAuth 2.0 authentication with GitHub and Google providers",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --preserve-symlinks -r ts-node/register -- node_modules/jasmine/bin/jasmine --config=./spec/support/jasmine.json",
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"tsc-watch": "^4.2.9",
|
|
35
35
|
"typescript": "5.4.5"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "58f5ef1c6307ebda4b3ad6e75c29fd56345a2a54"
|
|
38
38
|
}
|
|
@@ -12,135 +12,134 @@
|
|
|
12
12
|
* critical OAuth flow scenarios.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { FlinkApp } from
|
|
16
|
-
import * as http from
|
|
17
|
-
|
|
18
|
-
describe(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// the handler logic once the plugin integration is complete
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterAll(async () => {
|
|
33
|
-
if (app) {
|
|
34
|
-
await app.stop();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('InitiateOAuth Handler', () => {
|
|
39
|
-
it('should generate state, create session, and redirect to provider', async () => {
|
|
40
|
-
// Test that initiate handler:
|
|
41
|
-
// 1. Validates provider is configured
|
|
42
|
-
// 2. Generates cryptographically secure state parameter
|
|
43
|
-
// 3. Creates OAuth session with state
|
|
44
|
-
// 4. Returns 302 redirect to provider authorization URL
|
|
45
|
-
|
|
46
|
-
// This test will be implemented once plugin setup is complete
|
|
47
|
-
expect(true).toBe(true); // Placeholder
|
|
15
|
+
import { FlinkApp } from "@flink-app/flink";
|
|
16
|
+
import * as http from "@flink-app/test-utils";
|
|
17
|
+
|
|
18
|
+
describe("OAuth Handlers", () => {
|
|
19
|
+
let app: FlinkApp<any>;
|
|
20
|
+
let testSessionId: string;
|
|
21
|
+
let testState: string;
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
// Note: This is a placeholder test structure
|
|
25
|
+
// The actual FlinkApp setup with oauth plugin will be completed
|
|
26
|
+
// in Task Group 5 when the plugin is fully implemented
|
|
27
|
+
// For now, we're creating the test structure to validate
|
|
28
|
+
// the handler logic once the plugin integration is complete
|
|
48
29
|
});
|
|
49
30
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
expect(true).toBe(true); // Placeholder
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('CallbackOAuth Handler', () => {
|
|
59
|
-
it('should validate state, exchange code, and call onAuthSuccess with context', async () => {
|
|
60
|
-
// Test that callback handler:
|
|
61
|
-
// 1. Validates state parameter matches session
|
|
62
|
-
// 2. Exchanges authorization code for access token
|
|
63
|
-
// 3. Fetches user profile from provider
|
|
64
|
-
// 4. Calls onAuthSuccess callback with profile and context
|
|
65
|
-
// 5. Receives JWT token from callback
|
|
66
|
-
|
|
67
|
-
// This test will be implemented once plugin setup is complete
|
|
68
|
-
expect(true).toBe(true); // Placeholder
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should receive JWT token from callback and return to client', async () => {
|
|
72
|
-
// Test that callback handler:
|
|
73
|
-
// 1. Receives JWT token from onAuthSuccess callback
|
|
74
|
-
// 2. Returns token in appropriate format (redirect by default)
|
|
75
|
-
|
|
76
|
-
// This test will be implemented once plugin setup is complete
|
|
77
|
-
expect(true).toBe(true); // Placeholder
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
if (app) {
|
|
33
|
+
await app.stop();
|
|
34
|
+
}
|
|
78
35
|
});
|
|
79
36
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
37
|
+
describe("InitiateOAuth Handler", () => {
|
|
38
|
+
it("should generate state, create session, and redirect to provider", async () => {
|
|
39
|
+
// Test that initiate handler:
|
|
40
|
+
// 1. Validates provider is configured
|
|
41
|
+
// 2. Generates cryptographically secure state parameter
|
|
42
|
+
// 3. Creates OAuth session with state
|
|
43
|
+
// 4. Returns 302 redirect to provider authorization URL
|
|
85
44
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
45
|
+
// This test will be implemented once plugin setup is complete
|
|
46
|
+
expect(true).toBe(true); // Placeholder
|
|
47
|
+
});
|
|
89
48
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// 1. Detects state mismatch
|
|
93
|
-
// 2. Returns 400 error
|
|
94
|
-
// 3. Prevents CSRF attacks
|
|
49
|
+
it("should reject unsupported provider", async () => {
|
|
50
|
+
// Test that initiate handler returns 400 for invalid provider
|
|
95
51
|
|
|
96
|
-
|
|
97
|
-
|
|
52
|
+
// This test will be implemented once plugin setup is complete
|
|
53
|
+
expect(true).toBe(true); // Placeholder
|
|
54
|
+
});
|
|
98
55
|
});
|
|
99
56
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
57
|
+
describe("CallbackOAuth Handler", () => {
|
|
58
|
+
it("should validate state, exchange code, and call onAuthSuccess with context", async () => {
|
|
59
|
+
// Test that callback handler:
|
|
60
|
+
// 1. Validates state parameter matches session
|
|
61
|
+
// 2. Exchanges authorization code for access token
|
|
62
|
+
// 3. Fetches user profile from provider
|
|
63
|
+
// 4. Calls onAuthSuccess callback with profile and context
|
|
64
|
+
// 5. Receives JWT token from callback
|
|
65
|
+
|
|
66
|
+
// This test will be implemented once plugin setup is complete
|
|
67
|
+
expect(true).toBe(true); // Placeholder
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should receive JWT token from callback and return to client", async () => {
|
|
71
|
+
// Test that callback handler:
|
|
72
|
+
// 1. Receives JWT token from onAuthSuccess callback
|
|
73
|
+
// 2. Returns token in appropriate format (redirect by default)
|
|
74
|
+
|
|
75
|
+
// This test will be implemented once plugin setup is complete
|
|
76
|
+
expect(true).toBe(true); // Placeholder
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should support response_type=json query parameter", async () => {
|
|
80
|
+
// Test that callback handler:
|
|
81
|
+
// 1. Detects response_type=json in query parameters
|
|
82
|
+
// 2. Returns JSON response with user and token
|
|
83
|
+
// 3. Does not redirect when response_type=json
|
|
84
|
+
|
|
85
|
+
// This test will be implemented once plugin setup is complete
|
|
86
|
+
expect(true).toBe(true); // Placeholder
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should reject callback with invalid state parameter", async () => {
|
|
90
|
+
// Test that callback handler:
|
|
91
|
+
// 1. Detects state mismatch
|
|
92
|
+
// 2. Returns 400 error
|
|
93
|
+
// 3. Prevents CSRF attacks
|
|
94
|
+
|
|
95
|
+
// This test will be implemented once plugin setup is complete
|
|
96
|
+
expect(true).toBe(true); // Placeholder
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should reject callback with missing code parameter", async () => {
|
|
100
|
+
// Test that callback handler:
|
|
101
|
+
// 1. Detects missing authorization code
|
|
102
|
+
// 2. Returns 400 error with clear message
|
|
103
|
+
|
|
104
|
+
// This test will be implemented once plugin setup is complete
|
|
105
|
+
expect(true).toBe(true); // Placeholder
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should handle OAuth provider error (access_denied)", async () => {
|
|
109
|
+
// Test that callback handler:
|
|
110
|
+
// 1. Detects error parameter from provider
|
|
111
|
+
// 2. Maps error to user-friendly message
|
|
112
|
+
// 3. Calls onAuthError callback if provided
|
|
113
|
+
|
|
114
|
+
// This test will be implemented once plugin setup is complete
|
|
115
|
+
expect(true).toBe(true); // Placeholder
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should handle expired or missing session", async () => {
|
|
119
|
+
// Test that callback handler:
|
|
120
|
+
// 1. Detects missing session for state
|
|
121
|
+
// 2. Returns appropriate error message
|
|
122
|
+
// 3. Suggests user try logging in again
|
|
123
|
+
|
|
124
|
+
// This test will be implemented once plugin setup is complete
|
|
125
|
+
expect(true).toBe(true); // Placeholder
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should handle JWT generation failure gracefully", async () => {
|
|
129
|
+
// Test that callback handler:
|
|
130
|
+
// 1. Catches errors from onAuthSuccess callback
|
|
131
|
+
// 2. Logs error securely
|
|
132
|
+
// 3. Calls onAuthError callback if provided
|
|
133
|
+
// 4. Returns user-friendly error message
|
|
134
|
+
|
|
135
|
+
// This test will be implemented once plugin setup is complete
|
|
136
|
+
expect(true).toBe(true); // Placeholder
|
|
137
|
+
});
|
|
107
138
|
});
|
|
108
139
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// 3. Calls onAuthError callback if provided
|
|
114
|
-
|
|
115
|
-
// This test will be implemented once plugin setup is complete
|
|
116
|
-
expect(true).toBe(true); // Placeholder
|
|
140
|
+
describe("OAuth Flow End-to-End", () => {
|
|
141
|
+
// Note: Full end-to-end tests with mock OAuth provider responses
|
|
142
|
+
// will be added in Task Group 7 by the testing-engineer
|
|
143
|
+
// These placeholder tests establish the test structure
|
|
117
144
|
});
|
|
118
|
-
|
|
119
|
-
it('should handle expired or missing session', async () => {
|
|
120
|
-
// Test that callback handler:
|
|
121
|
-
// 1. Detects missing session for state
|
|
122
|
-
// 2. Returns appropriate error message
|
|
123
|
-
// 3. Suggests user try logging in again
|
|
124
|
-
|
|
125
|
-
// This test will be implemented once plugin setup is complete
|
|
126
|
-
expect(true).toBe(true); // Placeholder
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should handle JWT generation failure gracefully', async () => {
|
|
130
|
-
// Test that callback handler:
|
|
131
|
-
// 1. Catches errors from onAuthSuccess callback
|
|
132
|
-
// 2. Logs error securely
|
|
133
|
-
// 3. Calls onAuthError callback if provided
|
|
134
|
-
// 4. Returns user-friendly error message
|
|
135
|
-
|
|
136
|
-
// This test will be implemented once plugin setup is complete
|
|
137
|
-
expect(true).toBe(true); // Placeholder
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('OAuth Flow End-to-End', () => {
|
|
142
|
-
// Note: Full end-to-end tests with mock OAuth provider responses
|
|
143
|
-
// will be added in Task Group 7 by the testing-engineer
|
|
144
|
-
// These placeholder tests establish the test structure
|
|
145
|
-
});
|
|
146
145
|
});
|
package/src/OAuthPlugin.ts
CHANGED
|
@@ -7,6 +7,8 @@ import OAuthSessionRepo from "./repos/OAuthSessionRepo";
|
|
|
7
7
|
import OAuthConnectionRepo from "./repos/OAuthConnectionRepo";
|
|
8
8
|
import { encryptToken, decryptToken, validateEncryptionSecret } from "./utils/encryption-utils";
|
|
9
9
|
import OAuthConnection from "./schemas/OAuthConnection";
|
|
10
|
+
import * as InitiateOAuth from "./handlers/InitiateOAuth";
|
|
11
|
+
import * as CallbackOAuth from "./handlers/CallbackOAuth";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* OAuth Plugin Factory Function
|
|
@@ -156,10 +158,12 @@ export function oauthPlugin(options: OAuthPluginOptions): FlinkPlugin {
|
|
|
156
158
|
|
|
157
159
|
log.info(`OAuth Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
158
160
|
|
|
159
|
-
// Register handlers
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
// Register OAuth handlers
|
|
162
|
+
// Only register handlers if registerRoutes is enabled (default: true)
|
|
163
|
+
if (options.registerRoutes !== false) {
|
|
164
|
+
flinkApp.addHandler(InitiateOAuth);
|
|
165
|
+
flinkApp.addHandler(CallbackOAuth);
|
|
166
|
+
}
|
|
163
167
|
|
|
164
168
|
log.info(`OAuth Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
165
169
|
} catch (error) {
|
|
@@ -119,4 +119,11 @@ export interface OAuthPluginOptions {
|
|
|
119
119
|
* Recommended: Use a dedicated encryption key from environment variables
|
|
120
120
|
*/
|
|
121
121
|
encryptionKey?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Whether to register OAuth routes automatically
|
|
125
|
+
* If false, you must manually handle OAuth flow
|
|
126
|
+
* Default: true
|
|
127
|
+
*/
|
|
128
|
+
registerRoutes?: boolean;
|
|
122
129
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Route: GET /oauth/:provider/callback?code=...&state=...&response_type=json
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { GetHandler, RouteProps, badRequest, internalServerError, log } from "@flink-app/flink";
|
|
15
|
+
import { GetHandler, HttpMethod, RouteProps, badRequest, internalServerError, log } from "@flink-app/flink";
|
|
16
16
|
import CallbackRequest from "../schemas/CallbackRequest";
|
|
17
17
|
import { validateState } from "../utils/state-utils";
|
|
18
18
|
import { getProvider } from "../providers/ProviderRegistry";
|
|
@@ -35,6 +35,7 @@ interface PathParams {
|
|
|
35
35
|
*/
|
|
36
36
|
export const Route: RouteProps = {
|
|
37
37
|
path: "/oauth/:provider/callback",
|
|
38
|
+
method: HttpMethod.get,
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Route: GET /oauth/:provider/initiate?redirectUri={optional_redirect_url}
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { GetHandler, RouteProps, badRequest, internalServerError } from "@flink-app/flink";
|
|
14
|
+
import { GetHandler, HttpMethod, RouteProps, badRequest, internalServerError } from "@flink-app/flink";
|
|
15
15
|
import InitiateRequest from "../schemas/InitiateRequest";
|
|
16
16
|
import { generateState, generateSessionId } from "../utils/state-utils";
|
|
17
17
|
import { getProvider } from "../providers/ProviderRegistry";
|
|
@@ -32,6 +32,7 @@ interface PathParams {
|
|
|
32
32
|
*/
|
|
33
33
|
export const Route: RouteProps = {
|
|
34
34
|
path: "/oauth/:provider/initiate",
|
|
35
|
+
method: HttpMethod.get,
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
/**
|