tiny_auth 0.1.0 → 2.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
- 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