CSC/ECE 517 Spring 2025 - E2535 Reimplement User Profile Management (Frontend + Backend)
Overview
The goal of this project is to reimplement the User Profile Management feature in the Expertiza system, replacing the legacy Ruby-based frontend with a modern, responsive interface built using React with TypeScript, while retaining and enhancing the existing Ruby on Rails backend.
This modernization effort addresses the challenges posed by outdated UI, limited form validation, and insecure session handling in the current implementation. By leveraging modern frameworks and best practices, the new system will allow users to securely update their profile details, manage email and notification preferences, and change passwords.
Problem Statement
From a security perspective, the current system does not follow best practices for handling sensitive operations such as password changes and session management. For instance:
- Password updates do not log out other active sessions, leaving accounts vulnerable.
- API responses may expose too much information, enabling account enumeration.
- There is no rate limiting, making the system susceptible to brute-force attacks.
These limitations lead to:
- Poor user experience (UX) and accessibility.
- Increased technical debt and maintenance burden.
- Security risks in managing user authentication and sessions.
As Expertiza evolves, it is essential to rebuild the profile management feature using modern frontend technologies and robust backend practices that improve usability, security, and maintainability.
Existing Issues
- Inconsistent Form Validation:
- Validation is not uniform, leading to unclear user feedback.
- React Hook Form will be used for consistent validation.
- Limited Security for Password Changes:
- No session invalidation after password updates.
- New implementation will log out all active sessions and invalidate old tokens.
- Basic Error Handling:
- Current error handling is minimal and vague.
- Enhanced error messages and better handling for edge cases will be implemented.
Proposed Changes
Front-End
Enhance usability and data integrity through a tab-based layout that organizes the profile page into distinct sections, ensuring that users can update specific areas independently without risk of data loss.
Key Features
- Tab-based Layout
- The profile page will be restructured into multiple tabs, with each tab dedicated to a specific subsection (e.g., Personal Details, Password, Notification Settings).
- This separation allows users to navigate between different sections without losing unsaved data from other areas.
- Dedicated Forms for Each Section
- Implement separate forms for each tab using React Hook Form which enables efficient state management and robust schema-driven validations.
- Ensure that each form manages its own state and validation logic independently, avoiding interference between sections.
- Enhanced User Experience
- Users will benefit from clear, compartmentalized UI sections, reducing cognitive load and streamlining the update process.
- Real-time validation and dedicated feedback per section will help prevent input errors and improve overall form usability.
Proposed Wireframes
Attached Wireframes are not to be mistaken as actual designs or the live application/website.
File Modifications
- Edit the existing code in `src/pages/Profile/Edit.tsx` to implement the new layout.
Back-End
The backend will be enhanced to support secure, RESTful profile and password updates, while incorporating session management and protection against attacks.
User Model Enhancements
Add/update methods to:
- Safely update user profile fields (e.g., name, email)
- Change passwords using bcrypt for hashing
- Invalidate old sessions upon password update
New API Endpoints
There are some of the proposed endpoints for the reimplementation.
- `GET /api/v1/profile` – Fetch current user details
- `PUT /api/v1/profile` – Update user profile
- `PUT /api/v1/profile/password` – Update password securely
Session Management
- Log out all active sessions after a successful password change
- Revoke previous tokens to ensure account safety
Security Measures
- Implement rate limiting with `Rack::Attack` to prevent brute-force attacks
- Return generic error messages for failed logins to avoid account enumeration
Database Changes
The existing `users` table currently includes all the necessary fields required for user profile management, such as name, email, and password. As of now, no immediate changes to the schema are required. However, during the implementation phase, if additional fields are identified as necessary, the schema will be updated accordingly. Any modifications to the schema will be thoroughly reviewed and documented to ensure they align with the project requirements and maintain data integrity.
Overview of Classes
UsersController (app/controllers/api/v1/users_controller.rb)
Will be extended to:
- `GET /profile`: Return current user details (JSON)
- `PUT /profile`: Update user attributes like name, email, notification preferences
PasswordsController (app/controllers/api/v1/passwords_controller.rb)
Will handle secure password changes via:
- `PUT /password`:
- Authenticate using current password
- Update using bcrypt
- Invalidate old sessions/tokens
- Issue a new session token
User Model (app/models/user.rb)
Updated to include:
- `update_profile(params)` method for secure profile updates
- `change_password(current_password, new_password)` with proper validations
- Session/token handling logic after password changes
AuthenticationTokenService / Session Handler
- Issues new tokens upon password change
- Invalidates previous sessions to prevent unauthorized access
ApplicationController
- Handles token-based authentication for API access
- Provides common methods for current user lookup and error handling
Rack::Attack Configuration (config/initializers/rack_attack.rb)
- Implements rate limiting for profile and password update endpoints
- Helps mitigate brute-force and enumeration attacks
Class Diagram

Design Goals
Usability & User Experience (UX)
- Intuitive Interface: The user profile page should be easy to navigate with clear sections for updating profile information, changing passwords, and managing notifications. Each form will include:
- Informative tooltips and labels
- Clear error messages and success notifications
- Loading indicators to improve responsiveness
- Responsive Design: The profile page must be fully responsive across various devices, from desktops to mobile phones, ensuring an optimized experience for all users.
- Smooth Transitions: Using React Router, transitions between different sections (such as profile editing and password change) should feel seamless without page reloads, improving the overall user experience.
Security
- Password Management
- Use bcrypt to hash passwords securely before saving them to the database.
- Implement token-based authentication for user sessions.
- When the password is updated, invalidate all active sessions and issue a new authentication token to prevent unauthorized access.
- Rate Limiting: To prevent brute-force attacks, rate limiting will be implemented on sensitive endpoints like the password change and login routes using Rack::Attack.
- Account Enumeration Protection: Return generic error messages on failed login attempts and password changes to prevent attackers from determining whether a username/email is valid.
- Session Management: When a password is successfully updated, ensure that all other active sessions are logged out and new tokens are issued to prevent session hijacking.
Maintainability & Scalability
- Modular Codebase: The code will follow a modular approach by breaking down functionality into smaller, reusable components (both frontend and backend). For example, the frontend components will be encapsulated, and the backend logic will be implemented using service objects where appropriate.
- RESTful API Structure: The backend will adhere to RESTful principles for API endpoints, ensuring that the architecture is scalable and easily extendable in the future. Common conventions like GET, PUT, and PATCH will be used for profile and password management.
- Separation of Concerns: The frontend and backend will be clearly separated with well-defined API contracts. The frontend will handle the presentation and user interactions, while the backend will focus on business logic and data persistence.
- Testing and Quality Assurance: Automated tests will be implemented to ensure the robustness of the system. RSpec will be used for backend testing, and Jest will be used for frontend testing.
Code Quality
- Adhere to DRY Principles: The code will avoid repetition by creating reusable components and centralized validation logic for both frontend and backend. For example, validation logic for user input will be shared between the frontend (React Hook Form validation) and the backend (model validations).
- Consistent API Responses: API responses will be standardized across endpoints to ensure a consistent interface for the frontend. This includes:
- Returning relevant HTTP status codes
- Structuring responses with consistent fields
- Clear Documentation: The documentation will be maintained, detailing endpoint routes, required parameters, expected responses, and error handling procedures.
Implementation Plan
Frontend Implementation
- Create new React components
- Implement validation of form fields
- Connect to backend endpoints
- Add success/confirmation messages on profile updates
- Add loading states and error handling
- Implement responsive design
Backend Implementation
- Update UserController with new endpoints
- Implement password security measures
- Add rate limiting functionality
- Update user model validation
- Implement session management
Sequence Diagram

Implementation
Frontend Implementation
The new UI redesign replicates the functionality of the original page, now leveraging TypeScript for type safety and React for dynamic rendering. This approach enhances the user experience by providing a more responsive and accessible interface, while adhering to modern design principles. This feature redesigns the Edit Profile page, implementing a modern, form-based interface using React, TypeScript, and React Hook Form, with full backend integration for real-time data fetch and update. The new UI focuses on form validation, user experience, and code maintainability.
Key Features:
- Form Management: Uses `react-hook-form` and `yup` for structured form handling and ensuring reliable input checks and robust form validation
- Backend Integration: Profile data is fetched from the backend and is used to populate the form fields. On submission, this data is validated and triggers an API request to update profile and password (if the password is changed).
- API Communication: Uses `axios` for making HTTP requests to fetch user profile data and submit updates to the backend, including GET, PATCH and POST requests with authentication headers.
- UI Enhancements: Improved error handling and form usability. `Yup` validation checks data before it is sent to the backend, ensuring fast display of errors. The form also display success and error messages for API requests.
Backend Implementation
API Endpoints
GET /api/v1/users/:id/get_profile
- Fetches user profile data and preferences for the frontend.
- It returns basic attributes like
full_name
,email
, andhandle
.
# GET /api/v1/users/:id/get_profile : Returns basic user profile information and email preferences def get_profile user = User.includes(:institution).find(params[:id]) render json: { id: user.id, full_name: user.full_name, email: user.email, handle: user.handle || '', can_show_actions: user.can_show_actions, time_zone: user.time_zone || 'GMT-05:00', language: user.language || 'No Preference', email_on_review: user.email_on_review.nil? ? true : user.email_on_review, email_on_submission: user.email_on_submission.nil? ? true : user.email_on_submission, email_on_review_of_review: user.email_on_review_of_review.nil? ? true : user.email_on_review_of_review, institution: { id: user.institution&.id || 0, name: user.institution&.name || 'Other' } }, status: :ok rescue ActiveRecord::RecordNotFound render json: { error: 'User not found' }, status: :not_found end
PATCH /api/v1/users/:id/update_profile
- Updates editable user profile fields like
full_name
,email
,handle
,language
andtime_zone
.
- Updates editable user profile fields like
# PATCH /users/:id def update_profile user = User.find(params[:id]) if user.update(user_params) render json: { message: 'Profile updated successfully.', user: user.as_json(except: [:password_digest]) }, status: :ok else render json: { errors: user.errors.full_messages }, status: :unprocessable_entity end rescue ActiveRecord::RecordNotFound render json: { error: 'User not found' }, status: :not_found end
POST /api/v1/users/:id/update_password
- Securely allows users to change their passwords by comparing
password
andconfirmPassword
. - If the password is successfully updated, it issues a new JWT with updated
jwt_version
to invalidate old tokens.
- Securely allows users to change their passwords by comparing
# POST /users/:id/update_password def update_password user = User.find(params[:id]) password = params[:password] confirm_password = params[:confirmPassword] if password.blank? || confirm_password.blank? return render json: { error: 'Both password and confirmPassword are required' }, status: :bad_request end if password != confirm_password return render json: { error: 'Passwords do not match' }, status: :unprocessable_entity end if user.update(password: password, password_confirmation: confirm_password) # update jwt_version and issue new token user.update(jwt_version: SecureRandom.uuid) payload = { id: user.id, name: user.name, full_name: user.full_name, role: user.role.name, institution_id: user.institution.id, jwt_version: user.jwt_version } new_token = JsonWebToken.encode(payload, 24.hours.from_now) render json: { message: 'Password updated successfully', token: new_token, user: user.as_json(except: [:password_digest]) }, status: :ok else render json: { errors: user.errors.full_messages }, status: :unprocessable_entity end end
- Update routes.rb
member do get :get_profile # GET /api/v1/users/:id/get_profile patch :update_profile # PATCH /api/v1/users/:id/update_profile post :update_password # POST /api/v1/users/:id/update_password end
- Files changed:
- Compliance with Design Requirements:
update_profile
restricts changes to non-sensitive fields only.update_password
securely updates the password, and a new token is issued.- All endpoints return appropriate HTTP status codes.
Session Management for Password Updates
- This implementation ensures secure session handling whenever a user updates their password. The goal is to prevent unauthorized access using old tokens and provide a new authenticated session for the user.
- Log out all active sessions upon password change.
- Issue a new authentication token after a successful password reset.
- Invalidate old session tokens to enhance security.
# Invalidate token if jwt_version no longer matches if auth_token[:jwt_version] != @current_user.jwt_version render json: { error: 'Token has been invalidated. Please login again.' }, status: :unauthorized return end
- Files changed:
Security Enhancements
Application Level Security:
File: app/controllers/authentication_controller.rb
Changes:
- Introduced random delay (0.1 – 0.3 seconds) after each login attempt to mitigate timing attacks
def login # Add a small random delay to prevent timing attacks sleep(rand(0.1..0.3)) # ... rest of login logic end
- Used a generic error message
Invalid credentials
to prevent user enumeration
def login # ... authentication logic render json: { error: 'Invalid credentials' }, status: :unauthorized end
- Ensured proper status code usage:
401
Unauthorized for failed login attempts429
Too Many Requests when rate limit is exceeded
Rack::Attack.throttled_responder = lambda do |env| [ 429, # status { 'Content-Type' => 'application/json' }, # headers [{ error: 'Rate limit exceeded. Try again later.' }.to_json] # body ] end
Network/Request Level Security:
File: config/initializers/rack_attack.rb
Changes:
- Implemented in-memory rate limiting using a hash to track login attempts
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
- Set maximum number of attempts to 5 within a 20-second window
throttle('logins/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? req.ip end end
- Applied rate limiting based on both IP Address and Username
# IP-based throttling throttle('logins/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? req.ip end end
# Username-based throttling throttle('logins/username', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? req.params['user_name'].to_s.downcase end end
- Added conditional handling to allow unrestricted access from localhost (
127.0.0.1
and::1
) for testing
safelist('allow-localhost') do |req| '127.0.0.1' == req.ip || '::1' == req.ip end
Testing
R Spec Tests
- We have added an R spec test for the users_controller.rb in the file
users_spec.rb
. This test mainly checks the three methods we have implemented in this project.
Manual Testing for Sessions
- In order to test the token generation and invalidation, we can use the web browser console to test the API endpoint. The following commands are used for this testing:
fetch("http://localhost:3002/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ user_name: "admin", // add username password: "password123" // add password }) }) .then(res => res.json()) .then(data => { console.log("Token A:", data.token); window.tokenA = data.token; // Decode token to get user ID const base64Url = data.token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) ).join('')); const payload = JSON.parse(jsonPayload); window.userId = payload.id; console.log("User ID:", window.userId); });
NEXT:
fetch(`http://localhost:3002/api/v1/users/${window.userId}`, { method: "GET", headers: { "Authorization": "Bearer " + window.tokenA } }) .then(res => res.json()) .then(data => console.log(" User info with tokenA:", data));
NEXT:
fetch(`http://localhost:3002/api/v1/users/${window.userId}/update_password`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + window.tokenA }, body: JSON.stringify({ password: "newpass123", // New password confirmPassword: "newpass123" }) }) .then(res => res.json()) .then(data => { console.log(" Password updated!"); console.log(" Token B:", data.token); window.tokenB = data.token; });
NEXT:
fetch(`http://localhost:3002/api/v1/users/${window.userId}`, { method: "GET", headers: { "Authorization": "Bearer " + window.tokenB } }) .then(res => res.json()) .then(data => console.log(" User info with tokenB:", data));
NEXT:
fetch(`http://localhost:3002/api/v1/users/${window.userId}`, { method: "GET", headers: { "Authorization": "Bearer " + window.tokenA } }) .then(res => res.json()) .then(data => console.log(" Token A Response:", data)) .catch(err => console.error("Error:", err));
Testing Implementation for Security Enhancements
File: spec/requests/rack_attack_spec.rb
Changes:
- Added comprehensive test coverage for login rate limiting
- Verified successful login scenario
- Verified rate limiting triggered after too many failed login attempts from the same IP address
- Verified rate limiting triggered after too many failed attempts using the same username
- Verified localhost IPs are exempt from rate limiting
Manual Testing Script
File: test_rate_limiting.sh
Changes:
- Created shell script to simulate real-world usage patterns
- Verified rate limiting behavior using multiple incorrect login attempts
- Confirmed general login rate limiting functionality