aerogel-users 1.4.3

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +16 -0
  6. data/Rakefile +1 -0
  7. data/aerogel-users.gemspec +30 -0
  8. data/app/helpers/access_control.rb +32 -0
  9. data/app/helpers/auth.rb +58 -0
  10. data/app/helpers/auth_login_providers.rb +18 -0
  11. data/app/mailers/user.rb +43 -0
  12. data/app/routes/access_control.rb +15 -0
  13. data/app/routes/auth.rb +53 -0
  14. data/app/routes/user.rb +192 -0
  15. data/assets/README.md +1 -0
  16. data/assets/javascripts/.gitkeep +0 -0
  17. data/assets/stylesheets/.gitkeep +0 -0
  18. data/config/auth.conf +43 -0
  19. data/config/development/.keep +0 -0
  20. data/config/production/.keep +0 -0
  21. data/db/model/access.rb +67 -0
  22. data/db/model/authentication.rb +54 -0
  23. data/db/model/role.rb +17 -0
  24. data/db/model/user.rb +223 -0
  25. data/db/model/user_email.rb +24 -0
  26. data/db/model/user_registration_form.rb +23 -0
  27. data/db/model/user_request_account_activation_form.rb +30 -0
  28. data/db/model/user_request_email_confirmation_form.rb +40 -0
  29. data/db/model/user_request_password_reset_form.rb +37 -0
  30. data/db/model/user_reset_password_form.rb +41 -0
  31. data/db/seed/01_user_role.seed +8 -0
  32. data/db/seed/02_access_user.seed +25 -0
  33. data/db/seed/development/.keep +0 -0
  34. data/db/seed/development/test-user.seed +77 -0
  35. data/db/seed/production/.keep +0 -0
  36. data/lib/aerogel/users.rb +22 -0
  37. data/lib/aerogel/users/auth.rb +67 -0
  38. data/lib/aerogel/users/omniauth-failure_endpoint_ex.rb +21 -0
  39. data/lib/aerogel/users/omniauth-password.rb +63 -0
  40. data/lib/aerogel/users/secure_password.rb +55 -0
  41. data/lib/aerogel/users/version.rb +5 -0
  42. data/locales/actions/en.yml +18 -0
  43. data/locales/actions/ru.yml +17 -0
  44. data/locales/auth/en.yml +22 -0
  45. data/locales/auth/ru.yml +22 -0
  46. data/locales/mailers/en.yml +69 -0
  47. data/locales/mailers/ru.yml +73 -0
  48. data/locales/models/en.yml +65 -0
  49. data/locales/models/ru.yml +64 -0
  50. data/locales/views/en.yml +122 -0
  51. data/locales/views/ru.yml +126 -0
  52. data/public/README.md +1 -0
  53. data/rake/README.md +3 -0
  54. data/views/mailers/user/account_activation.html.erb +3 -0
  55. data/views/mailers/user/account_activation.text.erb +3 -0
  56. data/views/mailers/user/email_confirmation.html.erb +3 -0
  57. data/views/mailers/user/email_confirmation.text.erb +3 -0
  58. data/views/mailers/user/password_reset.html.erb +3 -0
  59. data/views/mailers/user/password_reset.text.erb +3 -0
  60. data/views/user/activate_account.html.erb +3 -0
  61. data/views/user/activate_account_failure.html.erb +3 -0
  62. data/views/user/confirm_email.html.erb +3 -0
  63. data/views/user/confirm_email_failure.html.erb +3 -0
  64. data/views/user/edit.html.erb +6 -0
  65. data/views/user/index.html.erb +19 -0
  66. data/views/user/login.html.erb +3 -0
  67. data/views/user/login/_form.html.erb +28 -0
  68. data/views/user/login/_provider.html.erb +5 -0
  69. data/views/user/logout.html.erb +3 -0
  70. data/views/user/register.html.erb +10 -0
  71. data/views/user/register_success.html.erb +3 -0
  72. data/views/user/request_account_activation.html.erb +9 -0
  73. data/views/user/request_account_activation_success.html.erb +3 -0
  74. data/views/user/request_email_confirmation.html.erb +8 -0
  75. data/views/user/request_email_confirmation_success.html.erb +3 -0
  76. data/views/user/request_password_reset.html.erb +8 -0
  77. data/views/user/request_password_reset_success.html.erb +3 -0
  78. data/views/user/reset_password.html.erb +9 -0
  79. data/views/user/reset_password_success.html.erb +3 -0
  80. metadata +234 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38e3f2209b78bded716b72e0d6fc1c3c018dce08
4
+ data.tar.gz: 15d5df47c1147bab9404240b00aebf2416ba642f
5
+ SHA512:
6
+ metadata.gz: 2fe81054db3c0fb713768328bc76632b45f4a0663b85a7961dbfb0cd468e58dadd61f3f14572c3d95e578c45e001738548bd877a538ac4235b6cebca49f8a0ab
7
+ data.tar.gz: 897182d5dac0d2e39d9f672eb02f4b4111d76fd8dfccf38b504284f75a9aeaae7542ee92aceb86275b41215fbe50dac1cad0976ea0551091569808a4ef1e7962
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
19
+ .ruby-gemset
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aerogel-module123.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alex Kukushkin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # aerogel-users
2
+
3
+ User management for aerogel CMS.
4
+
5
+ Provides user models, helpers and routs for user registration, authentication,
6
+ roles and access management.
7
+
8
+ ## Usage
9
+
10
+ In your application's config.ru:
11
+ ```ruby
12
+ require 'aerogel/core'
13
+ require 'aerogel/users'
14
+
15
+ run Aerogel::Application.load
16
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'aerogel/users/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aerogel-users"
8
+ spec.version = Aerogel::Users::VERSION
9
+ spec.authors = ["Alex Kukushkin"]
10
+ spec.email = ["alex@kukushk.in"]
11
+ spec.description = %q{User management for aerogel}
12
+ spec.summary = %q{User management for aerogel}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "aerogel-core", "~> 1.4"
22
+ spec.add_dependency "aerogel-forms", "~> 0.1"
23
+ spec.add_dependency "aerogel-mailer", "~> 1.1"
24
+ spec.add_dependency "aerogel-font_awesome"
25
+ spec.add_dependency "omniauth"
26
+ spec.add_dependency "bcrypt"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,32 @@
1
+ # Returns +true+ is user allowed to access +path+ with operation +access+.
2
+ #
3
+ def can? path, access = Access::READ
4
+ applied_rules = Access.rules_for_path path
5
+
6
+ # no rules for the path mean the access is not restricted
7
+ return true if applied_rules.blank?
8
+
9
+ # there are rules for the path, but the user is not authenticated
10
+ return false unless current_user?
11
+
12
+ # check if any rule grants access to path/access/user roles.
13
+ applied_rules.each do |access_rule|
14
+ return true if access_rule.grants?( path, access, current_user.roles )
15
+ end
16
+
17
+ # no luck
18
+ return false
19
+ end
20
+
21
+
22
+ # Returns constructed link if READ access to +url+ is allowed, returns empty string otherwise.
23
+ #
24
+ def link_to_if_can( url, text = url, opts = {} )
25
+ link_to( url, text, opts ) if can?( url )
26
+ end
27
+
28
+
29
+ def on_access_denied( &block )
30
+ @on_access_denied_callback = block if block_given?
31
+ @on_access_denied_callback
32
+ end
@@ -0,0 +1,58 @@
1
+ # Redirects after authentication process to the most appropriate origin URL.
2
+ #
3
+ # Possible origins (URLs to redirect to) are tried in following order:
4
+ # explicit_origin
5
+ # request.env['omniauth.origin']
6
+ # request.params['origin']
7
+ # default_origin
8
+ #
9
+ def auth_redirect_to_origin( explicit_origin, default_origin = "/" )
10
+ url = explicit_origin || request.env['omniauth.origin'] || request.params['origin'] || default_origin
11
+ redirect url
12
+ end
13
+
14
+ def auth_login( user, opts = {} )
15
+ session[:user_id] = user.id
16
+ session[:remember_me] = opts[:remember_me].present?
17
+ user.authenticated!( opts )
18
+ auth_keepalive_session
19
+ end
20
+
21
+ def auth_logout
22
+ session[:user_id] = nil
23
+ session[:remember_me] = nil
24
+ session.options[:expire_after] = nil
25
+ end
26
+
27
+ def auth_keepalive_session
28
+ session.options[:expire_after] = 2592000 if session[:remember_me]
29
+ end
30
+
31
+ # Returns User object of the current authenticated user or +nil+.
32
+ #
33
+ def current_user
34
+ auth_keepalive_session
35
+ @current_user ||= ( session[:user_id] ? User.find( session[:user_id] ) : nil )
36
+ end
37
+
38
+ # Returns +true+ if user is authenticated, +false+ otherwise.
39
+ #
40
+ def current_user?
41
+ !current_user.nil?
42
+ end
43
+
44
+ # Gets or sets auth state.
45
+ # 'auth state' is a one-time used Hash used to store auth system data between requests.
46
+ #
47
+ def auth_state( value = nil )
48
+ if value
49
+ @auth_state = value
50
+ session[:auth_state] = value.to_json
51
+ return @auth_state
52
+ end
53
+ unless @auth_state
54
+ @auth_state = session[:auth_state].nil? ? {} : JSON.parse( session[:auth_state] )
55
+ session[:auth_state] = nil
56
+ end
57
+ @auth_state
58
+ end
@@ -0,0 +1,18 @@
1
+
2
+ # Returns A HREF link to a login using given provider.
3
+ #
4
+ def link_to_social_login( provider_key, text = nil, on_success = nil )
5
+ provider = Aerogel::Auth.providers[provider_key] || {}
6
+ text ||= provider[:name]
7
+ link_to url_to_social_login( provider_key, on_success ), text.to_s, title: "log in using #{provider[:name]}"
8
+ end
9
+
10
+
11
+ # Returns URL to a login using given provider.
12
+ #
13
+ def url_to_social_login( provider_key, on_success = nil )
14
+ provider = Aerogel::Auth.providers[provider_key] || {}
15
+ origin = on_success || params['on_success']
16
+ query_string = origin ? "?origin=#{origin}" : ''
17
+ "/auth/#{provider_key}#{query_string}"
18
+ end
@@ -0,0 +1,43 @@
1
+
2
+ mailer 'user/email_confirmation' do |user_email|
3
+ user = user_email.user
4
+ url = url_to( "/user/confirm_email",
5
+ fqdn: true,
6
+ email: user_email.email,
7
+ token: user_email.confirmation_token
8
+ )
9
+
10
+ to user_email.email
11
+ subject t.aerogel.users.mailers.email_confirmation.subject
12
+ locals full_name: h(user.full_name), url: url
13
+ end
14
+
15
+
16
+ mailer 'user/account_activation' do |user|
17
+ user_email = user.emails.first
18
+ url = url_to( "/user/activate_account",
19
+ fqdn: true,
20
+ email: user_email.email,
21
+ token: user_email.confirmation_token
22
+ )
23
+
24
+ to user_email.email
25
+ subject t.aerogel.users.mailers.account_activation.subject
26
+ locals full_name: h(user.full_name), url: url
27
+ end
28
+
29
+
30
+ mailer 'user/password_reset' do |authentication|
31
+ user_email = authentication.email
32
+ user = user_email.user
33
+ url = url_to( "/user/reset_password",
34
+ fqdn: true,
35
+ email: user_email.email,
36
+ token: authentication.password_reset_token
37
+ )
38
+
39
+ user_email = authentication.email
40
+ to user_email.email
41
+ subject t.aerogel.users.mailers.password_reset.subject
42
+ locals full_name: h(user.full_name), url: url
43
+ end
@@ -0,0 +1,15 @@
1
+
2
+ # Before serving any route check for access permissions.
3
+ #
4
+ before do
5
+ access = %w(GET HEAD).include?( request.request_method.upcase.to_s ) ? Access::READ : Access::WRITE
6
+ unless can? request.path_info, access
7
+ flash[:error] = t.aerogel.auth.actions.access_denied path: request.path_info, access: access
8
+
9
+ # on_access_denied callback redefines flash[:error] and raises redirect exception
10
+ on_access_denied.call( request.path_info, access ) if on_access_denied.present?
11
+
12
+ redirect "/"
13
+ end
14
+ end
15
+
@@ -0,0 +1,53 @@
1
+ # Authentication system routes
2
+ route :get, :post, "/auth/:provider/callback" do
3
+ # flash[:debug] = "params.inspect: #{params.inspect}"
4
+
5
+ user = User.find_by_authentication( params[:provider].to_sym, request.env['omniauth.auth']['uid'] )
6
+ if params[:provider] == 'password' && !user.activated?( request.env['omniauth.auth']['uid'] )
7
+ auth_state params.merge(
8
+ result: 'error',
9
+ error: 'account_not_activated'
10
+ )
11
+ auth_redirect_to_origin( params['on_failure'] )
12
+ halt
13
+ end
14
+
15
+ if user
16
+ # registered user
17
+ auth_login( user, remember_me: !!params['remember_me'], provider: params[:provider], uid: request.env['omniauth.auth']['uid'] )
18
+ logger.debug( "successfully authenticated: #{params.inspect} ")
19
+ logger.debug( "user: #{user} ")
20
+ auth_state result: :success
21
+ flash[:notice] = t.aerogel.auth.welcome full_name: user.full_name
22
+ auth_redirect_to_origin( params["on_success"] )
23
+ else
24
+ user = User.create_from_omniauth request.env['omniauth.auth']
25
+ unless user.save
26
+ flash[:error] = t.aerogel.auth.errors.create_from_omniauth errors: user.errors.inspect
27
+ auth_redirect_to_origin( params["on_failure"] )
28
+ end
29
+ auth_login( user, remember_me: !!params['remember_me'], provider: params[:provider], uid: request.env['omniauth.auth']['uid'] )
30
+ flash[:notice] = t.aerogel.auth.welcome_new_user full_name: user.full_name
31
+ redirect "/user/edit"
32
+ end
33
+ end
34
+
35
+ get "/auth/failure" do
36
+ logger.debug( "failed to authenticate: #{params.inspect} ")
37
+ flash[:debug] = "params.inspect: #{params.inspect}"
38
+ auth_state params.merge(
39
+ result: 'error',
40
+ error: 'invalid_credentials',
41
+ provider: params['provider'] || params['strategy']
42
+ )
43
+ auth_redirect_to_origin( params['on_failure'] )
44
+ end
45
+
46
+
47
+ # Retrieves auth_state
48
+ #
49
+ before do
50
+ auth_state
51
+ end
52
+
53
+
@@ -0,0 +1,192 @@
1
+
2
+ namespace '/user' do
3
+
4
+ before do
5
+ on_access_denied do |path|
6
+ unless current_user?
7
+ redirect "/user/login?on_success=#{path}"
8
+ end
9
+ end
10
+ end
11
+
12
+ get '/login' do
13
+ @error_message = nil
14
+ params['on_success'] ||= auth_state['on_success'] || '/'
15
+ params['on_failure'] ||= auth_state['on_failure'] || '/user/login'
16
+ params['email'] ||= auth_state['email']
17
+ if auth_state['result'] == 'error'
18
+ auth_state['provider'] ||= "i_think_its_password"
19
+ # error messages are looked up in the following order:
20
+ # aerogel.auth.<provider>.errors.<message>
21
+ # aerogel.auth.errors.<message>
22
+ # aerogel.auth.errors.unknown
23
+ @error_message = t.aerogel.auth.send( auth_state['provider'].to_sym ).errors.send(
24
+ auth_state['error'].to_sym,
25
+ default: [ "aerogel.auth.errors.#{auth_state['error']}".to_sym, :'aerogel.auth.errors.unknown' ],
26
+ provider: auth_state['provider'],
27
+ message: auth_state['error']
28
+ )
29
+ end
30
+ pass
31
+ end
32
+
33
+ get '/logout' do
34
+ auth_logout
35
+ flash.now[:notice] = t.aerogel.users.actions.logged_out
36
+ pass
37
+ end
38
+
39
+ #
40
+ # Account management routes
41
+ #
42
+ get '/register' do
43
+ @user_registration_form = UserRegistrationForm.new
44
+ pass
45
+ end
46
+
47
+ post '/register' do
48
+ @user_registration_form = UserRegistrationForm.new( params[:user_registration_form] )
49
+ pass unless @user_registration_form.valid?
50
+ user = User.create_from @user_registration_form
51
+ unless user.save
52
+ flash[:error] = t.aerogel.db.errors.failed_to_save name: user.model_name.human,
53
+ errors: user.errors.full_messages.join(', ')
54
+ pass
55
+ end
56
+ user.request_activation!
57
+ email 'user/account_activation', user
58
+ view "user/register_success"
59
+ end
60
+
61
+ get '/request_account_activation' do
62
+ @user_request_account_activation_form = UserRequestAccountActivationForm.new
63
+ pass
64
+ end
65
+
66
+ post '/request_account_activation' do
67
+ @user_request_account_activation_form = UserRequestAccountActivationForm.new(
68
+ params[:user_request_account_activation_form]
69
+ )
70
+ pass unless @user_request_account_activation_form.valid?
71
+ user = @user_request_account_activation_form.user
72
+ user.request_activation!
73
+ email 'user/account_activation', user
74
+ view 'user/request_account_activation_success'
75
+ end
76
+
77
+
78
+ get '/activate_account' do
79
+ begin
80
+ @user = User.activate!( params['email'], params['token'] )
81
+ pass
82
+ rescue StandardError => e
83
+ flash.now[:error] = e.to_s
84
+ view 'user/activate_account_failure'
85
+ end
86
+ end
87
+
88
+ get '/request_email_confirmation' do
89
+ @user_request_email_confirmation_form = UserRequestEmailConfirmationForm.new
90
+ pass
91
+ end
92
+
93
+ post '/request_email_confirmation' do
94
+ @user_request_email_confirmation_form = UserRequestEmailConfirmationForm.new(
95
+ params[:user_request_email_confirmation_form]
96
+ )
97
+ pass unless @user_request_email_confirmation_form.valid?
98
+ user_email = @user_request_email_confirmation_form.user_email
99
+ user_email.user.request_email_confirmation! @user_request_email_confirmation_form.email
100
+ email 'user/email_confirmation', user_email
101
+ view 'user/request_email_confirmation_success'
102
+ end
103
+
104
+ get '/confirm_email' do
105
+ begin
106
+ user = User.confirm_email!( params['email'], params['token'] )
107
+ pass
108
+ rescue StandardError => e
109
+ flash.now[:error] = e.to_s
110
+ view 'user/confirm_email_failure'
111
+ end
112
+ end
113
+
114
+ get '/request_password_reset' do
115
+ @user_request_password_reset_form = UserRequestPasswordResetForm.new
116
+ pass
117
+ end
118
+
119
+ post '/request_password_reset' do
120
+ @user_request_password_reset_form = UserRequestPasswordResetForm.new(
121
+ params[:user_request_password_reset_form]
122
+ )
123
+ pass unless @user_request_password_reset_form.valid?
124
+ user = @user_request_password_reset_form.user
125
+ authentication = user.request_password_reset! @user_request_password_reset_form.email
126
+ email 'user/password_reset', authentication
127
+ view 'user/request_password_reset_success'
128
+ end
129
+
130
+ get '/reset_password' do
131
+ @user_reset_password_form = UserResetPasswordForm.new(
132
+ email: params['email'],
133
+ password_reset_token: params['token']
134
+ )
135
+ pass
136
+ end
137
+
138
+ post '/reset_password' do
139
+ @user_reset_password_form = UserResetPasswordForm.new(
140
+ params[:user_reset_password_form]
141
+ )
142
+ pass unless @user_reset_password_form.valid?
143
+ begin
144
+ user = User.reset_password!(
145
+ @user_reset_password_form.email,
146
+ @user_reset_password_form.password_reset_token,
147
+ @user_reset_password_form.password,
148
+ @user_reset_password_form.password_confirmation
149
+ )
150
+ view 'user/reset_password_success'
151
+ rescue StandardError => e
152
+ flash.now[:error] = e.to_s
153
+ pass
154
+ end
155
+ end
156
+
157
+ #
158
+ # Edit user profile
159
+ #
160
+ get '/edit' do
161
+ @user = current_user
162
+ pass
163
+ end
164
+
165
+ post '/edit' do
166
+ @user = User.find( params[:id] )
167
+ unless @user
168
+ flash.now[:error] = t.error.messages.user_not_found
169
+ pass
170
+ end
171
+ unless @user.id == current_user.id
172
+ redirect '/user', error: "Access denied, user profile does not belong to you"
173
+ end
174
+ unless @user.update_attributes params[:user].except( :roles )
175
+ flash.now[:error] = t.aerogel.db.errors.failed_to_save name: User.model_name.human,
176
+ errors: @user.errors.full_messages.join(", ")
177
+ pass
178
+ end
179
+ redirect '/user'
180
+ end
181
+
182
+ #
183
+ # Common User controller routes
184
+ #
185
+ route :get, :post, ['', '/:action'] do
186
+ action = params[:action] || 'index'
187
+ view "user/#{action}"
188
+ end
189
+
190
+
191
+ end # namespace '/user'
192
+