User registation working. Sidebar built. Dashboard built. TOTP enable works - TOTP login works
This commit is contained in:
12
app/controllers/dashboard_controller.rb
Normal file
12
app/controllers/dashboard_controller.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class DashboardController < ApplicationController
|
||||
def index
|
||||
# First run: redirect to signup
|
||||
if User.count.zero?
|
||||
redirect_to signup_path
|
||||
return
|
||||
end
|
||||
|
||||
# User must be authenticated
|
||||
@user = Current.session.user
|
||||
end
|
||||
end
|
||||
45
app/controllers/profiles_controller.rb
Normal file
45
app/controllers/profiles_controller.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
class ProfilesController < ApplicationController
|
||||
def show
|
||||
@user = Current.session.user
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
end
|
||||
|
||||
def update
|
||||
@user = Current.session.user
|
||||
|
||||
if params[:user][:password].present?
|
||||
# Updating password - requires current password
|
||||
unless @user.authenticate(params[:user][:current_password])
|
||||
@user.errors.add(:current_password, "is incorrect")
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
if @user.update(password_params)
|
||||
redirect_to profile_path, notice: "Password updated successfully."
|
||||
else
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
# Updating email
|
||||
if @user.update(email_params)
|
||||
redirect_to profile_path, notice: "Email updated successfully."
|
||||
else
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def email_params
|
||||
params.require(:user).permit(:email_address)
|
||||
end
|
||||
|
||||
def password_params
|
||||
params.require(:user).permit(:password, :password_confirmation)
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,47 @@
|
||||
class SessionsController < ApplicationController
|
||||
allow_unauthenticated_access only: %i[ new create ]
|
||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
|
||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to signin_path, alert: "Too many attempts. Try again later." }
|
||||
|
||||
def new
|
||||
# Redirect to signup if this is first run
|
||||
redirect_to signup_path if User.count.zero?
|
||||
end
|
||||
|
||||
def create
|
||||
if user = User.authenticate_by(params.permit(:email_address, :password))
|
||||
start_new_session_for user
|
||||
redirect_to after_authentication_url
|
||||
else
|
||||
redirect_to new_session_path, alert: "Try another email address or password."
|
||||
user = User.authenticate_by(params.permit(:email_address, :password))
|
||||
|
||||
if user.nil?
|
||||
redirect_to signin_path, alert: "Invalid email address or password."
|
||||
return
|
||||
end
|
||||
|
||||
# Check if user is active
|
||||
unless user.status == "active"
|
||||
redirect_to signin_path, alert: "Your account is not active. Please contact an administrator."
|
||||
return
|
||||
end
|
||||
|
||||
# Check if TOTP is required
|
||||
if user.totp_enabled?
|
||||
# TODO: Implement TOTP verification flow
|
||||
# For now, reject login if TOTP is enabled
|
||||
redirect_to signin_path, alert: "Two-factor authentication is enabled but not yet implemented. Please contact an administrator."
|
||||
return
|
||||
end
|
||||
|
||||
# Sign in successful
|
||||
start_new_session_for user
|
||||
redirect_to after_authentication_url, notice: "Signed in successfully."
|
||||
end
|
||||
|
||||
def destroy
|
||||
terminate_session
|
||||
redirect_to new_session_path, status: :see_other
|
||||
redirect_to signin_path, status: :see_other, notice: "Signed out successfully."
|
||||
end
|
||||
|
||||
def destroy_other
|
||||
session = Current.session.user.sessions.find(params[:id])
|
||||
session.destroy
|
||||
redirect_to profile_path, notice: "Session revoked successfully."
|
||||
end
|
||||
end
|
||||
|
||||
84
app/controllers/totp_controller.rb
Normal file
84
app/controllers/totp_controller.rb
Normal file
@@ -0,0 +1,84 @@
|
||||
class TotpController < ApplicationController
|
||||
before_action :set_user
|
||||
before_action :redirect_if_totp_enabled, only: [:new, :create]
|
||||
before_action :require_totp_enabled, only: [:backup_codes, :verify_password, :destroy]
|
||||
|
||||
# GET /totp/new - Show QR code to set up TOTP
|
||||
def new
|
||||
# Generate TOTP secret but don't save yet
|
||||
@totp_secret = ROTP::Base32.random
|
||||
@provisioning_uri = ROTP::TOTP.new(@totp_secret, issuer: "Clinch").provisioning_uri(@user.email_address)
|
||||
|
||||
# Generate QR code
|
||||
require "rqrcode"
|
||||
@qr_code = RQRCode::QRCode.new(@provisioning_uri)
|
||||
end
|
||||
|
||||
# POST /totp - Verify TOTP code and enable 2FA
|
||||
def create
|
||||
totp_secret = params[:totp_secret]
|
||||
code = params[:code]
|
||||
|
||||
# Verify the code works
|
||||
totp = ROTP::TOTP.new(totp_secret)
|
||||
if totp.verify(code, drift_behind: 30, drift_ahead: 30)
|
||||
# Save the secret and generate backup codes
|
||||
@user.totp_secret = totp_secret
|
||||
@user.backup_codes = generate_backup_codes
|
||||
@user.save!
|
||||
|
||||
# Redirect to backup codes page with success message
|
||||
redirect_to backup_codes_totp_path, notice: "Two-factor authentication has been enabled successfully! Save these backup codes now."
|
||||
else
|
||||
redirect_to new_totp_path, alert: "Invalid verification code. Please try again."
|
||||
end
|
||||
end
|
||||
|
||||
# GET /totp/backup_codes - Show backup codes (requires password)
|
||||
def backup_codes
|
||||
# This will be shown after password verification
|
||||
@backup_codes = @user.parsed_backup_codes
|
||||
end
|
||||
|
||||
# POST /totp/verify_password - Verify password before showing backup codes
|
||||
def verify_password
|
||||
if @user.authenticate(params[:password])
|
||||
redirect_to backup_codes_totp_path
|
||||
else
|
||||
redirect_to profile_path, alert: "Incorrect password."
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /totp - Disable TOTP (requires password)
|
||||
def destroy
|
||||
unless @user.authenticate(params[:password])
|
||||
redirect_to profile_path, alert: "Incorrect password. Could not disable 2FA."
|
||||
return
|
||||
end
|
||||
|
||||
@user.disable_totp!
|
||||
redirect_to profile_path, notice: "Two-factor authentication has been disabled."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = Current.session.user
|
||||
end
|
||||
|
||||
def redirect_if_totp_enabled
|
||||
if @user.totp_enabled?
|
||||
redirect_to profile_path, alert: "Two-factor authentication is already enabled."
|
||||
end
|
||||
end
|
||||
|
||||
def require_totp_enabled
|
||||
unless @user.totp_enabled?
|
||||
redirect_to profile_path, alert: "Two-factor authentication is not enabled."
|
||||
end
|
||||
end
|
||||
|
||||
def generate_backup_codes
|
||||
Array.new(10) { SecureRandom.alphanumeric(8).upcase }.to_json
|
||||
end
|
||||
end
|
||||
36
app/controllers/users_controller.rb
Normal file
36
app/controllers/users_controller.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class UsersController < ApplicationController
|
||||
allow_unauthenticated_access only: %i[ new create ]
|
||||
before_action :ensure_first_run, only: %i[ new create ]
|
||||
|
||||
def new
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
|
||||
# First user becomes admin automatically
|
||||
@user.admin = true if User.count.zero?
|
||||
@user.status = "active"
|
||||
|
||||
if @user.save
|
||||
start_new_session_for @user
|
||||
redirect_to root_path, notice: "Welcome to Clinch! Your account has been created."
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email_address, :password, :password_confirmation)
|
||||
end
|
||||
|
||||
def ensure_first_run
|
||||
# Only allow signup if there are no users (first-run scenario)
|
||||
if User.exists?
|
||||
redirect_to signin_path, alert: "Registration is closed. Please sign in."
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user