prémices d'une API avec authentification

This commit is contained in:
Jérémy Lecour 2022-01-24 23:50:12 +01:00 committed by Jérémy Lecour
parent 4707a7da36
commit 41d1d6d8fe
11 changed files with 175 additions and 14 deletions

View file

@ -0,0 +1,30 @@
class Api::V1::ApiKeysController < Api::V1::BaseController
# include ApiKeyAuthenticable
# # Require API key authentication
# prepend_before_action :authenticate_with_api_key!, only: %i[index destroy]
def index
render json: current_bearer.api_keys
end
def create
authenticate_with_http_basic do |email, password|
user = User.find_by email: email
if user&.authenticate(password)
api_key = user.api_keys.create! token: SecureRandom.hex
render json: api_key, status: :created and return
end
end
render status: :unauthorized
end
def destroy
api_key = current_bearer.api_keys.find(params[:id])
api_key.destroy
end
end

View file

@ -0,0 +1,25 @@
class Api::V1::BaseController < ApplicationController
# before_action :authenticate
protect_from_forgery with: :null_session
def ping
render json: { message: "pong" }, status: :ok
end
private
def authenticate
authenticate_user_with_token || handle_bad_authentication
end
def authenticate_user_with_token
authenticate_with_http_token do |token, options|
@user ||= User.find_by(private_api_key: token)
end
end
def handle_bad_authentication
render json: { message: "Bad credentials" }, status: :unauthorized
end
end

View file

@ -0,0 +1,25 @@
class Api::V1::ChecksController < Api::V1::BaseController
# POST /checks or /checks.json
def create
@check = Check.new(check_params)
if @check.save
render json: { message: "created" }, status: :created
else
render json: @check.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_check
@check = Check.find(params[:id])
end
# Only allow a list of trusted parameters through.
def check_params
params.require(:check).permit(:name, :description, :hostname)
end
end

View file

@ -0,0 +1,31 @@
module ApiKeyAuthenticatable
include ActionController::HttpAuthentication::Basic::ControllerMethods
include ActionController::HttpAuthentication::Token::ControllerMethods
extend ActiveSupport::Concern
attr_reader :current_api_key
attr_reader :current_bearer
# Use this to raise an error and automatically respond with a 401 HTTP status
# code when API key authentication fails
def authenticate_with_api_key!
@current_bearer = authenticate_or_request_with_http_token &method(:authenticator)
end
# Use this for optional API key authentication
def authenticate_with_api_key
@current_bearer = authenticate_with_http_token &method(:authenticator)
end
private
attr_writer :current_api_key
attr_writer :current_bearer
def authenticator(token, options)
@current_api_key = ApiKey.authenticate_by_token token
current_api_key&.bearer
end
end

View file

@ -1,49 +1,49 @@
module Authentication
extend ActiveSupport::Concern
included do
before_action :current_user
helper_method :current_user
helper_method :user_signed_in?
end
def authenticate_user!
store_location
redirect_to login_path, alert: "You need to login to access that page." unless user_signed_in?
end
def login(user)
reset_session
user.regenerate_session_token
session[:current_user_session_token] = user.reload.session_token
end
def forget(user)
cookies.delete :remember_token
user.regenerate_remember_token
end
def logout
user = current_user
reset_session
user.regenerate_session_token
end
def redirect_if_authenticated
redirect_to root_path, alert: "You are already logged in." if user_signed_in?
end
def remember(user)
user.regenerate_remember_token
cookies.permanent.encrypted[:remember_token] = user.remember_token
end
def store_location
session[:user_return_to] = request.original_url if request.get? && request.local?
end
private
def current_user
Current.user ||= if session[:current_user_session_token].present?
User.find_by(session_token: session[:current_user_session_token])
@ -51,9 +51,8 @@ module Authentication
User.find_by(remember_token: cookies.permanent.encrypted[:remember_token])
end
end
def user_signed_in?
Current.user.present?
end
end

15
app/models/api_key.rb Normal file
View file

@ -0,0 +1,15 @@
class ApiKey < ApplicationRecord
encrypts :token, deterministic: true
belongs_to :bearer, polymorphic: true
def self.authenticate_by_token!(token)
find_by! token: token
end
def self.authenticate_by_token(token)
authenticate_by_token! token
rescue ActiveRecord::RecordNotFound
nil
end
end

View file

@ -5,6 +5,8 @@ class User < ApplicationRecord
attr_accessor :current_password
has_many :api_keys, as: :bearer
has_secure_password
has_secure_token :remember_token
has_secure_token :session_token

View file

@ -1 +1 @@
LAf4+kWkL2cPoqOeoqqfRaf2Y00zX7cZEAFlZGQkK//YR3Fm2SRb9IeTkKM7Hr57zT+DIXRP5RIA1+p7fa7Bazkg21JGnpMQDbCGTuztA139rUVMcCWoM0tq2S1JnFh3kplxkSC3QLTIEhBcBlbINwXwlKgYcLATE5fR7eFwY3TcSmNnEKuIOliBp1GAs+0ZLDFD1ZR+hGmiOKeIefMzZ8vmoyDGzeEHFKALapLU9fqr+xD/W2AvPd5w30ZUv3bz7yBx4y5NhR1qlvZdR+dFdQK426ObyWfptL4mkc7PJHs47D0S1TtDc8Q7TH4jrxJkfiKU+cWfXcJWDomZVnsfQPocJYg2XPMbstQHRWYOlnVPci5UsEXmzxosjD/I4p1Wb6gBXEa21pxu4abhQraIx3fmzokav8a+hrRC--3aQgyb/0RcdkvuWC--zE2XmPmYd5thg8yPD1YbJA==
tX8j9/8froZx8hSUXJ1zaO6Fmn3jkubOCTvLpVsEGPvTf11VOtyL2LE7Hlgfsx9W0ZEVLlD5cIu09okLO6LpiHUOiTKRxzO/z32c+hT6eZyvCcWRJJewvitfsNxnSL/NN/TN2YgrQC7yEYqR5qQ2oyTFnpyN8VlmyGRJQhjJ5U8DZbPcyFmQ/wO6s05/jxRQU016+/dHG4Pa+7q//I6VHNe9TWsUNo6coVpjGtzyvPdeigfC3l9VoM88bdKuXx9wvQMu0usHhFDLkt5u18AW3A346+2xN+NJegVXH0Hr1NbhVPCNLkSYwy083ismXwisVj1bofENRzt0mf5Yjj8t2KsdtzQ+erXq9wb4scgx5U76hHC8qZXC36Z8S08k9Iq5Z7dECWzafXTiHjqkbjXifn/Zd0PcwGqj93NFOeIreP4SxWWYhic0lte2Ulx2vgLr6KV5U55syDQP5cSAqY8fA1OwcDseUN1O2JGLvLfI17+fjD/rbv5N1mzIV23l9NkyLsHySzyFW05wiPrqH4a2dM8uadhAsfr5g7PdbDfomF3JdVzP5ZNynUh4tzqdlXx6/dfZqcYs+7IWjNHRFrS9vd5D6mwI79QkxaGMVhXCY21IPADcTJI2avDqnJbxcb7cYmtj/zzZF2Qr4pHYHq8=--JXt6Z4gua+B0tGrh--iUcqTF0pAQA4ExORRzEx3w==

View file

@ -1,5 +1,15 @@
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
defaults format: :json do
get '/ping', to: 'base#ping'
resources :api_keys, path: 'api-keys', only: %i[index create destroy]
resources :checks, only: [:create]
end
end
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
root "static_pages#home"

View file

@ -0,0 +1,15 @@
class CreateApiKeys < ActiveRecord::Migration[7.0]
def up
create_table :api_keys do |t|
t.string :token
t.references :bearer, polymorphic: true, null: false
end
add_index :api_keys, [:bearer_id, :bearer_type]
add_index :api_keys, :token, unique: true
end
def down
drop_table :api_keys
end
end

11
db/schema.rb generated
View file

@ -10,7 +10,16 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_01_23_074307) do
ActiveRecord::Schema.define(version: 2022_01_24_181635) do
create_table "api_keys", force: :cascade do |t|
t.string "token"
t.string "bearer_type", null: false
t.integer "bearer_id", null: false
t.index ["bearer_id", "bearer_type"], name: "index_api_keys_on_bearer_id_and_bearer_type"
t.index ["bearer_type", "bearer_id"], name: "index_api_keys_on_bearer"
t.index ["token"], name: "index_api_keys_on_token", unique: true
end
create_table "checks", force: :cascade do |t|
t.string "name"