rodauth 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +146 -0
  3. data/README.rdoc +644 -220
  4. data/Rakefile +99 -11
  5. data/doc/account_expiration.rdoc +55 -0
  6. data/doc/base.rdoc +104 -0
  7. data/doc/change_login.rdoc +29 -0
  8. data/doc/change_password.rdoc +26 -0
  9. data/doc/close_account.rdoc +31 -0
  10. data/doc/confirm_password.rdoc +22 -0
  11. data/doc/create_account.rdoc +34 -0
  12. data/doc/disallow_password_reuse.rdoc +37 -0
  13. data/doc/email_base.rdoc +19 -0
  14. data/doc/jwt.rdoc +35 -0
  15. data/doc/lockout.rdoc +83 -0
  16. data/doc/login.rdoc +27 -0
  17. data/doc/login_password_requirements_base.rdoc +50 -0
  18. data/doc/logout.rdoc +21 -0
  19. data/doc/otp.rdoc +100 -0
  20. data/doc/password_complexity.rdoc +50 -0
  21. data/doc/password_expiration.rdoc +52 -0
  22. data/doc/password_grace_period.rdoc +10 -0
  23. data/doc/recovery_codes.rdoc +60 -0
  24. data/doc/release_notes/1.0.0.txt +443 -0
  25. data/doc/remember.rdoc +82 -0
  26. data/doc/reset_password.rdoc +70 -0
  27. data/doc/session_expiration.rdoc +27 -0
  28. data/doc/single_session.rdoc +43 -0
  29. data/doc/sms_codes.rdoc +119 -0
  30. data/doc/two_factor_base.rdoc +27 -0
  31. data/doc/verify_account.rdoc +70 -0
  32. data/doc/verify_account_grace_period.rdoc +15 -0
  33. data/doc/verify_change_login.rdoc +9 -0
  34. data/lib/roda/plugins/rodauth.rb +3 -262
  35. data/lib/rodauth.rb +260 -0
  36. data/lib/rodauth/features/account_expiration.rb +108 -0
  37. data/lib/rodauth/features/base.rb +479 -0
  38. data/lib/rodauth/features/change_login.rb +77 -0
  39. data/lib/rodauth/features/change_password.rb +66 -0
  40. data/lib/rodauth/features/close_account.rb +82 -0
  41. data/lib/rodauth/features/confirm_password.rb +51 -0
  42. data/lib/rodauth/features/create_account.rb +128 -0
  43. data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
  44. data/lib/rodauth/features/email_base.rb +63 -0
  45. data/lib/rodauth/features/jwt.rb +151 -0
  46. data/lib/rodauth/features/lockout.rb +262 -0
  47. data/lib/rodauth/features/login.rb +61 -0
  48. data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
  49. data/lib/rodauth/features/logout.rb +37 -0
  50. data/lib/rodauth/features/otp.rb +338 -0
  51. data/lib/rodauth/features/password_complexity.rb +89 -0
  52. data/lib/rodauth/features/password_expiration.rb +111 -0
  53. data/lib/rodauth/features/password_grace_period.rb +46 -0
  54. data/lib/rodauth/features/recovery_codes.rb +240 -0
  55. data/lib/rodauth/features/remember.rb +200 -0
  56. data/lib/rodauth/features/reset_password.rb +207 -0
  57. data/lib/rodauth/features/session_expiration.rb +55 -0
  58. data/lib/rodauth/features/single_session.rb +87 -0
  59. data/lib/rodauth/features/sms_codes.rb +498 -0
  60. data/lib/rodauth/features/two_factor_base.rb +135 -0
  61. data/lib/rodauth/features/verify_account.rb +232 -0
  62. data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
  63. data/lib/rodauth/features/verify_change_login.rb +20 -0
  64. data/lib/rodauth/migrations.rb +130 -0
  65. data/lib/rodauth/version.rb +9 -0
  66. data/spec/account_expiration_spec.rb +90 -0
  67. data/spec/all.rb +1 -0
  68. data/spec/change_login_spec.rb +149 -0
  69. data/spec/change_password_spec.rb +177 -0
  70. data/spec/close_account_spec.rb +162 -0
  71. data/spec/confirm_password_spec.rb +70 -0
  72. data/spec/create_account_spec.rb +127 -0
  73. data/spec/disallow_password_reuse_spec.rb +84 -0
  74. data/spec/lockout_spec.rb +228 -0
  75. data/spec/login_spec.rb +188 -0
  76. data/spec/migrate/001_tables.rb +103 -16
  77. data/spec/migrate/002_account_password_hash_column.rb +11 -0
  78. data/spec/migrate_password/001_tables.rb +60 -42
  79. data/spec/migrate_travis/001_tables.rb +116 -0
  80. data/spec/password_complexity_spec.rb +108 -0
  81. data/spec/password_expiration_spec.rb +243 -0
  82. data/spec/password_grace_period_spec.rb +93 -0
  83. data/spec/remember_spec.rb +424 -0
  84. data/spec/reset_password_spec.rb +185 -0
  85. data/spec/rodauth_spec.rb +57 -980
  86. data/spec/session_expiration_spec.rb +58 -0
  87. data/spec/single_session_spec.rb +107 -0
  88. data/spec/spec_helper.rb +202 -0
  89. data/spec/two_factor_spec.rb +1310 -0
  90. data/spec/verify_account_grace_period_spec.rb +135 -0
  91. data/spec/verify_account_spec.rb +142 -0
  92. data/spec/verify_change_login_spec.rb +46 -0
  93. data/spec/views/login.str +2 -2
  94. data/templates/add-recovery-codes.str +2 -0
  95. data/templates/button.str +5 -0
  96. data/templates/change-login.str +5 -18
  97. data/templates/change-password.str +6 -14
  98. data/templates/close-account.str +3 -6
  99. data/templates/confirm-password.str +4 -14
  100. data/templates/create-account.str +6 -30
  101. data/templates/login-confirm-field.str +6 -0
  102. data/templates/login-field.str +6 -0
  103. data/templates/login.str +5 -19
  104. data/templates/logout.str +2 -6
  105. data/templates/otp-auth-code-field.str +6 -0
  106. data/templates/otp-auth.str +8 -0
  107. data/templates/otp-disable.str +6 -0
  108. data/templates/otp-setup.str +21 -0
  109. data/templates/password-confirm-field.str +6 -0
  110. data/templates/password-field.str +6 -0
  111. data/templates/recovery-auth.str +12 -0
  112. data/templates/recovery-codes.str +6 -0
  113. data/templates/remember.str +8 -12
  114. data/templates/reset-password-request.str +2 -2
  115. data/templates/reset-password.str +4 -18
  116. data/templates/sms-auth.str +6 -0
  117. data/templates/sms-code-field.str +6 -0
  118. data/templates/sms-confirm.str +7 -0
  119. data/templates/sms-disable.str +7 -0
  120. data/templates/sms-request.str +5 -0
  121. data/templates/sms-setup.str +12 -0
  122. data/templates/unlock-account-request.str +3 -7
  123. data/templates/unlock-account.str +4 -7
  124. data/templates/verify-account-resend.str +2 -2
  125. data/templates/verify-account.str +2 -6
  126. metadata +191 -29
  127. data/lib/roda/plugins/rodauth/base.rb +0 -428
  128. data/lib/roda/plugins/rodauth/change_login.rb +0 -48
  129. data/lib/roda/plugins/rodauth/change_password.rb +0 -42
  130. data/lib/roda/plugins/rodauth/close_account.rb +0 -42
  131. data/lib/roda/plugins/rodauth/create_account.rb +0 -92
  132. data/lib/roda/plugins/rodauth/lockout.rb +0 -292
  133. data/lib/roda/plugins/rodauth/login.rb +0 -81
  134. data/lib/roda/plugins/rodauth/logout.rb +0 -36
  135. data/lib/roda/plugins/rodauth/remember.rb +0 -226
  136. data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
  137. data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
@@ -0,0 +1,135 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ TwoFactorBase = Feature.define(:two_factor_base) do
5
+ after :two_factor_authentication
6
+
7
+ redirect :two_factor_auth
8
+ redirect :two_factor_already_authenticated
9
+
10
+ notice_flash "You have been authenticated via 2nd factor", "two_factor_auth"
11
+
12
+ error_flash "This account has not been setup for two factor authentication", 'two_factor_not_setup'
13
+ error_flash "Already authenticated via 2nd factor", 'two_factor_already_authenticated'
14
+ error_flash "You need to authenticate via 2nd factor before continuing.", 'two_factor_need_authentication'
15
+
16
+ auth_value_method :two_factor_session_key, :two_factor_auth
17
+ auth_value_method :two_factor_setup_session_key, :two_factor_auth_setup
18
+ auth_value_method :two_factor_need_setup_redirect, nil
19
+
20
+ auth_value_methods(
21
+ :two_factor_auth_required_redirect,
22
+ :two_factor_modifications_require_password?
23
+ )
24
+
25
+ auth_methods(
26
+ :two_factor_authenticated?,
27
+ :two_factor_remove,
28
+ :two_factor_remove_auth_failures,
29
+ :two_factor_remove_session,
30
+ :two_factor_update_session
31
+ )
32
+
33
+ def two_factor_modifications_require_password?
34
+ modifications_require_password?
35
+ end
36
+
37
+ def authenticated?
38
+ super
39
+ two_factor_authenticated? if two_factor_authentication_setup?
40
+ end
41
+
42
+ def require_authentication
43
+ super
44
+ require_two_factor_authenticated if two_factor_authentication_setup?
45
+ end
46
+
47
+ def require_two_factor_setup
48
+ unless uses_two_factor_authentication?
49
+ set_redirect_error_flash two_factor_not_setup_error_flash
50
+ redirect two_factor_need_setup_redirect
51
+ end
52
+ end
53
+
54
+ def require_two_factor_not_authenticated
55
+ if two_factor_authenticated?
56
+ set_redirect_error_flash two_factor_already_authenticated_error_flash
57
+ redirect two_factor_already_authenticated_redirect
58
+ end
59
+ end
60
+
61
+ def require_two_factor_authenticated
62
+ unless two_factor_authenticated?
63
+ set_redirect_error_flash two_factor_need_authentication_error_flash
64
+ redirect _two_factor_auth_required_redirect
65
+ end
66
+ end
67
+
68
+ def two_factor_remove_auth_failures
69
+ nil
70
+ end
71
+
72
+ def two_factor_auth_required_redirect
73
+ nil
74
+ end
75
+
76
+ def two_factor_auth_fallback_redirect
77
+ nil
78
+ end
79
+
80
+ def two_factor_password_match?(password)
81
+ if two_factor_modifications_require_password?
82
+ password_match?(password)
83
+ else
84
+ true
85
+ end
86
+ end
87
+
88
+ def two_factor_authenticated?
89
+ !!session[two_factor_session_key]
90
+ end
91
+
92
+ def two_factor_authentication_setup?
93
+ false
94
+ end
95
+
96
+ def uses_two_factor_authentication?
97
+ return false unless logged_in?
98
+ session[two_factor_setup_session_key] = two_factor_authentication_setup? unless session.has_key?(two_factor_setup_session_key)
99
+ session[two_factor_setup_session_key]
100
+ end
101
+
102
+ def two_factor_remove
103
+ nil
104
+ end
105
+
106
+ private
107
+
108
+ def after_close_account
109
+ super if defined?(super)
110
+ two_factor_remove
111
+ end
112
+
113
+ def two_factor_authenticate(type)
114
+ two_factor_update_session(type)
115
+ two_factor_remove_auth_failures
116
+ after_two_factor_authentication
117
+ set_notice_flash two_factor_auth_notice_flash
118
+ redirect two_factor_auth_redirect
119
+ end
120
+
121
+ def two_factor_remove_session
122
+ session.delete(two_factor_session_key)
123
+ session[two_factor_setup_session_key] = false
124
+ end
125
+
126
+ def two_factor_update_session(type)
127
+ session[two_factor_session_key] = type
128
+ session[two_factor_setup_session_key] = true
129
+ end
130
+
131
+ def _two_factor_auth_required_redirect
132
+ two_factor_auth_required_redirect || two_factor_auth_fallback_redirect || default_redirect
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,232 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ VerifyAccount = Feature.define(:verify_account) do
5
+ depends :login, :create_account, :email_base
6
+
7
+ error_flash "Unable to verify account"
8
+ error_flash "Unable to resend verify account email", 'verify_account_resend'
9
+ notice_flash "Your account has been verified"
10
+ notice_flash "An email has been sent to you with a link to verify your account", 'verify_account_email_sent'
11
+ view 'verify-account', 'Verify Account'
12
+ view 'verify-account-resend', 'Resend Verification Email', 'resend_verify_account'
13
+ additional_form_tags
14
+ additional_form_tags 'verify_account_resend'
15
+ after
16
+ after 'verify_account_email_resend'
17
+ before
18
+ before 'verify_account_email_resend'
19
+ button 'Verify Account'
20
+ button 'Send Verification Email Again', 'verify_account_resend'
21
+ redirect
22
+ redirect(:verify_account_email_sent){require_login_redirect}
23
+
24
+ auth_value_method :no_matching_verify_account_key_message, "invalid verify account key"
25
+ auth_value_method :attempt_to_create_unverified_account_notice_message, "The account you tried to create is currently awaiting verification"
26
+ auth_value_method :attempt_to_login_to_unverified_account_notice_message, "The account you tried to login with is currently awaiting verification"
27
+ auth_value_method :verify_account_email_subject, 'Verify Account'
28
+ auth_value_method :verify_account_key_param, 'key'
29
+ auth_value_method :verify_account_autologin?, true
30
+ auth_value_method :verify_account_table, :account_verification_keys
31
+ auth_value_method :verify_account_id_column, :id
32
+ auth_value_method :verify_account_key_column, :key
33
+
34
+ auth_value_methods :verify_account_key_value
35
+
36
+ auth_methods(
37
+ :create_verify_account_key,
38
+ :create_verify_account_email,
39
+ :get_verify_account_key,
40
+ :remove_verify_account_key,
41
+ :resend_verify_account_view,
42
+ :send_verify_account_email,
43
+ :verify_account,
44
+ :verify_account_email_body,
45
+ :verify_account_email_link,
46
+ :verify_account_key_insert_hash
47
+ )
48
+
49
+ auth_private_methods(
50
+ :account_from_verify_account_key
51
+ )
52
+
53
+ route(:verify_account_resend) do |r|
54
+ verify_account_check_already_logged_in
55
+ before_verify_account_resend_route
56
+
57
+ r.post do
58
+ if account_from_login(param(login_param)) && !open_account?
59
+ before_verify_account_email_resend
60
+ if verify_account_email_resend
61
+ after_verify_account_email_resend
62
+ end
63
+
64
+ set_notice_flash verify_account_email_sent_notice_flash
65
+ else
66
+ set_redirect_error_flash verify_account_resend_error_flash
67
+ end
68
+
69
+ redirect verify_account_email_sent_redirect
70
+ end
71
+ end
72
+
73
+ route do |r|
74
+ verify_account_check_already_logged_in
75
+ before_verify_account_route
76
+
77
+ r.get do
78
+ if key = param_or_nil(verify_account_key_param)
79
+ if account_from_verify_account_key(key)
80
+ verify_account_view
81
+ else
82
+ set_redirect_error_flash no_matching_verify_account_key_message
83
+ redirect require_login_redirect
84
+ end
85
+ end
86
+ end
87
+
88
+ r.post do
89
+ key = param(verify_account_key_param)
90
+ unless account_from_verify_account_key(key)
91
+ set_redirect_error_flash verify_account_error_flash
92
+ redirect verify_account_redirect
93
+ end
94
+
95
+ transaction do
96
+ before_verify_account
97
+ verify_account
98
+ remove_verify_account_key
99
+ after_verify_account
100
+ end
101
+
102
+ if verify_account_autologin?
103
+ update_session
104
+ end
105
+
106
+ set_notice_flash verify_account_notice_flash
107
+ redirect verify_account_redirect
108
+ end
109
+ end
110
+
111
+ def remove_verify_account_key
112
+ verify_account_ds.delete
113
+ end
114
+
115
+ def verify_account
116
+ update_account(account_status_column=>account_open_status_value) == 1
117
+ end
118
+
119
+ def verify_account_email_resend
120
+ if @verify_account_key_value = get_verify_account_key(account_id)
121
+ send_verify_account_email
122
+ true
123
+ end
124
+ end
125
+
126
+ def create_account_notice_flash
127
+ verify_account_email_sent_notice_flash
128
+ end
129
+
130
+ def new_account(login)
131
+ if account_from_login(login)
132
+ set_error_flash attempt_to_create_unverified_account_notice_message
133
+ response.write resend_verify_account_view
134
+ request.halt
135
+ end
136
+ super
137
+ end
138
+
139
+ def account_from_verify_account_key(key)
140
+ @account = _account_from_verify_account_key(key)
141
+ end
142
+
143
+ def account_initial_status_value
144
+ account_unverified_status_value
145
+ end
146
+
147
+ def send_verify_account_email
148
+ create_verify_account_email.deliver!
149
+ end
150
+
151
+ def verify_account_email_link
152
+ token_link(verify_account_route, verify_account_key_param, verify_account_key_value)
153
+ end
154
+
155
+ def get_verify_account_key(id)
156
+ verify_account_ds(id).get(verify_account_key_column)
157
+ end
158
+
159
+ def skip_status_checks?
160
+ false
161
+ end
162
+
163
+ def create_account_autologin?
164
+ false
165
+ end
166
+
167
+ private
168
+
169
+ attr_reader :verify_account_key_value
170
+
171
+ def before_login_attempt
172
+ unless open_account?
173
+ set_error_flash attempt_to_login_to_unverified_account_notice_message
174
+ response.write resend_verify_account_view
175
+ request.halt
176
+ end
177
+ super
178
+ end
179
+
180
+ def after_create_account
181
+ setup_account_verification
182
+ super
183
+ end
184
+
185
+ def setup_account_verification
186
+ generate_verify_account_key_value
187
+ create_verify_account_key
188
+ send_verify_account_email
189
+ end
190
+
191
+ def verify_account_check_already_logged_in
192
+ check_already_logged_in
193
+ end
194
+
195
+ def generate_verify_account_key_value
196
+ @verify_account_key_value = random_key
197
+ end
198
+
199
+ def create_verify_account_key
200
+ ds = verify_account_ds
201
+ transaction do
202
+ if ds.empty?
203
+ if e = raised_uniqueness_violation{ds.insert(verify_account_key_insert_hash)}
204
+ # If inserting into the verify account table causes a violation, we can pull the
205
+ # key from the verify account table, or reraise.
206
+ raise e unless @verify_account_key_value = get_verify_account_key(account_id)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def verify_account_key_insert_hash
213
+ {verify_account_id_column=>account_id, verify_account_key_column=>verify_account_key_value}
214
+ end
215
+
216
+ def create_verify_account_email
217
+ create_email(verify_account_email_subject, verify_account_email_body)
218
+ end
219
+
220
+ def verify_account_email_body
221
+ render('verify-account-email')
222
+ end
223
+
224
+ def verify_account_ds(id=account_id)
225
+ db[verify_account_table].where(verify_account_id_column=>id)
226
+ end
227
+
228
+ def _account_from_verify_account_key(token)
229
+ account_from_key(token, account_unverified_status_value){|id| get_verify_account_key(id)}
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,76 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ VerifyAccountGracePeriod = Feature.define(:verify_account_grace_period) do
5
+ depends :verify_account
6
+ error_flash "Cannot change login for unverified account. Please verify this account before changing the login.", "unverified_change_login"
7
+ redirect :unverified_change_login
8
+
9
+ auth_value_method :verification_requested_at_column, :requested_at
10
+ auth_value_method :unverified_account_session_key, :unverified_account
11
+ auth_value_method :verify_account_grace_period, 86400
12
+
13
+ auth_methods(
14
+ :account_in_unverified_grace_period?
15
+ )
16
+
17
+ def verified_account?
18
+ logged_in? && !session[unverified_account_session_key]
19
+ end
20
+
21
+ def create_account_autologin?
22
+ true
23
+ end
24
+
25
+ def open_account?
26
+ super || account_in_unverified_grace_period?
27
+ end
28
+
29
+ private
30
+
31
+ def after_close_account
32
+ super if defined?(super)
33
+ verify_account_ds.delete
34
+ end
35
+
36
+ def before_change_login_route
37
+ unless verified_account?
38
+ set_redirect_error_flash unverified_change_login_error_flash
39
+ redirect unverified_change_login_redirect
40
+ end
41
+ super if defined?(super)
42
+ end
43
+
44
+ def verify_account_check_already_logged_in
45
+ nil
46
+ end
47
+
48
+ def account_session_status_filter
49
+ s = super
50
+ if verify_account_grace_period
51
+ grace_period_ds = db[verify_account_table].
52
+ select(verify_account_id_column).
53
+ where((Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP))
54
+ s = Sequel.|(s, Sequel.expr(account_status_column=>account_unverified_status_value) & {account_id_column => grace_period_ds})
55
+ end
56
+ s
57
+ end
58
+
59
+ def update_session
60
+ super
61
+ if account_in_unverified_grace_period?
62
+ session[unverified_account_session_key] = true
63
+ end
64
+ end
65
+
66
+ def account_in_unverified_grace_period?
67
+ account[account_status_column] == account_unverified_status_value &&
68
+ verify_account_grace_period &&
69
+ !verify_account_ds.where(Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP).empty?
70
+ end
71
+
72
+ def use_date_arithmetic?
73
+ true
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ VerifyChangeLogin = Feature.define(:verify_change_login) do
5
+ depends :change_login, :verify_account_grace_period
6
+
7
+ def change_login_notice_flash
8
+ "#{super}. #{verify_account_email_sent_notice_flash}"
9
+ end
10
+
11
+ private
12
+
13
+ def after_change_login
14
+ super
15
+ update_account(account_status_column=>account_unverified_status_value)
16
+ setup_account_verification
17
+ session[unverified_account_session_key] = true
18
+ end
19
+ end
20
+ end