auth_rails 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b6047449dbddff4af60565f7c096eae14f8800b47bd129dceb3ea7f90fb42f7
4
- data.tar.gz: 80776e48b7c557c9ab11ec1e9b05ae06e31bc15cca56a4143bfbf46254d2a109
3
+ metadata.gz: c69df81c88cacdf202e2c49cf329423ded23e509fc2dc67fd60d07fda1fbe12e
4
+ data.tar.gz: abbad690e4211d181950af7bf542e2df367b79c0c79c049f32c5192188106f4f
5
5
  SHA512:
6
- metadata.gz: 03502ea907678c9ca3122701789e82a145f9c3a143d415153bbde798ad7b5a375cc464ba1e1bf4d8a583d42b298501c00fcf00aadbc7b2be221b8b2044a149fe
7
- data.tar.gz: f90cfeab74aa249aebbe6a33deec189901e90c74bfdc0092d42b18a468a3e3eee3fe806611b24accf61a4c1ffc9fc0d1034b4a35cc4bb460fa94b5008790366f
6
+ metadata.gz: b97b6c42ed2386b526ce58bbc9e8fd2982e7b60357adebfb0c547f3b3cbf6d07f6cbca8e4cf13871a42f69935ae09daf2dc7528563dd024dcc95dc95e8611f5a
7
+ data.tar.gz: 758425cb02330e2efd68fedc8730e4b8879136098f0ecfb1d9543812e8220fdbc51f8ec1eaa15c19a09d17e29788c4968969121e08c9a6cf922a2ea4b60e005e
data/README.md CHANGED
@@ -6,8 +6,43 @@ Simple authentication for rails.
6
6
  gem 'auth_rails'
7
7
  ```
8
8
 
9
+ # CLI
10
+
11
+ - init `auth_rails`
12
+
13
+ ```sh
14
+ rails g auth_rails
15
+ ```
16
+
17
+ - init `auth_rails` with strategy
18
+
19
+ ```sh
20
+ rails g auth_rails --strategy allowed_token
21
+ ```
22
+
23
+ - create migration for `allowed_token` strategy
24
+
25
+ ```sh
26
+ rails g auth_rails:migration --strategy allowed_token
27
+ ```
28
+
29
+ - if your model is not User
30
+
31
+ ```sh
32
+ rails g auth_rails:migration --strategy allowed_token --model CustomUser
33
+ ```
34
+
9
35
  # Configuration
10
36
 
37
+ - User model must have `has_secure_password`
38
+
39
+ ```rb
40
+ # app/models/user.rb
41
+ class User < ApplicationRecord
42
+ has_secure_password
43
+ end
44
+ ```
45
+
11
46
  ```rb
12
47
  # config/initializers/auth_rails.rb
13
48
 
@@ -84,6 +119,8 @@ end
84
119
 
85
120
  class User < ApplicationRecord
86
121
  include AuthRails::Concerns::AllowedTokenStrategy
122
+
123
+ has_secure_password
87
124
  end
88
125
  ```
89
126
 
@@ -128,6 +165,54 @@ module Api
128
165
  end
129
166
  ```
130
167
 
168
+ - In case your identifier is not email
169
+
170
+ ```rb
171
+ Rails.application.config.to_prepare do
172
+ AuthRails.configure do |config|
173
+ config.resource_class = User # required
174
+ config.identifier_name = :username # must be string or symbol, default is email
175
+ end
176
+ end
177
+ ```
178
+
179
+ - If you have a custom method to validate password
180
+
181
+ ```rb
182
+ Rails.application.config.to_prepare do
183
+ AuthRails.configure do |config|
184
+ config.resource_class = User # required
185
+ config.identifier_name = :username # must be string or symbol, default is email
186
+ config.authenticate = ->(resource, password) { resource.password == password } # must be a proc, validate password
187
+ end
188
+ end
189
+ ```
190
+
191
+ - Sometimes, you have a complex logic to get the user
192
+
193
+ ```rb
194
+ Rails.application.config.to_prepare do
195
+ AuthRails.configure do |config|
196
+ config.resource_class = User # required
197
+ config.identifier_name = :username # this one is sub in jwt
198
+ config.dig_params = ->(params) { params[:identifier] } # must be a proc, how to get identifier from params
199
+
200
+ # how to get user from identifier
201
+ # identifier default is params[<identifier_name>]
202
+ # or extract from dig_params
203
+ config.retrieve_resource = lambda { |identifier|
204
+ User.where(email: identifier)
205
+ .or(User.where(username: identifier))
206
+ .first
207
+ }
208
+ end
209
+ end
210
+ ```
211
+
212
+ # Strategy list
213
+
214
+ - allowed_token
215
+
131
216
  # License
132
217
 
133
218
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -4,9 +4,9 @@ module AuthRails
4
4
  module Api
5
5
  class AuthController < ApiController
6
6
  def create
7
- resource = AuthRails.resource_class.find_by(email: params[:email])
7
+ resource = AuthRails.retrieve_resource(params: params)
8
8
 
9
- raise AuthRails.error_class, :unauthenticated if resource.blank? || !resource.authenticate(params[:password])
9
+ raise AuthRails.error_class, :unauthenticated if resource.blank? || !AuthRails.authenticate(resource: resource, password: params[:password])
10
10
 
11
11
  respond_to_create(generate_token(resource))
12
12
  end
@@ -22,6 +22,11 @@ module AuthRails
22
22
 
23
23
  raise AuthRails.error_class, :unauthenticated if resource.blank?
24
24
 
25
+ resource.allowed_tokens.find_by(
26
+ jti: decoded_payload[:jti],
27
+ aud: decoded_payload[:aud]
28
+ )&.destroy!
29
+
25
30
  respond_to_refresh(generate_token(resource))
26
31
  end
27
32
 
@@ -38,7 +43,7 @@ module AuthRails
38
43
 
39
44
  def payload(resource)
40
45
  {
41
- sub: resource.email
46
+ sub: resource.send(AuthRails.identifier_name)
42
47
  }
43
48
  end
44
49
 
@@ -10,7 +10,7 @@ module AuthRails
10
10
  secret_key: Configuration::Jwt::AccessToken.secret_key
11
11
  )
12
12
 
13
- CurrentAuth.user = AuthRails.resource_class.find_by(email: payload[:sub])
13
+ CurrentAuth.user = AuthRails.resource_class.find_by(AuthRails.identifier_name => payload[:sub])
14
14
 
15
15
  raise AuthRails.error_class, :unauthenticated unless CurrentAuth.user
16
16
  end
data/auth_rails.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  .git
29
29
  .circleci
30
30
  appveyor
31
+ examples/
31
32
  Gemfile
32
33
  .rubocop.yml
33
34
  .vscode/settings.json
@@ -40,5 +41,5 @@ Gem::Specification.new do |spec|
40
41
 
41
42
  spec.require_paths = ['lib']
42
43
 
43
- spec.add_dependency 'jwt'
44
+ spec.add_runtime_dependency 'jwt', '>= 2.7'
44
45
  end
@@ -14,6 +14,10 @@ module AuthRails
14
14
  @resource_class ||= Config.resource_class
15
15
  end
16
16
 
17
+ def identifier_name
18
+ @identifier_name ||= Config.identifier_name.to_sym || :email
19
+ end
20
+
17
21
  def error_class
18
22
  @error_class ||= Config.error_class || Error
19
23
  end
@@ -21,5 +25,45 @@ module AuthRails
21
25
  def jwt_strategy
22
26
  @jwt_strategy ||= Configuration::Jwt.strategy || Strategies::BaseStrategy
23
27
  end
28
+
29
+ def authenticate(resource:, password:)
30
+ if Config.authenticate.present?
31
+ raise_if_not_proc(Config.authenticate, 'Config.authenticate')
32
+
33
+ Config.authenticate.call(resource, password)
34
+ else
35
+ raise 'Don\'t know how to authenticate resource with password' unless resource.respond_to?(:authenticate)
36
+
37
+ resource.authenticate(password)
38
+ end
39
+ end
40
+
41
+ def dig_params(params:)
42
+ if Config.dig_params.present?
43
+ raise_if_not_proc(Config.dig_params, 'Config.dig_params')
44
+
45
+ Config.dig_params.call(params)
46
+ else
47
+ params[AuthRails.identifier_name]
48
+ end
49
+ end
50
+
51
+ def retrieve_resource(params:)
52
+ identifier = dig_params(params: params)
53
+
54
+ if Config.retrieve_resource.present?
55
+ raise_if_not_proc(Config.retrieve_resource, 'Config.retrieve_resource')
56
+
57
+ return Config.retrieve_resource.call(identifier)
58
+ end
59
+
60
+ AuthRails.resource_class.find_by(AuthRails.identifier_name => identifier)
61
+ end
62
+
63
+ private
64
+
65
+ def raise_if_not_proc(source, name)
66
+ raise "#{name} must be a Proc" unless source.is_a?(Proc)
67
+ end
24
68
  end
25
69
  end
@@ -3,8 +3,12 @@
3
3
  module AuthRails
4
4
  class Config
5
5
  class << self
6
- attr_accessor :error_class,
7
- :resource_class
6
+ attr_accessor :dig_params,
7
+ :error_class,
8
+ :authenticate,
9
+ :resource_class,
10
+ :identifier_name,
11
+ :retrieve_resource
8
12
 
9
13
  def jwt
10
14
  yield Configuration::Jwt
@@ -11,7 +11,7 @@ module AuthRails
11
11
  .joins(:allowed_tokens)
12
12
  .where(allowed_tokens: symbolized_payload.slice(:jti, :aud))
13
13
  .where('allowed_tokens.exp > ?', Time.current)
14
- .find_by(email: symbolized_payload[:sub])
14
+ .find_by(AuthRails.identifier_name => symbolized_payload[:sub])
15
15
  end
16
16
 
17
17
  def gen_token(resource:, payload:, exp: nil, secret_key: nil, algorithm: nil)
@@ -8,7 +8,7 @@ module AuthRails
8
8
  symbolized_payload = payload.symbolize_keys
9
9
 
10
10
  AuthRails.resource_class
11
- .find_by(email: symbolized_payload[:sub])
11
+ .find_by(AuthRails.identifier_name => symbolized_payload[:sub])
12
12
  end
13
13
 
14
14
  def gen_token(payload:, exp: nil, secret_key: nil, algorithm: nil, jti: nil, **)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AuthRails
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/auth_rails.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'jwt'
4
+
3
5
  require_relative 'auth_rails/config'
4
6
  require_relative 'auth_rails/version'
5
7
  require_relative 'auth_rails/class_methods'
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuthRails
4
+ class MigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ class_option :strategy,
10
+ aliases: '-strat',
11
+ type: :string,
12
+ desc: 'Strategy to use, default is AuthRails::Strategies::BaseStrategy',
13
+ default: 'base'
14
+
15
+ class_option :model,
16
+ aliases: '-m',
17
+ type: :string,
18
+ desc: 'Model for strategy to associate with',
19
+ default: 'user'
20
+
21
+ def create_migration_files
22
+ @model = (options[:model] || 'user').underscore.to_sym
23
+
24
+ case options[:strategy]
25
+ when 'allowed_token'
26
+ migration_template(
27
+ 'allowed_tokens.tt',
28
+ 'db/migrate/create_allowed_tokens.rb',
29
+ migration_version: migration_version
30
+ )
31
+ end
32
+ end
33
+
34
+ class << self
35
+ def next_migration_number(dirname)
36
+ next_migration_number = current_migration_number(dirname) + 1
37
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def versioned_migrations?
44
+ Rails::VERSION::MAJOR >= 5
45
+ end
46
+
47
+ def migration_version
48
+ return unless versioned_migrations?
49
+
50
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAllowedTokens < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :allowed_tokens do |t|
6
+ t.string :jti, null: false
7
+ t.string :aud
8
+ t.datetime :exp, null: false
9
+
10
+ t.timestamps
11
+
12
+ t.references :<%= @model %>, foreign_key: { on_delete: :cascade }, null: false
13
+
14
+ t.index %i[jti aud]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AuthRailsGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('templates', __dir__)
5
+
6
+ class_option :strategy,
7
+ aliases: '-strat',
8
+ type: :string,
9
+ desc: 'Strategy to use, default is AuthRails::Strategies::BaseStrategy',
10
+ default: 'base'
11
+
12
+ class_option :model,
13
+ aliases: '-m',
14
+ type: :string,
15
+ desc: 'Model for strategy to associate with',
16
+ default: 'user'
17
+
18
+ def generate_auth_rails
19
+ @model = (options[:model] || 'user').camelcase
20
+ @is_allowed_token = options[:strategy] == 'allowed_token'
21
+
22
+ template(
23
+ 'auth_rails.tt',
24
+ 'config/initializers/auth_rails.rb'
25
+ )
26
+ end
27
+
28
+ def create_allowed_tokens_strategy
29
+ return if options[:strategy].blank? || options[:strategy] != 'allowed_token'
30
+
31
+ invoke(
32
+ 'auth_rails:migration',
33
+ [],
34
+ strategy: 'allowed_token',
35
+ model: (options[:model] || 'user').camelcase
36
+ )
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuthRails.configure do |config|
4
+ config.jwt do |jwt|
5
+ jwt.access_token do |access_token|
6
+ access_token.exp = 1.hour.since
7
+ access_token.secret_key = ENV.fetch('JWT_SECRET', '')
8
+ end
9
+
10
+ <%= @is_allowed_token ? '' : '# ' %>jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy
11
+
12
+ # if you wanna use refresh token
13
+ # uncomment those lines below
14
+ # jwt.refresh_token do |refresh_token|
15
+ # refresh_token.http_only = true
16
+ # refresh_token.exp = 1.year.since
17
+ # refresh_token.algorithm = 'HS256'
18
+ # refresh_token.cookie_key = :ref_tok
19
+ # refresh_token.secret_key = ENV.fetch('JWT_SECRET', '')
20
+ # end
21
+ end
22
+ end
23
+
24
+ Rails.application.config.to_prepare do
25
+ AuthRails.configure do |config|
26
+ config.resource_class = <%= @model %>
27
+
28
+ # if you wanna use custom error classes
29
+ # uncomment code below
30
+ # config.error_class = AuthError
31
+ end
32
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auth_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alpha
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-25 00:00:00.000000000 Z
11
+ date: 2024-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.7'
27
27
  description: Simple authentication for Rails
28
28
  email:
29
29
  - alphanolucifer@gmail.com
@@ -47,6 +47,10 @@ files:
47
47
  - lib/auth_rails/strategies/allowed_token_strategy.rb
48
48
  - lib/auth_rails/strategies/base_strategy.rb
49
49
  - lib/auth_rails/version.rb
50
+ - lib/generators/auth_rails/migration_generator.rb
51
+ - lib/generators/auth_rails/templates/allowed_tokens.tt
52
+ - lib/generators/auth_rails_generator.rb
53
+ - lib/generators/templates/auth_rails.tt
50
54
  homepage: https://github.com/zgid123/auth_rails
51
55
  licenses:
52
56
  - MIT