UNPKG

@zestic/oauth-core

Version:

Framework-agnostic OAuth authentication library with support for multiple OAuth flows

163 lines 6.94 kB
"use strict"; /** * Base Magic Link Flow Handler * Abstract base class for all magic link flow implementations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseMagicLinkFlowHandler = void 0; const BaseCallbackFlowHandler_1 = require("./BaseCallbackFlowHandler"); const OAuthTypes_1 = require("../types/OAuthTypes"); const CallbackFlowTypes_1 = require("../types/CallbackFlowTypes"); const TokenManager_1 = require("../core/TokenManager"); const StateValidator_1 = require("../core/StateValidator"); const ErrorHandler_1 = require("../utils/ErrorHandler"); class BaseMagicLinkFlowHandler extends BaseCallbackFlowHandler_1.BaseCallbackFlowHandler { constructor() { super(...arguments); this.priority = CallbackFlowTypes_1.FLOW_PRIORITIES.HIGH; // Higher priority than standard OAuth } /** * Check if this handler can process the given parameters * Subclasses should override this to add flow-specific checks */ hasRequiredMagicLinkParams(params) { const token = params.get('token'); // Must have a non-empty token parameter with no leading/trailing whitespace return Boolean(token && token.length > 0 && token === token.trim()); } /** * Check if this flow is disabled in config */ isFlowDisabled(config) { return config?.flows?.disabledFlows?.includes(this.name) || false; } /** * Validate the magic link flow parameters */ async validate(params, config) { try { // Check for OAuth errors first this.checkForOAuthError(params); // Check if this flow is disabled if (this.isFlowDisabled(config)) { return false; } // Must have either token or magic_link_token const hasToken = this.hasRequiredMagicLinkParams(params); if (!hasToken) { return false; } // If state is present, validate it const state = params.get('state'); if (state) { // Note: In real usage, adapters would be passed from the handle method // For validation, we just check if state exists return true; } return true; } catch { return false; } } /** * Handle the magic link flow - shared implementation for all magic link flows */ async handle(params, adapters, config) { this.logFlowExecution('Starting magic link flow', params); return this.measureExecutionTime(async () => { try { // Check for OAuth errors this.checkForOAuthError(params); // Extract token (try both parameter names) const token = this.extractToken(params); const flow = this.getOptionalParam(params, 'flow'); const state = this.getOptionalParam(params, 'state'); // Validate state if present if (state) { await this.validateState(state, adapters); } // Build additional parameters for the token exchange const additionalParams = await this.buildAdditionalParams(params, flow, adapters); // Exchange magic link token for OAuth tokens const result = await this.exchangeMagicLinkToken(token, additionalParams, adapters, config); this.logFlowExecution('Magic link flow completed successfully'); return result; } catch (error) { this.logFlowExecution(`Magic link flow failed: ${ErrorHandler_1.ErrorHandler.formatError(error)}`); if (ErrorHandler_1.ErrorHandler.isOAuthError(error)) { throw error; } throw ErrorHandler_1.ErrorHandler.createError(`Magic link flow failed: ${error instanceof Error ? error.message : String(error)}`, OAuthTypes_1.OAUTH_ERROR_CODES.TOKEN_EXCHANGE_FAILED, error instanceof Error ? error : undefined); } }, 'Magic link token exchange'); } /** * Extract token from parameters */ extractToken(params) { const token = params.get('token'); if (!token || token.length === 0 || token !== token.trim()) { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('token'); } return token; } /** * Build additional parameters for token exchange */ async buildAdditionalParams(params, flow, adapters) { const additionalParams = {}; // Include flow type if specified if (flow) { additionalParams.flow = flow; } // Retrieve PKCE code_verifier from storage (where it was stored during generation) try { const storedCodeVerifier = await adapters.storage.getItem('pkce_code_verifier'); if (storedCodeVerifier) { additionalParams.codeVerifier = storedCodeVerifier; } } catch (error) { // If storage retrieval fails, continue without code_verifier // This ensures the flow doesn't break if storage is unavailable } // Include any PKCE parameters that might be present in URL (fallback for legacy support) const codeChallenge = params.get('code_challenge'); const codeChallengeMethod = params.get('code_challenge_method'); const urlCodeVerifier = params.get('code_verifier'); const state = params.get('state'); if (codeChallenge) { additionalParams.code_challenge = codeChallenge; } if (codeChallengeMethod) { additionalParams.code_challenge_method = codeChallengeMethod; } // Only use URL code_verifier if we didn't get one from storage // This maintains backward compatibility but prioritizes the secure storage approach if (urlCodeVerifier && !additionalParams.codeVerifier) { additionalParams.codeVerifier = urlCodeVerifier; } if (state) { additionalParams.state = state; } return additionalParams; } /** * Validate state parameter */ async validateState(state, adapters) { const stateValidator = new StateValidator_1.StateValidator(adapters.storage); await stateValidator.validateStateOrThrow(state); } /** * Exchange magic link token for OAuth tokens */ async exchangeMagicLinkToken(token, additionalParams, adapters, config) { const tokenManager = new TokenManager_1.TokenManager(adapters.http, adapters.storage); return tokenManager.exchangeMagicLinkToken(token, config, additionalParams); } } exports.BaseMagicLinkFlowHandler = BaseMagicLinkFlowHandler; //# sourceMappingURL=BaseMagicLinkFlowHandler.js.map