Forked from Fire-Dragon-DoL/application_controller.rb
Last active
August 29, 2015 14:16
-
-
Save kivanio/1b40410bb5bc9c08a409 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ApplicationController < ActionController::API | |
include AbstractController::Translation | |
include ActionController::MimeResponds | |
include ActionController::ImplicitRender | |
include ActionController::StrongParameters | |
# Gems that tries to include something in ActionController::Base | |
include JSend::Rails::Controller | |
include CanCan::ControllerAdditions | |
include ::TokenAuthentication # HERE FOR TOKEN AUTHENTICATION <-- ONLY REQUIRED LINE | |
rescue_from ::CanCan::AccessDenied, with: :cancan_to_api_error | |
rescue_from ::ApiError, with: :render_api_error | |
protected | |
def cancan_to_api_error(error) | |
raise ::AuthorizationError.from_cancan(error) | |
end | |
def render_api_error(error) | |
render_jsend error: error.message, code: error.code, data: { | |
backtrace: error.backtrace | |
} | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module TokenAuthentication | |
extend ActiveSupport::Concern | |
included do | |
private :authenticate_user_with_token! | |
private :fetch_username | |
private :fetch_token | |
attr_reader :current_user | |
before_action :authenticate_user_with_token! | |
end | |
def fetch_username | |
username = params[:username] | |
username ||= request.headers['X-UserSession-Username'] | |
username ||= request.headers['X_UserSession_Username'] | |
end | |
def fetch_token | |
token = params[:token] | |
token ||= request.headers['X-UserSession-Token'] | |
token ||= request.headers['X_UserSession_Token'] | |
end | |
def authenticate_user_with_token! | |
username = fetch_username | |
token = fetch_token | |
if username.nil? || token.nil? | |
raise ::AuthenticationError, I18n.t('authentication.failure.invalid_username') | |
end | |
@current_user ||= User.find_by(username: username).try( | |
:authenticate_with_token, | |
token | |
) | |
# XXX: Token expiration checked in :authenticate_with_token | |
unless current_user | |
raise ::AuthenticationError, I18n.t('authentication.failure.invalid_token') | |
end | |
current_user.renew_token! | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SignInUser | |
include Interactor | |
def perform | |
user = User.find_by(username: context[:username]) | |
if user.nil? | |
context.fail! message: I18n.t('authentication.failure.invalid_username') | |
else | |
if user.authenticate(context[:password]) | |
user.generate_token! if user.token_expired? | |
user.renew_token | |
user.save! | |
context[:user] = user | |
else | |
context.fail! message: I18n.t('authentication.failure.invalid_password') | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SignOutUser | |
include Interactor | |
def perform | |
user = context[:user] | |
user.token = nil | |
user.token_updated_at = nil | |
user.save! | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Tokenable | |
TOKEN_EXPIRATION = 1.hours | |
# NOTE: The two following methods are copied from Devise v3.3.0 | |
# https://github.com/plataformatec/devise/blob/df43642cd5e5a4ed666fc8e57a1bee46de864394/lib/devise.rb | |
# Generate a friendly string randomly to be used as token. | |
def self.friendly_token | |
SecureRandom.urlsafe_base64(15).tr('lIO0', 'sxyz') | |
end | |
# constant-time comparison algorithm to prevent timing attacks | |
def self.secure_compare(a, b) | |
return false if a.blank? || b.blank? || a.bytesize != b.bytesize | |
l = a.unpack "C#{a.bytesize}" | |
res = 0 | |
b.each_byte { |byte| res |= byte ^ l.shift } | |
res == 0 | |
end | |
def generate_token | |
loop do | |
auth_token = Tokenable.friendly_token | |
unless self.class.find_by(token: auth_token) | |
break auth_token | |
end | |
end | |
end | |
def generate_token! | |
self.token = generate_token | |
end | |
def renew_token | |
self.token_updated_at = Time.zone.now | |
end | |
def renew_token! | |
renew_token | |
save! | |
end | |
def token_expire_in | |
TOKEN_EXPIRATION | |
end | |
def token_expired? | |
return true if token_updated_at.nil? | |
(token_updated_at + token_expire_in) < Time.zone.now | |
end | |
def authenticate_with_token(token) | |
return nil if self.token.nil? || token_expired? | |
Tokenable.secure_compare(self.token, token) ? self : nil | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User < ActiveRecord::Base | |
include Tokenable | |
has_secure_password | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UserSessionsController < ApplicationController | |
skip_before_action :authenticate_user_with_token!, only: [:create] | |
def create | |
authorize! :create, UserSessionsController | |
if SignInUser.perform_on(self, users_session_params).success? | |
render_jsend success: { | |
username: @user.username, | |
token: @user.token, | |
role: @user.role | |
} | |
else | |
render_jsend fail: { message: @message } | |
end | |
end | |
def destroy | |
authorize! :destroy, UserSessionsController | |
SignOutUser.perform(user: current_user) | |
render_jsend :success | |
end | |
private | |
def users_session_params | |
params.require(:user_session).permit(:username, :password) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment