auth_rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []