Skip to content

Class Name: SelfRegisterController

Last Updated: 2025-10-22 Source Code: https://github.com/AANP-IT/I2C.Salesforce.Metadata/blob/STAGING/force-app/main/default/classes/SelfRegisterController.cls

API Name: SelfRegisterController Type: Controller (Experience Cloud) Test Coverage: To be determined

Business Purpose

The SelfRegisterController manages user authentication for Experience Cloud sites, providing: - Custom login functionality with username validation - Password reset capabilities - Login attempt tracking to prevent brute force attacks - Integration with Salesforce Site.login() and Site.forgotPassword() methods

This supports secure self-service user authentication for community portals.

Class Overview

  • Author: Ion Cernenchii
  • Created: 09-APR-2024
  • Last Modified: 15-MAY-2024
  • Scope/Sharing: without sharing - CRITICAL SECURITY CONCERN
  • Key Responsibilities:
  • User login with validation
  • Password reset requests
  • Login attempt tracking
  • Username validation

⚠️ CRITICAL: WITHOUT SHARING

Uses without sharing which bypasses record-level security. This allows: - Querying any User record regardless of permissions - Updating login attempts for any user - Potential security vulnerability

Justification: Likely needed for Experience Cloud guest user context (no user context), but should be carefully reviewed.

Public Methods

login

@AuraEnabled
public static String login(String username, String password, String startUrl)

Purpose: Authenticates user and returns redirect URL.

Parameters: - username (String) - User's username - password (String) - User's password - startUrl (String) - URL to redirect after successful login

Returns: String - Redirect URL or "Invalid Username"

Business Logic: 1. Username Validation (line 21):

Boolean isValidUsername = false;
if (Test.isRunningTest()) { isValidUsername = true; } else { isValidUsername = validUsername(username); }
- Bypasses validation in tests - Calls validUsername() in production

  1. Login Attempt (line 23):
    ApexPages.PageReference lgn = Site.login(username, password, startUrl);
    
  2. Uses Salesforce Site.login() method
  3. Returns PageReference with redirect URL

  4. Return URL (line 24):

    if (!Test.isRunningTest()) { return lgn.getUrl();} else { return startUrl; }
    

  5. Returns redirect URL in production
  6. Returns startUrl in tests

Issues/Concerns: - 🚨 Test.isRunningTest() in Production (lines 21, 24): Anti-pattern - Production code should not behave differently in tests - Use dependency injection or test-specific data instead - ⚠️ No Error Handling: Site.login() can throw exceptions - ⚠️ Null Pointer Risk: lgn could be null if login fails - ⚠️ No Login Attempt Tracking: Doesn't call updateLoginAttempts() on failure - ✅ Username Pre-Validation: Checks username exists before attempting login

forgotPassword

@AuraEnabled
public static Boolean forgotPassword (String email)

Purpose: Initiates password reset process.

Parameters: - email (String) - User's email address

Returns: Boolean - true if successful, false otherwise

Business Logic: - Delegates to Site.forgotPassword() - Sends password reset email

Issues/Concerns: - ⚠️ No Error Handling: Exception not caught - ⚠️ Parameter Name Mismatch: Parameter named email but doc says username - ✅ Simple Delegation: Leverages Salesforce built-in functionality

validUsername

@AuraEnabled
public static Boolean validUsername(String username)

Purpose: Checks if username exists in system.

Parameters: - username (String) - Username to validate

Returns: Boolean - true if user exists, false otherwise

Business Logic: 1. Checks for blank username (line 45) 2. Queries User object for matching username (line 46) 3. Returns true if user found

Issues/Concerns: - ⚠️ No Error Handling: Query exception not caught - ⚠️ Security Risk: Allows username enumeration - Attackers can discover valid usernames - Consider rate limiting or CAPTCHA - ✅ Efficient Query: Uses LIMIT 1 for performance

getCurrentUserLoginAttempts

@AuraEnabled
public static Integer getCurrentUserLoginAttempts(String username)

Purpose: Retrieves login attempt count for user.

Parameters: - username (String) - Username to check

Returns: Integer - Number of login attempts (0 if none or user not found)

Business Logic: 1. Returns 0 for blank username (line 52) 2. Queries User.Login_Attempts__c field (line 53) 3. Returns count or 0 if null

Issues/Concerns: - ⚠️ No Error Handling: Query exception not caught (user not found) - ⚠️ Custom Field: Requires Login_Attempts__c custom field on User - ✅ Null-Safe: Returns 0 if field is null

updateLoginAttempts

@AuraEnabled
public static String updateLoginAttempts(String username)

Purpose: Increments login attempt counter for user.

Parameters: - username (String) - Username to update

Returns: String - Success message or error

Business Logic: 1. Returns error for blank username (line 59) 2. Queries user record (line 61) 3. Increments Login_Attempts__c (lines 62-66): - Sets to 1 if null or 0 - Otherwise increments by 1 4. Updates user record (line 67)

Issues/Concerns: - 🚨 Test.isRunningTest() in DML (line 67): Bypasses update in tests - Should update in tests to verify functionality - ⚠️ No Maximum Limit: Doesn't cap login attempts - Should have maximum (e.g., 5) before account lockout - ⚠️ Race Condition: Concurrent logins could lose increment - Two simultaneous failures might only increment once - ✅ Error Handling: Catches and wraps exceptions

resetLoginAttempts

@AuraEnabled
public static String resetLoginAttempts(String username)

Purpose: Resets login attempt counter to 0.

Parameters: - username (String) - Username to reset

Returns: String - Success message or error

Business Logic: 1. Returns error for blank username (line 76) 2. Queries user record (line 78) 3. Sets Login_Attempts__c to 0 (line 79) 4. Updates user record (line 83)

Issues/Concerns: - 🚨 Test.isRunningTest() in DML (line 80): Bypasses update in tests - ⚠️ Called After Successful Login?: Documentation doesn't clarify when to call - ✅ Error Handling: Catches and wraps exceptions

Dependencies

Salesforce Objects

  • User (Standard Object)
  • Fields: Id, Username, Login_Attempts__c (custom)
  • Access: Read, Update

Custom Settings/Metadata

  • None

Other Classes

  • Site (Salesforce Class): login(), forgotPassword()

Design Patterns

  1. Facade Pattern: Wraps Salesforce Site methods
  2. LWC Controller Pattern: @AuraEnabled for Lightning components
  3. Counter Pattern: Tracks login attempts

Governor Limits Considerations

Current Impact

  • SOQL Queries: 1-2 per method call
  • DML Statements: 1 per update/reset
  • Heap Size: Minimal

Security Considerations

🚨 CRITICAL ISSUES

  1. WITHOUT SHARING (line 6): Bypasses all record-level security
  2. Can query/update any User record
  3. Required for guest user context but risky

  4. Username Enumeration (line 44): validUsername allows attackers to discover valid usernames

  5. Add rate limiting
  6. Consider CAPTCHA after N attempts
  7. Return generic error messages

  8. Test.isRunningTest() in Production (lines 21, 24, 67, 80): Anti-pattern

  9. Different behavior in tests vs production
  10. Breaks test validity

  11. No Login Attempt Lockout: Unlimited login attempts

  12. Add maximum attempts (e.g., 5)
  13. Implement temporary account lockout
  14. Add CAPTCHA after 3 attempts

  15. Race Condition: Login_Attempts__c updates not atomic

  16. Two concurrent failures might lose count
  17. Consider platform event for async tracking

Test Class Requirements

@IsTest
public class SelfRegisterControllerTest {
    @IsTest
    static void testLogin_Success() {
        // Test successful login
    }

    @IsTest
    static void testLogin_InvalidUsername() {
        String result = SelfRegisterController.login('invalid@user.com', 'password', '/home');
        Assert.areEqual('Invalid Username', result);
    }

    @IsTest
    static void testValidUsername() {
        User u = [SELECT Username FROM User WHERE IsActive = true LIMIT 1];
        Boolean valid = SelfRegisterController.validUsername(u.Username);
        Assert.isTrue(valid);
    }

    @IsTest
    static void testUpdateLoginAttempts() {
        User u = [SELECT Username FROM User WHERE IsActive = true LIMIT 1];
        String result = SelfRegisterController.updateLoginAttempts(u.Username);
        // Note: Current implementation bypasses update in tests
    }
}

Pre-Go-Live Concerns

🚨 CRITICAL

  • WITHOUT SHARING: Review security implications
  • Username Enumeration: Add rate limiting/CAPTCHA
  • Test.isRunningTest(): Remove from production code
  • No Login Lockout: Implement maximum attempts

HIGH

  • No Error Handling: Add try-catch to all methods
  • Race Conditions: Fix concurrent update issues
  • Null Pointer Risks: Check for null lgn in login()

MEDIUM

  • No Maximum Attempts: Cap at 5-10 attempts
  • Custom Field Dependency: Document Login_Attempts__c requirement

Changes & History

Date Author Description
2024-04-09 Ion Cernenchii Initial implementation
2024-05-15 Ion Cernenchii Added forgotPassword method

Documentation Status: ✅ Complete Code Review Status: 🚨 CRITICAL - Multiple security issues Test Coverage: Test class needed