devise_token_auth 0.1.13 → 0.1.14

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
2
  SHA1:
3
- metadata.gz: 834e6565e101d247f0c8941ebc6d3c9a5aeb57fb
4
- data.tar.gz: e0d5fec0bbf44b1d96bf0f91bdc2b525e5e87d02
3
+ metadata.gz: 85dbd55f7d4269a7061c016208d1fbb7c106f190
4
+ data.tar.gz: 3eefd3ae437e88ca1a7960556e4a9708c29b3cad
5
5
  SHA512:
6
- metadata.gz: 6f5d97700e9940fb9a6fe382efe248f2bb26332d1c232c563fb944e968adceeb122e77ca2a26bce437965980a56bfd6d7a7639e0e5d7475a990e0f614dfb4dbe
7
- data.tar.gz: c5cd9381e88c4cd41213967e2249a4fa4a6292a8b598e37a46c8a2562df4ef204f784417732ecb3751ab3a178ba5868b479f4b309b3fc35cd3846c7ab684cafe
6
+ metadata.gz: 8c8f4f54d6701084ffcbcd7217de162b7cb2b999e2d18fbddc65a24cca83f956ccf63900c052b3b253da1ee367f60339315745e4e7e306e9ba66851d3b15e527
7
+ data.tar.gz: 0e5002e2584cef474734197b538266643dfabfa9c28e84ca6a60b9b8770e47b22886f7224e816534204c04ec150bc14a72e85be76a567b78faded45ed1359d07
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Devise Token Auth
2
+
3
+ ![build](https://travis-ci.org/lynndylanhurley/devise_token_auth.svg)
4
+
5
+ This gem provides simple, secure token based authentication.
6
+
7
+ This gem was designed to work with the venerable [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) module for [angular.js](https://github.com/angular/angular.js).
8
+
9
+ # Demo
10
+
11
+ [Here is a demo](http://ng-token-auth-demo.herokuapp.com/) of this app running with the [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) module.
12
+
13
+ The fully configured api used in the demo can be found [here](https://github.com/lynndylanhurley/devise_token_auth_demo).
14
+
15
+ # Dependencies
16
+ This project leverages the following gems:
17
+
18
+ * [Devise](https://github.com/plataformatec/devise)
19
+ * [Omniauth](https://github.com/intridea/omniauth)
20
+
21
+ # Installation
22
+ Add the following to your `Gemfile`:
23
+
24
+ ~~~ruby
25
+ gem devise_token_auth
26
+ ~~~
27
+
28
+ Then install the gem using bundle:
29
+
30
+ ~~~bash
31
+ bundle install
32
+ ~~~
33
+
34
+ ## Migrations
35
+ You will need to create a user model. Run the following to generate and run the `User` model migration:
36
+
37
+ ~~~bash
38
+ rake devise_token_auth:install:migrations
39
+ ~~~
40
+
41
+ Then run the migration:
42
+
43
+ ~~~bash
44
+ rake db:migrate
45
+ ~~~
46
+
47
+ ## Omniauth authentication
48
+
49
+ If you wish to use omniauth authentication, add all of your desired authentication provider gems as well.
50
+
51
+ ##### Omniauth example using github, facebook, and google:
52
+ ~~~ruby
53
+ gem 'omniauth-github', :git => 'git://github.com/intridea/omniauth-github.git'
54
+ gem 'omniauth-facebook', :git => 'git://github.com/mkdynamic/omniauth-facebook.git'
55
+ gem 'omniauth-google-oauth2', :git => 'git://github.com/zquestz/omniauth-google-oauth2.git'
56
+ ~~~
57
+
58
+ Then run `bundle install`.
59
+
60
+ [List of oauth2 providers](https://github.com/intridea/omniauth/wiki/List-of-Strategies)
61
+
62
+ #### Provider settings
63
+ In `config/initializers/omniauth.rb`, add the settings for each of your providers.
64
+
65
+ These settings must be obtained from the providers themselves.
66
+
67
+ ##### Example using github, facebook, and google:
68
+ ~~~ruby
69
+ # config/initializers/omniauth.rb
70
+ Rails.application.config.middleware.use OmniAuth::Builder do
71
+ provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET'], scope: 'email,profile'
72
+ provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
73
+ provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET']
74
+ end
75
+ ~~~
76
+
77
+ The above example assumes that your provider keys and secrets are stored in environmental variables. Use the [figaro](https://github.com/laserlemon/figaro) gem (or [dotenv](https://github.com/bkeepers/dotenv) or [secrets.yml](https://github.com/rails/rails/blob/v4.1.0/railties/lib/rails/generators/rails/app/templates/config/secrets.yml) or equivalent) to accomplish this.
78
+
79
+
80
+ **Note for [pow](http://pow.cx/) and [xip.io](http://xip.io) users**: if you receive `redirect-uri-mismatch` errors from your provider when using pow or xip.io urls, set the following in your development config:
81
+
82
+ ~~~ruby
83
+ # config/environments/development.rb
84
+
85
+ # when using pow
86
+ OmniAuth.config.full_host = "http://app-name.dev"
87
+
88
+ # when using xip.io
89
+ OmniAuth.config.full_host = "http://xxx.xxx.xxx.app-name.xip.io"
90
+ ~~~
91
+
92
+ There may be a better way to accomplish this. Please post an issue if you have any suggestions.
93
+
94
+ ## Email authentication
95
+ If you wish to use email authentication, you must configure your Rails application to send email. [Read here](http://guides.rubyonrails.org/action_mailer_basics.html) for more information.
96
+
97
+ I recommend using [mailcatcher](http://mailcatcher.me/) for development.
98
+
99
+ ##### mailcatcher development example configuration:
100
+ ~~~ruby
101
+ # config/environments/development.rb
102
+ Rails.application.configure do
103
+ config.action_mailer.default_url_options = { :host => 'your-dev-host.dev' }
104
+ config.action_mailer.delivery_method = :smtp
105
+ config.action_mailer.smtp_settings = { :address => 'your-dev-host.dev', :port => 1025 }
106
+ end
107
+ ~~~
108
+
109
+ ## Routes
110
+
111
+ The authentication routes must be mounted to your project.
112
+
113
+ In `config/routes.rb`, add the following line:
114
+
115
+ ~~~ruby
116
+ # config/routes.rb
117
+ mount DeviseTokenAuth::Engine => "/auth"
118
+ ~~~
119
+
120
+ Note that you can mount this engine to any route that you like. `/auth` is used to conform to the defaults of the [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) module.
121
+
122
+ ## CORS
123
+
124
+ If your API and client live on different domains, you will need to configure your Rails API to allow cross origin requests. The [rack-cors](https://github.com/cyu/rack-cors) gem can be used to accomplish this.
125
+
126
+ The following example will allow cross domain requests from any domain.
127
+
128
+ ##### Example rack-cors configuration:
129
+ ~~~ruby
130
+ # gemfile
131
+ gem 'rack-cors', :require => 'rack/cors'
132
+
133
+ # config/application.rb
134
+ module YourApp
135
+ class Application < Rails::Application
136
+ config.middleware.use Rack::Cors do
137
+ allow do
138
+ origins '*'
139
+ resource '*',
140
+ :headers => :any,
141
+ :expose => ['Authorization'], # <-- important!
142
+ :methods => [:get, :post, :options, :delete, :put]
143
+ end
144
+ end
145
+ end
146
+ end
147
+ ~~~
148
+
149
+ Make extra sure that the `Access-Control-Expose-Headers` includes `Authorization` (as is set in the example above by the`:expose` param). If your client experiences erroneous 401 responses, this is likely the cause.
150
+
151
+ CORS may not be possible with older browsers (IE8, IE9). I usually set up a proxy for those browsers. See the [ng-token-auth readme](https://github.com/lynndylanhurley/ng-token-auth) for more information.
152
+
153
+ # Usage
154
+
155
+ The following routes are available for use by your client. These routes live relative to the path at which this engine is mounted (`/auth` in the example above).
156
+
157
+ | path | method | purpose |
158
+ |:-----|:-------|:--------|
159
+ | / | POST | email registration. accepts **email**, **password**, and **password_confirmation** params. |
160
+ | /sign_in | POST | email authentication. accepts **email** and **password** as params. |
161
+ | /sign_out | DELETE | invalidate tokens (end session) |
162
+ | /:provider | GET | set this route as the destination for client authentication. ideally this will happen in an external window or popup. |
163
+ | /:provider/callback | GET/POST | destination for the oauth2 provider's callback uri. `postMessage` events containing the authenticated user's data will be sent back to the main client window from this page. |
164
+ | /validate_token | POST | use this route to validate tokens on return visits to the client. accepts **uid** and **auth_token** as params. these values should correspond to the columns in your `User` table of the same names. |
165
+
166
+ If you're using [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) for angular.js, then your client is ready to go.
167
+
168
+ ## Identifying users in controllers
169
+
170
+ The authentication information should be included by the client in the `Authorization` header of each request. The header should follow this format:
171
+
172
+ ~~~
173
+ token=xxxxx client=yyyyy uid=zzzzz
174
+ ~~~
175
+
176
+ Replace `xxxxx` with the user's `auth_token` and `zzzzz` with the user's `uid`. The `client` field exists to allow for multiple simultaneous sessions per user. The client field defaults to `default` if omitted.
177
+
178
+ This all happens effortlessly and invisibly when using [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth).
179
+
180
+ ### DeviseTokenAuth::Concerns::SetUserByToken
181
+
182
+ This gem includes a [Rails concern](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html) that can be used to identify users by the `Authorization` header.
183
+
184
+ This concern runs a [before_action](http://guides.rubyonrails.org/action_controller_overview.html#filters), setting the `@user` variable for use in your controllers. The user will be signed in via devise for the duration of the request.
185
+
186
+ The concern also runs an [after_action](http://guides.rubyonrails.org/action_controller_overview.html#filters) that changes the auth token after each request.
187
+
188
+ It is recommended to include the concern in your base `ApplicationController` so that all children of that controller include the concern as well.
189
+
190
+ ~~~ruby
191
+ # app/controllers/application_controller.rb
192
+ class ApplicationController < ActionController::Base
193
+ include DeviseTokenAuth::Concerns::SetUserByToken
194
+ end
195
+
196
+ # app/controllers/test_controller.rb
197
+ class TestController < ApplicationController
198
+ def members_only
199
+ if @user
200
+ render json: {
201
+ data: {
202
+ message: "Welcome #{@user.name}",
203
+ user: @user
204
+ }
205
+ }, status: 200
206
+ else
207
+ render json: {
208
+ errors: ["Authorized users only."]
209
+ }, status: 401
210
+ end
211
+ end
212
+ end
213
+ ~~~
214
+
215
+ # Security
216
+
217
+ This gem takes the following steps to ensure security.
218
+
219
+ This gem uses auth tokens that are:
220
+ * changed after every request,
221
+ * [of cryptographic strength](http://ruby-doc.org/stdlib-2.1.0/libdoc/securerandom/rdoc/SecureRandom.html),
222
+ * hashed using [BCrypt](https://github.com/codahale/bcrypt-ruby) (not stored in plain-text),
223
+ * securely compared (to protect against timing attacks),
224
+ * invalidated after 2 weeks
225
+
226
+ These measures were inspired by [this stackoverflow post](http://stackoverflow.com/questions/18605294/is-devises-token-authenticatable-secure).
227
+
228
+ This gem further mitigates timing attacks by using [this technique](https://gist.github.com/josevalim/fb706b1e933ef01e4fb6).
229
+
230
+ But the most important step is to use HTTPS. You are on the hook for that.
231
+
232
+ # TODO
233
+
234
+ * Write tests
235
+ * `User` model is currently baked into this gem. Allow for dynamic definition using concerns (or other means).
236
+ * Find a way to expose devise + omniauth configs, maybe using generators.
237
+
238
+ # Contributing
239
+ Just send a pull request. I will grant you commit access if you send quality pull requests.
240
+
241
+ Guidelines will be posted if the need arises.
242
+
243
+ # License
244
+ This project uses the WTFPL
@@ -4,16 +4,17 @@ module DeviseTokenAuth
4
4
 
5
5
  def show
6
6
  @user = User.confirm_by_token(params[:confirmation_token])
7
- if @user
7
+
8
+ if @user.id
8
9
  # create client id
9
10
  client_id = SecureRandom.urlsafe_base64(nil, false)
10
-
11
11
  token = SecureRandom.urlsafe_base64(nil, false)
12
12
  token_hash = BCrypt::Password.create(token)
13
13
  @user.tokens[client_id] = {
14
14
  token: token_hash,
15
15
  expiry: Time.now + 2.weeks
16
16
  }
17
+
17
18
  @user.save!
18
19
 
19
20
  redirect_to generate_url(@user.confirm_success_url, {
@@ -11,18 +11,27 @@ module DeviseTokenAuth
11
11
  @resource.uid = resource_params[:email]
12
12
  @resource.provider = "email"
13
13
 
14
- if @resource.save
15
- render json: {
16
- status: 'success',
17
- data: @resource.as_json
18
- }
19
- else
14
+ begin
15
+ if @resource.save
16
+ render json: {
17
+ status: 'success',
18
+ data: @resource.as_json
19
+ }
20
+ else
21
+ clean_up_passwords @resource
22
+ render json: {
23
+ status: 'error',
24
+ data: @resource,
25
+ errors: ["An account already exists for #{@resource.email}"]
26
+ }, status: 403
27
+ end
28
+ rescue ActiveRecord::RecordNotUnique
20
29
  clean_up_passwords @resource
21
- render status: 403, json: {
30
+ render json: {
22
31
  status: 'error',
23
- data: @resource.as_json,
24
- errors: @resource.errors.full_messages
25
- }
32
+ data: @resource,
33
+ errors: ["An account already exists for #{@resource.email}"]
34
+ }, status: 403
26
35
  end
27
36
  end
28
37
 
@@ -8,17 +8,9 @@ module DeviseTokenAuth
8
8
  respond_to :json
9
9
 
10
10
  def create
11
- resource = User.find_by_email(resource_params[:email])
12
-
13
- unless resource and valid_params? and resource.valid_password?(params[:password])
14
- render json: {
15
- success: false,
16
- errors: ["Invalid login credentials. Please try again."]
17
- }, status: 401
18
-
19
- else
20
- @user = resource
11
+ @user = User.find_by_email(resource_params[:email])
21
12
 
13
+ if @user and valid_params? and @user.valid_password?(resource_params[:password]) and @user.confirmed?
22
14
  # create client id
23
15
  @client_id = SecureRandom.urlsafe_base64(nil, false)
24
16
  @token = SecureRandom.urlsafe_base64(nil, false)
@@ -31,8 +23,24 @@ module DeviseTokenAuth
31
23
 
32
24
  render json: {
33
25
  success: true,
34
- data: resource.as_json
26
+ data: @user.as_json
35
27
  }
28
+
29
+ elsif @user and not @user.confirmed?
30
+ render json: {
31
+ success: false,
32
+ errors: [
33
+ "A confirmation email was sent to your account at #{@user.email}. "+
34
+ "You must follow the instructions in the email before your account "+
35
+ "can be activated"
36
+ ]
37
+ }, status: 401
38
+
39
+ else
40
+ render json: {
41
+ success: false,
42
+ errors: ["Invalid login credentials. Please try again."]
43
+ }, status: 401
36
44
  end
37
45
  end
38
46
 
@@ -45,7 +53,7 @@ module DeviseTokenAuth
45
53
  end
46
54
 
47
55
  def valid_params?
48
- params[:password] && params[:email]
56
+ resource_params[:password] && resource_params[:email]
49
57
  end
50
58
 
51
59
  def resource_params
data/app/models/user.rb CHANGED
@@ -9,6 +9,7 @@ class User < ActiveRecord::Base
9
9
 
10
10
  # only validate unique emails among email registration users
11
11
  validates_uniqueness_of :email, conditions: -> { where(provider: 'email') }
12
+ validates_presence_of :confirm_success_url
12
13
 
13
14
  def valid_token?(client_id, token)
14
15
  return false unless self.tokens[client_id]['expiry'] > 2.weeks.ago
@@ -1,3 +1,3 @@
1
1
  module DeviseTokenAuth
2
- VERSION = "0.1.13"
2
+ VERSION = "0.1.14"
3
3
  end
@@ -0,0 +1,53 @@
1
+ require 'test_helper'
2
+
3
+ # was the web request successful?
4
+ # was the user redirected to the right page?
5
+ # was the user successfully authenticated?
6
+ # was the correct object stored in the response?
7
+ # was the appropriate message delivered in the json payload?
8
+
9
+ class DeviseTokenAuth::ConfirmationsControllerTest < ActionController::TestCase
10
+ describe DeviseTokenAuth::ConfirmationsController, "Confirmation" do
11
+ fixtures :users
12
+
13
+ before do
14
+ @new_user = users(:unconfirmed_email_user)
15
+ @new_user.send_confirmation_instructions
16
+ @mail = ActionMailer::Base.deliveries.last
17
+ @token = @mail.body.match(/confirmation_token=(.*)\"/)[1]
18
+ end
19
+
20
+ test 'should generate raw token' do
21
+ assert @token
22
+ end
23
+
24
+ test "should store token hash in user" do
25
+ assert @new_user.confirmation_token
26
+ end
27
+
28
+ describe "success" do
29
+ before do
30
+ xhr :get, :show, {confirmation_token: @token}
31
+ @user = assigns(:user)
32
+ end
33
+
34
+ test "user should now be confirmed" do
35
+ assert @user.confirmed?
36
+ end
37
+
38
+ test "should redirect to success url" do
39
+ assert_redirected_to(/^#{@user.confirm_success_url}/)
40
+ end
41
+ end
42
+
43
+ describe "failure" do
44
+ test "user should not be confirmed" do
45
+ assert_raises(ActionController::RoutingError) {
46
+ xhr :get, :show, {confirmation_token: "bogus"}
47
+ }
48
+ @user = assigns(:user)
49
+ refute @user.confirmed?
50
+ end
51
+ end
52
+ end
53
+ end