@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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +783 -0
  3. package/SECURITY.md +433 -0
  4. package/dist/OAuthInternalContext.d.ts +45 -0
  5. package/dist/OAuthInternalContext.js +2 -0
  6. package/dist/OAuthPlugin.d.ts +70 -0
  7. package/dist/OAuthPlugin.js +220 -0
  8. package/dist/OAuthPluginContext.d.ts +49 -0
  9. package/dist/OAuthPluginContext.js +2 -0
  10. package/dist/OAuthPluginOptions.d.ts +111 -0
  11. package/dist/OAuthPluginOptions.js +2 -0
  12. package/dist/index.d.ts +48 -0
  13. package/dist/index.js +66 -0
  14. package/dist/providers/GitHubProvider.d.ts +32 -0
  15. package/dist/providers/GitHubProvider.js +82 -0
  16. package/dist/providers/GoogleProvider.d.ts +32 -0
  17. package/dist/providers/GoogleProvider.js +83 -0
  18. package/dist/providers/OAuthProvider.d.ts +69 -0
  19. package/dist/providers/OAuthProvider.js +2 -0
  20. package/dist/providers/OAuthProviderBase.d.ts +32 -0
  21. package/dist/providers/OAuthProviderBase.js +86 -0
  22. package/dist/providers/ProviderRegistry.d.ts +14 -0
  23. package/dist/providers/ProviderRegistry.js +24 -0
  24. package/dist/repos/OAuthConnectionRepo.d.ts +30 -0
  25. package/dist/repos/OAuthConnectionRepo.js +38 -0
  26. package/dist/repos/OAuthSessionRepo.d.ts +22 -0
  27. package/dist/repos/OAuthSessionRepo.js +28 -0
  28. package/dist/schemas/OAuthConnection.d.ts +12 -0
  29. package/dist/schemas/OAuthConnection.js +2 -0
  30. package/dist/schemas/OAuthSession.d.ts +9 -0
  31. package/dist/schemas/OAuthSession.js +2 -0
  32. package/dist/utils/encryption-utils.d.ts +34 -0
  33. package/dist/utils/encryption-utils.js +134 -0
  34. package/dist/utils/error-utils.d.ts +68 -0
  35. package/dist/utils/error-utils.js +120 -0
  36. package/dist/utils/state-utils.d.ts +36 -0
  37. package/dist/utils/state-utils.js +72 -0
  38. package/examples/api-client-auth.ts +550 -0
  39. package/examples/basic-auth.ts +288 -0
  40. package/examples/multi-provider.ts +409 -0
  41. package/examples/token-storage.ts +490 -0
  42. package/package.json +38 -0
  43. package/spec/OAuthHandlers.spec.ts +146 -0
  44. package/spec/OAuthPluginSpec.ts +31 -0
  45. package/spec/ProvidersSpec.ts +178 -0
  46. package/spec/README.md +365 -0
  47. package/spec/helpers/mockJwtAuthPlugin.ts +104 -0
  48. package/spec/helpers/mockOAuthProviders.ts +189 -0
  49. package/spec/helpers/reporter.ts +41 -0
  50. package/spec/helpers/testDatabase.ts +107 -0
  51. package/spec/helpers/testHelpers.ts +192 -0
  52. package/spec/integration-critical.spec.ts +857 -0
  53. package/spec/integration.spec.ts +301 -0
  54. package/spec/repositories.spec.ts +181 -0
  55. package/spec/support/jasmine.json +7 -0
  56. package/spec/utils/security.spec.ts +243 -0
  57. package/src/OAuthInternalContext.ts +46 -0
  58. package/src/OAuthPlugin.ts +251 -0
  59. package/src/OAuthPluginContext.ts +53 -0
  60. package/src/OAuthPluginOptions.ts +122 -0
  61. package/src/handlers/CallbackOAuth.ts +238 -0
  62. package/src/handlers/InitiateOAuth.ts +99 -0
  63. package/src/index.ts +62 -0
  64. package/src/providers/GitHubProvider.ts +90 -0
  65. package/src/providers/GoogleProvider.ts +91 -0
  66. package/src/providers/OAuthProvider.ts +77 -0
  67. package/src/providers/OAuthProviderBase.ts +98 -0
  68. package/src/providers/ProviderRegistry.ts +27 -0
  69. package/src/repos/OAuthConnectionRepo.ts +41 -0
  70. package/src/repos/OAuthSessionRepo.ts +30 -0
  71. package/src/repos/TTL_INDEX_NOTE.md +28 -0
  72. package/src/schemas/CallbackRequest.ts +64 -0
  73. package/src/schemas/InitiateRequest.ts +10 -0
  74. package/src/schemas/OAuthConnection.ts +12 -0
  75. package/src/schemas/OAuthSession.ts +9 -0
  76. package/src/utils/encryption-utils.ts +148 -0
  77. package/src/utils/error-utils.ts +139 -0
  78. package/src/utils/state-utils.ts +70 -0
  79. package/src/utils/token-response-utils.ts +49 -0
  80. package/src/utils/validation-utils.ts +120 -0
  81. package/tsconfig.dist.json +4 -0
  82. package/tsconfig.json +24 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * OAuth Error Interface and Utilities
3
+ *
4
+ * Provides standardized error handling for OAuth flows including
5
+ * user-friendly messages and error code mapping.
6
+ */
7
+
8
+ /**
9
+ * Standardized OAuth error structure
10
+ */
11
+ export interface OAuthError {
12
+ /** Error code for programmatic handling */
13
+ code: string;
14
+
15
+ /** User-friendly error message */
16
+ message: string;
17
+
18
+ /** Additional error details (for logging, not user display) */
19
+ details?: any;
20
+ }
21
+
22
+ /**
23
+ * OAuth error codes
24
+ */
25
+ export const OAuthErrorCodes = {
26
+ INVALID_STATE: "invalid_state",
27
+ ACCESS_DENIED: "access_denied",
28
+ INVALID_GRANT: "invalid_grant",
29
+ NETWORK_ERROR: "network_error",
30
+ UNKNOWN_PROVIDER: "unknown_provider",
31
+ JWT_GENERATION_FAILED: "jwt_generation_failed",
32
+ INVALID_REQUEST: "invalid_request",
33
+ SERVER_ERROR: "server_error",
34
+ INVALID_PROVIDER: "invalid_provider",
35
+ SESSION_EXPIRED: "session_expired",
36
+ MISSING_CODE: "missing_code",
37
+ } as const;
38
+
39
+ /**
40
+ * Create a standardized OAuth error
41
+ *
42
+ * @param code - Error code from OAuthErrorCodes
43
+ * @param message - User-friendly error message
44
+ * @param details - Additional error details for logging
45
+ * @returns Standardized OAuthError object
46
+ */
47
+ export function createOAuthError(code: string, message: string, details?: any): OAuthError {
48
+ return {
49
+ code,
50
+ message,
51
+ details,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Map provider-specific errors to standardized OAuth errors
57
+ *
58
+ * This function converts various error types from OAuth providers
59
+ * into user-friendly standardized errors while sanitizing sensitive data.
60
+ *
61
+ * @param error - Error from OAuth provider or internal error
62
+ * @returns Standardized OAuthError
63
+ */
64
+ export function handleProviderError(error: any): OAuthError {
65
+ // Handle OAuth provider error responses
66
+ if (error.error) {
67
+ const errorCode = error.error;
68
+
69
+ switch (errorCode) {
70
+ case "access_denied":
71
+ return createOAuthError(OAuthErrorCodes.ACCESS_DENIED, "You denied access to your account. Please try again if you want to continue.", {
72
+ providerError: errorCode,
73
+ });
74
+
75
+ case "invalid_grant":
76
+ case "invalid_code":
77
+ return createOAuthError(OAuthErrorCodes.INVALID_GRANT, "The authorization code has expired or is invalid. Please try logging in again.", {
78
+ providerError: errorCode,
79
+ });
80
+
81
+ case "invalid_request":
82
+ return createOAuthError(OAuthErrorCodes.INVALID_REQUEST, "There was a problem with the authentication request. Please try again.", {
83
+ providerError: errorCode,
84
+ });
85
+
86
+ default:
87
+ return createOAuthError(OAuthErrorCodes.SERVER_ERROR, "An error occurred during authentication. Please try again.", {
88
+ providerError: errorCode,
89
+ });
90
+ }
91
+ }
92
+
93
+ // Handle network errors
94
+ if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED" || error.code === "ETIMEDOUT") {
95
+ return createOAuthError(OAuthErrorCodes.NETWORK_ERROR, "Unable to connect to the authentication service. Please check your connection and try again.", {
96
+ networkError: error.code,
97
+ });
98
+ }
99
+
100
+ // Handle JWT generation errors
101
+ if (error.message && error.message.includes("JWT")) {
102
+ return createOAuthError(OAuthErrorCodes.JWT_GENERATION_FAILED, "Failed to generate authentication token. Please try again.", {
103
+ originalError: error.message,
104
+ });
105
+ }
106
+
107
+ // Generic error fallback
108
+ return createOAuthError(OAuthErrorCodes.SERVER_ERROR, "An unexpected error occurred. Please try again.", {
109
+ originalError: error.message || "Unknown error",
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Validate provider name
115
+ *
116
+ * @param provider - Provider name to validate
117
+ * @returns true if valid provider
118
+ * @throws OAuthError if invalid
119
+ */
120
+ export function validateProvider(provider: string): provider is "github" | "google" {
121
+ if (provider !== "github" && provider !== "google") {
122
+ throw createOAuthError(OAuthErrorCodes.INVALID_PROVIDER, `Provider "${provider}" is not supported. Supported providers: github, google`, { provider });
123
+ }
124
+ return true;
125
+ }
126
+
127
+ /**
128
+ * Validate response_type parameter
129
+ *
130
+ * @param responseType - Response type to validate
131
+ * @returns true if valid
132
+ * @throws OAuthError if invalid
133
+ */
134
+ export function validateResponseType(responseType?: string): boolean {
135
+ if (responseType && responseType !== "json") {
136
+ throw createOAuthError(OAuthErrorCodes.INVALID_REQUEST, `Invalid response_type "${responseType}". Supported values: json`, { responseType });
137
+ }
138
+ return true;
139
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * State Parameter Utilities for CSRF Protection
3
+ *
4
+ * Provides cryptographically secure state parameter generation
5
+ * and constant-time comparison for OAuth CSRF protection.
6
+ */
7
+
8
+ import crypto from "crypto";
9
+
10
+ /**
11
+ * Generate a cryptographically secure state parameter
12
+ *
13
+ * Creates a 32-byte random state parameter for CSRF protection
14
+ * in OAuth flows. This state is stored in the session and must
15
+ * match when the OAuth provider redirects back.
16
+ *
17
+ * @returns Hex-encoded 32-byte random string (64 characters)
18
+ */
19
+ export function generateState(): string {
20
+ return crypto.randomBytes(32).toString("hex");
21
+ }
22
+
23
+ /**
24
+ * Validate state parameter using constant-time comparison
25
+ *
26
+ * Compares the provided state with the stored state using a
27
+ * constant-time algorithm to prevent timing attacks.
28
+ *
29
+ * @param provided - State parameter from OAuth provider callback
30
+ * @param stored - State parameter stored in session
31
+ * @returns true if states match, false otherwise
32
+ */
33
+ export function validateState(provided: string, stored: string): boolean {
34
+ if (!provided || !stored) {
35
+ return false;
36
+ }
37
+
38
+ // Ensure both strings are the same length to prevent timing attacks
39
+ if (provided.length !== stored.length) {
40
+ return false;
41
+ }
42
+
43
+ try {
44
+ // Use Node.js crypto.timingSafeEqual for constant-time comparison
45
+ const providedBuffer = Buffer.from(provided, "utf8");
46
+ const storedBuffer = Buffer.from(stored, "utf8");
47
+
48
+ // Both buffers must be same length for timingSafeEqual
49
+ if (providedBuffer.length !== storedBuffer.length) {
50
+ return false;
51
+ }
52
+
53
+ return crypto.timingSafeEqual(providedBuffer, storedBuffer);
54
+ } catch (error) {
55
+ // If comparison fails for any reason, return false
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Generate a session ID for OAuth flow tracking
62
+ *
63
+ * Creates a unique session identifier for correlating OAuth
64
+ * initiation with callback.
65
+ *
66
+ * @returns Hex-encoded random string
67
+ */
68
+ export function generateSessionId(): string {
69
+ return crypto.randomBytes(16).toString("hex");
70
+ }
@@ -0,0 +1,49 @@
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 function formatTokenResponse(token: string, user: any, redirectUrl?: string, responseType?: string): any {
15
+ // JSON response format
16
+ if (responseType === "json") {
17
+ return {
18
+ status: 200,
19
+ data: {
20
+ user,
21
+ token,
22
+ },
23
+ };
24
+ }
25
+
26
+ // Redirect format
27
+ if (redirectUrl) {
28
+ // Use URL fragment for better security (token not sent to server)
29
+ const separator = redirectUrl.includes("#") ? "&" : "#";
30
+ const fullRedirectUrl = `${redirectUrl}${separator}token=${encodeURIComponent(token)}`;
31
+
32
+ return {
33
+ status: 302,
34
+ headers: {
35
+ Location: fullRedirectUrl,
36
+ },
37
+ data: {},
38
+ };
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
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Input Validation Utilities
3
+ *
4
+ * Provides validation functions for OAuth handler inputs including
5
+ * provider names, query parameters, and redirect URIs.
6
+ */
7
+
8
+ import { createOAuthError, OAuthErrorCodes } from "./error-utils";
9
+
10
+ /**
11
+ * Validate provider parameter
12
+ *
13
+ * @param provider - Provider name from URL parameter
14
+ * @returns Validated provider name
15
+ * @throws OAuthError if invalid
16
+ */
17
+ export function validateProvider(provider: string): "github" | "google" {
18
+ if (!provider) {
19
+ throw createOAuthError(OAuthErrorCodes.INVALID_PROVIDER, "Provider is required", { provider });
20
+ }
21
+
22
+ if (provider !== "github" && provider !== "google") {
23
+ throw createOAuthError(OAuthErrorCodes.INVALID_PROVIDER, 'Provider "' + provider + '" is not supported. Supported providers: github, google', {
24
+ provider,
25
+ });
26
+ }
27
+
28
+ return provider;
29
+ }
30
+
31
+ /**
32
+ * Validate response_type parameter
33
+ *
34
+ * @param responseType - Response type from query parameter
35
+ * @returns Validated response type or undefined
36
+ * @throws OAuthError if invalid
37
+ */
38
+ export function validateResponseType(responseType?: string): string | undefined {
39
+ if (!responseType) {
40
+ return undefined;
41
+ }
42
+
43
+ if (responseType !== "json") {
44
+ throw createOAuthError(OAuthErrorCodes.INVALID_REQUEST, 'Invalid response_type "' + responseType + '". Supported values: json', { responseType });
45
+ }
46
+
47
+ return responseType;
48
+ }
49
+
50
+ /**
51
+ * Validate redirect URI format
52
+ *
53
+ * @param redirectUri - Redirect URI to validate
54
+ * @returns true if valid or undefined
55
+ * @throws OAuthError if invalid
56
+ */
57
+ export function validateRedirectUri(redirectUri?: string): boolean {
58
+ if (!redirectUri) {
59
+ return true;
60
+ }
61
+
62
+ try {
63
+ const url = new URL(redirectUri);
64
+
65
+ // Check protocol
66
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
67
+ throw createOAuthError(OAuthErrorCodes.INVALID_REQUEST, "Redirect URI must use HTTP or HTTPS protocol", { redirectUri });
68
+ }
69
+
70
+ return true;
71
+ } catch (error) {
72
+ if (error && typeof error === "object" && "code" in error) {
73
+ throw error;
74
+ }
75
+
76
+ throw createOAuthError(OAuthErrorCodes.INVALID_REQUEST, "Invalid redirect URI format", { redirectUri });
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Validate state parameter exists
82
+ *
83
+ * @param state - State parameter from query
84
+ * @throws OAuthError if missing
85
+ */
86
+ export function validateStateParam(state?: string): void {
87
+ if (!state) {
88
+ throw createOAuthError(OAuthErrorCodes.INVALID_STATE, "State parameter is required", { state });
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Validate authorization code exists
94
+ *
95
+ * @param code - Authorization code from query
96
+ * @throws OAuthError if missing
97
+ */
98
+ export function validateAuthCode(code?: string): void {
99
+ if (!code) {
100
+ throw createOAuthError(OAuthErrorCodes.MISSING_CODE, "Authorization code is required", { code });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Check for OAuth error in callback query parameters
106
+ *
107
+ * @param errorParam - Error parameter from OAuth provider
108
+ * @param errorDescription - Error description from OAuth provider
109
+ * @throws OAuthError if error present
110
+ */
111
+ export function checkOAuthErrorParam(errorParam?: string, errorDescription?: string): void {
112
+ if (errorParam) {
113
+ const message = errorDescription || "Authentication failed";
114
+
115
+ throw createOAuthError(errorParam === "access_denied" ? OAuthErrorCodes.ACCESS_DENIED : OAuthErrorCodes.SERVER_ERROR, message, {
116
+ error: errorParam,
117
+ description: errorDescription,
118
+ });
119
+ }
120
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig",
3
+ "exclude": ["spec/**/*.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "module": "commonjs",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "noEmit": false,
16
+ "declaration": true,
17
+ "experimentalDecorators": true,
18
+ "checkJs": true,
19
+ "outDir": "dist",
20
+ "typeRoots": ["./node_modules/@types"]
21
+ },
22
+ "include": ["./src/*", "./spec/*"],
23
+ "exclude": ["./node_modules/*"]
24
+ }