rodauth 0.10.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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