tiny_auth 2.0.0 → 3.0.0.rc1

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: ff3ff4ff25d9895b64f12d3e506d3c4ebba61c5c0ba702afc37501aeb13dc956
4
- data.tar.gz: 6f71e8f393807eb1b5ab944d720982a7b14e4cbb43a00f2ede0f822ad8cb85cc
3
+ metadata.gz: 2647d2fe76d8180529cfa176c812f50723d643c37e649ab80a57820ace588f46
4
+ data.tar.gz: e9abd283be1c37c8eaeebd884e9ac6bba07ee25f2d433b37e560397a7e8b50a5
5
5
  SHA512:
6
- metadata.gz: d8d987c6ca4ef3387d245a9f4e6a263729a928839466617b227db3592d96ffce8605513fbf78f63d33802de18712c36a97b04f8961570a7fb3e57c45e2101341
7
- data.tar.gz: 72c2cee4eb4bc47dbe50dfa55473d3ae3be6eca231ebbd5581d000c633dfffe204c79493f9171a51087cffa419135ef3392ebe09416623b42c70a569e8787445
6
+ metadata.gz: c550af99cb5fc1b38e1ad68f287a2f0f1b19df3ba8b3c7fa33a4506bacf904717294d6acb9da376a2309d29aac4152d14fa1a04f1a44dbc9b8171f99c92fa4a2
7
+ data.tar.gz: 571ef546820aed2fc724ea2bbd1871107c6a83257467599fefeed34b270014b610af027680e8bab07cb1d41d70cd7f85013341e39bb0c6265e2ec2ff287b3bb8
data/README.md CHANGED
@@ -16,17 +16,17 @@ And then execute:
16
16
 
17
17
  ## Usage
18
18
 
19
+ ### `TinyAuth::Model`
20
+
19
21
  First, create a table to store your users:
20
22
 
21
23
  ```ruby
22
24
  create_table :users do |t|
23
25
  t.string :email, null: false
24
26
  t.string :password_digest, null: false
25
- t.string :reset_token_digest
26
- t.datetime :reset_token_expires_at
27
-
27
+ t.integer :token_version, null: false, default: 0
28
28
  t.index :email, unique: true
29
- t.index :reset_token_digest, unique: true
29
+ t.index [:id, :token_version], unique: true
30
30
  end
31
31
  ```
32
32
 
@@ -35,41 +35,95 @@ Your model should look like this:
35
35
  ```ruby
36
36
  class User < ApplicationRecord
37
37
  include TinyAuth::Model
38
- has_secure_password
39
38
  end
40
39
  ```
41
40
 
42
- Now, you're ready to authenticate!
41
+ #### `#generate_token(purpose: :access, expires_in: 24.hours)`
42
+
43
+ Generate a token. The token is generated from the user's `id` and their `token_version`.
44
+
45
+ If the `token_version` changes, all previously issued tokens will be revoked. Anytime the
46
+ user's password changes, this will happen automatically.
43
47
 
44
48
  ```ruby
45
- user = User.find_by_email("user@example.com")
46
- user = User.find_by_credentials("user@example.com", "password")
49
+ irb> user.generate_token
50
+ "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJ..."
51
+
52
+ irb> user.generate_token(purpose: :reset, expires_in: 1.hour)
53
+ "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJ..."
54
+ ```
55
+
56
+ #### `#invalidate_tokens`
57
+
58
+ Increments the `#token_version`, but does not apply the change to the database.
59
+
60
+ #### `#invalidate_tokens!`
47
61
 
48
- token = user.generate_token
49
- user = User.find_by_token(token)
62
+ Increments the `#token_version` and applies the change to the database.
50
63
 
51
- reset_token = user.generate_reset_token
52
- user = User.exchange_reset_token(reset_token)
64
+ #### `.find_by_email(email)`
65
+
66
+ Find a user by their email address. The query will disregard casing.
67
+
68
+ ```ruby
69
+ irb> User.find_by_email("user@example.com")
70
+ #<User id: 1, email: "user@example.com">
53
71
  ```
54
72
 
55
- Oh, and you can add authentication to your controllers:
73
+ #### `.find_by_credentials(email, password)`
74
+
75
+ Find a user by their email, then check that the password matches.
76
+
77
+ If the email doesn't exist, `nil` will be returned. If the password doesn't match, `nil` will be returned.
56
78
 
57
79
  ```ruby
58
- class ApplicationController < ActionController::Base
59
- include TinyAuth::Controller.new(model: User)
80
+ irb> User.find_by_credentials("user@example.com", "testing123")
81
+ #<User id: 1, email: "user@example.com">
82
+
83
+ irb> User.find_by_credentials("user@example.com", "")
84
+ nil
85
+
86
+ irb> User.find_by_credentials("", "")
87
+ nil
88
+ ```
89
+
90
+ #### `.find_by_token(token, purpose: :access)`
91
+
92
+ Find a user by their token. If the user can't be found, `nil` will be returned.
93
+
94
+ ```ruby
95
+ irb> User.find_by_token(token)
96
+ #<User id: 1, email: "user@example.com">
60
97
 
61
- before_action :authenticate_user
98
+ irb> User.find_by_token(reset_token, purpose: :reset)
99
+ #<User id: 1, email: "user@example.com">
62
100
 
63
- def index
64
- if user_signed_in?
65
- render json: {id: current_user.id}
66
- else
67
- head :unauthorized
68
- end
69
- end
101
+ irb> User.find_by_token("")
102
+ nil
103
+ ```
104
+
105
+ ### `TinyAuth::Controller`
106
+
107
+ ```ruby
108
+ class ApplicationController < ActionController::Base
109
+ include TinyAuth::Controller.new(model: User)
70
110
  end
71
111
  ```
72
112
 
113
+ The example above would generate the following methods based on the model's name:
114
+
115
+ #### `#authenticate_user`
116
+
117
+ This method should be called in a `before_action`. If an `Authorization` header is found, it will attempt to locate a user.
118
+
119
+ #### `#current_user`
120
+
121
+ An accessor that can be used to obtain access to the authenticated user after calling `authenticate_user`.
122
+
123
+ #### `#user_signed_in?`
124
+
125
+ A convenience method to determine if a user is signed in.
126
+
73
127
  ## Contributing
74
128
 
75
129
  Bug reports and pull requests are welcome on GitHub at https://github.com/rzane/tiny_auth. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/bin/console ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "irb"
5
+ require "active_record"
6
+ require "tiny_auth"
7
+
8
+ require_relative "../spec/support/schema"
9
+ require_relative "../spec/support/models"
10
+
11
+ TinyAuth.secret = "supersecret"
12
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
13
+
14
+ User.create!(email: "user@example.com", password: "testing123")
15
+
16
+ IRB.start(__FILE__)
data/docs/UPGRADING.md ADDED
@@ -0,0 +1,17 @@
1
+ # Upgrading
2
+
3
+ ## 2.x to 3.x
4
+
5
+ - Change all occurences of `generate_reset_token` to use `generate_token`.
6
+ - Change all occurences of `exchange_reset_token` to use `find_by_token`.
7
+ - Add a new migration:
8
+
9
+ ```ruby
10
+ change_table :users do |t|
11
+ t.remove :reset_token_digest
12
+ t.remove :reset_token_expires_at
13
+
14
+ t.integer :token_version, null: false, default: 0
15
+ t.index [:id, :token_version], unique: true
16
+ end
17
+ ```
data/lib/tiny_auth.rb CHANGED
@@ -1,42 +1,25 @@
1
- require "openssl"
2
- require "tiny_auth/model"
3
1
  require "tiny_auth/controller"
2
+ require "tiny_auth/model"
3
+ require "tiny_auth/verifier"
4
4
  require "tiny_auth/version"
5
5
 
6
6
  module TinyAuth
7
7
  class << self
8
- # A secret that is used for hashing tokens.
9
- #
10
- # If `Rails` is defined, it will attempt to use
11
- # `Rails.application.secret_key_base`.
12
- #
13
- # @raise [RuntimeError]
14
- # @return [String]
15
- def secret
16
- @secret || secret_key_base || missing_secret!
17
- end
18
-
19
- # Configure the secret that is used for hashing tokens.
8
+ # Configure the secret used to sign and verify tokens.
20
9
  # @param secret [String]
21
10
  def secret=(secret)
22
- @secret = secret
11
+ @verifier = Verifier.new(secret)
23
12
  end
24
13
 
25
- # Create a hash from a value using the secret
26
- # @param value [String]
27
- # @return [String]
28
- def hexdigest(value)
29
- OpenSSL::HMAC.hexdigest("SHA256", secret, value)
30
- end
31
-
32
- private
33
-
34
- def secret_key_base
35
- Rails.application.secret_key_base if defined? Rails
36
- end
37
-
38
- def missing_secret!
39
- raise "You need to configure TinyAuth.secret"
14
+ def verifier # :nodoc:
15
+ @verifier || raise("Secret has not been configured")
40
16
  end
41
17
  end
42
18
  end
19
+
20
+ begin
21
+ require "rails/railtie"
22
+ rescue LoadError
23
+ else
24
+ require "tiny_auth/railtie"
25
+ end
@@ -1,12 +1,12 @@
1
1
  require "active_record"
2
- require "globalid"
3
2
  require "active_support/core_ext/numeric/time"
4
- require "active_support/core_ext/securerandom"
5
3
 
6
4
  module TinyAuth
7
5
  module Model
8
6
  def self.included(base)
9
7
  base.extend ClassMethods
8
+ base.has_secure_password
9
+ base.before_save :invalidate_tokens, if: :password_digest_changed?
10
10
  end
11
11
 
12
12
  module ClassMethods
@@ -14,7 +14,7 @@ module TinyAuth
14
14
  # @param email [String]
15
15
  # @return [ActiveRecord::Base,nil]
16
16
  def find_by_email(email)
17
- find_by arel_table[:email].lower.eq(email.downcase)
17
+ find_by(arel_table[:email].lower.eq(email.downcase))
18
18
  end
19
19
 
20
20
  # Find a resource by their email address and password
@@ -29,47 +29,38 @@ module TinyAuth
29
29
 
30
30
  # Finds a resource by a token
31
31
  # @param token [String]
32
+ # @param purpose [Symbol] defaults to `:access`
32
33
  # @return [ActiveRecord::Base,nil]
33
- def find_by_token(token)
34
- resource = GlobalID::Locator.locate_signed(token, for: :access)
35
- resource if resource.kind_of?(self)
36
- rescue ActiveRecord::RecordNotFound
37
- end
38
-
39
- # Finds a resource by their reset token and nillifies `reset_password_digest`
40
- # and `reset_token_expires_at` fields
41
- # @param token [String]
42
- # @return [ActiveRecord::Base,nil]
43
- def exchange_reset_token(token)
44
- digest = TinyAuth.hexdigest(token)
45
- not_expired = arel_table[:reset_token_expires_at].gt(Time.now)
46
- resource = where(not_expired).find_by(reset_token_digest: digest)
47
- resource&.reset_token_digest = nil
48
- resource&.reset_token_expires_at = nil
49
- resource
34
+ def find_by_token(token, purpose: :access)
35
+ id, token_version = TinyAuth.verifier.verify(token, purpose: purpose)
36
+ find_by(id: id, token_version: token_version)
37
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
50
38
  end
51
39
  end
52
40
 
53
- # Generates a stateless token for a resource
41
+ # Generates a token for this resource.
54
42
  # @param expires_in [ActiveSupport::Duration] defaults to 24 hours
55
- def generate_token(expires_in: 24.hours)
56
- to_signed_global_id(expires_in: expires_in, for: :access).to_s
43
+ # @param purpose [Symbol] defaults to `:access`
44
+ # @return [String]
45
+ def generate_token(purpose: :access, expires_in: 24.hours)
46
+ TinyAuth.verifier.generate(
47
+ [id, token_version],
48
+ purpose: purpose,
49
+ expires_in: expires_in
50
+ )
57
51
  end
58
52
 
59
- # Generates a reset token for a resource. A hashed version of the token
60
- # is stored in the database
61
- # @param expires_in [ActiveSupport::Duration] defaults to 2 hours
62
- def generate_reset_token(expires_in: 2.hours)
63
- token = SecureRandom.base58(24)
64
- digest = TinyAuth.hexdigest(token)
65
- expiry = expires_in.from_now
66
-
67
- update_columns(
68
- reset_token_digest: digest,
69
- reset_token_expires_at: expiry
70
- )
53
+ # Invalidate all tokens for this resource. The token version will
54
+ # be incremented and written to the database.
55
+ # @return [self]
56
+ def invalidate_tokens!
57
+ increment!(:token_version)
58
+ end
71
59
 
72
- token
60
+ # Invalidate all tokens for this resource. The token version will
61
+ # be incremented, but it will not be written to the database.
62
+ def invalidate_tokens
63
+ increment(:token_version)
73
64
  end
74
65
  end
75
66
  end
@@ -0,0 +1,7 @@
1
+ module TinyAuth
2
+ class Railtie < Rails::Railtie # :nodoc:
3
+ initializer "tiny_auth" do |app|
4
+ TinyAuth.secret = app.key_generator.generate_key("tiny_auth")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ require "base64"
2
+ require "active_support/message_verifier"
3
+
4
+ module TinyAuth
5
+ class Verifier < ActiveSupport::MessageVerifier # :nodoc:
6
+ private
7
+
8
+ def encode(data)
9
+ ::Base64.urlsafe_encode64(data)
10
+ end
11
+
12
+ def decode(data)
13
+ ::Base64.urlsafe_decode64(data)
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module TinyAuth
2
- VERSION = "2.0.0"
2
+ VERSION = "3.0.0.rc1"
3
3
  end
data/tiny_auth.gemspec CHANGED
@@ -26,7 +26,6 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency "activerecord", "~> 6.0"
28
28
  spec.add_dependency "activesupport", "~> 6.0"
29
- spec.add_dependency "globalid", "~> 0.4"
30
29
 
31
30
  spec.add_development_dependency "bundler", "~> 2.0"
32
31
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiny_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Zane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-26 00:00:00.000000000 Z
11
+ date: 2021-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '6.0'
41
- - !ruby/object:Gem::Dependency
42
- name: globalid
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.4'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.4'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: bundler
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -137,9 +123,13 @@ files:
137
123
  - LICENSE.txt
138
124
  - README.md
139
125
  - Rakefile
126
+ - bin/console
127
+ - docs/UPGRADING.md
140
128
  - lib/tiny_auth.rb
141
129
  - lib/tiny_auth/controller.rb
142
130
  - lib/tiny_auth/model.rb
131
+ - lib/tiny_auth/railtie.rb
132
+ - lib/tiny_auth/verifier.rb
143
133
  - lib/tiny_auth/version.rb
144
134
  - tiny_auth.gemspec
145
135
  homepage: https://github.com/rzane/tiny_auth
@@ -148,7 +138,7 @@ licenses:
148
138
  metadata:
149
139
  homepage_uri: https://github.com/rzane/tiny_auth
150
140
  source_code_uri: https://github.com/rzane/tiny_auth
151
- post_install_message:
141
+ post_install_message:
152
142
  rdoc_options: []
153
143
  require_paths:
154
144
  - lib
@@ -159,13 +149,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
149
  version: '0'
160
150
  required_rubygems_version: !ruby/object:Gem::Requirement
161
151
  requirements:
162
- - - ">="
152
+ - - ">"
163
153
  - !ruby/object:Gem::Version
164
- version: '0'
154
+ version: 1.3.1
165
155
  requirements: []
166
- rubyforge_project:
167
- rubygems_version: 2.7.6
168
- signing_key:
156
+ rubygems_version: 3.2.3
157
+ signing_key:
169
158
  specification_version: 4
170
159
  summary: Bare-minimum authentication for APIs
171
160
  test_files: []