feat(auth): implement authentication system foundation
Add user authentication infrastructure with login/logout functionality, session management, and basic user model. Changes: - Create users table with email, password_digest, confirmation tokens - Add Users repository with authentication methods - Add User entity with authentication helpers - Implement login form and authentication actions - Add logout action - Add BCrypt gem for password hashing - Update base Action class with authentication helpers - Add login/logout routes Next steps: Registration flow, password reset, email confirmation
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EuchreCamp
|
||||
module Repositories
|
||||
class Users < ::EuchreCamp::Repository[:users]
|
||||
|
||||
# Find user by ID
|
||||
def by_id(id)
|
||||
users.by_pk(id).one
|
||||
end
|
||||
|
||||
# Find user by email
|
||||
def by_email(email)
|
||||
users.where(email: email).one
|
||||
end
|
||||
|
||||
# Find user by player ID
|
||||
def by_player_id(player_id)
|
||||
users.where(player_id: player_id).one
|
||||
end
|
||||
|
||||
# Create new user
|
||||
def create(email:, password_digest:, player_id:)
|
||||
users.changeset(:create, {
|
||||
email: email,
|
||||
password_digest: password_digest,
|
||||
player_id: player_id,
|
||||
confirmed: false,
|
||||
confirmation_token: generate_token,
|
||||
confirmation_sent_at: Time.now
|
||||
}).commit
|
||||
end
|
||||
|
||||
# Confirm user account
|
||||
def confirm(token)
|
||||
user = users.where(confirmation_token: token).one
|
||||
return nil unless user
|
||||
|
||||
users.where(id: user[:id]).update(
|
||||
confirmed: true,
|
||||
confirmation_token: nil,
|
||||
confirmation_sent_at: nil
|
||||
)
|
||||
user
|
||||
end
|
||||
|
||||
# Request password reset
|
||||
def request_password_reset(email)
|
||||
user = by_email(email)
|
||||
return nil unless user
|
||||
|
||||
token = generate_token
|
||||
users.where(id: user[:id]).update(
|
||||
reset_password_token: token,
|
||||
reset_password_sent_at: Time.now
|
||||
)
|
||||
token
|
||||
end
|
||||
|
||||
# Reset password
|
||||
def reset_password(token, new_password_digest)
|
||||
user = users.where(reset_password_token: token).one
|
||||
return nil unless user
|
||||
|
||||
# Check if token is expired (1 hour)
|
||||
if user[:reset_password_sent_at] && user[:reset_password_sent_at] < (Time.now - 3600)
|
||||
return nil
|
||||
end
|
||||
|
||||
users.where(id: user[:id]).update(
|
||||
password_digest: new_password_digest,
|
||||
reset_password_token: nil,
|
||||
reset_password_sent_at: nil
|
||||
)
|
||||
user
|
||||
end
|
||||
|
||||
# Record successful login
|
||||
def record_login(user_id, ip_address)
|
||||
users.where(id: user_id).update(
|
||||
last_login_at: Time.now,
|
||||
last_login_ip: ip_address,
|
||||
failed_login_attempts: 0,
|
||||
locked_until: nil
|
||||
)
|
||||
end
|
||||
|
||||
# Record failed login
|
||||
def record_failed_login(user_id)
|
||||
user = users.where(id: user_id).one
|
||||
return unless user
|
||||
|
||||
attempts = user[:failed_login_attempts] + 1
|
||||
locked_until = attempts >= 5 ? (Time.now + 900) : nil # Lock for 15 minutes after 5 attempts
|
||||
|
||||
users.where(id: user_id).update(
|
||||
failed_login_attempts: attempts,
|
||||
locked_until: locked_until
|
||||
)
|
||||
end
|
||||
|
||||
# Check if account is locked
|
||||
def account_locked?(user_id)
|
||||
user = users.where(id: user_id).one
|
||||
return false unless user
|
||||
|
||||
user[:locked_until] && user[:locked_until] > Time.now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_token
|
||||
SecureRandom.urlsafe_base64(32)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user