morpho 0.1.0 → 0.1.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/api/concerns/morpho/grape/http_responses.rb +41 -0
  4. data/app/api/concerns/morpho/grape/jwt_authentication.rb +77 -0
  5. data/app/api/concerns/morpho/grape/user_activation.rb +13 -0
  6. data/app/api/concerns/morpho/grape/user_password_reset.rb +13 -0
  7. data/app/api/concerns/morpho/grape/user_registration.rb +19 -0
  8. data/app/api/concerns/morpho/grape/user_unlock.rb +13 -0
  9. data/app/api/morpho/api.rb +24 -0
  10. data/app/api/morpho/entities/authentication_token.rb +8 -0
  11. data/app/api/morpho/entities/user.rb +7 -0
  12. data/app/api/morpho/entities/user_sign_in.rb +8 -0
  13. data/app/api/morpho/entities/user_sign_up.rb +9 -0
  14. data/app/api/morpho/resources/activations.rb +29 -0
  15. data/app/api/morpho/resources/passwords.rb +25 -0
  16. data/app/api/morpho/resources/tokens.rb +19 -0
  17. data/app/api/morpho/resources/unlocks.rb +29 -0
  18. data/app/api/morpho/resources/users.rb +19 -0
  19. data/app/assets/images/morpho/morpho.png +0 -0
  20. data/app/assets/images/morpho/morpho.svg +89 -0
  21. data/app/assets/stylesheets/morpho/application.css +74 -1
  22. data/app/controllers/morpho/activations_controller.rb +44 -0
  23. data/app/controllers/morpho/application_controller.rb +9 -0
  24. data/app/controllers/morpho/home_controller.rb +6 -0
  25. data/app/controllers/morpho/passwords_controller.rb +56 -0
  26. data/app/controllers/morpho/sessions_controller.rb +24 -0
  27. data/app/controllers/morpho/unlocks_controller.rb +44 -0
  28. data/app/controllers/morpho/users_controller.rb +25 -0
  29. data/app/mailers/morpho/application_mailer.rb +0 -1
  30. data/app/mailers/morpho/user_mailer.rb +31 -0
  31. data/app/models/morpho/authentication.rb +5 -0
  32. data/app/models/morpho/user.rb +60 -0
  33. data/app/views/layouts/morpho/application.html.erb +6 -4
  34. data/app/views/layouts/morpho/mailer.html.erb +13 -0
  35. data/app/views/layouts/morpho/mailer.text.erb +1 -0
  36. data/app/views/morpho/activations/new.html.erb +16 -0
  37. data/app/views/morpho/home/index.html.erb +4 -0
  38. data/app/views/morpho/passwords/edit.html.erb +18 -0
  39. data/app/views/morpho/passwords/new.html.erb +16 -0
  40. data/app/views/morpho/sessions/new.html.erb +23 -0
  41. data/app/views/morpho/unlocks/new.html.erb +16 -0
  42. data/app/views/morpho/user_mailer/activation_needed_email.html.erb +7 -0
  43. data/app/views/morpho/user_mailer/activation_needed_email.text.erb +7 -0
  44. data/app/views/morpho/user_mailer/activation_success_email.html.erb +7 -0
  45. data/app/views/morpho/user_mailer/activation_success_email.text.erb +7 -0
  46. data/app/views/morpho/user_mailer/reset_password_email.html.erb +7 -0
  47. data/app/views/morpho/user_mailer/reset_password_email.text.erb +7 -0
  48. data/app/views/morpho/user_mailer/unlock_token_email.html.erb +7 -0
  49. data/app/views/morpho/user_mailer/unlock_token_email.text.erb +7 -0
  50. data/app/views/morpho/users/new.html.erb +20 -0
  51. data/config/initializers/flash_rails_messages_skeleton.rb +22 -0
  52. data/config/initializers/simple_form.rb +182 -0
  53. data/config/initializers/sorcery.rb +513 -0
  54. data/config/locales/morpho.en.yml +93 -0
  55. data/config/routes.rb +25 -0
  56. data/db/migrate/20180919162009_sorcery_core.rb +13 -0
  57. data/db/migrate/20180919162055_sorcery_remember_me.rb +8 -0
  58. data/db/migrate/20180919162056_sorcery_reset_password.rb +10 -0
  59. data/db/migrate/20180919162057_sorcery_user_activation.rb +9 -0
  60. data/db/migrate/20180919162058_sorcery_brute_force_protection.rb +9 -0
  61. data/db/migrate/20180919162059_sorcery_activity_logging.rb +10 -0
  62. data/db/migrate/20180919162100_sorcery_external.rb +12 -0
  63. data/lib/generators/morpho/install/install_generator.rb +7 -0
  64. data/lib/generators/morpho/install/templates/config/initializers/morpho.rb +17 -0
  65. data/lib/generators/morpho/install/templates/public/favicon-16x16.png +0 -0
  66. data/lib/generators/morpho/install/templates/public/favicon-32x32.png +0 -0
  67. data/lib/generators/morpho/install/templates/public/favicon.ico +0 -0
  68. data/lib/morpho.rb +15 -2
  69. data/lib/morpho/configuration.rb +24 -0
  70. data/lib/morpho/configurations/api.rb +31 -0
  71. data/lib/morpho/configurations/auth.rb +11 -0
  72. data/lib/morpho/configurations/jwt.rb +17 -0
  73. data/lib/morpho/configurations/mailer.rb +23 -0
  74. data/lib/morpho/engine.rb +33 -0
  75. data/lib/morpho/loader.rb +11 -0
  76. data/lib/morpho/version.rb +1 -1
  77. data/lib/tasks/morpho_tasks.rake +1 -1
  78. data/lib/templates/erb/scaffold/_form.html.erb +15 -0
  79. metadata +223 -2
@@ -10,6 +10,79 @@
10
10
  * files in this directory. Styles in this file should be added after the last require_* statement.
11
11
  * It is generally better to create a new file per style scope.
12
12
  *
13
- *= require_tree .
14
13
  *= require_self
15
14
  */
15
+ @import 'https://fonts.googleapis.com/css?family=Raleway:400,300,600';
16
+ @import 'https://cdn.jsdelivr.net/npm/skeleton-css@2.0.4/css/normalize.css';
17
+ @import 'https://cdn.jsdelivr.net/npm/skeleton-css@2.0.4/css/skeleton.css';
18
+
19
+ body {
20
+ padding: 2em 1em;
21
+ }
22
+
23
+ .alert {
24
+ display: block;
25
+ padding: 1em;
26
+ border-left: 5px solid;
27
+ margin-bottom: 1em;
28
+ }
29
+
30
+ .alert-success {
31
+ background-color: #D5F5E3;
32
+ border-left-color: #2ECC71;
33
+ color: #2ECC71;
34
+ }
35
+
36
+ .alert-info {
37
+ background-color: #D6EAF8;
38
+ border-left-color: #3498DB;
39
+ color: #3498DB;
40
+ }
41
+
42
+ .alert-warning {
43
+ background-color: #FCF3CF;
44
+ border-left-color: #F1C40F;
45
+ color: #F1C40F;
46
+ }
47
+
48
+ .alert-error {
49
+ background-color: #F2D7D5;
50
+ border-left-color: #C0392B;
51
+ color: #C0392B;
52
+ }
53
+
54
+ .field_with_errors {
55
+ padding-bottom: 1em;
56
+ }
57
+
58
+ .field_with_errors input, .field_with_errors textarea, .field_with_errors select {
59
+ margin-bottom: 0.5em;
60
+ }
61
+
62
+ .field_with_errors span.error {
63
+ color: #C0392B;
64
+ text-transform: lowercase;
65
+ }
66
+
67
+ .input label.boolean {
68
+ display: inline;
69
+ font-weight: normal;
70
+ margin-left: 0.5em;
71
+ }
72
+
73
+ ul.unstyled {
74
+ list-style: none;
75
+ }
76
+
77
+ .sign-in-form, .sign-up-form, .activation-form, .reset-password-form, .unlock-form {
78
+ position: relative;
79
+ height: 50vh;
80
+ }
81
+
82
+ .sign-in-form form, .sign-up-form form, .activation-form form, .reset-password-form form, .unlock-form form {
83
+ position: absolute;
84
+ top: 50%;
85
+ left: 50%;
86
+ transform: translate(-50%, -50%);
87
+ width: 50vh;
88
+ }
@@ -0,0 +1,44 @@
1
+ module Morpho
2
+ class ActivationsController < ApplicationController
3
+ skip_before_action :require_login, only: [ :new, :create, :show ]
4
+ helper_method :user
5
+
6
+ def create
7
+ if user && !user.active?
8
+ user.resend_activation_needed_email!
9
+ flash.now[:notice] = I18n.t('morpho.messages.activations.create.success')
10
+ render :new
11
+ else
12
+ flash.now[:error] = I18n.t('morpho.messages.activations.create.failure')
13
+ render :new
14
+ end
15
+ end
16
+
17
+ def show
18
+ if user && !user.active?
19
+ user.activate!
20
+ redirect_to sign_in_path, success: I18n.t('morpho.messages.activations.activate.success')
21
+ else
22
+ redirect_to sign_in_path, error: I18n.t('morpho.messages.activations.activate.failure')
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def user
29
+ @user ||= case
30
+ when %w(show).include?(action_name)
31
+ Morpho::User.load_from_activation_token(params[:token])
32
+ when %(create).include?(action_name)
33
+ Morpho::User.find_by(email_params)
34
+ else
35
+ Morpho::User.new
36
+ end
37
+
38
+ end
39
+
40
+ def email_params
41
+ params.require(:user).permit(:email)
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,14 @@
1
1
  module Morpho
2
2
  class ApplicationController < ActionController::Base
3
3
  protect_from_forgery with: :exception
4
+ add_flash_types :notice, :alert, :error, :success
5
+ layout 'morpho/application'
6
+ before_action :require_login
7
+
8
+ protected
9
+
10
+ def not_authenticated
11
+ redirect_to sign_in_path, notice: I18n.t('morpho.messages.unauthenticated')
12
+ end
4
13
  end
5
14
  end
@@ -0,0 +1,6 @@
1
+ module Morpho
2
+ class HomeController < ApplicationController
3
+ def index
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,56 @@
1
+ module Morpho
2
+ class PasswordsController < ApplicationController
3
+ skip_before_action :require_login, only: [ :new, :create, :edit, :update ]
4
+ helper_method :user
5
+
6
+ def create
7
+ if user
8
+ user.deliver_reset_password_instructions!
9
+ flash.now[:notice] = I18n.t('morpho.messages.passwords.create.success')
10
+ render :new
11
+ else
12
+ flash.now[:error] = I18n.t('morpho.messages.passwords.create.failure')
13
+ render :new
14
+ end
15
+ end
16
+
17
+ def edit
18
+ unless user
19
+ flash.now[:error] = I18n.t('morpho.messages.passwords.edit.failure')
20
+ render :new
21
+ end
22
+ end
23
+
24
+ def update
25
+ if user.update_attributes(password_params)
26
+ user.change_password!(password_params[:password])
27
+ redirect_to sign_in_path, success: I18n.t('morpho.messages.passwords.update.success')
28
+ else
29
+ flash.now[:error] = I18n.t('morpho.messages.passwords.update.failure')
30
+ render :edit
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def user
37
+ @user ||= case
38
+ when %w(edit update).include?(action_name)
39
+ Morpho::User.load_from_reset_password_token(params[:token])
40
+ when %(create).include?(action_name)
41
+ Morpho::User.find_by(email_params)
42
+ else
43
+ Morpho::User.new
44
+ end
45
+
46
+ end
47
+
48
+ def email_params
49
+ params.require(:user).permit(:email)
50
+ end
51
+
52
+ def password_params
53
+ params.require(:user).permit(:password, :password_confirmation)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ module Morpho
2
+ class SessionsController < ApplicationController
3
+ skip_before_action :require_login, only: [ :new, :create ]
4
+
5
+ def create
6
+ if login(session_params[:email], session_params[:password], session_params[:remember_me])
7
+ redirect_to root_path, success: I18n.t('morpho.messages.sessions.create.success') and return
8
+ else
9
+ flash.now[:error] = I18n.t('morpho.messages.sessions.create.failure')
10
+ render :new
11
+ end
12
+ end
13
+
14
+ def destroy
15
+ redirect_to sign_in_path, success: I18n.t('morpho.messages.sessions.destroy.success') and return if logout
16
+ end
17
+
18
+ protected
19
+
20
+ def session_params
21
+ params.require(:session).permit(:email, :password, :remember_me)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ module Morpho
2
+ class UnlocksController < ApplicationController
3
+ skip_before_action :require_login, only: [ :new, :create, :show ]
4
+ helper_method :user
5
+
6
+ def create
7
+ if user && user.login_locked?
8
+ user.resend_unlock_token_email!
9
+ flash.now[:notice] = I18n.t('morpho.messages.unlocks.unlock.success')
10
+ render :new
11
+ else
12
+ flash.now[:error] = I18n.t('morpho.messages.unlocks.unlock.failure')
13
+ render :new
14
+ end
15
+ end
16
+
17
+ def show
18
+ if user && user.login_locked?
19
+ user.login_unlock!
20
+ redirect_to sign_in_path, success: I18n.t('morpho.messages.unlocks.unlock.success')
21
+ else
22
+ redirect_to sign_in_path, error: I18n.t('morpho.messages.unlocks.unlock.failure')
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def user
29
+ @user ||= case
30
+ when %w(show).include?(action_name)
31
+ Morpho::User.load_from_unlock_token(params[:token])
32
+ when %(create).include?(action_name)
33
+ Morpho::User.find_by(email_params)
34
+ else
35
+ Morpho::User.new
36
+ end
37
+
38
+ end
39
+
40
+ def email_params
41
+ params.require(:user).permit(:email)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module Morpho
2
+ class UsersController < ApplicationController
3
+ skip_before_action :require_login, only: [ :new, :create ]
4
+ helper_method :user
5
+
6
+ def create
7
+ if user.update_attributes(user_params)
8
+ redirect_to sign_in_path, success: I18n.t('morpho.messages.users.create.success')
9
+ else
10
+ flash.now[:alert] = I18n.t('morpho.messages.users.create.failure')
11
+ render :new
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def user
18
+ @user ||= Morpho::User.new
19
+ end
20
+
21
+ def user_params
22
+ params.require(:user).permit(:email, :password, :password_confirmation)
23
+ end
24
+ end
25
+ end
@@ -1,6 +1,5 @@
1
1
  module Morpho
2
2
  class ApplicationMailer < ActionMailer::Base
3
- default from: 'from@example.com'
4
3
  layout 'mailer'
5
4
  end
6
5
  end
@@ -0,0 +1,31 @@
1
+ module Morpho
2
+ class UserMailer < ApplicationMailer
3
+ def activation_needed_email(user)
4
+ @user = user
5
+ @url = activate_url(token: @user.activation_token)
6
+
7
+ mail(to: @user.email, subject: t('morpho.mailer.activation_needed.subject'))
8
+ end
9
+
10
+ def activation_success_email(user)
11
+ @user = user
12
+ @url = sign_in_url()
13
+
14
+ mail(to: @user.email, subject: t('morpho.mailer.activation_success.subject'))
15
+ end
16
+
17
+ def reset_password_email(user)
18
+ @user = user
19
+ @url = new_password_url(token: @user.reset_password_token)
20
+
21
+ mail(to: @user.email, subject: t('morpho.mailer.reset_password.subject'))
22
+ end
23
+
24
+ def unlock_token_email(user)
25
+ @user = user
26
+ @url = unlock_url(token: @user.unlock_token)
27
+
28
+ mail(to: @user.email, subject: t('morpho.mailer.unlock_token.subject'))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Morpho
2
+ class Authentication < ApplicationRecord
3
+ belongs_to :user
4
+ end
5
+ end
@@ -0,0 +1,60 @@
1
+ module Morpho
2
+ class User < ApplicationRecord
3
+ authenticates_with_sorcery!
4
+
5
+ has_many :authentications, dependent: :destroy
6
+ accepts_nested_attributes_for :authentications
7
+
8
+ validates :password, length: { minimum: 12 }
9
+ validates :password, confirmation: true
10
+ validates :email, uniqueness: true
11
+ validates_email_format_of :email
12
+
13
+ def active?
14
+ self.activation_state == 'active'
15
+ end
16
+
17
+ def resend_activation_needed_email!
18
+ self.setup_activation
19
+ self.reload
20
+ self.send_activation_needed_email!
21
+ end
22
+
23
+ def resend_unlock_token_email!
24
+ self.login_lock!
25
+ end
26
+
27
+ class << self
28
+ # bugfix: https://github.com/Sorcery/sorcery/issues/146
29
+ def create_and_validate_from_provider(provider, uid, attrs)
30
+ user = new(attrs)
31
+
32
+ user.send(sorcery_config.authentications_class.name.demodulize.underscore.pluralize).build(
33
+ sorcery_config.provider_uid_attribute_name => uid,
34
+ sorcery_config.provider_attribute_name => provider
35
+ )
36
+
37
+ saved = user.sorcery_adapter.save
38
+ [user, saved]
39
+ end
40
+ end
41
+
42
+ # bugfix: https://github.com/Sorcery/sorcery/issues/146
43
+ def add_provider_to_user(provider, uid)
44
+ authentications = sorcery_config.authentications_class.name.demodulize.underscore.pluralize
45
+
46
+ if sorcery_adapter.find_authentication_by_oauth_credentials(authentications, provider, uid).nil?
47
+ user = send(authentications).build(
48
+ sorcery_config.provider_uid_attribute_name => uid,
49
+ sorcery_config.provider_attribute_name => provider
50
+ )
51
+
52
+ user.sorcery_adapter.save(validate: false)
53
+ else
54
+ user = false
55
+ end
56
+
57
+ user
58
+ end
59
+ end
60
+ end
@@ -5,12 +5,14 @@
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
- <%= stylesheet_link_tag "morpho/application", media: "all" %>
9
- <%= javascript_include_tag "morpho/application" %>
8
+ <%= stylesheet_link_tag 'morpho/application', media: 'all' %>
9
+ <%= javascript_include_tag 'morpho/application' %>
10
10
  </head>
11
11
  <body>
12
-
13
- <%= yield %>
12
+ <div class="container">
13
+ <%= render_flash_messages %>
14
+ <%= yield %>
15
+ </div>
14
16
 
15
17
  </body>
16
18
  </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>