Users & Auth

This commit is contained in:
2018-11-10 18:46:47 -06:00
parent 904a071fc0
commit 8a7b3d8ae0
26 changed files with 663 additions and 14 deletions

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class AuthenticateUser < Imperator::Command
include ActiveModel::Validations
string :email
string :password
validates :email, presence: true
validates :password, presence: true
def action
JsonWebToken.encode(user_id: user.id) if user
end
def user
user = @user ||= User.find_by(email: @email)
return user if user&.authenticate(@password)
errors.add :user_authentication, 'invalid credentials'
nil
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class AuthorizeRequest < Imperator::Command
include ActiveModel::Validations
attr_reader :headers
def initialize(headers)
@headers = headers
end
def action
user
end
def valid?
headers["Authorization"].present?
end
private
def user
@user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
@user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
@decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
return headers["Authorization"].split(' ').last if valid?
errors.add(:token, "Missing token")
nil
end
end

View File

@ -1,4 +1,31 @@
# frozen_string_literal: true
class ApplicationController < ActionController::API
include Pundit
before_action :authenticate_request
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def index; end
private
def current_user
@current_user ||= authenticate_request
end
def authenticate_request
return nil if request.authorization.blank?
@authenticate_request ||= AuthorizeRequest.new(request.headers).perform
end
def user_not_authorized
render \
json: { authorization: ["You are not authorized to perform this action."] },
status: :unauthorized
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module V1
class AuthenticationController < ApplicationController
skip_after_action :verify_authorized
skip_after_action :verify_policy_scoped
def authenticate
command = AuthenticateUser.new(auth_params)
@token = command.perform
@user = command.user
render "v1/authentication/authenticate" and return unless @token.nil?
render json: command.errors, status: :unauthorized
end
private
def auth_params
params.permit(:email, :password)
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module V1
class UsersController < ApplicationController
before_action :set_user, only: %i[show update destroy]
def index
@users = policy_scope User.all
end
def show; end
def create
@user = User.new(user_params)
authorize @user
if @user.save
render :show, status: :created, location: v1_users_url(@user)
else
render json: @user.errors, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
render :show, status: :ok, location: v1_users_url(@user)
else
render json: @user.errors, status: :unprocessable_entity
end
end
def destroy
@user.destroy
end
private
def set_user
@user = User.find(params[:id])
authorize @user
end
def user_params
params.require(:user).permit(policy(User).permitted_attributes)
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class JsonWebToken
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, ENV['jwt'])
end
def decode(token)
body = JWT.decode(token, ENV['jwt'])[0]
HashWithIndifferentAccess.new body
end
end
end

40
app/models/user.rb Normal file
View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# display_name :string not null
# email :string not null
# password_digest :string not null
# role :integer default("author"), not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_users_on_email (email)
#
class User < ApplicationRecord
has_secure_password
validates :display_name, presence: true
validates :email, presence: true, email_format: true, uniqueness: true
validates :password_confirmation, presence: true, if: ->(m) { m.password.present? }
validates :role, presence: true
enum role: {
author: 0,
admin: 2
}
def acts_as_admin?
admin?
end
def acts_as_author?
admin? || author?
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope.all
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
class UserPolicy < ApplicationPolicy
def show?
raise Pundit::NotAuthorizedError if user.nil?
return true if user&.acts_as_admin?
user == record
end
def update?
raise Pundit::NotAuthorizedError if user.nil?
show?
end
def destroy?
raise Pundit::NotAuthorizedError if user.nil?
user&.acts_as_admin?
end
def create?
raise Pundit::NotAuthorizedError if user.nil?
user&.acts_as_admin?
end
def permitted_attributes
return base_attributes + %i[role] if user&.acts_as_admin?
base_attributes
end
def base_attributes
%i[
display_name
email
password
password_confirmation
]
end
class Scope < Scope
def resolve
raise Pundit::NotAuthorizedError if user.nil?
return scope if user.acts_as_admin?
scope.where(id: user.id)
end
end
end

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
json.auth_token @token
json.user @user, partial: "v1/users/user", as: :user

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
json.extract! user,
:id,
:display_name,
:email,
:role
json.url v1_user_url(user, format: :json)

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.array! @users, partial: 'v1/users/user', as: :user

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.partial! "v1/users/user", user: @user