tiny_auth 0.1.1 → 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
- SHA1:
3
- metadata.gz: ce2aaf5f006569620b52ab1de117c4aa5d5d3d31
4
- data.tar.gz: d458f358c87410b997a213875dfce64f128dbe08
2
+ SHA256:
3
+ metadata.gz: 2647d2fe76d8180529cfa176c812f50723d643c37e649ab80a57820ace588f46
4
+ data.tar.gz: e9abd283be1c37c8eaeebd884e9ac6bba07ee25f2d433b37e560397a7e8b50a5
5
5
  SHA512:
6
- metadata.gz: ea724d058e16ea66db6fe5bb6f9b570bba1ceade53df751357b192ed493b5b33be168a9f8f72731fa57fbca413c81708d899b970f24658bc418e2b7f77f5cb39
7
- data.tar.gz: f8330414be12225fbf6c1c5e817c1db7decf604dc15d8e9f12285b0af35c5bd1a026738dba46e4d2b2e70e3e65750d1da5adf879406958d2b5574eef91555067
6
+ metadata.gz: c550af99cb5fc1b38e1ad68f287a2f0f1b19df3ba8b3c7fa33a4506bacf904717294d6acb9da376a2309d29aac4152d14fa1a04f1a44dbc9b8171f99c92fa4a2
7
+ data.tar.gz: 571ef546820aed2fc724ea2bbd1871107c6a83257467599fefeed34b270014b610af027680e8bab07cb1d41d70cd7f85013341e39bb0c6265e2ec2ff287b3bb8
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ /Gemfile.lock
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in tiny_auth.gemspec
4
4
  gemspec
5
+
6
+ gem "simplecov"
7
+ gem "coveralls"
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # TinyAuth
1
+ # TinyAuth [![Build Status](https://travis-ci.org/rzane/tiny_auth.svg?branch=master)](https://travis-ci.org/rzane/tiny_auth) [![Coverage Status](https://coveralls.io/repos/github/rzane/tiny_auth/badge.svg?branch=master)](https://coveralls.io/github/rzane/tiny_auth?branch=master)
2
2
 
3
3
  A utility for minimal user authentication.
4
4
 
@@ -16,16 +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
26
- t.datetime :reset_token_expires_at
27
+ t.integer :token_version, null: false, default: 0
27
28
  t.index :email, unique: true
28
- t.index :reset_token, unique: true
29
+ t.index [:id, :token_version], unique: true
29
30
  end
30
31
  ```
31
32
 
@@ -33,34 +34,99 @@ Your model should look like this:
33
34
 
34
35
  ```ruby
35
36
  class User < ApplicationRecord
36
- has_secure_password
37
+ include TinyAuth::Model
37
38
  end
38
39
  ```
39
40
 
40
- Now, you're ready to use `TinyAuth`:
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.
47
+
48
+ ```ruby
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!`
61
+
62
+ Increments the `#token_version` and applies the change to the database.
63
+
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">
71
+ ```
72
+
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.
41
78
 
42
79
  ```ruby
43
- auth = TinyAuth.new(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
+ ```
44
89
 
45
- user = auth.find_by_email('user@example.com')
46
- user = auth.find_by_credentials('user@example.com', 'password')
90
+ #### `.find_by_token(token, purpose: :access)`
47
91
 
48
- token = auth.generate_token(user)
49
- user = auth.find_by_token(token)
92
+ Find a user by their token. If the user can't be found, `nil` will be returned.
50
93
 
51
- reset_token = auth.generate_reset_token(user)
52
- user = auth.exchange_reset_token(user, password: "changed")
94
+ ```ruby
95
+ irb> User.find_by_token(token)
96
+ #<User id: 1, email: "user@example.com">
97
+
98
+ irb> User.find_by_token(reset_token, purpose: :reset)
99
+ #<User id: 1, email: "user@example.com">
100
+
101
+ irb> User.find_by_token("")
102
+ nil
53
103
  ```
54
104
 
55
- ## Development
105
+ ### `TinyAuth::Controller`
106
+
107
+ ```ruby
108
+ class ApplicationController < ActionController::Base
109
+ include TinyAuth::Controller.new(model: User)
110
+ end
111
+ ```
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`.
56
122
 
57
- 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.
123
+ #### `#user_signed_in?`
58
124
 
59
- 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+ A convenience method to determine if a user is signed in.
60
126
 
61
127
  ## Contributing
62
128
 
63
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/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.
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.
64
130
 
65
131
  ## License
66
132
 
@@ -68,4 +134,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
68
134
 
69
135
  ## Code of Conduct
70
136
 
71
- Everyone interacting in the TinyAuth project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tiny_auth/blob/master/CODE_OF_CONDUCT.md).
137
+ Everyone interacting in the TinyAuth project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rzane/tiny_auth/blob/master/CODE_OF_CONDUCT.md).
data/bin/console CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
+ require "irb"
5
+ require "active_record"
4
6
  require "tiny_auth"
5
7
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
+ require_relative "../spec/support/schema"
9
+ require_relative "../spec/support/models"
8
10
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
11
+ TinyAuth.secret = "supersecret"
12
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
13
+
14
+ User.create!(email: "user@example.com", password: "testing123")
12
15
 
13
- require "irb"
14
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,60 +1,25 @@
1
+ require "tiny_auth/controller"
2
+ require "tiny_auth/model"
3
+ require "tiny_auth/verifier"
1
4
  require "tiny_auth/version"
2
- require "globalid"
3
- require "active_record"
4
- require "active_support/core_ext/securerandom"
5
5
 
6
- class TinyAuth
7
- class Error < StandardError; end
8
- class PersistError < StandardError; end
9
-
10
- def initialize(model, scope: model)
11
- @model = model
12
- @scope = scope
13
- end
14
-
15
- def find_by_email(email)
16
- @scope.find_by(@model.arel_table[:email].lower.eq(email.downcase))
17
- end
18
-
19
- def find_by_credentials(email, password)
20
- resource = find_by_email(email)
21
- resource if resource&.authenticate(password)
22
- end
23
-
24
- def generate_token(resource, purpose: :access, expires_in: 24.hours)
25
- resource.to_sgid(expires_in: expires_in, for: purpose).to_s
26
- end
27
-
28
- def find_by_token(token, purpose: :access)
29
- GlobalID::Locator.locate_signed(token, for: purpose)
30
- end
31
-
32
- def generate_reset_token(resource, expires_in: 2.hours)
33
- update_reset(
34
- resource,
35
- reset_token: SecureRandom.base58(24),
36
- reset_token_expires_at: Time.now + expires_in
37
- )
38
-
39
- resource.reset_token
40
- end
41
-
42
- def exchange_reset_token(reset_token, changes = {})
43
- changes = changes.merge(reset_token: nil, reset_token_expires_at: nil)
44
- not_expired = @model.arel_table[:reset_token_expires_at].gt(Time.now)
45
- resource = @scope.where(not_expired).find_by(reset_token: reset_token)
46
-
47
- yield resource if resource && block_given?
48
- update_reset(resource, changes) if resource
49
- end
50
-
51
- private
6
+ module TinyAuth
7
+ class << self
8
+ # Configure the secret used to sign and verify tokens.
9
+ # @param secret [String]
10
+ def secret=(secret)
11
+ @verifier = Verifier.new(secret)
12
+ end
52
13
 
53
- def update_reset(resource, changes)
54
- if resource.update(changes)
55
- resource
56
- else
57
- raise PersistError, "Failed to reset password."
14
+ def verifier # :nodoc:
15
+ @verifier || raise("Secret has not been configured")
58
16
  end
59
17
  end
60
18
  end
19
+
20
+ begin
21
+ require "rails/railtie"
22
+ rescue LoadError
23
+ else
24
+ require "tiny_auth/railtie"
25
+ end
@@ -0,0 +1,55 @@
1
+ require "active_support/core_ext/object/blank"
2
+
3
+ module TinyAuth
4
+ class Controller < Module
5
+ # Extract a token from a request
6
+ # @param request [ActionDispatch::HTTP::Request]
7
+ # @return [String,nil]
8
+ def self.token(request)
9
+ header = request.authorization.to_s
10
+ header[/^Bearer (.*)$/, 1].presence
11
+ end
12
+
13
+ # Defines a before action that will authenticate the resource.
14
+ # It also defines methods for accessing the currently authenticated
15
+ # resource.
16
+ # @param model [ActiveRecord::Base]
17
+ # @param name [Symbol] Used to define methods like `current_user`
18
+ #
19
+ # @example
20
+ # class ApplicationController < ActionController::Base
21
+ # include TinyAuth::Controller.new(model: User)
22
+ #
23
+ # before_action :authenticate_user
24
+ #
25
+ # def index
26
+ # if user_signed_in?
27
+ # render json: current_user
28
+ # else
29
+ # head :unauthorized
30
+ # end
31
+ # end
32
+ # end
33
+ def initialize(model:, name: model.model_name.singular)
34
+ authenticate = :"authenticate_#{name}"
35
+ current = :"current_#{name}"
36
+ current_ivar = :"@current_#{name}"
37
+ signed_in = :"#{name}_signed_in?"
38
+
39
+ attr_reader current
40
+
41
+ define_method(signed_in) do
42
+ !send(current).nil?
43
+ end
44
+
45
+ define_method(authenticate) do
46
+ token = TinyAuth::Controller.token(request)
47
+
48
+ if token
49
+ resource = model.find_by_token(token)
50
+ instance_variable_set(current_ivar, resource)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,66 @@
1
+ require "active_record"
2
+ require "active_support/core_ext/numeric/time"
3
+
4
+ module TinyAuth
5
+ module Model
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.has_secure_password
9
+ base.before_save :invalidate_tokens, if: :password_digest_changed?
10
+ end
11
+
12
+ module ClassMethods
13
+ # Find a resource by email, ignoring case
14
+ # @param email [String]
15
+ # @return [ActiveRecord::Base,nil]
16
+ def find_by_email(email)
17
+ find_by(arel_table[:email].lower.eq(email.downcase))
18
+ end
19
+
20
+ # Find a resource by their email address and password
21
+ # This assumes that you've added `has_secure_password` to your model.
22
+ # @param email [String]
23
+ # @param password [String]
24
+ # @return [ActiveRecord::Base,nil]
25
+ def find_by_credentials(email, password)
26
+ resource = find_by_email(email)
27
+ resource if resource&.authenticate(password)
28
+ end
29
+
30
+ # Finds a resource by a token
31
+ # @param token [String]
32
+ # @param purpose [Symbol] defaults to `:access`
33
+ # @return [ActiveRecord::Base,nil]
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
38
+ end
39
+ end
40
+
41
+ # Generates a token for this resource.
42
+ # @param expires_in [ActiveSupport::Duration] defaults to 24 hours
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
+ )
51
+ end
52
+
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
59
+
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)
64
+ end
65
+ end
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
- class TinyAuth
2
- VERSION = "0.1.1"
1
+ module TinyAuth
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: 0.1.1
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: 2019-11-15 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
@@ -134,13 +120,16 @@ files:
134
120
  - ".travis.yml"
135
121
  - CODE_OF_CONDUCT.md
136
122
  - Gemfile
137
- - Gemfile.lock
138
123
  - LICENSE.txt
139
124
  - README.md
140
125
  - Rakefile
141
126
  - bin/console
142
- - bin/setup
127
+ - docs/UPGRADING.md
143
128
  - lib/tiny_auth.rb
129
+ - lib/tiny_auth/controller.rb
130
+ - lib/tiny_auth/model.rb
131
+ - lib/tiny_auth/railtie.rb
132
+ - lib/tiny_auth/verifier.rb
144
133
  - lib/tiny_auth/version.rb
145
134
  - tiny_auth.gemspec
146
135
  homepage: https://github.com/rzane/tiny_auth
@@ -149,7 +138,7 @@ licenses:
149
138
  metadata:
150
139
  homepage_uri: https://github.com/rzane/tiny_auth
151
140
  source_code_uri: https://github.com/rzane/tiny_auth
152
- post_install_message:
141
+ post_install_message:
153
142
  rdoc_options: []
154
143
  require_paths:
155
144
  - lib
@@ -160,13 +149,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
149
  version: '0'
161
150
  required_rubygems_version: !ruby/object:Gem::Requirement
162
151
  requirements:
163
- - - ">="
152
+ - - ">"
164
153
  - !ruby/object:Gem::Version
165
- version: '0'
154
+ version: 1.3.1
166
155
  requirements: []
167
- rubyforge_project:
168
- rubygems_version: 2.6.14
169
- signing_key:
156
+ rubygems_version: 3.2.3
157
+ signing_key:
170
158
  specification_version: 4
171
159
  summary: Bare-minimum authentication for APIs
172
160
  test_files: []
data/Gemfile.lock DELETED
@@ -1,63 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- tiny_auth (0.1.1)
5
- activerecord (~> 6.0)
6
- activesupport (~> 6.0)
7
- globalid (~> 0.4)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activemodel (6.0.1)
13
- activesupport (= 6.0.1)
14
- activerecord (6.0.1)
15
- activemodel (= 6.0.1)
16
- activesupport (= 6.0.1)
17
- activesupport (6.0.1)
18
- concurrent-ruby (~> 1.0, >= 1.0.2)
19
- i18n (>= 0.7, < 2)
20
- minitest (~> 5.1)
21
- tzinfo (~> 1.1)
22
- zeitwerk (~> 2.2)
23
- bcrypt (3.1.13)
24
- concurrent-ruby (1.1.5)
25
- diff-lcs (1.3)
26
- globalid (0.4.2)
27
- activesupport (>= 4.2.0)
28
- i18n (1.7.0)
29
- concurrent-ruby (~> 1.0)
30
- minitest (5.13.0)
31
- rake (10.5.0)
32
- rspec (3.9.0)
33
- rspec-core (~> 3.9.0)
34
- rspec-expectations (~> 3.9.0)
35
- rspec-mocks (~> 3.9.0)
36
- rspec-core (3.9.0)
37
- rspec-support (~> 3.9.0)
38
- rspec-expectations (3.9.0)
39
- diff-lcs (>= 1.2.0, < 2.0)
40
- rspec-support (~> 3.9.0)
41
- rspec-mocks (3.9.0)
42
- diff-lcs (>= 1.2.0, < 2.0)
43
- rspec-support (~> 3.9.0)
44
- rspec-support (3.9.0)
45
- sqlite3 (1.4.1)
46
- thread_safe (0.3.6)
47
- tzinfo (1.2.5)
48
- thread_safe (~> 0.1)
49
- zeitwerk (2.2.1)
50
-
51
- PLATFORMS
52
- ruby
53
-
54
- DEPENDENCIES
55
- bcrypt (~> 3.1)
56
- bundler (~> 2.0)
57
- rake (~> 10.0)
58
- rspec (~> 3.0)
59
- sqlite3 (~> 1.4)
60
- tiny_auth!
61
-
62
- BUNDLED WITH
63
- 2.0.2
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here