tiny_auth 2.0.0 → 3.0.0.rc1

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 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: []