morpho 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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>