tiny_auth 0.2.0 → 1.0.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
- SHA256:
3
- metadata.gz: d2c08fa6193667861a991bb4442e3669e32d559a8fd6de39001e9bcef25857f0
4
- data.tar.gz: 7edd1dc4569763af9f68e19066bcc1c0f8d273976f123d01b0a2439414fdd5ae
2
+ SHA1:
3
+ metadata.gz: ab849294a31f7be0e5da2033ab81e9bd91850e67
4
+ data.tar.gz: cd1c89e452847d5b48cf69235c9e0c46f0ecd1ea
5
5
  SHA512:
6
- metadata.gz: ce1f2b71ff644335d65683a044f75280f89bc752b7cd0754d5103ce93b921d051a8fdcc5b7298cec43d32fa516eb0ae03ab6f3a181cab2724a59715d59fb4beb
7
- data.tar.gz: ecfe0672b60c176ea9e8687cb5690af60207814da193a08ec4a7800bad2da3743667061768ce7a245a4557862a6cb3a1ea0d01d4619f88a57c1e271c1a7dd7e6
6
+ metadata.gz: 399030a4ad8844703aee70fa18185fc36f65e566941a1c8fd01a39c36ebe4773604a4ee270e00a5656153f010e3c6dc0bff02af8c410a01b58a530d7049edf32
7
+ data.tar.gz: 63dbff0e1f10d646a14939ba3c6d199860027aae7660657bc9450c90d29c2e4c6b5cc1697637d87bc7ec067d48e075f335a69690bf2208aa3770f88823bf5dd7
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,4 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in tiny_auth.gemspec
4
4
  gemspec
5
- gem 'simplecov', require: false, group: :test
5
+
6
+ gem "simplecov"
7
+ gem "coveralls"
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # TinyAuth [![Build Status](https://travis-ci.org/rzane/tiny_auth.svg?branch=master)](https://travis-ci.org/rzane/tiny_auth)
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
 
@@ -22,10 +22,11 @@ First, create a table to store your users:
22
22
  create_table :users do |t|
23
23
  t.string :email, null: false
24
24
  t.string :password_digest, null: false
25
- t.string :reset_token
25
+ t.string :reset_token_digest
26
26
  t.datetime :reset_token_expires_at
27
+
27
28
  t.index :email, unique: true
28
- t.index :reset_token, unique: true
29
+ t.index :reset_token_digest, unique: true
29
30
  end
30
31
  ```
31
32
 
@@ -33,32 +34,41 @@ Your model should look like this:
33
34
 
34
35
  ```ruby
35
36
  class User < ApplicationRecord
37
+ include TinyAuth::Model
36
38
  has_secure_password
37
39
  end
38
40
  ```
39
41
 
40
- Now, you're ready to use `TinyAuth`:
42
+ Now, you're ready to authenticate!
41
43
 
42
44
  ```ruby
43
- auth = TinyAuth.new(User)
44
-
45
- user = auth.find_by_email('user@example.com')
46
- user = auth.find_by_credentials('user@example.com', 'password')
45
+ user = User.find_by_email("user@example.com")
46
+ user = User.find_by_credentials("user@example.com", "password")
47
47
 
48
- token = auth.generate_token(user)
49
- user = auth.find_by_token(token)
48
+ token = user.generate_token
49
+ user = User.find_by_token(token)
50
50
 
51
- reset_token = auth.generate_reset_token(user)
52
- user = auth.exchange_reset_token(user, password: "changed")
51
+ reset_token = user.generate_reset_token
52
+ user = User.exchange_reset_token(reset_token)
53
53
  ```
54
54
 
55
- ## Development
56
-
57
- After checking out the repo, run `bundle install` to install dependencies.
58
-
59
- Then, run `bundle exec rspec` to run the tests.
55
+ Oh, and you can add authentication to your controllers:
60
56
 
61
- 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).
57
+ ```ruby
58
+ class ApplicationController < ActionController::Base
59
+ extend TinyAuth::Controller
60
+
61
+ authenticates model: User
62
+
63
+ def index
64
+ if user_signed_in?
65
+ render json: {id: current_user.id}
66
+ else
67
+ head :unauthorized
68
+ end
69
+ end
70
+ end
71
+ ```
62
72
 
63
73
  ## Contributing
64
74
 
@@ -1,76 +1,42 @@
1
+ require "openssl"
2
+ require "tiny_auth/model"
3
+ require "tiny_auth/controller"
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
- def initialize(model, scope: model, secret: secret_key_base)
8
- @model = model
9
- @scope = scope
10
- @secret = secret
11
-
12
- raise ArgumentError, "missing argument: model" if model.nil?
13
- raise ArgumentError, "missing keyword: secret" if secret.nil?
14
- end
15
-
16
- def find_by_email(email)
17
- scope.find_by(model.arel_table[:email].lower.eq(email.downcase))
18
- end
19
-
20
- def find_by_credentials(email, password)
21
- resource = find_by_email(email)
22
- resource if resource&.authenticate(password)
23
- end
24
-
25
- def generate_token(resource, purpose: :access, expires_in: 24.hours)
26
- resource.to_sgid(expires_in: expires_in, for: purpose).to_s
27
- end
28
-
29
- def find_by_token(token, purpose: :access)
30
- GlobalID::Locator.locate_signed(token, for: purpose)
31
- rescue ActiveRecord::RecordNotFound
32
- end
33
-
34
- def generate_reset_token(resource, **opts)
35
- generate_single_use_token(resource, purpose: :reset, **opts)
36
- end
37
-
38
- def generate_single_use_token(resource, purpose:, expires_in: 2.hours)
39
- token = SecureRandom.base58(24)
40
-
41
- resource.update!(
42
- "#{purpose}_token" => hmac(token),
43
- "#{purpose}_token_expires_at" => expires_in.from_now
44
- )
45
-
46
- token
47
- end
48
-
49
- def exchange_reset_token(token, **opts, &block)
50
- exchange_single_use_token(token, purpose: :reset, **opts, &block)
51
- end
52
-
53
- def exchange_single_use_token(token, purpose:, update: {})
54
- not_expired = model.arel_table[:"#{purpose}_token_expires_at"].gt(Time.now)
55
- resource = scope.where(not_expired).find_by(:"#{purpose}_token" => hmac(token))
56
-
57
- return if resource.nil?
58
- yield resource if block_given?
59
-
60
- resource.assign_attributes(update)
61
- resource.update!("#{purpose}_token" => nil, "#{purpose}_token_expires_at" => nil)
62
- resource
63
- end
64
-
65
- private
66
-
67
- attr_reader :model, :scope, :secret
68
-
69
- def hmac(value)
70
- OpenSSL::HMAC.hexdigest("SHA256", secret, value)
71
- end
72
-
73
- def secret_key_base
74
- Rails.application.secret_key_base if defined?(Rails)
6
+ module TinyAuth
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.
20
+ # @param secret [String]
21
+ def secret=(secret)
22
+ @secret = secret
23
+ end
24
+
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"
40
+ end
75
41
  end
76
42
  end
@@ -0,0 +1,58 @@
1
+ require "active_support/core_ext/object/blank"
2
+
3
+ module TinyAuth
4
+ module Controller
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
+ # @param options [Hash] Additional arguments for `before_action`
19
+ #
20
+ # @example
21
+ # class ApplicationController < ActionController::Base
22
+ # extend TinyAuth::Controller
23
+ #
24
+ # authenticates model: User, only: :index
25
+ #
26
+ # def index
27
+ # if user_signed_in?
28
+ # render json: current_user
29
+ # else
30
+ # head :unauthorized
31
+ # end
32
+ # end
33
+ # end
34
+ def authenticates(model:, name: model.model_name.singular, **options)
35
+ authenticate = :"authenticate_#{name}"
36
+ current = :"current_#{name}"
37
+ current_ivar = :"@current_#{name}"
38
+ signed_in = :"#{name}_signed_in?"
39
+
40
+ attr_reader current
41
+
42
+ define_method(signed_in) do
43
+ !send(current).nil?
44
+ end
45
+
46
+ define_method(authenticate) do
47
+ token = TinyAuth::Controller.token(request)
48
+
49
+ if token
50
+ resource = model.find_by_token(token)
51
+ instance_variable_set(current_ivar, resource)
52
+ end
53
+ end
54
+
55
+ before_action(authenticate, **options)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,75 @@
1
+ require "active_record"
2
+ require "globalid"
3
+ require "active_support/core_ext/numeric/time"
4
+ require "active_support/core_ext/securerandom"
5
+
6
+ module TinyAuth
7
+ module Model
8
+ def self.included(base)
9
+ base.extend ClassMethods
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
+ # @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
50
+ end
51
+ end
52
+
53
+ # Generates a stateless token for a resource
54
+ # @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
57
+ end
58
+
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
+ )
71
+
72
+ token
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
- class TinyAuth
2
- VERSION = "0.2.0"
1
+ module TinyAuth
2
+ VERSION = "1.0.0"
3
3
  end
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.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Zane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-16 00:00:00.000000000 Z
11
+ date: 2019-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -134,11 +134,12 @@ files:
134
134
  - ".travis.yml"
135
135
  - CODE_OF_CONDUCT.md
136
136
  - Gemfile
137
- - Gemfile.lock
138
137
  - LICENSE.txt
139
138
  - README.md
140
139
  - Rakefile
141
140
  - lib/tiny_auth.rb
141
+ - lib/tiny_auth/controller.rb
142
+ - lib/tiny_auth/model.rb
142
143
  - lib/tiny_auth/version.rb
143
144
  - tiny_auth.gemspec
144
145
  homepage: https://github.com/rzane/tiny_auth
@@ -163,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
164
  version: '0'
164
165
  requirements: []
165
166
  rubyforge_project:
166
- rubygems_version: 2.7.6
167
+ rubygems_version: 2.6.14
167
168
  signing_key:
168
169
  specification_version: 4
169
170
  summary: Bare-minimum authentication for APIs
@@ -1,71 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- tiny_auth (0.1.2)
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
- docile (1.3.2)
27
- globalid (0.4.2)
28
- activesupport (>= 4.2.0)
29
- i18n (1.7.0)
30
- concurrent-ruby (~> 1.0)
31
- json (2.2.0)
32
- minitest (5.13.0)
33
- rake (10.5.0)
34
- rspec (3.9.0)
35
- rspec-core (~> 3.9.0)
36
- rspec-expectations (~> 3.9.0)
37
- rspec-mocks (~> 3.9.0)
38
- rspec-core (3.9.0)
39
- rspec-support (~> 3.9.0)
40
- rspec-expectations (3.9.0)
41
- diff-lcs (>= 1.2.0, < 2.0)
42
- rspec-support (~> 3.9.0)
43
- rspec-mocks (3.9.0)
44
- diff-lcs (>= 1.2.0, < 2.0)
45
- rspec-support (~> 3.9.0)
46
- rspec-support (3.9.0)
47
- simplecov (0.17.0)
48
- docile (~> 1.1)
49
- json (>= 1.8, < 3)
50
- simplecov-html (~> 0.10.0)
51
- simplecov-html (0.10.2)
52
- sqlite3 (1.4.1)
53
- thread_safe (0.3.6)
54
- tzinfo (1.2.5)
55
- thread_safe (~> 0.1)
56
- zeitwerk (2.2.1)
57
-
58
- PLATFORMS
59
- ruby
60
-
61
- DEPENDENCIES
62
- bcrypt (~> 3.1)
63
- bundler (~> 2.0)
64
- rake (~> 10.0)
65
- rspec (~> 3.0)
66
- simplecov
67
- sqlite3 (~> 1.4)
68
- tiny_auth!
69
-
70
- BUNDLED WITH
71
- 2.0.2