Skip to content

Commit

Permalink
feat(devise): implement devise lockable (#5258)
Browse files Browse the repository at this point in the history
* feat(devise): implement devise lockable

* fix(signIn): fixed signIn modal not 'finishing'

* client translates strings, use nice email layout for unlock
  • Loading branch information
gregorykan authored and robguthrie committed Mar 11, 2019
1 parent 387b72b commit 26b6bb8
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 19 deletions.
1 change: 1 addition & 0 deletions .env.development
Expand Up @@ -10,3 +10,4 @@ SMTP_DOMAIN=localhost
WELCOME_EMAIL_SENDER_NAME=dev
WELCOME_EMAIL_SENDER_EMAIL=dev@dev.com
DISABLED_PLUGINS=""
MAX_LOGIN_ATTEMPTS=3
1 change: 1 addition & 0 deletions .env.test
Expand Up @@ -11,3 +11,4 @@ PRIVACY_URL="https://www.loomio.org/privacy"
WELCOME_EMAIL_SENDER_NAME=dev
WELCOME_EMAIL_SENDER_EMAIL=dev@dev.com
DISABLED_PLUGINS=""
MAX_LOGIN_ATTEMPTS=3
10 changes: 9 additions & 1 deletion app/controllers/api/sessions_controller.rb
Expand Up @@ -10,7 +10,7 @@ def create
user.update(name: resource_params[:name]) if resource_params[:name]
render json: Boot::User.new(user).payload
else
render json: { errors: { password: [t(:"user.error.bad_login")] } }, status: 401
render json: { errors: failure_message }, status: 401
end
session.delete(:pending_login_token)
end
Expand All @@ -24,6 +24,14 @@ def destroy

private

def failure_message
if resource_params[:password] && User.where(email: resource_params[:email]).where.not(locked_at: nil).exists?
{ password: [:'auth_form.account_locked'] }
else
{ password: [:'auth_form.invalid_password'] }
end
end

def attempt_login
if pending_login_token&.useable?
pending_login_token.user
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Expand Up @@ -23,7 +23,7 @@ class User < ApplicationRecord
demo_bot: ENV['DEMO_BOT_EMAIL'] || 'contact+demo@loomio.org'
}.freeze

devise :database_authenticatable, :recoverable, :registerable, :rememberable
devise :database_authenticatable, :recoverable, :registerable, :rememberable, :lockable
attr_accessor :recaptcha
attr_accessor :restricted
attr_accessor :token
Expand Down
Expand Up @@ -14,8 +14,10 @@ angular.module('loomioApp').directive 'authSigninForm', ->
$scope.signIn = ->
EventBus.emit $scope, 'processing'
$scope.user.name = $scope.vars.name if $scope.vars.name?
AuthService.signIn($scope.user, hardReload).finally ->
EventBus.emit $scope, 'doneProcessing'
finished = ->
EventBus.emit $scope, 'doneProcessing';
$scope.$apply();
AuthService.signIn($scope.user, hardReload, finished).finally finished

$scope.signInAndSetPassword = ->
LmoUrlService.params('set_password', true)
Expand Down
14 changes: 8 additions & 6 deletions client/shared/services/auth_service.coffee
Expand Up @@ -17,16 +17,18 @@ module.exports = new class AuthService
user.update(hasToken: data.has_token)
user

signIn: (user = {}, onSuccess) ->
signIn: (user = {}, onSuccess, finished) ->
Records.sessions.build(
_.pick(user, ['email', 'name', 'password'])
).save().then ->
onSuccess()
, () ->
user.errors = if user.hasToken
{ token: [I18n.t('auth_form.invalid_token')] }
else
{ password: [I18n.t('auth_form.invalid_password')] }
, (response) ->
response.json().then (data) ->
user.errors = if user.hasToken
{ token: [I18n.t('auth_form.invalid_token')] }
else
{ password: _.map(data.errors.password, (key) -> I18n.t(key)) }
finished()

signUp: (user, onSuccess) ->
Records.registrations.build(
Expand Down
12 changes: 6 additions & 6 deletions config/initializers/devise.rb
Expand Up @@ -9,7 +9,7 @@
# Configure the class responsible to send e-mails.
# config.mailer = "Devise::Mailer"
config.parent_mailer = "BaseMailer"

Devise::Mailer.layout "invite_people_mailer"
# Automatically apply schema changes in tableless databases

# ==> ORM configuration
Expand Down Expand Up @@ -136,24 +136,24 @@
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
config.lock_strategy = :failed_attempts

# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [ :email ]
config.unlock_keys = [ :email ]

# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
config.unlock_strategy = :both

# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
config.maximum_attempts = ENV.fetch('MAX_LOGIN_ATTEMPTS', 20).to_i

# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
config.unlock_in = 6.hours

# ==> Configuration for :recoverable
#
Expand Down
1 change: 1 addition & 0 deletions config/locales/client.en.yml
Expand Up @@ -1798,6 +1798,7 @@ en:
invalid_email: "Sorry, that doesn't look like a valid email address"
invalid_password: "Sorry, that password doesn't match the one we have on file"
invalid_token: "Sorry, we weren't able to use that token to log you in. Click below to send another one."
account_locked: "Your account has been locked after too many failed login attempts. Check your email for an unlock link"
email_not_found: "Sorry, we couldn't find an account with that email address"
email_placeholder: you@example.com
login_with_token: You're all set! Click the link below to continue.
Expand Down
2 changes: 1 addition & 1 deletion config/locales/server.en.yml
Expand Up @@ -22,7 +22,7 @@ en:
placeholder_name: "Invited user (%{hostname})"
error:
recaptcha: "We weren't able to verify that you're not a robot. Please try again!"
bad_login: "Unable to log you in. Please try again."
bad_login: "Sorry, that password doesn't match the one we have on file"
username_must_be_alphanumeric: "Usernames must be lower case letters and numbers only"

attachment:
Expand Down
8 changes: 8 additions & 0 deletions db/migrate/20190310194641_add_lockable_to_devise.rb
@@ -0,0 +1,8 @@
class AddLockableToDevise < ActiveRecord::Migration[5.2]
def change
add_column :users, :failed_attempts, :integer, default: 0, null: false # Only if lock strategy is :failed_attempts
add_column :users, :unlock_token, :string # Only if unlock strategy is :email or :both
add_column :users, :locked_at, :datetime
add_index :users, :unlock_token, unique: true
end
end
7 changes: 5 additions & 2 deletions db/schema.rb
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_03_06_010911) do
ActiveRecord::Schema.define(version: 2019_03_10_194641) do

# These are extensions that must be enabled in order to support this database
enable_extension "citext"
Expand Down Expand Up @@ -604,7 +604,6 @@
t.integer "max_members"
t.integer "max_orgs"
t.string "state", default: "active", null: false
t.index ["chargify_subscription_id"], name: "index_subscriptions_on_chargify_subscription_id", unique: true
t.index ["owner_id"], name: "index_subscriptions_on_owner_id"
end

Expand Down Expand Up @@ -696,11 +695,15 @@
t.datetime "last_seen_at"
t.datetime "legal_accepted_at"
t.boolean "email_newsletter", default: false, null: false
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["deactivated_at"], name: "index_users_on_deactivated_at"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["email_verified"], name: "index_users_on_email_verified"
t.index ["key"], name: "index_users_on_key", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
t.index ["unsubscribe_token"], name: "index_users_on_unsubscribe_token", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
Expand Down

0 comments on commit 26b6bb8

Please sign in to comment.