tiny_auth 0.1.0 → 2.0.0

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: 067c6cf62a8fe40b8fa2da418605dbc53cd790bc
4
- data.tar.gz: 91a2dc9cbceecf73688c9204445d7d6026a2080a
2
+ SHA256:
3
+ metadata.gz: ff3ff4ff25d9895b64f12d3e506d3c4ebba61c5c0ba702afc37501aeb13dc956
4
+ data.tar.gz: 6f71e8f393807eb1b5ab944d720982a7b14e4cbb43a00f2ede0f822ad8cb85cc
5
5
  SHA512:
6
- metadata.gz: 97074ce70bc487d7219b36f55d8c5c3512c2a2c94206c3b12d2045f47a90c46cc516d09abcaa36db00f93cfbc72e349eed75323f7bd12ccc24aa9909ef1a1923
7
- data.tar.gz: 2ea66468d7fad3f816153b402a423742312d3b8447fd80c1c9be305043dc8114e2232b847cf3d461cb86acbb8b54856c77d18314badc899e5707e34776acb73c
6
+ metadata.gz: d8d987c6ca4ef3387d245a9f4e6a263729a928839466617b227db3592d96ffce8605513fbf78f63d33802de18712c36a97b04f8961570a7fb3e57c45e2101341
7
+ data.tar.gz: 72c2cee4eb4bc47dbe50dfa55473d3ae3be6eca231ebbd5581d000c633dfffe204c79493f9171a51087cffa419135ef3392ebe09416623b42c70a569e8787445
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
 
@@ -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,34 +34,45 @@ 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 `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.
55
+ Oh, and you can add authentication to your controllers:
58
56
 
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).
57
+ ```ruby
58
+ class ApplicationController < ActionController::Base
59
+ include TinyAuth::Controller.new(model: User)
60
+
61
+ before_action :authenticate_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
+ ```
60
72
 
61
73
  ## Contributing
62
74
 
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.
75
+ 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
76
 
65
77
  ## License
66
78
 
@@ -68,4 +80,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
68
80
 
69
81
  ## Code of Conduct
70
82
 
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).
83
+ 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).
@@ -1,60 +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
- 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)
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
- )
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
38
18
 
39
- resource.reset_token
40
- end
19
+ # Configure the secret that is used for hashing tokens.
20
+ # @param secret [String]
21
+ def secret=(secret)
22
+ @secret = secret
23
+ end
41
24
 
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)
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
46
31
 
47
- yield resource if resource && block_given?
48
- update_reset(resource, changes) if resource
49
- end
32
+ private
50
33
 
51
- private
34
+ def secret_key_base
35
+ Rails.application.secret_key_base if defined? Rails
36
+ end
52
37
 
53
- def update_reset(resource, changes)
54
- if resource.update(changes)
55
- resource
56
- else
57
- raise PersistError, "Failed to reset password."
38
+ def missing_secret!
39
+ raise "You need to configure TinyAuth.secret"
58
40
  end
59
41
  end
60
42
  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,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.1.0"
1
+ module TinyAuth
2
+ VERSION = "2.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.1.0
4
+ version: 2.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-15 00:00:00.000000000 Z
11
+ date: 2020-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -134,13 +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
- - bin/console
142
- - bin/setup
143
140
  - lib/tiny_auth.rb
141
+ - lib/tiny_auth/controller.rb
142
+ - lib/tiny_auth/model.rb
144
143
  - lib/tiny_auth/version.rb
145
144
  - tiny_auth.gemspec
146
145
  homepage: https://github.com/rzane/tiny_auth
@@ -165,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
164
  version: '0'
166
165
  requirements: []
167
166
  rubyforge_project:
168
- rubygems_version: 2.6.14
167
+ rubygems_version: 2.7.6
169
168
  signing_key:
170
169
  specification_version: 4
171
170
  summary: Bare-minimum authentication for APIs
@@ -1,63 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- tiny_auth (0.1.0)
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
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "tiny_auth"
5
-
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
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
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