accounts 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +53 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +167 -0
- data/README.mkd +105 -0
- data/Rakefile +24 -0
- data/accounts.gemspec +52 -0
- data/demo/views/change_email.haml +14 -0
- data/demo/views/change_password.haml +36 -0
- data/demo/views/forgot_password.haml +21 -0
- data/demo/views/logon.haml +24 -0
- data/demo/views/register.haml +15 -0
- data/demo/web_app.rb +99 -0
- data/features/change-email.feature +51 -0
- data/features/change-password.feature +89 -0
- data/features/register.feature +42 -0
- data/features/step_definitions/steps.rb +102 -0
- data/features/step_definitions/web_steps.rb +228 -0
- data/features/support/env.rb +18 -0
- data/features/support/paths.rb +29 -0
- data/features/support/unused.rb +29 -0
- data/lib/accounts.rb +79 -0
- data/lib/accounts/configure.rb +122 -0
- data/lib/accounts/helpers.rb +90 -0
- data/lib/accounts/model.rb +126 -0
- data/lib/accounts/version.rb +5 -0
- data/spec/user_model_spec.rb +105 -0
- metadata +263 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
!!!
|
2
|
+
%html{html_attrs}
|
3
|
+
/ Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
4
|
+
|
5
|
+
%body.forgot_password
|
6
|
+
|
7
|
+
%h1 Forgot my password
|
8
|
+
|
9
|
+
%form.forgot_password(action="/forgot-password" method="POST")
|
10
|
+
.label_input
|
11
|
+
%label{:for=>"email"} E-mail
|
12
|
+
%input{:name=>"email", :id=>"email", :type=>"text"}
|
13
|
+
.button
|
14
|
+
%button{:type => "submit", } Submit
|
15
|
+
|
16
|
+
%p
|
17
|
+
We will send an e-mail to you with a one-time link that will
|
18
|
+
take you to a page that will allow you to change your password.
|
19
|
+
|
20
|
+
%p
|
21
|
+
Meanwhile, if you remember your password, it will still work until you change it.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
!!!
|
2
|
+
|
3
|
+
%html{html_attrs}
|
4
|
+
/ Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
5
|
+
|
6
|
+
%body
|
7
|
+
|
8
|
+
%h1 Sign in
|
9
|
+
|
10
|
+
%form#login_form(action="logon" method="POST")
|
11
|
+
.label_input
|
12
|
+
%label{:for=>"email"} E-mail
|
13
|
+
%input{:name=>"email", :id=>"email", :type=>"text", :value=>@email}
|
14
|
+
.label_input
|
15
|
+
%label{:for=>"password"} Password
|
16
|
+
%input{:name=>"password", :id=>"password", :type=>"password"}
|
17
|
+
.button
|
18
|
+
%button{:type => "submit"} Submit
|
19
|
+
|
20
|
+
.reset_password
|
21
|
+
%a{:href=>"/forgot-password"} I forgot my password.
|
22
|
+
|
23
|
+
.register
|
24
|
+
%a{:href=>"/register"} Register!
|
@@ -0,0 +1,15 @@
|
|
1
|
+
!!!
|
2
|
+
|
3
|
+
%html{html_attrs}
|
4
|
+
/ Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
5
|
+
|
6
|
+
%body
|
7
|
+
|
8
|
+
%h1 Register
|
9
|
+
|
10
|
+
%form#login_form{:action => '/register', :method => 'post'}
|
11
|
+
.label_input
|
12
|
+
%label{:for=>"email"} E-mail
|
13
|
+
%input{:name=>"email", :id=>"email", :type=>"text"}
|
14
|
+
.button
|
15
|
+
%button{:type => "submit"} Submit
|
data/demo/web_app.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler/setup'
|
6
|
+
#Bundler.require(:default, :test, :development) # didn't work
|
7
|
+
|
8
|
+
# development
|
9
|
+
require 'mail-store-agent'
|
10
|
+
require 'mail-single_file_delivery'
|
11
|
+
require 'haml'
|
12
|
+
|
13
|
+
# runtime
|
14
|
+
require 'rack'
|
15
|
+
require 'sinatra'
|
16
|
+
require 'thin'
|
17
|
+
require 'data_mapper'
|
18
|
+
require 'dm-types'
|
19
|
+
require 'dm-timestamps'
|
20
|
+
require 'dm-postgres-adapter'
|
21
|
+
require 'mail'
|
22
|
+
require 'logger'
|
23
|
+
require 'accounts'
|
24
|
+
|
25
|
+
class MyWebApp < Sinatra::Base
|
26
|
+
use ::Accounts::Server;
|
27
|
+
|
28
|
+
DataMapper.auto_migrate! # empty database
|
29
|
+
STDERR.puts "WARNING: called DataMapper.auto_migrate! to clear database"
|
30
|
+
|
31
|
+
enable :logging
|
32
|
+
|
33
|
+
# Web app class must define this.
|
34
|
+
# In a production environment you might want to replace this with something like Rack::Session::Pool
|
35
|
+
enable :sessions
|
36
|
+
|
37
|
+
not_found do
|
38
|
+
%Q{Page not found. Go to <a href="home">home page</a>.}
|
39
|
+
end
|
40
|
+
|
41
|
+
error 403 do
|
42
|
+
"Access denied"
|
43
|
+
end
|
44
|
+
|
45
|
+
get '/' do
|
46
|
+
%Q{Welcome visitor! Please <a href="/logon">log on</a>.}
|
47
|
+
end
|
48
|
+
|
49
|
+
get '/welcome' do
|
50
|
+
account = Accounts::Account.get(session[:account_id]) or return 403
|
51
|
+
"Welcome #{account.email}!"
|
52
|
+
end
|
53
|
+
|
54
|
+
get '/logon' do
|
55
|
+
@email = params[:email] || ''
|
56
|
+
haml :logon
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/logout' do
|
60
|
+
session[:account_id] = nil
|
61
|
+
redirect to('/logon')
|
62
|
+
end
|
63
|
+
|
64
|
+
get '/register' do
|
65
|
+
haml :register
|
66
|
+
end
|
67
|
+
|
68
|
+
get '/forgot-password' do
|
69
|
+
haml :forgot_password
|
70
|
+
end
|
71
|
+
|
72
|
+
get '/change-password' do
|
73
|
+
return 403 unless session[:account_id]
|
74
|
+
haml :change_password
|
75
|
+
end
|
76
|
+
|
77
|
+
get '/change-email' do
|
78
|
+
return 403 unless session[:account_id]
|
79
|
+
haml :change_email
|
80
|
+
end
|
81
|
+
|
82
|
+
if app_file == $0
|
83
|
+
STDERR.puts "Running standalone"
|
84
|
+
# Run as stand-along web app
|
85
|
+
Mail.defaults do
|
86
|
+
delivery_method Mail::SingleFileDelivery::Agent, :filename => '/tmp/mail-test-fifo'
|
87
|
+
end
|
88
|
+
require 'sinatra/reloader'
|
89
|
+
register Sinatra::Reloader
|
90
|
+
enable :reloader
|
91
|
+
run!
|
92
|
+
else
|
93
|
+
# Probably running under Cucumber
|
94
|
+
Mail.defaults do
|
95
|
+
delivery_method(:test)
|
96
|
+
Mail::TestMailer.deliveries = MailStoreAgent.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
2
|
+
|
3
|
+
@change-email
|
4
|
+
Feature: Users can change their e-mails
|
5
|
+
|
6
|
+
@email-taken
|
7
|
+
Scenario: Alice cannot change her e-mail to one that is already taken
|
8
|
+
Given I register "alice@wunder.land" with password "caterpillar"
|
9
|
+
And I register "caterpillar@wunder.land" with password "caterpillar"
|
10
|
+
And "alice@wunder.land" is authenticated with password "caterpillar"
|
11
|
+
When Alice visits "/change-email"
|
12
|
+
And she fills in "email" with "caterpillar@wunder.land"
|
13
|
+
And she presses "Submit"
|
14
|
+
Then she should see "caterpillar@wunder.land is already taken"
|
15
|
+
|
16
|
+
@request-change-email
|
17
|
+
Scenario: Alice requests to change email to an available email
|
18
|
+
Given "alice@wunder.land" is registered with password "caterpillar"
|
19
|
+
And "alice@wunder.land" is authenticated with password "caterpillar"
|
20
|
+
When she visits "/change-email"
|
21
|
+
And she fills in "email" with "alice@looking.glass"
|
22
|
+
And she presses "Submit"
|
23
|
+
Then she should see "Check your e-mail"
|
24
|
+
And "alice@wunder.land" opens an email containing "You have requested to change your e-mail to alice@looking.glass"
|
25
|
+
And "alice@looking.glass" should receive an email
|
26
|
+
|
27
|
+
Scenario: Alice can still log on with her previous e-mail
|
28
|
+
Given "alice@wunder.land" is registered with password "caterpillar"
|
29
|
+
When she visits "/logon"
|
30
|
+
And she fills in "email" with "alice@wunder.land"
|
31
|
+
And she fills in "password" with "caterpillar"
|
32
|
+
And she presses "Submit"
|
33
|
+
Then she should be on "/welcome"
|
34
|
+
And she should see "Welcome alice@wunder.land"
|
35
|
+
|
36
|
+
Scenario: Alice follows confirmation link
|
37
|
+
Given "alice@looking.glass" opens an email containing "/response-token/"
|
38
|
+
When she visits link from email
|
39
|
+
And "email" form-input should contain "alice@looking.glass"
|
40
|
+
And she fills in "password" with "caterpillar"
|
41
|
+
And she presses "Submit"
|
42
|
+
Then she should be on "/welcome"
|
43
|
+
And she should see "Welcome alice@looking.glass"
|
44
|
+
|
45
|
+
Scenario: Alice can not log on with her previous e-mail
|
46
|
+
Given "alice@looking.glass" is registered with password "caterpillar"
|
47
|
+
When she visits "/logon"
|
48
|
+
And she fills in "email" with "alice@wunder.land"
|
49
|
+
And she fills in "password" with "caterpillar"
|
50
|
+
And she presses "Submit"
|
51
|
+
Then she should not see "Welcome alice@wunder.land"
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
2
|
+
|
3
|
+
@change-password
|
4
|
+
Feature: Users can change their passwords
|
5
|
+
|
6
|
+
@registers-confirms
|
7
|
+
Scenario: Alice requests to register
|
8
|
+
Given I have unregistered "alice@wunder.land"
|
9
|
+
When she visits "/register"
|
10
|
+
And she fills in "email" with "alice@wunder.land"
|
11
|
+
And she presses "Submit"
|
12
|
+
Then she should see "Check your e-mail"
|
13
|
+
|
14
|
+
Scenario: Alice changes her password
|
15
|
+
Given "alice@wunder.land" opens an email containing "/response-token/"
|
16
|
+
Then she visits link from email
|
17
|
+
And she should see "Change Password"
|
18
|
+
And she fills in "password" with "caterpillar"
|
19
|
+
And she fills in "password2" with "caterpillar"
|
20
|
+
And she presses "Submit"
|
21
|
+
Then she should see "You have changed your password."
|
22
|
+
And "alice@wunder.land" should receive an email
|
23
|
+
And "admin@accounts.test" should receive an email
|
24
|
+
And "admin@accounts.test" opens an email containing "alice@wunder.land has registered and confirmed"
|
25
|
+
|
26
|
+
Scenario: Alice can log on with password "caterpillar"
|
27
|
+
Given "alice@wunder.land" opens an email containing "The password for alice@wunder.land has changed."
|
28
|
+
When she visits "/logon"
|
29
|
+
And she fills in "email" with "alice@wunder.land"
|
30
|
+
And she fills in "password" with "caterpillar"
|
31
|
+
And she presses "Submit"
|
32
|
+
Then she should be on "/welcome"
|
33
|
+
And she should see "Welcome alice@wunder.land"
|
34
|
+
|
35
|
+
Scenario: Alice can not log on with password "alice"
|
36
|
+
When she visits "/logon"
|
37
|
+
And she fills in "email" with "alice@wunder.land"
|
38
|
+
And she fills in "password" with "alice"
|
39
|
+
And she presses "Submit"
|
40
|
+
Then she should be on "/logon"
|
41
|
+
And she should not see "Welcome alice@wunder.land"
|
42
|
+
|
43
|
+
Scenario: Alice attempts to visit restricted page without first authenticating
|
44
|
+
Given Alice has logged out
|
45
|
+
When she visits "/welcome"
|
46
|
+
And she should not see "Welcome alice@wunder.land"
|
47
|
+
|
48
|
+
Scenario: Alice changes her password again
|
49
|
+
Given Alice visits "/logon"
|
50
|
+
And she fills in "email" with "alice@wunder.land"
|
51
|
+
And she fills in "password" with "caterpillar"
|
52
|
+
And she presses "Submit"
|
53
|
+
And she visits "/change-password"
|
54
|
+
Then she fills in "password" with "whiterabbit"
|
55
|
+
And she fills in "password2" with "whiterabbit"
|
56
|
+
And she presses "Submit"
|
57
|
+
Then she should see "You have changed your password."
|
58
|
+
|
59
|
+
Scenario: Alice tries to log on with her defunct password
|
60
|
+
Given "alice@wunder.land" opens an email containing "The password for alice@wunder.land has changed."
|
61
|
+
When she visits "/logon"
|
62
|
+
And she fills in "email" with "alice@wunder.land"
|
63
|
+
And she fills in "password" with "caterpillar"
|
64
|
+
And she presses "Submit"
|
65
|
+
Then she should not see "Welcome alice@wunder.land"
|
66
|
+
|
67
|
+
Scenario: Only authenticated users can change their password
|
68
|
+
Given I have unregistered "dormouse@alice.com"
|
69
|
+
When he visits "/change-password"
|
70
|
+
Then he should not see "Change Password"
|
71
|
+
|
72
|
+
Scenario: Alice forgets her password
|
73
|
+
Given Alice visits "/forgot-password"
|
74
|
+
When she fills in "email" with "alice@wunder.land"
|
75
|
+
And she presses "Submit"
|
76
|
+
Then she should see "Check your e-mail"
|
77
|
+
And "alice@wunder.land" should receive an email
|
78
|
+
|
79
|
+
Scenario: Alice can reset her password again
|
80
|
+
Given "alice@wunder.land" opens an email containing "/response-token/"
|
81
|
+
When she visits link from email
|
82
|
+
Then alice should see "Change Password"
|
83
|
+
#But she remembers it again and goes to tea with Caterpillar. :)
|
84
|
+
|
85
|
+
Scenario: Unknown user attempts to reset password
|
86
|
+
Given MadHatter visits "/forgot-password"
|
87
|
+
When he fills in "email" with "madhatter@wunder.land"
|
88
|
+
And he presses "Submit"
|
89
|
+
Then he should see "madhatter@wunder.land does not match any account"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
2
|
+
|
3
|
+
@register
|
4
|
+
Feature: Visitors can register
|
5
|
+
|
6
|
+
Scenario: Unregistered user requests to register
|
7
|
+
Given I have unregistered "alice@wunder.land"
|
8
|
+
When she visits "/register"
|
9
|
+
And she fills in "email" with "alice@wunder.land"
|
10
|
+
And she presses "Submit"
|
11
|
+
Then she should see "Check your e-mail"
|
12
|
+
And "alice@wunder.land" should receive an email
|
13
|
+
|
14
|
+
Scenario: Unconfirmed user requests to register again will get another e-mail
|
15
|
+
Given "alice@wunder.land" is registered
|
16
|
+
When she visits "/register"
|
17
|
+
And she fills in "email" with "alice@wunder.land"
|
18
|
+
And she presses "Submit"
|
19
|
+
Then page has content "Check your e-mail"
|
20
|
+
And "alice@wunder.land" should receive an email
|
21
|
+
|
22
|
+
Scenario: Confirm registration with stale link doesn't do anything
|
23
|
+
Given "alice@wunder.land" opens an email containing "/response-token/"
|
24
|
+
When she visits link from email
|
25
|
+
Then Alice should see "Page not found"
|
26
|
+
And "alice@wunder.land" is not confirmed
|
27
|
+
|
28
|
+
Scenario: Confirm registration with active link is successful
|
29
|
+
Given "alice@wunder.land" opens an email containing "/response-token/"
|
30
|
+
When she visits link from email
|
31
|
+
Then alice should see "Change Password"
|
32
|
+
And "admin@accounts.test" should receive an email
|
33
|
+
And "admin@accounts.test" opens an email containing "alice@wunder.land has registered and confirmed"
|
34
|
+
And "alice@wunder.land" is confirmed
|
35
|
+
|
36
|
+
Scenario: Confirmed user requests to register again will fail
|
37
|
+
Given "alice@wunder.land" is registered
|
38
|
+
When I visits "/register"
|
39
|
+
And I fills in "email" with "alice@wunder.land"
|
40
|
+
And I presses "Submit"
|
41
|
+
Then page has content "alice@wunder.land is already registered"
|
42
|
+
And "alice@wunder.land" should not receive an email
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Copyright Westside Consulting LLC, Ann Arbor, MI, USA, 2012
|
2
|
+
|
3
|
+
When /^page has "([^"]*)"$/ do |arg1|
|
4
|
+
#STDERR.puts page.body
|
5
|
+
page.body.should have_selector(arg1)
|
6
|
+
end
|
7
|
+
|
8
|
+
When /^page has content "([^"]*)"$/ do |arg1|
|
9
|
+
#STDERR.puts page.body
|
10
|
+
page.body.should have_content(arg1)
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^"([^"]*)" is suspended$/ do |arg1|
|
14
|
+
pending # express the regexp above with the code you wish you had
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^\w+ visits link from email$/ do
|
18
|
+
@new_mail.should_not be_nil
|
19
|
+
@new_mail.body.to_s =~ /(http\S+)/
|
20
|
+
link = $1
|
21
|
+
link.should_not be_nil
|
22
|
+
#STDERR.puts "link = #{link}"
|
23
|
+
visit link
|
24
|
+
end
|
25
|
+
|
26
|
+
When /^"([^"]*)" is (not )?confirmed$/ do |arg1, bool|
|
27
|
+
account = Accounts::Account.first( :email => arg1 )
|
28
|
+
should_be_confirmed = !bool
|
29
|
+
|
30
|
+
if (should_be_confirmed) then
|
31
|
+
account.should_not be_nil
|
32
|
+
account.status.should include :email_confirmed
|
33
|
+
else
|
34
|
+
account.status.should_not include :email_confirmed if account
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
When /^\w+ visits? "([^"]*)"$/ do |arg1|
|
39
|
+
visit arg1
|
40
|
+
#save_and_open_page
|
41
|
+
end
|
42
|
+
|
43
|
+
When /^I should see raw html: "([^"]*)"$/ do |arg1|
|
44
|
+
page.html.should match /#{arg1}/
|
45
|
+
end
|
46
|
+
|
47
|
+
When /^I have unregistered "([^"]*)"$/ do |arg1|
|
48
|
+
account = Accounts::Account.all( :email => arg1 )
|
49
|
+
account.destroy if account
|
50
|
+
Accounts::Account.all( :email => arg1 ).should have(0).items
|
51
|
+
end
|
52
|
+
|
53
|
+
When /^"([^"]*)" is (not )?registered$/ do |arg1, bool|
|
54
|
+
Accounts::Account.all( :email => arg1 ).should have(bool ? 0 : 1).items
|
55
|
+
end
|
56
|
+
|
57
|
+
When /^"([^"]*)" opens an email containing "([^"]*)"$/ do |arg1, arg2|
|
58
|
+
Mail::TestMailer.deliveries.accounts.should include(arg1)
|
59
|
+
@new_mail = Mail::TestMailer.deliveries.get(arg1)
|
60
|
+
@new_mail.should_not be_nil
|
61
|
+
@new_mail.body.should match /#{arg2}/
|
62
|
+
end
|
63
|
+
|
64
|
+
When /^"([^"]*)" should receive an email$/ do |arg1|
|
65
|
+
Mail::TestMailer.deliveries.accounts.should include(arg1)
|
66
|
+
Mail::TestMailer.deliveries.peek(arg1).should_not be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
When /^"([^"]*)" should not receive an email$/ do |arg1|
|
70
|
+
msg = Mail::TestMailer.deliveries.peek(arg1)
|
71
|
+
msg.should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
When /^(?:\S+ )(?:is|has) logged out$/ do
|
75
|
+
visit '/logout'
|
76
|
+
end
|
77
|
+
|
78
|
+
When /^I register "([^"]*)" with password "([^"]*)"$/ do |arg1, arg2|
|
79
|
+
account = Accounts::Account.create ({ :email => arg1 })
|
80
|
+
account.set_password arg2
|
81
|
+
end
|
82
|
+
|
83
|
+
When /^"([^"]*)" is registered with password "([^"]*)"$/ do |arg1, arg2|
|
84
|
+
account = Accounts::Account.first ({ :email => arg1 })
|
85
|
+
account.should_not be_nil
|
86
|
+
account.confirm_password(arg2).should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
When /^"([^"]*)" is authenticated with password "([^"]*)"$/ do |arg1, arg2|
|
90
|
+
visit '/logon'
|
91
|
+
current_path.should be == '/logon'
|
92
|
+
fill_in('email', :with => arg1)
|
93
|
+
fill_in('password', :with => arg2)
|
94
|
+
click_button("Submit")
|
95
|
+
current_path.should be == '/welcome'
|
96
|
+
page.body.should match "Welcome #{arg1}"
|
97
|
+
end
|
98
|
+
|
99
|
+
Then /^"([^"]*)" form\-input should contain "([^"]*)"$/ do |arg1, arg2|
|
100
|
+
find("input[@name=#{arg1}]").value.should be == arg2
|
101
|
+
end
|
102
|
+
|