devise_token_auth 0.1.18 → 0.1.19.alpha1

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: f24edb4af4fee31bc5c77072b92812bbe0cb2129
4
- data.tar.gz: ea93c5bf5a750cd737eb52fac07f4b9226ecc31a
3
+ metadata.gz: 083650c6db3ad913ee66ea89099e0e2d96329750
4
+ data.tar.gz: ad8d32dbeae4612f739df26ad4f13e23c740faf5
5
5
  SHA512:
6
- metadata.gz: b7826b9eb84c15688f007dabc52ed76c8e430d821db75417ce92e79724f4ac06195c7da28932b775143a4638be93505cc7d6211055e5180ee01f79a177b6f75c
7
- data.tar.gz: c34c3a6378dd57ac8d0cb715fcbbd4ec4a44cf8385b8856b31c470603023221d6ba77941791b46b58d9a6515309c528310c662d3522a6dcd8b723f6731b63f05
6
+ metadata.gz: dd7d2c7bb0dc38c4eecd86c5a61131d44a17359146ec6bfcf114edccc815de1130a747f9b4730b4aeac1e54287fffb3cc7b0f8ea33a738d87b84644394f0f24f
7
+ data.tar.gz: dc2c50e3798e379b1d184f509d82820b32259d5d1f94e7ec2b7b8a33b5e0a3ded0dd86150cf6b435cd289b03a002a18bf0dff724a59439f8c1614e7d1b3e3513
data/README.md CHANGED
@@ -162,12 +162,15 @@ The following routes are available for use by your client. These routes live rel
162
162
 
163
163
  | path | method | purpose |
164
164
  |:-----|:-------|:--------|
165
- | / | POST | email registration. accepts **email**, **password**, and **password_confirmation** params. |
166
- | /sign_in | POST | email authentication. accepts **email** and **password** as params. |
165
+ | / | POST | email registration. accepts **`email`**, **`password`**, and **`password_confirmation`** params. |
166
+ | /sign_in | POST | email authentication. accepts **`email`** and **`password`** as params. |
167
167
  | /sign_out | DELETE | invalidate tokens (end session) |
168
168
  | /:provider | GET | set this route as the destination for client authentication. ideally this will happen in an external window or popup. |
169
169
  | /: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. |
170
- | /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. |
170
+ | /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. |
171
+ | /password | POST | send password email to users that registered by email. accepts **`email`** and **`redirect_url`** as params. The user matching the `email` param will be sent instructions on how to reset their password. `redirect_url` is the url to which the user will be redirected after visiting the link contained in the email. |
172
+ | /password | PUT | password change for users that registered by email. accepts **`password`** and **`password_confirmation`** as params. |
173
+ | /password/edit | GET | verify user by password reset token. must contain **`reset_password_token`** and **`redirect_url`** as params. These values will be set automatically by the confirmation email that is generated by the password reset request. |
171
174
 
172
175
  If you're using [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) for angular.js, then your client is ready to go.
173
176
 
@@ -190,6 +193,9 @@ The `Authorization` header is made up of the following components:
190
193
 
191
194
  The `Authorization` header required for each request will be available in the response from the previous request. If you are using the [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth) module for angular.js, this functionality is already provided.
192
195
 
196
+ ## Handling batch requests
197
+
198
+ Sometimes it's necessary to send several concurrent requests to the API. In these cases, the concurrent requests will need to share the same auth token (tokens are usually changed after each request). [Read here](https://github.com/lynndylanhurley/ng-token-auth#about-batch-requests) for an overview on how this gem deals with batch requests.
193
199
 
194
200
  ## The `User` model
195
201
 
@@ -40,18 +40,21 @@ module DeviseTokenAuth::Concerns::SetUserByToken
40
40
 
41
41
 
42
42
  def update_auth_header
43
+ # cannot save object if model has invalid params
44
+ return unless @user and @user.valid? and @client_id
43
45
 
44
46
  auth_header = nil
47
+
45
48
  if not DeviseTokenAuth.change_headers_on_each_request
46
49
  auth_header = @user.build_auth_header(@token, @client_id)
47
50
 
48
51
  # extend expiration of batch buffer to account for the duration of
49
52
  # this request
50
- elsif @is_batch_request and @client_id and @user
53
+ elsif @is_batch_request
51
54
  auth_header = @user.extend_batch_buffer(@token, @client_id)
52
55
 
53
56
  # update Authorization response header with new token
54
- elsif @user and @client_id
57
+ else
55
58
  auth_header = @user.create_new_auth_token(@client_id)
56
59
  end
57
60
 
@@ -5,32 +5,27 @@ module DeviseTokenAuth
5
5
  def show
6
6
  @user = User.confirm_by_token(params[:confirmation_token])
7
7
 
8
- if @user.id
8
+ if @user and @user.id
9
9
  # create client id
10
10
  client_id = SecureRandom.urlsafe_base64(nil, false)
11
11
  token = SecureRandom.urlsafe_base64(nil, false)
12
12
  token_hash = BCrypt::Password.create(token)
13
+ expiry = Time.now + DeviseTokenAuth.token_lifespan
14
+
13
15
  @user.tokens[client_id] = {
14
16
  token: token_hash,
15
- expiry: Time.now + DeviseTokenAuth.token_lifespan
17
+ expiry: expiry
16
18
  }
17
19
 
18
20
  @user.save!
19
21
 
20
- redirect_to generate_url(@user.confirm_success_url, {
22
+ redirect_to(@user.build_auth_url(@user.confirm_success_url, {
21
23
  token: token,
22
- client_id: client_id,
23
- uid: @user.uid
24
- })
24
+ client_id: client_id
25
+ }))
25
26
  else
26
27
  raise ActionController::RoutingError.new('Not Found')
27
28
  end
28
29
  end
29
-
30
- def generate_url(url, params = {})
31
- uri = URI(url)
32
- uri.query = params.to_query
33
- uri.to_s
34
- end
35
30
  end
36
31
  end
@@ -1,53 +1,141 @@
1
1
  module DeviseTokenAuth
2
2
  class PasswordsController < Devise::PasswordsController
3
3
  include Devise::Controllers::Helpers
4
+ include DeviseTokenAuth::Concerns::SetUserByToken
4
5
 
6
+ skip_before_filter :set_user_by_token, :only => [:create, :edit]
7
+ skip_after_filter :update_auth_header, :only => [:create, :edit]
8
+
9
+ # this action is responsible for generating password reset tokens and
10
+ # sending emails
5
11
  def create
6
- self.resource = resource_class.send_reset_password_instructions(resource_params)
7
- yield resource if block_given?
12
+ unless resource_params[:email]
13
+ return render json: {
14
+ success: false,
15
+ errors: ['You must provide an email address.']
16
+ }, status: 401
17
+ end
18
+
19
+ unless resource_params[:redirect_url]
20
+ return render json: {
21
+ success: false,
22
+ errors: ['Missing redirect url.']
23
+ }, status: 401
24
+ end
8
25
 
9
- throw "Not implemented"
26
+ @user = User.where({
27
+ email: resource_params[:email],
28
+ provider: 'email'
29
+ }).first
10
30
 
11
- if resource.errors.empty?
12
- render json: {
13
- success: true
14
- }
31
+ errors = nil
32
+
33
+ if @user
34
+ @user.update_attributes({
35
+ reset_password_redirect_url: resource_params[:redirect_url]
36
+ })
37
+
38
+ @user = User.send_reset_password_instructions({
39
+ email: resource_params[:email],
40
+ provider: 'email'
41
+ })
42
+
43
+ if @user.errors.empty?
44
+ render json: {
45
+ success: true,
46
+ message: "An email has been sent to #{@user.email} containing "+
47
+ "instructions for resetting your password."
48
+ }
49
+ else
50
+ errors = @user.errors
51
+ end
15
52
  else
53
+ errors = ["Unable to find user with email '#{resource_params[:email]}'."]
54
+ end
55
+
56
+ if errors
16
57
  render json: {
17
58
  success: false,
18
- errors: ["Something went wrong. Please contact support@healthbox.com."]
19
- }, status: 401
59
+ errors: errors
60
+ }, status: 400
20
61
  end
21
62
  end
22
63
 
23
64
 
65
+ # this is where users arrive after visiting the email confirmation link
66
+ def edit
67
+ @user = User.reset_password_by_token({
68
+ reset_password_token: resource_params[:reset_password_token]
69
+ })
70
+
71
+ if @user and @user.id
72
+ client_id = SecureRandom.urlsafe_base64(nil, false)
73
+ token = SecureRandom.urlsafe_base64(nil, false)
74
+ token_hash = BCrypt::Password.create(token)
75
+ expiry = (Time.now + DeviseTokenAuth.token_lifespan).to_i
76
+
77
+ @user.tokens[client_id] = {
78
+ token: token_hash,
79
+ expiry: expiry
80
+ }
81
+
82
+ @user.save!
83
+
84
+ redirect_to(@user.build_auth_url(resource_params[:redirect_url], {
85
+ token: token,
86
+ client_id: client_id,
87
+ reset_password: true
88
+ }))
89
+ else
90
+ raise ActionController::RoutingError.new('Not Found')
91
+ end
92
+ end
93
+
24
94
  def update
25
- self.resource = resource_class.reset_password_by_token(resource_params)
26
- yield resource if block_given?
95
+ # make sure user is authorized
96
+ unless @user
97
+ return render json: {
98
+ success: false,
99
+ errors: ['Unauthorized']
100
+ }, status: 401
101
+ end
27
102
 
28
- throw "Not implemented"
103
+ # make sure account doesn't use oauth2 provider
104
+ unless @user.provider == 'email'
105
+ return render json: {
106
+ success: false,
107
+ errors: ["This account does not require a password. Sign in using "+
108
+ "your #{@user.provider.humanize} account instead."]
109
+ }, status: 422
110
+ end
29
111
 
30
- if resource.errors.empty?
31
- resource.unlock_access! if unlockable?(resource)
112
+ # ensure that password params were sent
113
+ unless resource_params[:password] and resource_params[:password_confirmation]
114
+ return render json: {
115
+ success: false,
116
+ errors: ['You must fill out the fields labeled "password" and "password confirmation".']
117
+ }, status: 422
118
+ end
32
119
 
33
- render json: {
120
+ if @user.update_attributes(resource_params)
121
+ return render json: {
34
122
  success: true,
35
123
  data: {
36
- user: self.resource
124
+ user: @user,
125
+ message: "Your password has been successfully updated."
37
126
  }
38
127
  }
39
128
  else
40
- render json: {
129
+ return render json: {
41
130
  success: false,
42
- errors: ["Something went wrong. Please contact support@healthbox.com."]
43
- }, status: 401
131
+ errors: @user.errors
132
+ }, status: 422
44
133
  end
45
134
  end
46
135
 
47
136
 
48
137
  def resource_params
49
- params.permit(:email, :password, :password_confirmation, :reset_password_token)
138
+ params.permit(:email, :password, :password_confirmation, :reset_password_token, :redirect_url)
50
139
  end
51
-
52
140
  end
53
141
  end
@@ -82,6 +82,21 @@ class User < ActiveRecord::Base
82
82
  end
83
83
 
84
84
 
85
+ def build_auth_url(base_url, args)
86
+ args[:uid] = self.uid
87
+ args[:expiry] = self.tokens[args[:client_id]]['expiry']
88
+
89
+ generate_url(base_url, args)
90
+ end
91
+
92
+
93
+ def generate_url(url, params = {})
94
+ uri = URI(url)
95
+ uri.query = params.to_query
96
+ uri.to_s
97
+ end
98
+
99
+
85
100
  def extend_batch_buffer(token, client_id)
86
101
  self.tokens[client_id]['updated_at'] = Time.now
87
102
  self.save!
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>Someone has requested a link to change your password. You can do this through the link below.</p>
4
4
 
5
- <p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
5
+ <p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token, redirect_url: @resource.reset_password_redirect_url) %></p>
6
6
 
7
7
  <p>If you didn't request this, please ignore this email.</p>
8
8
  <p>Your password won't change until you access the link above and create a new one.</p>
@@ -1,3 +1,3 @@
1
1
  module DeviseTokenAuth
2
- VERSION = "0.1.18"
2
+ VERSION = "0.1.19.alpha1"
3
3
  end
@@ -8,6 +8,7 @@ class DeviseTokenAuthCreateUsers < ActiveRecord::Migration
8
8
  ## Recoverable
9
9
  t.string :reset_password_token
10
10
  t.datetime :reset_password_sent_at
11
+ t.string :reset_password_redirect_url
11
12
 
12
13
  ## Rememberable
13
14
  t.datetime :remember_created_at
@@ -19,9 +19,9 @@ class DemoControllerTest < ActionController::TestCase
19
19
 
20
20
  @auth_header = @user.create_new_auth_token
21
21
 
22
- @token = @auth_header[/token=(.*?) /,1]
23
- @client_id = @auth_header[/client=(.*?) /,1]
24
- @expiry = @auth_header[/expiry=(.*?) /,1]
22
+ @token = @auth_header[/token=(.*?) /,1]
23
+ @client_id = @auth_header[/client=(.*?) /,1]
24
+ @expiry = @auth_header[/expiry=(.*?) /,1]
25
25
  end
26
26
 
27
27
  describe 'successful request' do
@@ -0,0 +1,167 @@
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::PasswordsControllerTest < ActionController::TestCase
10
+ describe DeviseTokenAuth::PasswordsController, "Password reset" do
11
+ fixtures :users
12
+
13
+ before do
14
+ @user = users(:confirmed_email_user)
15
+ @redirect_url = 'http://ng-token-auth.dev'
16
+ end
17
+
18
+ describe 'request password reset' do
19
+ before do
20
+ xhr :post, :create, {
21
+ email: @user.email,
22
+ redirect_url: @redirect_url
23
+ }
24
+
25
+ @mail = ActionMailer::Base.deliveries.last
26
+ @user.reload
27
+
28
+ @mail_reset_token = @mail.body.match(/reset_password_token=(.*)\"/)[1]
29
+ @mail_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=(.*)&amp;/)[1])
30
+ end
31
+
32
+ test 'response should return success status' do
33
+ assert_equal 200, response.status
34
+ end
35
+
36
+ test 'action should save password_reset_redirect_url to user table' do
37
+ assert_equal @redirect_url, @user.reset_password_redirect_url
38
+ end
39
+
40
+ test 'action should send an email' do
41
+ assert @mail
42
+ end
43
+
44
+ test 'the email should be addressed to the user' do
45
+ assert_equal @mail.to.first, @user.email
46
+ end
47
+
48
+ test 'the email body should contain a link with redirect url as a query param' do
49
+ assert_equal @redirect_url, @mail_redirect_url
50
+ end
51
+
52
+ test 'the email body should contain a link with reset token as a query param' do
53
+ user = User.reset_password_by_token({
54
+ reset_password_token: @mail_reset_token
55
+ })
56
+
57
+ assert_equal user.id, @user.id
58
+ end
59
+
60
+ describe 'password reset link failure' do
61
+ test 'request should not be authorized' do
62
+ assert_raises(ActionController::RoutingError) {
63
+ xhr :get, :edit, {
64
+ reset_password_token: 'bogus',
65
+ redirect_url: @mail_redirect_url
66
+ }
67
+ }
68
+ end
69
+ end
70
+
71
+ describe 'password reset link success' do
72
+ before do
73
+ xhr :get, :edit, {
74
+ reset_password_token: @mail_reset_token,
75
+ redirect_url: @mail_redirect_url
76
+ }
77
+
78
+ @user.reload
79
+
80
+ @uri = URI.parse(response.location)
81
+ @qs = CGI::parse(@uri.query)
82
+
83
+ @client_id = @qs["client_id"].first
84
+ @expiry = @qs["expiry"].first
85
+ @reset_password = @qs["reset_password"].first
86
+ @token = @qs["token"].first
87
+ @uid = @qs["uid"].first
88
+ end
89
+
90
+ test 'respones should have success redirect status' do
91
+ assert_equal 302, response.status
92
+ end
93
+
94
+ test 'response should contain auth params' do
95
+ assert @client_id
96
+ assert @expiry
97
+ assert @reset_password
98
+ assert @token
99
+ assert @uid
100
+ end
101
+
102
+ test 'response auth params should be valid' do
103
+ assert @user.valid_token?(@token, @client_id)
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "change password" do
109
+ describe 'success' do
110
+ before do
111
+ @auth_header = @user.create_new_auth_token
112
+ request.headers['Authorization'] = @auth_header
113
+ @new_password = Faker::Internet.password
114
+
115
+ xhr :put, :update, {
116
+ password: @new_password,
117
+ password_confirmation: @new_password
118
+ }
119
+
120
+ @user.reload
121
+ end
122
+
123
+ test "request should be successful" do
124
+ assert_equal 200, response.status
125
+ end
126
+
127
+ test "new password should authenticate user" do
128
+ assert @user.valid_password?(@new_password)
129
+ end
130
+ end
131
+
132
+ describe 'password mismatch error' do
133
+ before do
134
+ @auth_header = @user.create_new_auth_token
135
+ request.headers['Authorization'] = @auth_header
136
+ @new_password = Faker::Internet.password
137
+
138
+ xhr :put, :update, {
139
+ password: 'chong',
140
+ password_confirmation: 'bong'
141
+ }
142
+ end
143
+
144
+ test 'response should fail' do
145
+ assert_equal 422, response.status
146
+ end
147
+ end
148
+
149
+ describe 'unauthorized user' do
150
+ before do
151
+ @auth_header = @user.create_new_auth_token
152
+ @new_password = Faker::Internet.password
153
+
154
+ xhr :put, :update, {
155
+ password: @new_password,
156
+ password_confirmation: @new_password
157
+ }
158
+ end
159
+
160
+ test 'response should fail' do
161
+ assert_equal 401, response.status
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+