blunt 0.0.2 → 0.1.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
2
  SHA1:
3
- metadata.gz: 2c71c52c585bf1704dac5c8b46ded0b13ed651fd
4
- data.tar.gz: 9ed0c21e46a12cfe925171a88867dfa1a4993c80
3
+ metadata.gz: 7dda9fac934e626ea58083ba4ed77bcf97d1f04e
4
+ data.tar.gz: b80600b6a7b1d798360788c7e3a2e1739e00ce04
5
5
  SHA512:
6
- metadata.gz: 28683f5d8f9152a5ebf60ae3314266999661ec4ddc55427e93fd5b900eb0e73a4d285092c3090fd4631a7315616f8ea7343ae42864b35c5f6fab3e5b0ea57607
7
- data.tar.gz: 62b66de1365600d72e32b029260c206237e12b75dc7cd04753a94b3d66f8151d1aff05ae10bc74e98f09fb985188a8739dd0bfba1376cd68cc8522e73d51ed1d
6
+ metadata.gz: 3580a3b4252ee6aa22e68de538eccc9807c4d9cbc61e426ff4733102f320dd5db3c3acdd53945b991101af318043c70c9f1708e2b5203eba0bbb89c5dd66554c
7
+ data.tar.gz: a20e5f4b2ef1644b877c0effa9ad81dd0737099e8fa074615d33a669b72d8a2bebd6937137370b77d47a4b71cb824050e1e52b513cdb695f59c09c81034102c7
data/Gemfile CHANGED
@@ -1,2 +1,14 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
+
4
+ group :development do
5
+ gem 'pry'
6
+ gem 'guard'
7
+ gem 'guard-minitest'
8
+ gem 'rake-notes'
9
+ end
10
+
11
+ group :test do
12
+ gem 'minitest', '~> 5.0'
13
+ gem 'minitest-reporters'
14
+ end
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Blunt
2
2
 
3
- **Blunt** provides framework-agnostic authentication using [JSON Web Tokens](https://jwt.io). It wraps [ruby-jwt](https://github.com/jwt/ruby-jwt) with an easy-to-use interface and some common conventions. Great for APIs.
3
+ [![Gem Version](https://badge.fury.io/rb/blunt.svg)](https://badge.fury.io/rb/blunt)
4
+
5
+ **Blunt** is a token-based authentication library for Hanami. Its goal is to easily implement a single authentication scheme across multiple interfaces, i.e. both web/multi-page and mobile/SPA/API/async.
4
6
 
5
7
  ## Installation
6
8
 
@@ -19,44 +21,135 @@ Or:
19
21
  gem install blunt
20
22
  ```
21
23
 
22
- ## Usage
24
+ ## Setup
25
+
26
+ ### Environment
27
+
28
+ - `ENV['BLUNT_SECRET']` (String)
29
+
30
+ ### Models
31
+
32
+ - `User` entity (+ `UserRepository`)
33
+ + `email`
34
+ + `password_digest`
35
+ - `RefreshToken` entity (+ `RefreshTokenRepository`)
36
+ + `user_id`
37
+ + `valid` (Boolean)
38
+ + timestamps
39
+
40
+ ### Token Handling
41
+
42
+ Blunt offers several strategies for token storage. Just include the appropriate module, and Blunt will handle tokens on the back-end - they never need to touch your code!
43
+
44
+ ```ruby
45
+ Hanami::Controller.configure do
46
+ prepare do
47
+ include Blunt::Controller::__STRATEGY__
48
+ end
49
+ end
50
+ ```
51
+
52
+ #### Cookies
53
+
54
+ `Blunt::Controller::Web`
55
+
56
+ The ultimate hands-off approach. No setup is required on the front-end. Ideal for multi-page apps.
57
+
58
+ TODO: incorporate asynchronous JSON responses
59
+
60
+ #### HTTP Headers
61
+
62
+ `Blunt::Controller::API`
63
+
64
+ TODO: develop this
65
+
66
+ Pass the token from the client in a request header: `'HTTP_AUTHORIZATION' => 'Bearer <TOKEN>'`.
23
67
 
24
- Add a secret key at `ENV['BLUNT_SECRET']`. You can generate one with `Blunt.new_secret`.
68
+ #### Custom Strategy
69
+
70
+ Define a few methods to govern the access token lifecycle as it gets passed to and from the client. Blunt will use these 3 methods to handle the token. The example below shows a simple implementation for synchronous requests using cookies, but a custom strategy will likely be more complex.
71
+
72
+ ```ruby
73
+ module MyCustomStrategy
74
+ include Blunt::Controller
75
+
76
+ private
77
+ def set_access_token(token)
78
+ # Login or renew
79
+ cookies[:access_token] = token
80
+ end
81
+
82
+ def get_access_token
83
+ # Authenticate
84
+ cookies[:access_token]
85
+ end
86
+
87
+ def unset_access_token
88
+ # Logout
89
+ cookies[:access_token] = nil
90
+ end
91
+ end
92
+ ```
93
+
94
+ ## Usage
25
95
 
26
96
  ### Signup
27
97
 
28
98
  ```ruby
29
- # inside your signup interactor
30
- if digest = Blunt.signup(password, password_confirmation)
31
- # create user
99
+ # your signup interactor
100
+ class MySignupClass < Thalamus::Interactor
101
+ validations do
102
+ # password length, complexity, etc
103
+ # ...
104
+ end
105
+ def call
106
+ @user = Blunt.signup(params)
107
+ end
108
+ expose :user
109
+ end
110
+ # inside your signup controller
111
+ if user = MySignupClass.call(params).user
112
+ login!(user)
113
+ # ...
32
114
  else
33
- # trigger an error
115
+ # ...
34
116
  end
35
117
  ```
36
118
 
37
- Pretty straightforward: returns an encrypted password if the unencrypted inputs match, otherwise nil. You may want to validate the password first, e.g. minimum length.
119
+ `Blunt.signup` compares a `:password` and `:password_confirmation` field. If they match, it creates a new `User` and saves it to the database with a `:password_digest`. If they don't match, it returns nil.
120
+
121
+ If you don't use the interactor pattern, You can also use the shortcut `#signup!` directly from your controller. However, you may wish to validate user data and password complexity yourself.
122
+
123
+ ```ruby
124
+ # inside your signup controller
125
+ if signup!(params)
126
+ # Blunt calls #login! under the hood
127
+ # ...
128
+ else
129
+ # ...
130
+ end
131
+ ```
38
132
 
39
133
  ### Login
40
134
 
41
135
  ```ruby
42
136
  # inside your login controller
43
- token = Blunt.login(expected, attempted, claims)
137
+ token = login!(params)
138
+ # ...
44
139
  ```
45
140
 
46
- - `expected` is the user's encrypted password as stored in the database.
47
- - `attempted` is the unencrypted password attempt as sent by the client.
48
- - `claims` is a hash of JWT claims. It _must_ contain a `:sub` key whose value is any unique way to identify the user. You can also send optional JWT claims with the payload, such as `:exp`. Refer to the [ruby-jwt docs](https://github.com/jwt/ruby-jwt) for more information.
49
-
50
- If the passwords match and a `:sub` claim is present, a token will be generated for the claims. If the login attempt fails, the token will be nil. Have your controller return the token to the client and store it somewhere (cookies, local storage, etc).
141
+ `#login!` finds a user with `:email` and checks that `:password` is correct. Returns an access token if successful, otherwise nil.
51
142
 
52
143
  ### Request Authentication
53
144
 
54
- Pass the token from the client in a request header: `'HTTP_AUTHORIZATION' => 'Bearer <TOKEN>`.
55
-
56
- `include Blunt::Controller` in your controller class. `current_user` will memoize whatever is in `:sub` in the token's payload, or nil if there are any errors.
57
-
58
- If the hash of request headers is not at `request.env`, you will need to override `#_blunt_request_env` to return it. (This works out of the box for Rails and Hanami.)
145
+ All strategies provide `#current_user` (`User` entity or nil) and `#authenticated?` (Boolean) instance methods to the controller. The token will not be checked until one of these methods is called.
59
146
 
60
147
  ### Logout
61
148
 
62
- To logout, simply have the controller respond to the client with instructions to unset the token, wherever it is stored.
149
+ ```ruby
150
+ # inside your logout controller
151
+ logout!
152
+ # ...
153
+ ```
154
+
155
+ That's it!
data/TODO.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # TODO
2
2
 
3
- - Error handling
4
- - Defaults for important reserved claims (exp, iss, iat)
3
+ - Configurable (access & refresh expiry times, etc)
5
4
  - Devise-esque modules (rememberable, confirmable, reset password, etc)
6
- - Multiple credentials (Omniauth)
7
- - Adapters for common implementation patterns
5
+ - Separate credentials from user business concerns, multiple credentials (Omniauth)
6
+ - Increased security features (user agent checking, etc)
7
+ - Hard hanami-model dependency, or adapt gem to different stacks?
@@ -1,11 +1,11 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ $:.unshift(lib) unless $:.include?(lib)
4
4
  require "blunt/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "blunt"
8
- spec.version = Blunt::VERSION
8
+ spec.version = Blunt.version
9
9
  spec.authors = ["Josh Greenberg"]
10
10
  spec.email = ["joshgreenberg91@gmail.com"]
11
11
  spec.homepage = "https://github.com/joshgreenberg/blunt"
@@ -22,14 +22,9 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.15"
25
- spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_development_dependency "minitest", "~> 5.0"
27
- spec.add_development_dependency "minitest-reporters"
28
- spec.add_development_dependency "pry"
29
- spec.add_development_dependency "guard"
30
- spec.add_development_dependency "guard-minitest"
31
- spec.add_development_dependency "rake-notes"
25
+ spec.add_development_dependency "rake", "~> 12.0"
32
26
 
33
- spec.add_runtime_dependency "jwt"
34
- spec.add_runtime_dependency "bcrypt", ">= 3.1.11"
27
+ spec.add_dependency "jwt", "~> 2.0"
28
+ spec.add_dependency "bcrypt", "3.1.11"
29
+ spec.add_dependency "hanami-model", "~> 1.0"
35
30
  end
@@ -1,23 +1,10 @@
1
- require "blunt/version"
2
- require "blunt/token"
3
- require "blunt/controller"
4
-
5
1
  module Blunt
6
- extend self
7
-
8
- def new_secret(n = 64)
2
+ def self.new_secret(n = 64)
9
3
  require 'securerandom'
10
4
  SecureRandom.urlsafe_base64(n)
11
5
  end
12
-
13
- def signup(a, b)
14
- require 'bcrypt'
15
- BCrypt::Password.create(a) if a == b
16
- end
17
-
18
- def login(expected, attempted, claims = {})
19
- return unless claims[:sub]
20
- require 'bcrypt'
21
- Token.issue(claims) if BCrypt::Password.new(expected) == attempted
22
- end
23
6
  end
7
+
8
+ require 'blunt/signup'
9
+ require 'blunt/login'
10
+ require 'blunt/controller'
@@ -4,21 +4,59 @@ module Blunt
4
4
  module Controller
5
5
  private
6
6
 
7
- def current_user
8
- @current_user ||= Blunt::Token.decode(token)[:sub]
7
+ def signup!(params = {})
8
+ if user = Blunt.signup(params)
9
+ login!(user)
10
+ end
9
11
  end
10
12
 
11
- def logged_in?
12
- !!current_user
13
+ def login!(params = {})
14
+ if token = Blunt.login(params)
15
+ set_access_token(token.to_s)
16
+ else
17
+ unset_access_token
18
+ end
19
+ end
20
+
21
+ def logout!
22
+ access_token && access_token.invalidate!
23
+ @current_user = nil
24
+ unset_access_token
25
+ end
26
+
27
+ def authenticate!
28
+ redirect_to '/login' unless authenticated?
13
29
  end
14
30
 
15
- def token
16
- _blunt_request_env.fetch("HTTP_AUTHORIZATION", "").scan(/Bearer (.*)$/).flatten.last
31
+ def authenticated?
32
+ !!current_user
17
33
  end
18
34
 
19
- def _blunt_request_env
20
- request.env
35
+ def current_user
36
+ @current_user ||= begin
37
+ user = begin
38
+ if access_token && access_token.valid?
39
+ UserRepository.new.find(access_token.sub)
40
+ end
41
+ rescue
42
+ nil
43
+ end
44
+ set_access_token(access_token.to_s)
45
+ user
46
+ end
21
47
  end
22
48
 
23
- end
49
+ def access_token
50
+ @access_token ||= begin
51
+ encoded_jwt = get_access_token
52
+ if encoded_jwt.nil? || encoded_jwt.empty?
53
+ nil
54
+ else
55
+ Token.new(encoded_jwt)
56
+ end
57
+ end
58
+ end
59
+ end # module Controller
24
60
  end
61
+
62
+ require 'blunt/controller/web'
@@ -0,0 +1,24 @@
1
+ module Blunt
2
+ module Controller
3
+ module Web
4
+ include Controller
5
+
6
+ private
7
+
8
+ def set_access_token(token)
9
+ # Login or renew
10
+ cookies[:access_token] = token
11
+ end
12
+
13
+ def get_access_token
14
+ # Authenticate
15
+ cookies[:access_token]
16
+ end
17
+
18
+ def unset_access_token
19
+ # Logout
20
+ cookies[:access_token] = nil
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'bcrypt'
2
+
3
+ module Blunt
4
+ def self.login(params = {})
5
+ user = case params
6
+ when Hash
7
+ UserRepository.new.find_by_email(params[:email])
8
+ else
9
+ params
10
+ end
11
+ if user && BCrypt::Password.new(user.password_digest) == params[:password]
12
+ Token.create(user.id)
13
+ else
14
+ nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'bcrypt'
2
+
3
+ module Blunt
4
+ def self.signup(params = {})
5
+ password, password_confirmation = params[:password], params[:password_confirmation]
6
+ if password && password == password_confirmation
7
+ digest = BCrypt::Password.create(password)
8
+ params.delete(:password)
9
+ params.delete(:password_confirmation)
10
+ params[:password_digest] = digest
11
+ UserRepository.new.create(params)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
@@ -1,25 +1,137 @@
1
- require "json"
2
- require "jwt"
1
+ require 'jwt'
2
+ require 'openssl'
3
+ require 'base64'
3
4
 
4
5
  module Blunt
5
- module Token
6
- extend self
6
+ class Token
7
+ ALGORITHM = 'HS256'.freeze
8
+ EXPIRY_TIME = 3600.freeze
7
9
 
8
- ALGORITHM = 'HS256'
10
+ def initialize(token)
11
+ if token.is_a? String
12
+ @payload = begin
13
+ decode(token)
14
+ rescue
15
+ nil
16
+ end
17
+ else
18
+ @payload = {
19
+ sub: token.user_id,
20
+ jti: encrypt(token.id),
21
+ exp: token.created_at.to_i + EXPIRY_TIME
22
+ }
23
+ end
24
+ end
9
25
 
10
- def issue(claims)
11
- JWT.encode(claims, secret, ALGORITHM)
26
+ def self.create(sub)
27
+ new(repo.create(user_id: sub))
12
28
  end
13
29
 
14
- def decode(token)
15
- JWT.decode(token, secret, true, {algorithm: ALGORITHM}).first.map{|k,v|[k.to_sym,v]}.to_h
30
+ def inspect
31
+ "\#<Blunt::Token #{to_s}>"
32
+ end
33
+
34
+ def to_s
35
+ to_h ? encode : nil
36
+ end
37
+
38
+ def to_h
39
+ @payload
40
+ end
41
+
42
+ def sub
43
+ @payload[:sub]
44
+ end
45
+
46
+ def valid?
47
+ if @payload.nil?
48
+ # Invalid token format
49
+ false
50
+ elsif @payload[:exp].to_i <= Time.now.to_i
51
+ # Access token expired
52
+ record = repo.find(jti)
53
+ if record && record.valid?
54
+ # Refresh token valid
55
+ @payload[:exp] = Time.now.to_i + EXPIRY_TIME
56
+ repo.update(record, {}) if record.respond_to?(:updated_at)
57
+ @encoded = nil
58
+ encode
59
+ else
60
+ # Refresh token expired
61
+ false
62
+ end
63
+ else
64
+ # Access token valid
65
+ true
66
+ end
67
+ end
68
+
69
+ def invalidate!
70
+ # Destroy refresh token
71
+ repo.delete(jti)
72
+ rescue
73
+ nil
74
+ ensure
75
+ @payload = nil
16
76
  end
17
77
 
18
78
  private
19
79
 
80
+ def repo
81
+ self.class.repo
82
+ end
83
+
84
+ def self.repo
85
+ @repo ||= RefreshTokenRepository.new
86
+ end
87
+
88
+ def jti
89
+ decrypt(@payload[:jti])
90
+ end
91
+
20
92
  def secret
21
- ENV["BLUNT_SECRET"]
93
+ @secret ||= ENV['BLUNT_SECRET']
94
+ end
95
+
96
+ def encode
97
+ @encoded ||= JWT::Encode.new(@payload, secret, ALGORITHM, {}).segments
22
98
  end
23
99
 
24
- end
100
+ def decode(token)
101
+ raise JWT::DecodeError, 'Nil JSON web token' unless token
102
+ decoder = JWT::Decode.new(token, true)
103
+ header, payload, signature, signing_input = decoder.decode_segments
104
+
105
+ # Verify signature
106
+ JWT.decode_verify_signature(secret, header, payload, signature, signing_input, algorithm: ALGORITHM)
107
+
108
+ # Verify JWT format
109
+ raise JWT::DecodeError, 'Incorrect number of segments' unless header && payload
110
+ payload = payload.map{ |k,v| [k.to_sym, v] }.to_h
111
+
112
+ # Verify Blunt format
113
+ raise JWT::DecodeError, 'JWT claims do not meet Blunt specifications' unless payload.key?(:sub) && payload.key?(:exp) && payload.key?(:jti)
114
+
115
+ payload
116
+ end
117
+
118
+ # Two-way encryption methods for JTI
119
+ def encrypt(string)
120
+ cipher = OpenSSL::Cipher::AES256.new(:CBC)
121
+ cipher.encrypt
122
+ cipher.key = Digest::SHA256.digest(secret)
123
+ cipher.iv = iv = cipher.random_iv
124
+ encrypted = iv + cipher.update(string.to_s) + cipher.final
125
+ Base64.encode64(encrypted).gsub(/\s/, '')
126
+ end
127
+
128
+ def decrypt(encrypted)
129
+ string = Base64.decode64(encrypted)
130
+ cipher = OpenSSL::Cipher::AES256.new(:CBC)
131
+ cipher.decrypt
132
+ cipher.key = Digest::SHA256.digest(secret)
133
+ cipher.iv = string.slice!(0, 16)
134
+ cipher.update(string) + cipher.final
135
+ end
136
+ end # class Token
25
137
  end
@@ -1,3 +1,14 @@
1
1
  module Blunt
2
- VERSION = "0.0.2"
2
+ def self.version
3
+ Gem::Version.new VERSION::STRING
4
+ end
5
+
6
+ module VERSION
7
+ MAJOR = 0
8
+ MINOR = 1
9
+ TINY = 0
10
+ PRE = nil
11
+
12
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
13
+ end
3
14
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blunt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Greenberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-04 00:00:00.000000000 Z
11
+ date: 2017-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,126 +30,56 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '12.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: jwt
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
48
- type: :development
47
+ version: '2.0'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.0'
54
+ version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: minitest-reporters
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: guard
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: guard-minitest
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rake-notes
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: jwt
56
+ name: bcrypt
127
57
  requirement: !ruby/object:Gem::Requirement
128
58
  requirements:
129
- - - ">="
59
+ - - '='
130
60
  - !ruby/object:Gem::Version
131
- version: '0'
61
+ version: 3.1.11
132
62
  type: :runtime
133
63
  prerelease: false
134
64
  version_requirements: !ruby/object:Gem::Requirement
135
65
  requirements:
136
- - - ">="
66
+ - - '='
137
67
  - !ruby/object:Gem::Version
138
- version: '0'
68
+ version: 3.1.11
139
69
  - !ruby/object:Gem::Dependency
140
- name: bcrypt
70
+ name: hanami-model
141
71
  requirement: !ruby/object:Gem::Requirement
142
72
  requirements:
143
- - - ">="
73
+ - - "~>"
144
74
  - !ruby/object:Gem::Version
145
- version: 3.1.11
75
+ version: '1.0'
146
76
  type: :runtime
147
77
  prerelease: false
148
78
  version_requirements: !ruby/object:Gem::Requirement
149
79
  requirements:
150
- - - ">="
80
+ - - "~>"
151
81
  - !ruby/object:Gem::Version
152
- version: 3.1.11
82
+ version: '1.0'
153
83
  description: Token authentication
154
84
  email:
155
85
  - joshgreenberg91@gmail.com
@@ -166,10 +96,12 @@ files:
166
96
  - Rakefile
167
97
  - TODO.md
168
98
  - bin/console
169
- - bin/setup
170
99
  - blunt.gemspec
171
100
  - lib/blunt.rb
172
101
  - lib/blunt/controller.rb
102
+ - lib/blunt/controller/web.rb
103
+ - lib/blunt/login.rb
104
+ - lib/blunt/signup.rb
173
105
  - lib/blunt/token.rb
174
106
  - lib/blunt/version.rb
175
107
  homepage: https://github.com/joshgreenberg/blunt
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