minato-rails-auth 0.1.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: f230a5cc5a2ed2896b958555a3d1df46afdb1ff882d7a781b98415e91c47bf31
4
+ data.tar.gz: c0cda43ffd8b1385efb1fcfba78fd04dece987e19c9882b3b82b9e75b9096fa3
5
+ SHA512:
6
+ metadata.gz: 889f2d69ba35ebdea6e1ff24f2d278977ba907b294678955afc2af748092716c361311914ef2f8e4617e0846d09f405a1675832da9c222ff6989dc3bf78f2da8
7
+ data.tar.gz: aaeb0db38de916e67427c617325ff610020155ec4a659ccb6de393f79a2fc4d11d81151efb53bc418f27922a7b15fa8391878982f639910162b8a78b30f3f9ff
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,40 @@
1
+ require:
2
+ - rubocop-rails
3
+ - rubocop-rspec
4
+ - rubocop-rake
5
+
6
+ AllCops:
7
+ DisplayCopNames: true
8
+ DisplayStyleGuide: true
9
+ ExtraDetails: false
10
+ NewCops: enable
11
+ TargetRubyVersion: 2.7.3
12
+
13
+ Metrics/BlockLength:
14
+ AllowedMethods: ['describe', 'it', 'context']
15
+ Max: 50
16
+
17
+ RSpec/EmptyExampleGroup:
18
+ Enabled: false
19
+
20
+ RSpec/ExampleLength:
21
+ Max: 50
22
+
23
+ RSpec/ImplicitExpect:
24
+ Enabled: false
25
+
26
+ RSpec/MultipleExpectations:
27
+ Enabled: false
28
+
29
+ RSpec/VerifiedDoubleReference:
30
+ EnforcedStyle: string
31
+
32
+ Style/Documentation:
33
+ Enabled: false
34
+
35
+ Layout/HashAlignment:
36
+ EnforcedColonStyle: table
37
+ EnforcedHashRocketStyle: table
38
+
39
+ RSpec/MultipleMemoizedHelpers:
40
+ Max: 7
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Minato::Rails::Auth
2
+
3
+ Minato Rails Auth is a library designed to be an authentication layer in Rails applications. It has a JWT module, that can be included in Rails Controllers to authenticate requests. And has a RSpec shared contexts, to simplify authentication in automated tests.
4
+
5
+ ## Installation
6
+
7
+ The recommended installation way is by [Gem](https://guides.rubygems.org/rubygems-basics/#installing-gems) or [Bundler](https://bundler.io/):
8
+
9
+ ### By Gem
10
+
11
+ $ gem install minato-rails-auth
12
+
13
+ ### By Bundler
14
+
15
+ $ bundle add minato-rails-auth
16
+
17
+ Or just add the following line on your `Gemfile` file:
18
+
19
+ gem 'minato-rails-auth'
20
+
21
+ And you can specify the version:
22
+
23
+ gem 'minato-rails-auth', '~> 0.1.0'
24
+
25
+ ## Configuration
26
+
27
+ To configure the Minato Rails Auth lib on your Rails application, is necessary to follow the steps:
28
+
29
+ 1. Create a configuration file:
30
+
31
+ [rails-app]/config/initializers/minato_rails_auth.rb
32
+
33
+ 2. Add the following content in the file:
34
+
35
+ ```ruby
36
+ require 'minato/rails/auth'
37
+
38
+ Minato::Rails::Auth.configure do |conf|
39
+ conf.jwt.issuer = Settings.api.secure.issuer
40
+ conf.jwt.jwks_uri = Settings.api.secure.jwks_uri
41
+ end
42
+ ```
43
+
44
+ - Each option specs:
45
+
46
+ - `conf.jwt`
47
+ - stores all jwt configuration
48
+ - `conf.jwt.issuer`
49
+ - example: 'http://localhost'
50
+ - is the jwt issuer, when the system uses Ory, it's the gateway host
51
+ - `conf.jwt.jwks_uri`
52
+ - example: 'http://localhost:4456/.well-known/jwks.json'
53
+ - is the jwks obtetion uri, when the system uses Ory, it's the direct gateway host
54
+ - `conf.jwt.jwks`
55
+ - default: nil
56
+ - this is the jwks data, if this is specified it will not be necessary the conf.jwt.jwks_uri attribute
57
+ - `conf.jwt.algorithms`
58
+ - default: 'RS256'
59
+ - alghorithm used to decode the jwt
60
+ - `conf.jwt.verify_iss`
61
+ - default: true
62
+ - this specify if the issuer should be checked
63
+ - `conf.jwt.verify_token_signature`
64
+ - default: true
65
+ - this specify if the token signature should be checked
66
+ - `conf.jwt.hmac_secret`
67
+ - default: nil
68
+ - hmac secret, is necessary only in HMAC algorithms
69
+ - `conf.jwt.additional_params`
70
+ - default: {}
71
+ - is JWT.decode additional options
72
+ - docs: https://github.com/jwt/ruby-jwt/blob/main/README.md
73
+
74
+ And now, the library was configured in your Rails application.
75
+
76
+ ## Usage
77
+
78
+ ### Auth system
79
+
80
+ To add the JWT authentication system in your Rails application, just follow the steps:
81
+
82
+ 1. Include the JWT module in your `ApplicationController`:
83
+
84
+ ```ruby
85
+ class ApplicationController < ActionController::API
86
+ include Minato::Rails::Auth::JWT
87
+ end
88
+ ```
89
+
90
+ 2. Now it is available the method `authenticate_request!` in your controllers, and you can call it when you want to auth a controller:
91
+
92
+ ```ruby
93
+ class FooController < ApplicationController
94
+ before_action :authenticate_request!
95
+ end
96
+ ```
97
+
98
+ 3. Now the variable `current_user` is availabe in your controller, containing the following methods:
99
+
100
+ - `.machine?`
101
+ - This will return a boolean value that will tell you if the client is a machine or not
102
+
103
+ - `.human?`
104
+ - This works like the `.machine?` method, but tells you if the client is human.
105
+
106
+ - `.session`
107
+ - This returns a hash with the JWT payload, if the client is human.
108
+
109
+ - `.machine`
110
+ - This returns a string with the machine name, when the client is a machine.
111
+
112
+ ### Testing tools
113
+
114
+ The Minato Rails Auth makes available the RSpec shared contexts, to make it available you can include the module in your `[rails-app]/spec/spec_helper.rb`:
115
+
116
+ ```ruby
117
+ require 'minato/rails/auth/spec'
118
+ ```
119
+
120
+ And now it will be available the following shared contexts, that you can use following [the docs](https://rspec.info/features/3-12/rspec-core/example-groups/shared-context/):
121
+
122
+ - `with jwt authentication`:
123
+ - Just create a jwt with empty payload. To set the payload you should create a `jwt_payload` variable using `let`.
124
+ - `with human jwt authentication`:
125
+ - Creates a jwt with human payload. You set the user attributes with variables `jwt_email`, `jwt_account_id`, `jwt_session_id`, `jwt_identity_id` and `jwt_payload`.
126
+ - `with machine jwt authentication`:
127
+ - Creates a jwt with machine payload. You can set the machine name with the variable `jwt_machine`.
128
+ - `with missing jwt authentication`:
129
+ - Raise a `::JWT::VerificationError`, simulating when the user is not authenticated.
130
+
131
+ ## Development
132
+
133
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
134
+
135
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
136
+
137
+ ## Contributing
138
+
139
+ Bug reports and pull requests are welcome on GitLab at https://gitlab.com/ferreri/minato/minato-rails-auth.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/compose.yml ADDED
@@ -0,0 +1,12 @@
1
+ services:
2
+ minato-rails-auth:
3
+ environment:
4
+ GITLAB_AUTH_TOKEN: ${GITLAB_AUTH_TOKEN}
5
+ image: ruby:2.7.3
6
+ working_dir: /usr/src/api
7
+ volumes:
8
+ - .:/usr/src/api:z
9
+ - bundle:/usr/local/bundle
10
+
11
+ volumes:
12
+ bundle:
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jwt/configuration'
4
+ require 'active_support'
5
+
6
+ module Minato
7
+ module Rails
8
+ module Auth
9
+ class Configuration
10
+ attr_accessor :jwt
11
+
12
+ def initialize(jwt = Auth::JWT::Configuration.new)
13
+ @jwt = jwt
14
+ end
15
+
16
+ def configure
17
+ yield(self) if block_given?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'user'
4
+ require 'active_support'
5
+
6
+ module Minato
7
+ module Rails
8
+ module Auth
9
+ module JWT
10
+ class Configuration
11
+ attr_accessor :issuer, :jwks_uri, :verify_token_signature, :algorithms, :verify_iss, :hmac_secret,
12
+ :additional_params
13
+
14
+ def initialize
15
+ @verify_token_signature = true
16
+ @algorithms = 'RS256'
17
+ @verify_iss = true
18
+ @hmac_secret = nil
19
+ @decode_params = nil
20
+ @additional_params = {}
21
+ @jwks = nil
22
+ end
23
+
24
+ attr_writer :decode_params, :jwks
25
+
26
+ def decode_params
27
+ return @decode_params.merge(additional_params || {}) if @decode_params.present?
28
+
29
+ {
30
+ algorithms: algorithms,
31
+ iss: issuer,
32
+ verify_iss: verify_iss,
33
+ jwks: jwks
34
+ }.merge(additional_params || {})
35
+ end
36
+
37
+ def configure
38
+ yield(self) if block_given?
39
+ end
40
+
41
+ def jwks
42
+ return @jwks if @jwks.present?
43
+
44
+ jwks_raw = Net::HTTP.get URI(jwks_uri)
45
+ JSON.parse(jwks_raw, symbolize_names: true)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minato
4
+ module Rails
5
+ module Auth
6
+ module JWT
7
+ class Decode
8
+ def self.call(jwt)
9
+ ::JWT.decode(jwt,
10
+ Auth::CONFIG.jwt.hmac_secret,
11
+ Auth::CONFIG.jwt.verify_token_signature,
12
+ Auth::CONFIG.jwt.decode_params).first
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minato
4
+ module Rails
5
+ module Auth
6
+ module JWT
7
+ module Contexts
8
+ RSpec.shared_context 'with jwt authentication', shared_context: :metadata do
9
+ let(:jwt_payload) do
10
+ {}
11
+ end
12
+ let(:Authorization) { 'Bearer token' }
13
+ before do
14
+ allow(Decode).to receive(:call).and_return(jwt_payload)
15
+ end
16
+ end
17
+
18
+ RSpec.shared_context 'with human jwt authentication', shared_context: :metadata do
19
+ let(:jwt_email) { 'test@email.com' }
20
+ let(:jwt_account_id) { '555' }
21
+ let(:jwt_session_id) { '67cf621f-4f49-4ca5-9365-62520849170b' }
22
+ let(:jwt_identity_id) { 'adb1e688-17c0-4241-bc24-912fa6b5f6c3' }
23
+ let(:jwt_payload) do
24
+ {
25
+ 'session' => {
26
+ 'id' => jwt_session_id,
27
+ 'expires_at' => '2021-09-29T20:15:44.731576Z',
28
+ 'identity' => {
29
+ 'id' => jwt_identity_id,
30
+ 'traits' => {
31
+ 'email' => jwt_email
32
+ },
33
+ 'metadata_public' => {
34
+ 'account_id' => jwt_account_id
35
+ }
36
+ }
37
+ }
38
+ }
39
+ end
40
+ let(:Authorization) { 'Bearer token' }
41
+ before do
42
+ allow(Decode).to receive(:call).and_return(jwt_payload)
43
+ end
44
+ end
45
+
46
+ RSpec.shared_context 'with machine jwt authentication', shared_context: :metadata do
47
+ let(:jwt_machine) { 'foo-svc' }
48
+ let(:jwt_payload) do
49
+ {
50
+ 'sub' => jwt_machine
51
+ }
52
+ end
53
+ let(:Authorization) { 'Bearer token' }
54
+ before do
55
+ allow(Decode).to receive(:call).and_return(jwt_payload)
56
+ end
57
+ end
58
+
59
+ RSpec.shared_context 'with missing jwt authentication', shared_context: :metadata do
60
+ let(:Authorization) { nil }
61
+ before do
62
+ allow(Decode).to receive(:call).and_raise(::JWT::VerificationError)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'object_hash'
4
+
5
+ module Minato
6
+ module Rails
7
+ module Auth
8
+ module JWT
9
+ class User
10
+ attr_reader :token_payload
11
+
12
+ def initialize(token_payload)
13
+ @token_payload = ObjectHash.new(token_payload)
14
+ end
15
+
16
+ def human?
17
+ @human ||= @token_payload.respond_to?(:session)
18
+ end
19
+
20
+ def machine?
21
+ @machine ||= !human?
22
+ end
23
+
24
+ def session
25
+ @token_payload.session
26
+ end
27
+
28
+ def subject
29
+ @token_payload.sub
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jwt/user'
4
+ require_relative 'configuration'
5
+ require_relative 'jwt/decode'
6
+ require 'active_support'
7
+ require 'net/http'
8
+ require 'uri'
9
+ require 'jwt'
10
+
11
+ module Minato
12
+ module Rails
13
+ module Auth
14
+ module JWT
15
+ extend ActiveSupport::Concern
16
+
17
+ private
18
+
19
+ def authenticate_request!
20
+ @current_user = User.new(auth_token)
21
+ end
22
+
23
+ def http_token
24
+ return nil if request.headers['Authorization'].blank?
25
+
26
+ request.headers['Authorization'].split.last
27
+ end
28
+
29
+ def auth_token
30
+ @auth_token ||= Decode.call(http_token)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jwt/spec/contexts'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minato
4
+ module Rails
5
+ module Auth
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'auth/version'
4
+ require_relative 'auth/jwt'
5
+
6
+ module Minato
7
+ module Rails
8
+ module Auth
9
+ CONFIG = Configuration.new
10
+ class << self
11
+ def configure
12
+ yield(CONFIG) if block_given?
13
+ CONFIG
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ObjectHash
4
+ def initialize(hash)
5
+ @hash = hash
6
+ end
7
+
8
+ def method_missing(name)
9
+ find_hash_value(name)
10
+ end
11
+
12
+ def respond_to_missing?(name, include_private = false)
13
+ return true if @hash.keys.include?(name.to_s)
14
+ return true if @hash.keys.include?(name.to_sym)
15
+
16
+ super
17
+ end
18
+
19
+ private
20
+
21
+ def find_hash_value(name)
22
+ value = @hash[name.to_s] || @hash[name.to_sym]
23
+ return ObjectHash.new(value) if value.instance_of?(Hash)
24
+
25
+ if value.instance_of?(Array)
26
+ return value.map do |item|
27
+ next ObjectHash.new(item) if item.instance_of?(Hash)
28
+
29
+ item
30
+ end
31
+ end
32
+
33
+ value
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/minato/rails/auth/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'minato-rails-auth'
7
+ spec.version = Minato::Rails::Auth::VERSION
8
+ spec.authors = ['Ferreri']
9
+ spec.email = ['contato@ferreri.co']
10
+
11
+ spec.summary = 'Minato Rails Auth'
12
+ spec.description = 'Library to create an authentication layer in Rails applications'
13
+ spec.homepage = 'https://gitlab.com/ferreri/minato/minato-rails-auth'
14
+ spec.required_ruby_version = '>= 2.7'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = spec.homepage
18
+ spec.metadata['changelog_uri'] = 'https://gitlab.com/ferreri/minato/minato-rails-auth/-/commits/main?ref_type=heads'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_dependency 'activesupport', '~> 7.0.4'
33
+ spec.add_dependency 'jwt', '~> 2.9'
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ spec.metadata['rubygems_mfa_required'] = 'true'
41
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minato-rails-auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ferreri
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.9'
41
+ description: Library to create an authentication layer in Rails applications
42
+ email:
43
+ - contato@ferreri.co
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rubocop.yml"
50
+ - README.md
51
+ - Rakefile
52
+ - compose.yml
53
+ - lib/minato/rails/auth.rb
54
+ - lib/minato/rails/auth/configuration.rb
55
+ - lib/minato/rails/auth/jwt.rb
56
+ - lib/minato/rails/auth/jwt/configuration.rb
57
+ - lib/minato/rails/auth/jwt/decode.rb
58
+ - lib/minato/rails/auth/jwt/spec/contexts.rb
59
+ - lib/minato/rails/auth/jwt/user.rb
60
+ - lib/minato/rails/auth/spec.rb
61
+ - lib/minato/rails/auth/version.rb
62
+ - lib/object_hash.rb
63
+ - minato-rails-auth.gemspec
64
+ homepage: https://gitlab.com/ferreri/minato/minato-rails-auth
65
+ licenses: []
66
+ metadata:
67
+ homepage_uri: https://gitlab.com/ferreri/minato/minato-rails-auth
68
+ source_code_uri: https://gitlab.com/ferreri/minato/minato-rails-auth
69
+ changelog_uri: https://gitlab.com/ferreri/minato/minato-rails-auth/-/commits/main?ref_type=heads
70
+ rubygems_mfa_required: 'true'
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '2.7'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 3.1.6
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Minato Rails Auth
90
+ test_files: []