UNPKG

@zestic/oauth-core

Version:

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

168 lines 7.02 kB
"use strict"; /** * Registration Service * Handles user registration with OAuth integration */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RegistrationService = void 0; exports.createRegistrationService = createRegistrationService; const StateValidator_1 = require("../core/StateValidator"); const ErrorHandler_1 = require("../utils/ErrorHandler"); const OAuthTypes_1 = require("../types/OAuthTypes"); class RegistrationService { constructor(adapters) { this.adapters = adapters; this.stateValidator = new StateValidator_1.StateValidator(adapters.storage); } /** * Register a new user with OAuth integration */ async register(input) { try { // Validate input this.validateRegistrationInput(input); // Check if user already exists const userExists = await this.adapters.user.userExists(input.email); if (userExists) { return { success: false, message: 'User already exists with this email address', code: 'USER_EXISTS' }; } // Store PKCE challenge for later use in OAuth flow await this.storePKCEChallenge(input); // Store and validate state await this.stateValidator.storeState(input.state); // Register the user const registrationResult = await this.adapters.user.registerUser(input.email, input.additionalData); if (!registrationResult.success) { return { success: false, message: registrationResult.message || 'Registration failed', code: 'REGISTRATION_FAILED' }; } // Trigger server-side registration confirmation via GraphQL try { await this.adapters.graphql.sendRegistrationConfirmationMutation(input.email, { subject: 'Registration Successful', templateData: { email: input.email, redirectUri: input.redirectUri } }); } catch (graphqlError) { // Log GraphQL error but don't fail the registration console.warn('Failed to trigger registration confirmation:', graphqlError); } return { success: true, message: 'User registered successfully', code: 'REGISTRATION_SUCCESS' }; } catch (error) { if (ErrorHandler_1.ErrorHandler.isOAuthError(error)) { throw error; } throw ErrorHandler_1.ErrorHandler.createError(`Registration failed: ${error instanceof Error ? error.message : String(error)}`, OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_CONFIGURATION, error instanceof Error ? error : undefined); } } /** * Validate registration input parameters */ validateRegistrationInput(input) { if (!input.email || typeof input.email !== 'string') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('email'); } if (!this.isValidEmail(input.email)) { throw ErrorHandler_1.ErrorHandler.createError('Invalid email format', OAuthTypes_1.OAUTH_ERROR_CODES.MISSING_REQUIRED_PARAMETER); } if (!input.codeChallenge || typeof input.codeChallenge !== 'string') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('codeChallenge'); } if (!input.codeChallengeMethod || typeof input.codeChallengeMethod !== 'string') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('codeChallengeMethod'); } if (!input.redirectUri || typeof input.redirectUri !== 'string') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('redirectUri'); } if (!input.state || typeof input.state !== 'string') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('state'); } if (!input.additionalData || typeof input.additionalData !== 'object') { throw ErrorHandler_1.ErrorHandler.handleMissingParameter('additionalData'); } // Validate PKCE method if (!['S256', 'plain'].includes(input.codeChallengeMethod)) { throw ErrorHandler_1.ErrorHandler.createError('Invalid code challenge method. Must be S256 or plain', OAuthTypes_1.OAUTH_ERROR_CODES.MISSING_PKCE); } // Validate redirect URI format try { new URL(input.redirectUri); } catch { throw ErrorHandler_1.ErrorHandler.createError('Invalid redirect URI format', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_CONFIGURATION); } } /** * Store PKCE challenge for later use in OAuth flow */ async storePKCEChallenge(input) { // Store using the same keys that PKCEManager uses await this.adapters.storage.setItem('pkce_challenge', input.codeChallenge); await this.adapters.storage.setItem('pkce_method', input.codeChallengeMethod); await this.adapters.storage.setItem('pkce_state', input.state); await this.adapters.storage.setItem('pkce_redirect_uri', input.redirectUri); } /** * Simple email validation - ReDoS safe implementation */ isValidEmail(email) { // Input length validation to prevent ReDoS attacks if (email.length > 254) { return false; } // ReDoS-safe email regex pattern const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; return emailRegex.test(email); } /** * Get registration status for a user */ async getRegistrationStatus(email) { try { const userExists = await this.adapters.user.userExists(email); if (userExists) { const user = await this.adapters.user.getUserByEmail(email); return { success: true, data: { exists: true, user }, message: 'User found' }; } return { success: true, data: { exists: false }, message: 'User not found' }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), message: 'Failed to check registration status' }; } } } exports.RegistrationService = RegistrationService; /** * Factory function to create registration service */ function createRegistrationService(adapters) { return new RegistrationService(adapters); } //# sourceMappingURL=RegistrationService.js.map