Finished phase005
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
class AccessKey < ApplicationRecord
|
||||
DEFAULT_ROTATION_FREQUENCY_DAYS = 30
|
||||
KEY_LENGTH = 32
|
||||
|
||||
validates :raw_key, presence: true
|
||||
validates :generated_at, presence: true
|
||||
validates :auto_rotation_frequency_days,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
def self.current
|
||||
order(generated_at: :desc, id: :desc).first || create_current!
|
||||
end
|
||||
|
||||
def self.create_current!
|
||||
create!(
|
||||
raw_key: generate_raw_key,
|
||||
generated_at: Time.current,
|
||||
auto_rotate: true,
|
||||
auto_rotation_frequency_days: DEFAULT_ROTATION_FREQUENCY_DAYS
|
||||
)
|
||||
end
|
||||
|
||||
def self.rotate!
|
||||
previous_key = current
|
||||
|
||||
transaction do
|
||||
UserSession.delete_all
|
||||
create!(
|
||||
raw_key: generate_raw_key,
|
||||
generated_at: Time.current,
|
||||
auto_rotate: previous_key.auto_rotate,
|
||||
auto_rotation_frequency_days: previous_key.auto_rotation_frequency_days
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def self.rotate_if_needed!
|
||||
key = current
|
||||
return key unless key.auto_rotate? && key.expired?
|
||||
|
||||
rotate!
|
||||
end
|
||||
|
||||
def self.generate_raw_key
|
||||
SecureRandom.urlsafe_base64(KEY_LENGTH)
|
||||
end
|
||||
|
||||
def matches?(submitted_key)
|
||||
ActiveSupport::SecurityUtils.secure_compare(raw_key, submitted_key.to_s)
|
||||
rescue ArgumentError
|
||||
false
|
||||
end
|
||||
|
||||
def expired?
|
||||
generated_at <= auto_rotation_frequency_days.days.ago
|
||||
end
|
||||
|
||||
def disable_auto_rotation!
|
||||
update!(auto_rotate: false)
|
||||
end
|
||||
|
||||
def enable_auto_rotation!
|
||||
update!(auto_rotate: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class Current < ActiveSupport::CurrentAttributes
|
||||
attribute :user, :user_session
|
||||
end
|
||||
@@ -7,6 +7,7 @@ class FuelEntry < ApplicationRecord
|
||||
validates :price_paid, presence: true, numericality: { greater_than: 0 }
|
||||
validates :date, presence: true
|
||||
validate :date_cannot_be_in_the_future
|
||||
validate :odometer_cannot_be_less_than_vehicle_current_odometer
|
||||
|
||||
private
|
||||
|
||||
@@ -15,4 +16,11 @@ class FuelEntry < ApplicationRecord
|
||||
|
||||
errors.add(:date, "can't be in the future")
|
||||
end
|
||||
|
||||
def odometer_cannot_be_less_than_vehicle_current_odometer
|
||||
return if vehicle.blank? || odometer.blank?
|
||||
return if odometer >= vehicle.current_odometer
|
||||
|
||||
errors.add(:odometer, "cannot be less than the vehicle's current odometer")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class User < ApplicationRecord
|
||||
has_many :fuel_entries, foreign_key: :updated_by_user_id, dependent: :restrict_with_error, inverse_of: :updated_by_user
|
||||
has_many :maintenance_entries, foreign_key: :updated_by_user_id, dependent: :restrict_with_error, inverse_of: :updated_by_user
|
||||
has_many :user_sessions, dependent: :destroy
|
||||
|
||||
before_validation :normalize_email
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
class UserSession < ApplicationRecord
|
||||
TOKEN_LENGTH = 32
|
||||
|
||||
belongs_to :user
|
||||
|
||||
validates :token_digest, presence: true, uniqueness: true
|
||||
|
||||
def self.create_for!(user)
|
||||
raw_token = generate_raw_token
|
||||
user_session = create!(user: user, token_digest: digest(raw_token), last_used_at: Time.current)
|
||||
|
||||
[ user_session, raw_token ]
|
||||
end
|
||||
|
||||
def self.find_by_token(raw_token)
|
||||
return if raw_token.blank?
|
||||
|
||||
find_by(token_digest: digest(raw_token))
|
||||
end
|
||||
|
||||
def self.generate_raw_token
|
||||
SecureRandom.urlsafe_base64(TOKEN_LENGTH)
|
||||
end
|
||||
|
||||
def self.digest(raw_token)
|
||||
Digest::SHA256.hexdigest(raw_token.to_s)
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@ class Vehicle < ApplicationRecord
|
||||
has_many :fuel_entries, dependent: :restrict_with_error
|
||||
has_many :maintenance_entries, dependent: :restrict_with_error
|
||||
|
||||
before_validation :normalize_vin
|
||||
before_validation :normalize_vin, :normalize_licence_plate
|
||||
|
||||
validates :make, :model, :color, :vin, :licence_plate, presence: true
|
||||
validates :vin, uniqueness: true
|
||||
@@ -19,4 +19,8 @@ class Vehicle < ApplicationRecord
|
||||
def normalize_vin
|
||||
self.vin = vin.to_s.strip.upcase if vin.present?
|
||||
end
|
||||
|
||||
def normalize_licence_plate
|
||||
self.licence_plate = licence_plate.to_s.strip.upcase if licence_plate.present?
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user