@zestic/oauth-core
Version:
Framework-agnostic OAuth authentication library with support for multiple OAuth flows
209 lines • 8.79 kB
JavaScript
;
/**
* Main OAuth orchestrator
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OAuthCore = void 0;
const CallbackFlowRegistry_1 = require("./CallbackFlowRegistry");
const PKCEManager_1 = require("./PKCEManager");
const TokenManager_1 = require("./TokenManager");
const StateValidator_1 = require("./StateValidator");
const OAuthTypes_1 = require("../types/OAuthTypes");
const ErrorHandler_1 = require("../utils/ErrorHandler");
const UrlParser_1 = require("../utils/UrlParser");
// Note: Flow handlers are now registered manually by the user
class OAuthCore {
constructor(config, adapters, flowConfig) {
this.config = config;
this.adapters = adapters;
this.flowRegistry = new CallbackFlowRegistry_1.CallbackFlowRegistry();
this.pkceManager = new PKCEManager_1.PKCEManager(adapters.pkce, adapters.storage);
this.tokenManager = new TokenManager_1.TokenManager(adapters.http, adapters.storage);
this.stateValidator = new StateValidator_1.StateValidator(adapters.storage);
this.initializeFlows(flowConfig);
}
/**
* Handle OAuth callback with automatic flow detection
*/
async handleCallback(params, explicitFlow) {
try {
// Parse parameters if string provided
const urlParams = typeof params === 'string' ? UrlParser_1.UrlParser.parseParams(params) : params;
// Log callback attempt (with sanitized parameters)
console.log('[OAuthCore] Handling callback:', UrlParser_1.UrlParser.sanitizeForLogging(urlParams));
// Validate that we have at least one flow handler registered
if (this.flowRegistry.getHandlerCount() === 0) {
throw ErrorHandler_1.ErrorHandler.handleInvalidConfiguration('No flow handlers registered. Use registerFlow() to register at least one flow handler.');
}
let handler;
// Try explicit flow first if specified
if (explicitFlow) {
handler = this.flowRegistry.getHandler(explicitFlow);
if (!handler) {
throw ErrorHandler_1.ErrorHandler.handleUnknownFlow(explicitFlow);
}
}
else {
// Auto-detect flow using registered handlers
handler = this.flowRegistry.detectFlow(urlParams, this.config);
}
if (!handler) {
throw ErrorHandler_1.ErrorHandler.handleNoFlowHandler();
}
console.log(`[OAuthCore] Using flow handler: ${handler.name}`);
// Validate parameters before handling
if (!(await handler.validate(urlParams, this.config))) {
throw ErrorHandler_1.ErrorHandler.handleFlowValidationFailed(handler.name);
}
// Handle the flow
const result = await handler.handle(urlParams, this.adapters, this.config);
console.log(`[OAuthCore] Flow ${handler.name} completed:`, {
success: result.success,
hasAccessToken: !!result.accessToken,
hasRefreshToken: !!result.refreshToken,
});
return result;
}
catch (error) {
console.error('[OAuthCore] Callback handling failed:', ErrorHandler_1.ErrorHandler.formatError(error));
if (ErrorHandler_1.ErrorHandler.isOAuthError(error)) {
throw error;
}
throw ErrorHandler_1.ErrorHandler.createError(`OAuth callback handling failed: ${error instanceof Error ? error.message : String(error)}`, OAuthTypes_1.OAUTH_ERROR_CODES.TOKEN_EXCHANGE_FAILED, error instanceof Error ? error : undefined);
}
}
/**
* Generate PKCE challenge for authorization request
*/
async generatePKCEChallenge() {
return this.pkceManager.generateChallenge();
}
/**
* Generate OAuth state parameter
*/
async generateState() {
const state = await this.pkceManager.generateState();
await this.stateValidator.storeState(state);
return state;
}
/**
* Generate complete authorization URL with PKCE parameters
* This method handles all OAuth logic including PKCE generation and state management
*/
async generateAuthorizationUrl(additionalParams) {
try {
// Generate and store PKCE challenge
const pkceChallenge = await this.generatePKCEChallenge();
// Generate and store state
const state = await this.generateState();
// Build authorization URL parameters
const params = new URLSearchParams({
response_type: 'code',
client_id: this.config.clientId,
redirect_uri: this.config.redirectUri,
scope: this.config.scopes.join(' '),
state,
code_challenge: pkceChallenge.codeChallenge,
code_challenge_method: pkceChallenge.codeChallengeMethod,
...additionalParams,
});
const url = `${this.config.endpoints.authorization}?${params.toString()}`;
console.log('[OAuthCore] Generated authorization URL with PKCE parameters');
return { url, state };
}
catch (error) {
console.error('[OAuthCore] Failed to generate authorization URL:', ErrorHandler_1.ErrorHandler.formatError(error));
throw ErrorHandler_1.ErrorHandler.createError(`Failed to generate authorization URL: ${error instanceof Error ? error.message : String(error)}`, OAuthTypes_1.OAUTH_ERROR_CODES.MISSING_PKCE, error instanceof Error ? error : undefined);
}
}
/**
* Get current access token
*/
async getAccessToken() {
return this.tokenManager.getAccessToken();
}
/**
* Get current refresh token
*/
async getRefreshToken() {
return this.tokenManager.getRefreshToken();
}
/**
* Check if current token is expired
*/
async isTokenExpired() {
return this.tokenManager.isTokenExpired();
}
/**
* Refresh access token using refresh token
*/
async refreshAccessToken() {
const refreshToken = await this.tokenManager.getRefreshToken();
if (!refreshToken) {
throw ErrorHandler_1.ErrorHandler.createError('No refresh token available', OAuthTypes_1.OAUTH_ERROR_CODES.MISSING_REQUIRED_PARAMETER);
}
return this.tokenManager.refreshToken(refreshToken, this.config);
}
/**
* Revoke tokens and clear storage
*/
async logout() {
await this.tokenManager.revokeTokens(this.config);
await this.pkceManager.clearPKCEData();
await this.stateValidator.clearState();
}
/**
* Register a custom flow handler
*/
registerFlow(handler) {
this.flowRegistry.register(handler);
}
/**
* Unregister a flow handler
*/
unregisterFlow(name) {
this.flowRegistry.unregister(name);
}
/**
* Get all registered flow handlers
*/
getRegisteredFlows() {
return this.flowRegistry.getAllHandlers();
}
/**
* Get compatible handlers for given parameters
*/
getCompatibleHandlers(params) {
const urlParams = typeof params === 'string' ? UrlParser_1.UrlParser.parseParams(params) : params;
return this.flowRegistry.getCompatibleHandlers(urlParams, this.config);
}
/**
* Initialize flow handlers based on configuration
*/
initializeFlows(flowConfig) {
// No built-in flows are automatically registered
// Users must manually register the specific flow handlers they need:
// - MagicLinkLoginFlowHandler for login flows
// - MagicLinkVerifyFlowHandler for verification flows
// Register custom flows if provided
if (flowConfig?.customFlows) {
for (const handler of flowConfig.customFlows) {
this.flowRegistry.register(handler);
}
}
// If enabledFlows is specified, remove all others
if (flowConfig?.enabledFlows) {
const allHandlers = this.flowRegistry.getAllHandlers();
for (const handler of allHandlers) {
if (!flowConfig.enabledFlows.includes(handler.name)) {
this.flowRegistry.unregister(handler.name);
}
}
}
// Note: Flow handlers must be manually registered after initialization
// The validation will happen when handleCallback is called
console.log('[OAuthCore] Initialized. Flow handlers must be registered manually.');
}
}
exports.OAuthCore = OAuthCore;
//# sourceMappingURL=OAuthCore.js.map