devise_token_auth 0.1.13 → 0.1.14

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: 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