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 +4 -4
- data/README.md +9 -3
- data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +5 -2
- data/app/controllers/devise_token_auth/confirmations_controller.rb +7 -12
- data/app/controllers/devise_token_auth/passwords_controller.rb +109 -21
- data/app/models/user.rb +15 -0
- data/app/views/{confirmable → devise}/mailer/confirmation_instructions.html.erb +0 -0
- data/app/views/{confirmable → devise}/mailer/reset_password_instructions.html.erb +1 -1
- data/app/views/{confirmable → devise}/mailer/unlock_instructions.html.erb +0 -0
- data/lib/devise_token_auth/version.rb +1 -1
- data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb +1 -0
- data/test/controllers/demo_controller_test.rb +3 -3
- data/test/controllers/devise_token_auth/passwords_controller_test.rb +167 -0
- data/test/dummy/config/initializers/devise_token_auth.rb +5 -8
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/{20140713062503_devise_token_auth_create_users.rb → 20140714021615_devise_token_auth_create_users.rb} +1 -0
- data/test/dummy/db/schema.rb +6 -5
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +4870 -0
- data/test/dummy/log/test.log +31792 -0
- metadata +11 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 083650c6db3ad913ee66ea89099e0e2d96329750
|
4
|
+
data.tar.gz: ad8d32dbeae4612f739df26ad4f13e23c740faf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
166
|
-
| /sign_in | POST | email authentication. accepts
|
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
|
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
|
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
|
-
|
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:
|
17
|
+
expiry: expiry
|
16
18
|
}
|
17
19
|
|
18
20
|
@user.save!
|
19
21
|
|
20
|
-
redirect_to
|
22
|
+
redirect_to(@user.build_auth_url(@user.confirm_success_url, {
|
21
23
|
token: token,
|
22
|
-
client_id: client_id
|
23
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
26
|
+
@user = User.where({
|
27
|
+
email: resource_params[:email],
|
28
|
+
provider: 'email'
|
29
|
+
}).first
|
10
30
|
|
11
|
-
|
12
|
-
|
13
|
-
|
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:
|
19
|
-
}, status:
|
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
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
120
|
+
if @user.update_attributes(resource_params)
|
121
|
+
return render json: {
|
34
122
|
success: true,
|
35
123
|
data: {
|
36
|
-
user:
|
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:
|
43
|
-
}, status:
|
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
|
data/app/models/user.rb
CHANGED
@@ -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!
|
File without changes
|
@@ -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>
|
File without changes
|
@@ -19,9 +19,9 @@ class DemoControllerTest < ActionController::TestCase
|
|
19
19
|
|
20
20
|
@auth_header = @user.create_new_auth_token
|
21
21
|
|
22
|
-
@token
|
23
|
-
@client_id
|
24
|
-
@expiry
|
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=(.*)&/)[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
|
+
|