| File was renamed from inc/managers/MagicLinkManager.php |
| | |
| | | |
| | | use JetBrains\PhpStorm\NoReturn; |
| | | use JVBase\base\Site; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\services\Cloudflare; |
| | | use JVBase\meta\Form; |
| | | use JVBase\ui\Tabs; |
| | | use WP_User; |
| | |
| | | * NOTE: Login form integration is now handled by LoginManager.php |
| | | * This class focuses solely on magic link generation and verification |
| | | */ |
| | | class MagicLinkManager |
| | | class MagicLinks |
| | | { |
| | | use Instance; |
| | | protected Cache $cache; |
| | | protected Cache $referral_cache; |
| | | protected Cache $rateLimitCache; |
| | | |
| | | // Token settings |
| | | protected int $token_expiry = 900; // 15 minutes in seconds |
| | |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache = Cache::for('magic_links', $this->token_expiry); |
| | | $this->referral_cache = Cache::for('referral_magic_links', 14 * DAY_IN_SECONDS); |
| | | $this->cache = Cache::for('magic_links',null, $this->token_expiry); |
| | | $this->referral_cache = Cache::for('referral_magic_links', null, 14 * DAY_IN_SECONDS); |
| | | $this->rateLimitCache = Cache::for('magic_link_rate_limit', null, $this->rate_limit_window); |
| | | |
| | | // Hook into WordPress auth flow |
| | | add_action('template_redirect', [$this, 'handleMagicLinkClick']); |
| | |
| | | */ |
| | | protected function checkRateLimit(string $email): bool|WP_Error |
| | | { |
| | | $cache_key = 'rate_limit_' . md5($email); |
| | | $attempts = $this->cache->get($cache_key); |
| | | $key = md5($email); |
| | | $attempts = $this->rateLimitCache->get($key); |
| | | |
| | | if (!$attempts) { |
| | | $attempts = ['count' => 0, 'timestamp' => time()]; |
| | | } |
| | | |
| | | // Reset counter if window has passed |
| | | if (time() - $attempts['timestamp'] > $this->rate_limit_window) { |
| | | $attempts = ['count' => 0, 'timestamp' => time()]; |
| | | $attempts = 1; |
| | | } |
| | | |
| | | // Check if limit exceeded |
| | | if ($attempts['count'] >= $this->max_attempts_per_hour) { |
| | | if ($attempts >= $this->max_attempts_per_hour) { |
| | | return new WP_Error( |
| | | 'rate_limit_exceeded', |
| | | 'Too many magic link requests. Please try again in an hour.' |
| | |
| | | } |
| | | |
| | | // Increment counter |
| | | $attempts['count']++; |
| | | $this->cache->set($cache_key, $attempts, $this->rate_limit_window); |
| | | $attempts++; |
| | | $this->rateLimitCache->set($key, $attempts); |
| | | |
| | | return true; |
| | | } |
| | |
| | | $subject = 'Sign in to ' . get_bloginfo('name'); |
| | | $message = $this->getLoginEmailTemplate($user->display_name, $magic_url); |
| | | |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name')); |
| | | $sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name')); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send magic link'); |
| | | } |
| | |
| | | $subject = 'Complete your ' . get_bloginfo('name') . ' registration'; |
| | | $message = $this->getSignupEmailTemplate($context['name'] ?? '', $magic_url); |
| | | |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Complete Registration'); |
| | | $sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Complete Registration'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send signup link'); |
| | | } |
| | |
| | | $subject = (array_key_exists('subject', $context) && $context['subject'] !== '') ? $context['subject'] : $referrer_name . ' invited you to join ' . get_bloginfo('name'); |
| | | $message = $this->getReferralEmailTemplate($context['name'] ?? '', $referrer_name, $magic_url, $reward_text, $context); |
| | | |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Accept Invitation'); |
| | | $sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Accept Invitation'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send referral link'); |
| | | } |
| | |
| | | $subject = 'Reset your password'; |
| | | $message = $this->getResetEmailTemplate($user->display_name, $magic_url); |
| | | |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Reset Password'); |
| | | $sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Reset Password'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send reset link'); |
| | | } |
| | |
| | | $this->processSignup($token_data); |
| | | } |
| | | |
| | | $role = JVB()->referrals()->getRole(); |
| | | $role = Referrals::getInstance()->getRole(); |
| | | $pass = wp_generate_password(20, true, true); |
| | | $name = sanitize_text_field($token_data['name']); |
| | | $user_id = wp_insert_user([ |
| | |
| | | |
| | | protected function getLoginEmailTemplate(string $name, string $magic_url): string |
| | | { |
| | | $content = JVB()->email()->h2('Hey ' . esc_html($name) . '!'); |
| | | $content = Emails::getInstance()->h2('Hey ' . esc_html($name) . '!'); |
| | | $content .= '<p>Click the button below to sign in to your account instantly - no password needed!</p>'; |
| | | $content .= JVB()->email()->button($magic_url, 'Sign In Now'); |
| | | $content .= Emails::getInstance()->button($magic_url, 'Sign In Now'); |
| | | $content .= '<p>Or copy and paste this link into your browser:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->divider(); |
| | | $content .= Emails::getInstance()->link($magic_url); |
| | | $content .= Emails::getInstance()->divider(); |
| | | $content .= '<p>If you didn\'t request this, you can safely ignore this email. This link expires in 15 minutes.</p>'; |
| | | return $content; |
| | | } |
| | | |
| | | protected function getSignupEmailTemplate(string $name, string $magic_url): string |
| | | { |
| | | $content = JVB()->email()->h2('Welcome' . ($name ? ', ' . esc_html($name) : '') . '!'); |
| | | $content = Emails::getInstance()->h2('Welcome' . ($name ? ', ' . esc_html($name) : '') . '!'); |
| | | $content .= '<p>Click the button below to complete your registration and access your account!</p>'; |
| | | $content .= JVB()->email()->button($magic_url, 'Complete Registration'); |
| | | $content .= Emails::getInstance()->button($magic_url, 'Complete Registration'); |
| | | $content .= '<p>Or copy and paste this link:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->spacer(10); |
| | | $content .= Emails::getInstance()->link($magic_url); |
| | | $content .= Emails::getInstance()->spacer(10); |
| | | $content .= '<p><small>This link expires in 24 hours.</small></p>'; |
| | | return $content; |
| | | } |
| | | |
| | | protected function getReferralEmailTemplate(string $name, string $referrer_name, string $magic_url, string $reward_text, array $context): string |
| | | { |
| | | $content = JVB()->email()->h2('Hey' . ($name ? ' ' . esc_html($name) : '') . '!'); |
| | | $content = Emails::getInstance()->h2('Hey' . ($name ? ' ' . esc_html($name) : '') . '!'); |
| | | $content .= sprintf( |
| | | '<p><strong>%s</strong> thinks you\'d love %s!</p>', |
| | | esc_html($referrer_name), |
| | |
| | | ); |
| | | |
| | | if (!empty($context['message'])) { |
| | | $content .= JVB()->email()->callout(nl2br(esc_html($context['message']))); |
| | | $content .= Emails::getInstance()->callout(nl2br(esc_html($context['message']))); |
| | | } |
| | | |
| | | if ($reward_text) { |
| | | $content .= JVB()->email()->alert( |
| | | $content .= Emails::getInstance()->alert( |
| | | '<strong>Special Welcome Offer:</strong> ' . esc_html($reward_text), |
| | | 'success' |
| | | ); |
| | | } |
| | | |
| | | $content .= JVB()->email()->button($magic_url, 'Join Now'); |
| | | $content .= Emails::getInstance()->button($magic_url, 'Join Now'); |
| | | $content .= '<p>Or copy and paste this link:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->spacer(10); |
| | | $content .= Emails::getInstance()->link($magic_url); |
| | | $content .= Emails::getInstance()->spacer(10); |
| | | $content .= '<p><small>This invitation expires in 14 days.</small></p>'; |
| | | |
| | | return $content; |
| | |
| | | public function renderForm():string |
| | | { |
| | | ob_start(); |
| | | JVB()->connect('cloudflare')->renderTurnstile(); |
| | | Cloudflare::getInstance()->renderTurnstile(); |
| | | $turnstile = ob_get_clean(); |
| | | global $wp; |
| | | |
| | |
| | | // wp_login_url() |
| | | ); |
| | | |
| | | $login = LoginManager::getInstance()->renderLoginForm('login', get_home_url(null,'/'.$wp->request)); |
| | | $login = Login::getInstance()->renderLoginForm('login', get_home_url(null,'/'.$wp->request)); |
| | | |
| | | $tabs = new Tabs(); |
| | | $tabs->addTab('magic') |