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