Skip to content

Instantly share code, notes, and snippets.

@peimelo
Created September 18, 2021 13:48
Show Gist options
  • Save peimelo/b5ec31a30555439c6e575391e939b2c6 to your computer and use it in GitHub Desktop.
Save peimelo/b5ec31a30555439c6e575391e939b2c6 to your computer and use it in GitHub Desktop.
Devise: Password complexity
# app/services/application_service.rb
class ApplicationService
def self.call(*args, &block)
new(*args, &block).call
end
end
# app/services/check_password_complexity_service.rb
class CheckPasswordComplexityService < ApplicationService
attr_reader :password, :required_complexity
def initialize(password, required_complexity = 4)
@password = password
@required_complexity = required_complexity
end
def call
score = uppercase_letters? + digits? + extra_chars? + downcase_letters?
score >= required_complexity
end
private
def digits?
@password.match(/\d/) ? 1 : 0
end
def downcase_letters?
@password.match(/[a-z]{1}/) ? 1 : 0
end
def extra_chars?
@password.match(/[\W_]/) ? 1 : 0
end
def uppercase_letters?
@password.match(/[A-Z]/) ? 1 : 0
end
end
# config/locales/en.yml
en:
activerecord:
errors:
messages:
complexity: 'complexity requirement not met. Please use: 1 uppercase, 1 lowercase, 1 digit and 1 special character'
# app/models/user.rb
class User < ActiveRecord::Base
devise :confirmable,
:database_authenticatable,
:recoverable,
:registerable,
:rememberable,
:trackable,
:validatable
validate :password_complexity
private
def password_complexity
return if password.nil?
errors.add :password, :complexity unless CheckPasswordComplexityService.call(password)
end
end
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_length_of(:password).is_at_least(8) }
end
describe 'password_complexity' do
context 'valid' do
['`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
'-', '_', '=', '+', '[', ']', '{', '}', '\\', '|', ';', ':',
"'", '"', ',', '.', '<', '>', '/', '?'].each do |extra_char|
it { should allow_value("Password12#{extra_char}").for(:password) }
end
end
context 'invalid' do
%w(12345678 password =+[]{}\| PASSWORD password1 pa$$word Password1 password_1).each do |password|
it { should_not allow_value(password).for(:password) }
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment