auth_rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b6047449dbddff4af60565f7c096eae14f8800b47bd129dceb3ea7f90fb42f7
4
+ data.tar.gz: 80776e48b7c557c9ab11ec1e9b05ae06e31bc15cca56a4143bfbf46254d2a109
5
+ SHA512:
6
+ metadata.gz: 03502ea907678c9ca3122701789e82a145f9c3a143d415153bbde798ad7b5a375cc464ba1e1bf4d8a583d42b298501c00fcf00aadbc7b2be221b8b2044a149fe
7
+ data.tar.gz: f90cfeab74aa249aebbe6a33deec189901e90c74bfdc0092d42b18a468a3e3eee3fe806611b24accf61a4c1ffc9fc0d1034b4a35cc4bb460fa94b5008790366f
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ Simple authentication for rails.
2
+
3
+ # Installation
4
+
5
+ ```sh
6
+ gem 'auth_rails'
7
+ ```
8
+
9
+ # Configuration
10
+
11
+ ```rb
12
+ # config/initializers/auth_rails.rb
13
+
14
+ AuthRails.configure do |config|
15
+ config.jwt do |jwt|
16
+ jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy # default is AuthRails::Strategies::BaseStrategy
17
+
18
+ jwt.access_token do |access_token|
19
+ access_token.exp = 1.hour.since # optional
20
+ access_token.algorithm = 'HS256' # optional, default is HS256
21
+ access_token.secret_key = ENV.fetch('JWT_SECRET', 'secret_key') # optional
22
+ end
23
+
24
+ jwt.refresh_token do |refresh_token|
25
+ refresh_token.http_only = true # optional
26
+ refresh_token.exp = 1.year.since # optional
27
+ refresh_token.algorithm = 'HS256' # optional, must provide if project supports refresh token
28
+ refresh_token.cookie_key = :project_ref_tok # optional
29
+ refresh_token.secret_key = ENV.fetch('JWT_SECRET', 'secret_key') # optional
30
+ end
31
+ end
32
+ end
33
+
34
+ Rails.application.config.to_prepare do
35
+ AuthRails.configure do |config|
36
+ config.resource_class = User # required
37
+ config.error_class = ProjectError # optional
38
+ end
39
+ end
40
+ ```
41
+
42
+ # Usage
43
+
44
+ ```rb
45
+ # config/routes.rb
46
+
47
+ Rails.application.routes.draw do
48
+ namespace :api do
49
+ resource :auth, path: 'auth', controller: 'auth', only: %i[create] do
50
+ collection do
51
+ get :refresh
52
+ end
53
+ end
54
+ end
55
+ end
56
+ ```
57
+
58
+ ```rb
59
+ # app/controllers/application_controller.rb
60
+
61
+ class ApplicationController < ActionController::API
62
+ # to include helpers for authenticating using access_token
63
+ include AuthRails::Authentication
64
+
65
+ # this action will assign user to CurrentAuth.user if valid token
66
+ # else raise error: AuthRails::Error or custom error in configuration
67
+ before_action :authenticate_user!
68
+ end
69
+ ```
70
+
71
+ ```rb
72
+ # app/controllers/api/auth_controller.rb
73
+
74
+ module Api
75
+ class AuthController < AuthRails::Api::AuthController
76
+ end
77
+ end
78
+ ```
79
+
80
+ - If you want to support refresh token
81
+
82
+ ```rb
83
+ # app/models/user.rb
84
+
85
+ class User < ApplicationRecord
86
+ include AuthRails::Concerns::AllowedTokenStrategy
87
+ end
88
+ ```
89
+
90
+ # Customize
91
+
92
+ - Custom Strategy
93
+
94
+ ```rb
95
+ class CustomStrategy < AuthRails::Strategies::BaseStrategy
96
+ class << self
97
+ # this is for getting user/resource using payload from access_token
98
+ def retrieve_resource(payload:)
99
+ super
100
+ end
101
+
102
+ # this is for generating refresh token
103
+ # if you do not support refresh token, can ignore this one
104
+ def gen_token(resource:, payload:, exp: nil, secret_key: nil, algorithm: nil)
105
+ super
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+ - Custom response for controller
112
+
113
+ ```rb
114
+ module Api
115
+ class AuthController < AuthRails::Api::AuthController
116
+ private
117
+
118
+ def respond_to_create(data)
119
+ render json: {
120
+ profile: CurrentAuth.user,
121
+ tokens: {
122
+ auth_token: data[:access_token],
123
+ refresh_token: data[:refresh_token]
124
+ }
125
+ }
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ # License
132
+
133
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Api
5
+ class AuthController < ApiController
6
+ def create
7
+ resource = AuthRails.resource_class.find_by(email: params[:email])
8
+
9
+ raise AuthRails.error_class, :unauthenticated if resource.blank? || !resource.authenticate(params[:password])
10
+
11
+ respond_to_create(generate_token(resource))
12
+ end
13
+
14
+ def refresh
15
+ decoded_payload = Services::JwtService.verify_token(
16
+ token: lookup_refresh_token,
17
+ algorithm: Configuration::Jwt::RefreshToken.algorithm,
18
+ secret_key: Configuration::Jwt::RefreshToken.secret_key
19
+ )
20
+
21
+ resource = AuthRails.jwt_strategy.retrieve_resource(payload: decoded_payload)
22
+
23
+ raise AuthRails.error_class, :unauthenticated if resource.blank?
24
+
25
+ respond_to_refresh(generate_token(resource))
26
+ end
27
+
28
+ private
29
+
30
+ def respond_to_create(data)
31
+ render json: {
32
+ **data,
33
+ user: CurrentAuth.user
34
+ }
35
+ end
36
+
37
+ alias respond_to_refresh respond_to_create
38
+
39
+ def payload(resource)
40
+ {
41
+ sub: resource.email
42
+ }
43
+ end
44
+
45
+ def basic_payload
46
+ {
47
+ aud: request.host
48
+ }
49
+ end
50
+
51
+ def generate_token(resource)
52
+ CurrentAuth.user = resource
53
+ token_payload = basic_payload.merge(payload(resource))
54
+
55
+ result = {
56
+ access_token: Services::JwtService.gen_token(
57
+ payload: token_payload,
58
+ secret_key: Configuration::Jwt::AccessToken.secret_key
59
+ )
60
+ }
61
+
62
+ if Configuration::Jwt::RefreshToken.algorithm.present?
63
+ result[:refresh_token] = AuthRails.jwt_strategy.gen_token(
64
+ resource: resource,
65
+ payload: token_payload,
66
+ exp: Configuration::Jwt::RefreshToken.exp.to_i,
67
+ algorithm: Configuration::Jwt::RefreshToken.algorithm,
68
+ secret_key: Configuration::Jwt::RefreshToken.secret_key
69
+ )
70
+
71
+ cookie_http_only(result[:refresh_token])
72
+ end
73
+
74
+ result
75
+ end
76
+
77
+ def cookie_http_only(refresh_token)
78
+ return if Configuration::Jwt::RefreshToken.http_only.blank?
79
+
80
+ cookies[Configuration::Jwt::RefreshToken.cookie_key.to_sym || :ref_tok] = {
81
+ httponly: true,
82
+ value: refresh_token,
83
+ secure: Rails.env.production?,
84
+ expires: Time.at(Configuration::Jwt::RefreshToken.exp).to_datetime
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ class ApiController < ActionController::API
5
+ include ActionController::Cookies
6
+ include AuthRails::Authentication
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Authentication
5
+ extend ActiveSupport::Concern
6
+
7
+ def authenticate_user!
8
+ payload = Services::JwtService.verify_token(
9
+ token: lookup_access_token,
10
+ secret_key: Configuration::Jwt::AccessToken.secret_key
11
+ )
12
+
13
+ CurrentAuth.user = AuthRails.resource_class.find_by(email: payload[:sub])
14
+
15
+ raise AuthRails.error_class, :unauthenticated unless CurrentAuth.user
16
+ end
17
+
18
+ private
19
+
20
+ def lookup_access_token
21
+ token_match = request.headers['Authorization']&.match(/bearer (.+)/i)
22
+ token_match[1] if token_match
23
+ end
24
+
25
+ def lookup_refresh_token
26
+ cookies[Configuration::Jwt::RefreshToken.cookie_key.to_sym].presence || params[:refresh_token]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Concerns
5
+ module AllowedTokenStrategy
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :allowed_tokens, dependent: :destroy
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CurrentAuth < ActiveSupport::CurrentAttributes
4
+ attribute :user
5
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/auth_rails/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'auth_rails'
7
+ spec.version = AuthRails::VERSION
8
+ spec.authors = ['Alpha']
9
+ spec.email = ['alphanolucifer@gmail.com']
10
+
11
+ spec.summary = 'Simple authentication for Rails'
12
+ spec.description = 'Simple authentication for Rails'
13
+ spec.homepage = 'https://github.com/zgid123/auth_rails'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(__dir__) do
20
+ `git ls-files -z`.split("\x0").reject do |f|
21
+ (File.expand_path(f) == __FILE__) ||
22
+ f.start_with?(
23
+ *%w[
24
+ bin/
25
+ test/
26
+ spec/
27
+ features/
28
+ .git
29
+ .circleci
30
+ appveyor
31
+ Gemfile
32
+ .rubocop.yml
33
+ .vscode/settings.json
34
+ LICENSE.txt
35
+ lefthook.yml
36
+ ]
37
+ )
38
+ end
39
+ end
40
+
41
+ spec.require_paths = ['lib']
42
+
43
+ spec.add_dependency 'jwt'
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ class << self
5
+ def configure
6
+ yield Config
7
+ end
8
+
9
+ def configuration
10
+ Config
11
+ end
12
+
13
+ def resource_class
14
+ @resource_class ||= Config.resource_class
15
+ end
16
+
17
+ def error_class
18
+ @error_class ||= Config.error_class || Error
19
+ end
20
+
21
+ def jwt_strategy
22
+ @jwt_strategy ||= Configuration::Jwt.strategy || Strategies::BaseStrategy
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ class Config
5
+ class << self
6
+ attr_accessor :error_class,
7
+ :resource_class
8
+
9
+ def jwt
10
+ yield Configuration::Jwt
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Configuration
5
+ class Jwt
6
+ class << self
7
+ attr_accessor :strategy
8
+
9
+ def access_token
10
+ yield AccessToken
11
+
12
+ AccessToken.algorithm ||= 'HS256'
13
+ end
14
+
15
+ def refresh_token
16
+ yield RefreshToken
17
+
18
+ RefreshToken.cookie_key ||= :ref_tok
19
+ end
20
+ end
21
+
22
+ class AccessToken
23
+ class << self
24
+ attr_accessor :exp,
25
+ :algorithm,
26
+ :secret_key
27
+ end
28
+ end
29
+
30
+ class RefreshToken < AccessToken
31
+ class << self
32
+ attr_accessor :http_only, :cookie_key
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Services
5
+ class JwtService
6
+ class << self
7
+ def gen_token(payload:, exp: nil, secret_key: nil, algorithm: nil, jti: nil)
8
+ exp ||= Configuration::Jwt::AccessToken.exp.to_i
9
+
10
+ JWT.encode(
11
+ (payload || {}).merge(jti: jti || SecureRandom.hex(20)),
12
+ secret_key,
13
+ algo(algorithm),
14
+ {
15
+ exp: exp.to_i
16
+ }
17
+ )
18
+ end
19
+
20
+ def verify_token(token:, secret_key: nil, algorithm: nil)
21
+ JWT.decode(
22
+ token,
23
+ secret_key,
24
+ true,
25
+ {
26
+ algorithm: algo(algorithm)
27
+ }
28
+ )[0].deep_symbolize_keys
29
+ rescue StandardError
30
+ {}
31
+ end
32
+
33
+ private
34
+
35
+ def algo(algorithm)
36
+ algorithm || Configuration::Jwt::AccessToken.algorithm
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Strategies
5
+ class AllowedTokenStrategy < BaseStrategy
6
+ class << self
7
+ def retrieve_resource(payload:)
8
+ symbolized_payload = payload.symbolize_keys
9
+
10
+ AuthRails.resource_class
11
+ .joins(:allowed_tokens)
12
+ .where(allowed_tokens: symbolized_payload.slice(:jti, :aud))
13
+ .where('allowed_tokens.exp > ?', Time.current)
14
+ .find_by(email: symbolized_payload[:sub])
15
+ end
16
+
17
+ def gen_token(resource:, payload:, exp: nil, secret_key: nil, algorithm: nil)
18
+ jti = SecureRandom.hex(20)
19
+
20
+ resource.allowed_tokens
21
+ .create!(
22
+ jti: jti,
23
+ aud: payload[:aud],
24
+ exp: Time.zone.at(exp)
25
+ )
26
+
27
+ super(
28
+ jti: jti,
29
+ exp: exp,
30
+ payload: payload,
31
+ algorithm: algorithm,
32
+ secret_key: secret_key
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ module Strategies
5
+ class BaseStrategy
6
+ class << self
7
+ def retrieve_resource(payload:)
8
+ symbolized_payload = payload.symbolize_keys
9
+
10
+ AuthRails.resource_class
11
+ .find_by(email: symbolized_payload[:sub])
12
+ end
13
+
14
+ def gen_token(payload:, exp: nil, secret_key: nil, algorithm: nil, jti: nil, **)
15
+ Services::JwtService.gen_token(
16
+ exp: exp,
17
+ jti: jti,
18
+ payload: payload,
19
+ algorithm: algorithm,
20
+ secret_key: secret_key
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ VERSION = '1.0.0'
5
+ end
data/lib/auth_rails.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'auth_rails/config'
4
+ require_relative 'auth_rails/version'
5
+ require_relative 'auth_rails/class_methods'
6
+ require_relative 'auth_rails/configuration/jwt'
7
+ require_relative 'auth_rails/services/jwt_service'
8
+ require_relative 'auth_rails/strategies/base_strategy'
9
+ require_relative 'auth_rails/strategies/allowed_token_strategy'
10
+
11
+ module AuthRails
12
+ class Error < StandardError; end
13
+
14
+ class Engine < ::Rails::Engine
15
+ isolate_namespace AuthRails
16
+
17
+ config.autoload_paths << File.expand_path('app/models', __dir__)
18
+ config.autoload_paths << File.expand_path('app/supports', __dir__)
19
+ config.autoload_paths << File.expand_path('app/controllers', __dir__)
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auth_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Alpha
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-12-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Simple authentication for Rails
28
+ email:
29
+ - alphanolucifer@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - Rakefile
36
+ - app/controllers/auth_rails/api/auth_controller.rb
37
+ - app/controllers/auth_rails/api_controller.rb
38
+ - app/controllers/concerns/auth_rails/authentication.rb
39
+ - app/models/concerns/auth_rails/concerns/allowed_token_strategy.rb
40
+ - app/supports/current_auth.rb
41
+ - auth_rails.gemspec
42
+ - lib/auth_rails.rb
43
+ - lib/auth_rails/class_methods.rb
44
+ - lib/auth_rails/config.rb
45
+ - lib/auth_rails/configuration/jwt.rb
46
+ - lib/auth_rails/services/jwt_service.rb
47
+ - lib/auth_rails/strategies/allowed_token_strategy.rb
48
+ - lib/auth_rails/strategies/base_strategy.rb
49
+ - lib/auth_rails/version.rb
50
+ homepage: https://github.com/zgid123/auth_rails
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ homepage_uri: https://github.com/zgid123/auth_rails
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.6.0
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubygems_version: 3.4.13
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Simple authentication for Rails
74
+ test_files: []