researchable_jwt-authenticable 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: 29e69720db718107838d0d590f9e3f0e7b7bef17232f3f3fa7fb0e2ba750fa5e
4
+ data.tar.gz: e5964dae9cad2b5af854fda60d509dd0891f60a6f6bf562c5c012d54243ac064
5
+ SHA512:
6
+ metadata.gz: 47347676d2cb240d3d085a9fda217dbc47312a547ed2eda2953cf44e731b97defb18da07c7ccc4ed7f9977fdf2361fd7747ceed4639670b3d51af7c453963625
7
+ data.tar.gz: c05395fd58a2c0cbeaca4a7345fb69e1ed51972bc91ddbc4bb1e216fc66d9bd40dc966993a1c9fbed744a23fd59db4420df7af3a3a5b81dab013bc4fc40601ea
data/.editorconfig ADDED
@@ -0,0 +1,13 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 2
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
13
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ inherit_gem:
2
+ ruboguide:
3
+ - styles/rubocop-rails.yml
4
+
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ jwt_authenticable
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in jwt_authenticable.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+
10
+ gem 'rspec', '~> 3.0'
11
+
12
+ gem 'ruboguide', '~> 1.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,94 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ researchable_jwt-authenticable (0.1.1)
5
+ dry-configurable (~> 0.16)
6
+ jwt (~> 2.6)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (7.0.4.3)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ ast (2.4.2)
17
+ concurrent-ruby (1.2.2)
18
+ diff-lcs (1.5.0)
19
+ dry-configurable (0.16.1)
20
+ dry-core (~> 0.6)
21
+ zeitwerk (~> 2.6)
22
+ dry-core (0.9.1)
23
+ concurrent-ruby (~> 1.0)
24
+ zeitwerk (~> 2.6)
25
+ i18n (1.12.0)
26
+ concurrent-ruby (~> 1.0)
27
+ jwt (2.7.0)
28
+ minitest (5.18.0)
29
+ parallel (1.22.1)
30
+ parser (3.2.1.1)
31
+ ast (~> 2.4.1)
32
+ rack (3.0.7)
33
+ rainbow (3.1.1)
34
+ rake (13.0.6)
35
+ regexp_parser (2.7.0)
36
+ rexml (3.2.5)
37
+ rspec (3.12.0)
38
+ rspec-core (~> 3.12.0)
39
+ rspec-expectations (~> 3.12.0)
40
+ rspec-mocks (~> 3.12.0)
41
+ rspec-core (3.12.1)
42
+ rspec-support (~> 3.12.0)
43
+ rspec-expectations (3.12.2)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.12.0)
46
+ rspec-mocks (3.12.4)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.12.0)
49
+ rspec-support (3.12.0)
50
+ rubocop (1.23.0)
51
+ parallel (~> 1.10)
52
+ parser (>= 3.0.0.0)
53
+ rainbow (>= 2.2.2, < 4.0)
54
+ regexp_parser (>= 1.8, < 3.0)
55
+ rexml
56
+ rubocop-ast (>= 1.12.0, < 2.0)
57
+ ruby-progressbar (~> 1.7)
58
+ unicode-display_width (>= 1.4.0, < 3.0)
59
+ rubocop-ast (1.28.0)
60
+ parser (>= 3.2.1.0)
61
+ rubocop-performance (1.12.0)
62
+ rubocop (>= 1.7.0, < 2.0)
63
+ rubocop-ast (>= 0.4.0)
64
+ rubocop-rails (2.12.4)
65
+ activesupport (>= 4.2.0)
66
+ rack (>= 1.1)
67
+ rubocop (>= 1.7.0, < 2.0)
68
+ rubocop-rake (0.6.0)
69
+ rubocop (~> 1.0)
70
+ rubocop-rspec (2.6.0)
71
+ rubocop (~> 1.19)
72
+ ruboguide (1.0.0)
73
+ rubocop (= 1.23.0)
74
+ rubocop-performance (= 1.12.0)
75
+ rubocop-rails (= 2.12.4)
76
+ rubocop-rake (= 0.6.0)
77
+ rubocop-rspec (= 2.6.0)
78
+ ruby-progressbar (1.13.0)
79
+ tzinfo (2.0.6)
80
+ concurrent-ruby (~> 1.0)
81
+ unicode-display_width (2.4.2)
82
+ zeitwerk (2.6.7)
83
+
84
+ PLATFORMS
85
+ x86_64-linux
86
+
87
+ DEPENDENCIES
88
+ rake (~> 13.0)
89
+ researchable_jwt-authenticable!
90
+ rspec (~> 3.0)
91
+ ruboguide (~> 1.0)
92
+
93
+ BUNDLED WITH
94
+ 2.3.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Frank Blaauw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # JwtAuthenticable
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/jwt_authenticable`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'jwt_authenticable'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install jwt_authenticable
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ 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.
30
+
31
+ 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).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/frbl/jwt_authenticable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/frbl/jwt_authenticable/blob/master/CODE_OF_CONDUCT.md).
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the JwtAuthenticable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/frbl/jwt_authenticable/blob/master/CODE_OF_CONDUCT.md).
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]
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+
5
+ # This module implements JWT authentication
6
+ module JwtAuthenticable
7
+ # Module that adds jwt authentication methods to the client
8
+ module Auth
9
+ include Exceptions
10
+ include Responses
11
+
12
+ ALGORITHM = JwtAuthenticable.config.algorithm
13
+
14
+ # Authenticates a user.
15
+ # @raise MissingAuthScope if the jwt does not have the right scope
16
+ def authenticate_user!
17
+ validate_jwt_token! token: authorization_token!
18
+ rescue MissingAuth, MissingAuthScope, InvalidAuthScheme, JWT::VerificationError, JWT::ExpiredSignature => e
19
+ unauthorized(e.message)
20
+ end
21
+
22
+ # Consider any method below as private and not meant to be used by including classes
23
+
24
+ # Validate that the JWT token signature and the following claims are valid:
25
+ # - exp
26
+ # - scope
27
+ # @param token [String] JWT token string (just the token, with the header, payload and signature separated by '.')
28
+ # @param is_researcher [Boolean] Whether to validate the token as a researcher's or a participant's
29
+ # @raise AuthorizationError if the user is trying to login with the incorrect rights.
30
+ # @return [Hash] the JWT payload
31
+ def validate_jwt_token!(token:)
32
+ # NOTE: it is still safe if JWT_SECRET_KEY is not set. The method will trigger a JWT exception
33
+ JWT.decode(token, JwtAuthenticable.config.jwt_secret_key, true, { algorithm: ALGORITHM }).first
34
+ end
35
+
36
+ # Extracts the authorization token from the Authorization header
37
+ # @note For now we only support Bearer schema with JWT
38
+ # @raise [Exceptions::MissingAuth] Authorization header not present or empty
39
+ # @raise [Exceptions::InvalidAuthScheme] Authorization scheme not understood or not supported
40
+ # @return [String] the JWT token string
41
+ def authorization_token!
42
+ raise InvalidIncluder unless defined? request
43
+
44
+ auth_token = request.headers['Authorization']
45
+ auth_token ||= request.cookies['bearer_token']
46
+
47
+ raise MissingAuth if auth_token.nil? || auth_token == ''
48
+ raise InvalidAuthScheme if auth_token[0..6] != 'Bearer '
49
+
50
+ auth_token[7..]
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add here our custom exceptions
4
+ module JwtAuthenticable
5
+ module Exceptions
6
+ # Exception for invalid auth schemes. E.g. users trying to authenticate with basic auth
7
+ class InvalidAuthScheme < StandardError
8
+ def message
9
+ 'Invalid authentication scheme. Only Bearer is supported'
10
+ end
11
+ end
12
+
13
+ # Exception when the includer is not invalid
14
+ class InvalidIncluder < StandardError
15
+ def message
16
+ "The includer should export the 'request' method (i.e., it should be a rails controller)"
17
+ end
18
+ end
19
+
20
+ # Exception for missing Authentication header
21
+ class MissingAuth < StandardError
22
+ def message
23
+ 'Authentication header must be present'
24
+ end
25
+ end
26
+
27
+ # Exception for missing scopes on the JWT
28
+ class MissingAuthScope < StandardError
29
+ def initialize(scope)
30
+ @scope = scope
31
+ super(scope)
32
+ end
33
+
34
+ def message
35
+ "Auth token must contain the #{@scope} scope"
36
+ end
37
+ end
38
+
39
+ # Generic exception during the authorization process
40
+ class AuthorizationError < StandardError
41
+ def initialize(msg = nil)
42
+ @msg = msg
43
+ super(msg)
44
+ end
45
+
46
+ def message
47
+ "Authorization error: #{@msg}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module gives some information about the gem configuration
4
+ module JwtAuthenticable
5
+ # This module gives some information about the gem configuration
6
+ module Info
7
+ # This method prints the gem configuration
8
+ def info
9
+ return Rails.logger.info JwtAuthenticable.config if defined?(Rails)
10
+
11
+ puts JwtAuthenticable.config # rubocop:disable Rails/Output
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JwtAuthenticable
4
+ # Provides a suite of methods to return standardized JSON Http responses
5
+ module Responses
6
+ # renders a resource (eg json file) or rendes an error of there are errors in the resource
7
+ def render_resource(resource)
8
+ if resource.errors.empty?
9
+ render json: resource
10
+ else
11
+ validation_error(resource.errors)
12
+ end
13
+ end
14
+
15
+ # Method to render validation errors in a consistent way
16
+ # @param resource_errors the errors to render
17
+ def validation_error(resource_errors)
18
+ render json: {
19
+ errors: [
20
+ {
21
+ status: '400',
22
+ title: 'Bad Request',
23
+ detail: resource_errors,
24
+ code: '100'
25
+ }
26
+ ]
27
+ }, status: :bad_request
28
+ end
29
+
30
+ # Method to render access denied errors in a consistent way
31
+ # @param resource_errors the errors to render
32
+ def access_denied(resource_errors)
33
+ render json: {
34
+ errors: [
35
+ {
36
+ status: '403',
37
+ title: 'Access Denied',
38
+ detail: resource_errors,
39
+ code: '100'
40
+ }
41
+ ]
42
+ }, status: :forbidden
43
+ end
44
+
45
+ # Method to render not found errors in a consistent way
46
+ # @param resource_errors the errors to render
47
+ def not_found(resource_errors)
48
+ render json: {
49
+ errors: [
50
+ {
51
+ status: '404',
52
+ title: 'Not Found',
53
+ detail: resource_errors,
54
+ code: '100'
55
+ }
56
+ ]
57
+ }, status: :not_found
58
+ end
59
+
60
+ # Method to render created status in a consistent way
61
+ def created(instance = nil)
62
+ render json: {
63
+ result: [
64
+ {
65
+ status: '201',
66
+ title: 'created',
67
+ detail: 'resource created',
68
+ code: '100',
69
+ instance: instance
70
+ }
71
+ ]
72
+ }, status: :created
73
+ end
74
+
75
+ # Method to render success status in a consistent way
76
+ def render_success_resource(resource)
77
+ render json: {
78
+ result: [
79
+ {
80
+ status: '200',
81
+ title: 'Success',
82
+ detail: resource,
83
+ code: '100'
84
+ }
85
+ ]
86
+ }, status: :ok
87
+ end
88
+
89
+ # Method to render accepted status in a consistent way
90
+ def accepted
91
+ render json: {
92
+ result: [
93
+ {
94
+ status: '202',
95
+ title: 'Accepted',
96
+ detail: 'Your request will be processed',
97
+ code: '100'
98
+ }
99
+ ]
100
+ }, status: :accepted
101
+ end
102
+
103
+ # Method to render destroyed status in a consistent way
104
+ def destroyed
105
+ render json: {
106
+ result: [
107
+ {
108
+ status: '200',
109
+ title: 'destroyed',
110
+ detail: 'resource destroyed',
111
+ code: '100'
112
+ }
113
+ ]
114
+ }, status: :ok
115
+ end
116
+
117
+ # Method to render unprocessable entity errors in a consistent way
118
+ # @param resource_errors the errors to render
119
+ def unprocessable_entity(resource_errors)
120
+ render json: {
121
+ errors: [
122
+ {
123
+ status: '422',
124
+ title: 'unprocessable',
125
+ detail: resource_errors,
126
+ code: '100'
127
+ }
128
+ ]
129
+ }, status: :unprocessable_entity
130
+ end
131
+
132
+ # Method to render no content in a consistent way
133
+ def no_content
134
+ render json: {
135
+ result: [
136
+ {
137
+ status: '204',
138
+ title: 'no content',
139
+ detail: 'no content provided',
140
+ code: '100'
141
+ }
142
+ ]
143
+ }, status: :no_content
144
+ end
145
+
146
+ def unauthorized(detail)
147
+ render json: {
148
+ result: [
149
+ {
150
+ status: '401',
151
+ title: 'unauthorized',
152
+ detail: detail || 'invalid or missing credentials',
153
+ code: '100'
154
+ }
155
+ ]
156
+ }, status: :unauthorized
157
+ end
158
+
159
+ def render_page(paginated_query, _config = {})
160
+ render json: {
161
+ data: paginated_query,
162
+ page: {
163
+ current_page: paginated_query.current_page,
164
+ next_page: paginated_query.next_page,
165
+ total_count: paginated_query.total_count,
166
+ total_pages: paginated_query.total_pages,
167
+ has_next: !paginated_query.last_page? && paginated_query.size.positive?
168
+ }
169
+ }
170
+ end
171
+
172
+ def payload_too_large(_details)
173
+ render json: {
174
+ result: [
175
+ {
176
+ status: '413',
177
+ title: 'payload too large',
178
+ details: nil,
179
+ code: '100'
180
+ }
181
+ ]
182
+ }, status: :payload_too_large
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JwtAuthenticable
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-configurable'
4
+
5
+ # The module 'JwtAuth' provides jwt authentication for rails using the jwt gem
6
+ # @author Researchable
7
+ module JwtAuthenticable
8
+ extend Dry::Configurable
9
+ setting :algorithm
10
+ setting :jwt_secret_key, default: nil
11
+
12
+ class Error < StandardError; end
13
+ end
14
+
15
+ require_relative 'jwt_authenticable/version'
16
+ require_relative 'jwt_authenticable/exceptions'
17
+ require_relative 'jwt_authenticable/responses'
18
+ require_relative 'jwt_authenticable/auth'
19
+ require_relative 'jwt_authenticable/info'
@@ -0,0 +1,4 @@
1
+ module JwtAuthenticable
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: researchable_jwt-authenticable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Researchable
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-configurable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.16'
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.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
41
+ description: " Researchable's JwtAuthenticable relies on Json Web Tokens to deal
42
+ with authentication.\n"
43
+ email:
44
+ - info@researchable.nl
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".editorconfig"
50
+ - ".rspec"
51
+ - ".rubocop.yml"
52
+ - ".ruby-gemset"
53
+ - ".ruby-version"
54
+ - Gemfile
55
+ - Gemfile.lock
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/jwt_authenticable.rb
60
+ - lib/jwt_authenticable/auth.rb
61
+ - lib/jwt_authenticable/exceptions.rb
62
+ - lib/jwt_authenticable/info.rb
63
+ - lib/jwt_authenticable/responses.rb
64
+ - lib/jwt_authenticable/version.rb
65
+ - sig/jwt_authenticable.rbs
66
+ homepage: https://gitlab.com/researchable/general/gems/jwt-authenticable/-/blob/v1.0.0/README.md
67
+ licenses:
68
+ - MIT
69
+ metadata:
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: '3.0'
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.3.26
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Researchable's gem to deal with JWT authentication
90
+ test_files: []