@zestic/oauth-core
Version:
Framework-agnostic OAuth authentication library with support for multiple OAuth flows
163 lines • 6.94 kB
JavaScript
;
/**
* 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