diff --git a/Gemfile b/Gemfile index 0d627828a..4c8788a89 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'figaro' gem 'pg' gem 'devise', '3.5.6' gem 'devise_invitable' +gem 'simple_token_authentication', '~> 1.0' # Token authentication for Devise gem 'bootstrap-sass', '~> 3.3.5' gem 'sass-rails', '~> 5.0' gem 'bootstrap_form' diff --git a/Gemfile.lock b/Gemfile.lock index 1fe1a4364..c4968f4f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,6 +279,10 @@ GEM shoulda-context (1.2.1) shoulda-matchers (3.1.1) activesupport (>= 4.0.0) + simple_token_authentication (1.14.0) + actionmailer (>= 3.2.6, < 6) + actionpack (>= 3.2.6, < 6) + devise (>= 3.2, < 6) skylight (0.10.0) activesupport (>= 3.0.0) sourcemap (0.1.1) @@ -382,6 +386,7 @@ DEPENDENCIES sdoc (~> 0.4.0) shoulda-context shoulda-matchers (>= 3.0.1) + simple_token_authentication (~> 1.0) skylight sneaky-save! spinjs-rails diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6a763a0c3..c6e7e2ade 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ class ApplicationController < ActionController::Base include PermissionHelper include FirstTimeDataGenerator + acts_as_token_authentication_handler_for User # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index a9bda1e3f..5110baee9 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -1,5 +1,5 @@ class Users::SessionsController < Devise::SessionsController -# before_filter :configure_sign_in_params, only: [:create] + # before_filter :configure_sign_in_params, only: [:create] # GET /resource/sign_in # def new @@ -16,11 +16,36 @@ class Users::SessionsController < Devise::SessionsController # super # end + # Singing in with authentication token (needed when signing in automatically + # from another website). NOTE: For some reason URL needs to end with '/'. + def auth_token_create + user = User.find_by_email(params[:user_email]) + user_token = params[:user_token] + # Remove trailing slash if present + user_token.chop! if !user_token.nil? && user_token.end_with?('/') + + if user && user.authentication_token == user_token + sign_in(:user, user) + # This will cause new token to be generated + user.update(authentication_token: nil) + + redirect_url = root_path + else + flash[:error] = t('devise.sessions.auth_token_create.wrong_credentials') + redirect_url = new_user_session_path + end + + respond_to do |format| + format.html do + redirect_to redirect_url + end + end + end + protected # If you have extra params to permit, append them to the sanitizer. def configure_sign_in_params devise_parameter_sanitizer.for(:sign_in) << :attribute end - end diff --git a/app/models/user.rb b/app/models/user.rb index aa9332fc6..198b08553 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,7 @@ class User < ActiveRecord::Base include SearchableModel + acts_as_token_authenticatable devise :invitable, :confirmable, :database_authenticatable, :registerable, :async, :recoverable, :rememberable, :trackable, :validatable, stretches: Constants::PASSWORD_STRETCH_FACTOR diff --git a/config/initializers/simple_token_authentication.rb b/config/initializers/simple_token_authentication.rb new file mode 100644 index 000000000..b66288f53 --- /dev/null +++ b/config/initializers/simple_token_authentication.rb @@ -0,0 +1,75 @@ +SimpleTokenAuthentication.configure do |config| + # Configure the session persistence policy after a successful sign in, + # in other words, if the authentication token acts as a signin token. + # If true, user is stored in the session and the authentication token and + # email may be provided only once. + # If false, users must provide their authentication token and email at every + # request. + # config.sign_in_token = false + + # Configure the name of the HTTP headers watched for authentication. + # + # Default header names for a given token authenticatable entity follow the + # pattern: + # { entity: { authentication_token: 'X-Entity-Token', email: + # 'X-Entity-Email'} } + # + # When several token authenticatable models are defined, custom header names + # can be specified for none, any, or all of them. + # + # Note: when using the identifiers options, this option behaviour is modified. + # Please see the example below. + # + # Examples + # + # Given User and SuperAdmin are token authenticatable, + # When the following configuration is used: + # `config.header_names = { super_admin: { authentication_token: + # 'X-Admin-Auth-Token' } }` + # Then the token authentification handler for User watches the following + # headers: + # `X-User-Token, X-User-Email` + # And the token authentification handler for SuperAdmin watches the + # following headers: + # `X-Admin-Auth-Token, X-SuperAdmin-Email` + # + # When the identifiers option is set: + # `config.identifiers = { super_admin: :phone_number }` + # Then both the header names identifier key and default value are modified + # accordingly: + # `config.header_names = { super_admin: { phone_number: + # 'X-SuperAdmin-PhoneNumber' } }` + # + # config.header_names = { user: { authentication_token: 'X-User-Token', email: + # 'X-User-Email' } } + + # Configure the name of the attribute used to identify the user for + # authentication. + # That attribute must exist in your model. + # + # The default identifiers follow the pattern: + # { entity: 'email' } + # + # Note: the identifer must match your Devise configuration, + # see https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address#tell-devise-to-use-username-in-the-authentication_keys + # + # Note: setting this option does modify the header_names behaviour, + # see the header_names section above. + # + # Example: + # + # `config.identifiers = { super_admin: 'phone_number', user: 'uuid' }` + # + # config.identifiers = { user: 'email' } + + # Configure the Devise trackable strategy integration. + # + # If true, tracking is disabled for token authentication: signing in through + # token authentication won't modify the Devise trackable statistics. + # + # If false, given Devise trackable is configured for the relevant model, + # then signing in through token authentication will be tracked as any other + # sign in. + # + # config.skip_devise_trackable = true +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 969c13beb..3d2e89d33 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -31,6 +31,8 @@ en: submit: "Log in" create: org_name: "%{user}'s projects" + auth_token_create: + wrong_credentials: "Failed to automatically sign in (wrong credentials)." unlocks: new: head_title: "Resend unlock instructions" diff --git a/config/routes.rb b/config/routes.rb index 91302df55..a2f8e2447 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -289,5 +289,6 @@ Rails.application.routes.draw do devise_scope :user do get 'avatar/:id/:style' => 'users/registrations#avatar', as: 'avatar' post 'avatar_signature' => 'users/registrations#signature' + get 'users/auth_token_sign_in' => 'users/sessions#auth_token_create' end end diff --git a/db/migrate/20161129171012_add_authentication_token_to_users.rb b/db/migrate/20161129171012_add_authentication_token_to_users.rb new file mode 100644 index 000000000..cf69f8ee5 --- /dev/null +++ b/db/migrate/20161129171012_add_authentication_token_to_users.rb @@ -0,0 +1,6 @@ +class AddAuthenticationTokenToUsers < ActiveRecord::Migration + def change + add_column :users, :authentication_token, :string, limit: 30 + add_index :users, :authentication_token, unique: true + end +end