morpho 1.2.0 → 1.2.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/api/morpho/entities/user.rb +0 -1
  3. data/app/api/morpho/resources/activations.rb +9 -4
  4. data/app/api/morpho/resources/externals.rb +8 -2
  5. data/app/api/morpho/resources/passwords.rb +9 -4
  6. data/app/api/morpho/resources/tokens.rb +19 -5
  7. data/app/api/morpho/resources/unlocks.rb +10 -5
  8. data/app/api/morpho/resources/users.rb +8 -3
  9. data/app/concepts/morpho/contracts/user/deliver_email.rb +13 -0
  10. data/app/concepts/morpho/contracts/user/external_sign_in.rb +17 -0
  11. data/app/concepts/morpho/contracts/user/refresh_token.rb +13 -0
  12. data/app/concepts/morpho/contracts/user/resend_activation_email.rb +8 -0
  13. data/app/concepts/morpho/contracts/user/resend_unlock_email.rb +8 -0
  14. data/app/concepts/morpho/contracts/user/send_reset_password_email.rb +8 -0
  15. data/app/concepts/morpho/contracts/user/sign_in.rb +15 -0
  16. data/app/concepts/morpho/contracts/user/sign_up.rb +25 -0
  17. data/app/concepts/morpho/operations/base/create.rb +14 -0
  18. data/app/concepts/morpho/operations/base/delete.rb +16 -0
  19. data/app/concepts/morpho/operations/base/fetch.rb +106 -0
  20. data/app/concepts/morpho/operations/base/find.rb +55 -0
  21. data/app/concepts/morpho/operations/base/form.rb +84 -0
  22. data/app/concepts/morpho/operations/base/update.rb +19 -0
  23. data/app/concepts/morpho/operations/user/deliver_email.rb +38 -0
  24. data/app/concepts/morpho/operations/user/external_sign_in.rb +38 -0
  25. data/app/concepts/morpho/operations/user/generate_token.rb +33 -0
  26. data/app/concepts/morpho/operations/user/refresh_token.rb +41 -0
  27. data/app/concepts/morpho/operations/user/resend_activation_email.rb +31 -0
  28. data/app/concepts/morpho/operations/user/resend_unlock_email.rb +31 -0
  29. data/app/concepts/morpho/operations/user/send_reset_password_email.rb +30 -0
  30. data/app/concepts/morpho/operations/user/sign_in.rb +49 -0
  31. data/app/concepts/morpho/operations/user/sign_up.rb +8 -0
  32. data/app/models/morpho/user.rb +6 -2
  33. data/config/locales/morpho.en.yml +2 -0
  34. data/config/locales/morpho.es.yml +2 -0
  35. data/lib/generators/morpho/install/templates/app/api/morpho/api.rb +12 -2
  36. data/lib/generators/morpho/install/templates/config/initializers/morpho.rb +2 -1
  37. data/lib/morpho/configuration.rb +2 -0
  38. data/lib/morpho/engine.rb +1 -6
  39. data/lib/morpho/version.rb +1 -1
  40. metadata +26 -17
  41. data/app/concepts/morpho/user/contract/activate.rb +0 -8
  42. data/app/concepts/morpho/user/contract/external_sign_in.rb +0 -12
  43. data/app/concepts/morpho/user/contract/refresh_token.rb +0 -8
  44. data/app/concepts/morpho/user/contract/reset_password.rb +0 -8
  45. data/app/concepts/morpho/user/contract/sign_in.rb +0 -10
  46. data/app/concepts/morpho/user/contract/sign_up.rb +0 -22
  47. data/app/concepts/morpho/user/contract/unlock.rb +0 -8
  48. data/app/concepts/morpho/user/operation/activate.rb +0 -42
  49. data/app/concepts/morpho/user/operation/external_sign_in.rb +0 -70
  50. data/app/concepts/morpho/user/operation/refresh_token.rb +0 -37
  51. data/app/concepts/morpho/user/operation/reset_password.rb +0 -40
  52. data/app/concepts/morpho/user/operation/sign_in.rb +0 -73
  53. data/app/concepts/morpho/user/operation/sign_up.rb +0 -32
  54. data/app/concepts/morpho/user/operation/unlock.rb +0 -42
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpho
4
+ module Operations
5
+ module Base
6
+ # Base find operation
7
+ class Find < Trailblazer::Operation
8
+ pass :params!
9
+ pass :model_class!
10
+ pass :presenter_class!
11
+ pass :build_model!
12
+ pass :present!
13
+
14
+ def params!(options, **)
15
+ options['params'] ||= {}
16
+ end
17
+
18
+ def model_class!(options, **)
19
+ model_class = options['model.class']
20
+ unless model_class.is_a?(Class) && model_class.ancestors
21
+ .include?(
22
+ ::ActiveRecord::Base
23
+ )
24
+ raise ArgumentError,
25
+ 'Supply a valid model.class option'
26
+ end
27
+ end
28
+
29
+ def presenter_class!(options, **)
30
+ presenter_class = options['presenter.class']
31
+ unless presenter_class.is_a?(Class) && presenter_class.ancestors
32
+ .include?(
33
+ ::Grape::Entity
34
+ )
35
+ raise ArgumentError,
36
+ 'Supply a valid presenter.class option'
37
+ end
38
+ end
39
+
40
+ def build_model!(options, params:, **)
41
+ options['model'] = options['model.class'].find_by(uuid: params['uuid'])
42
+
43
+ if options['model']
44
+ .nil?
45
+ raise Morpho::Exceptions::StandardError.new(status: 404)
46
+ end
47
+ end
48
+
49
+ def present!(options, model:, **)
50
+ options['response'] = options['presenter.class'].represent(model)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpho
4
+ module Operations
5
+ module Base
6
+ # Base form operation
7
+ class Form < Trailblazer::Operation
8
+ pass :params!
9
+ pass :data!
10
+ pass :model_class!
11
+ pass :presenter_class!
12
+ pass :contract_class!
13
+ pass :build_model!
14
+ pass :validate!
15
+ pass :persist!
16
+ pass :present!
17
+
18
+ def params!(options, **)
19
+ options['params'] ||= {}
20
+ end
21
+
22
+ def data!(options, params:, **)
23
+ options['data'] = params['data'].is_a?(Hash) ? params.fetch('data') : {}
24
+ end
25
+
26
+ def model_class!(options, **)
27
+ model_class = options['model.class']
28
+ unless model_class.is_a?(Class) && model_class.ancestors
29
+ .include?(
30
+ ::ActiveRecord::Base
31
+ )
32
+ raise ArgumentError,
33
+ 'Supply a valid model.class option'
34
+ end
35
+ end
36
+
37
+ def presenter_class!(options, **)
38
+ presenter_class = options['presenter.class']
39
+ unless presenter_class.is_a?(Class) && presenter_class.ancestors
40
+ .include?(
41
+ ::Grape::Entity
42
+ )
43
+ raise ArgumentError,
44
+ 'Supply a valid presenter.class option'
45
+ end
46
+ end
47
+
48
+ def contract_class!(options, **)
49
+ contract_class = options['contract.class']
50
+ unless contract_class.is_a?(Class) && contract_class.ancestors
51
+ .include?(
52
+ ::Reform::Form
53
+ )
54
+ raise ArgumentError,
55
+ 'Supply a valid contract.class option'
56
+ end
57
+ end
58
+
59
+ def build_model!(_options, **)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ def validate!(options, model:, data:, **)
64
+ options['contract'] = options['contract.class'].new(model)
65
+
66
+ valid = options['contract'].validate(data)
67
+
68
+ raise Morpho::Exceptions::StandardError.new(errors: options['contract'].errors) unless valid
69
+ end
70
+
71
+ def persist!(options, contract:, **)
72
+ contract.sync
73
+ saved = contract.model.save
74
+
75
+ raise Morpho::Exceptions::StandardError.new(errors: contract.errors) unless saved
76
+ end
77
+
78
+ def present!(options, model:, **)
79
+ options['response'] = options['presenter.class'].represent(model)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpho
4
+ module Operations
5
+ module Base
6
+ # Base update operation
7
+ class Update < Morpho::Operations::Base::Form
8
+ def build_model!(options, params:, **)
9
+ options['model'] = options['model.class'].find_by(uuid: params['uuid'])
10
+
11
+ if options['model']
12
+ .nil?
13
+ raise Morpho::Exceptions::StandardError.new(status: 404)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class DeliverEmail < Morpho::Operations::Base::Find
5
+ pass :data!, after: :params!
6
+ pass :validate!, before: :build_model!
7
+ pass :check!, before: :present!
8
+ pass :deliver!, after: :present!
9
+
10
+ def data!(options, params:, **)
11
+ options['data'] = params['data'].is_a?(Hash) ? params.fetch('data') : {}
12
+ end
13
+
14
+ def validate!(options, data:, **)
15
+ options['contract'] = options['contract.class'].new(OpenStruct.new)
16
+
17
+ unless options['contract'].validate(data)
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ errors: options['contract'].errors
20
+ )
21
+ end
22
+ end
23
+
24
+ def build_model!(_options, **)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def check!(_options, **)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def deliver!(_options, **)
33
+ raise NotImplementedError
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class ExternalSignIn < Morpho::Operations::User::GenerateToken
5
+ step nil, delete: :check!, id: ""
6
+
7
+ def build_model!(options, data:, **)
8
+ provider = data.fetch('provider').downcase
9
+
10
+ options['model'] = Morpho::User.load_from_provider(
11
+ provider, data.fetch('uid')
12
+ )
13
+
14
+ if options['model'].nil?
15
+ options['model'] = Morpho::User.find_by(email: data.fetch('email'))
16
+
17
+ unless options['model'].nil?
18
+ options['model'].add_provider_to_user(provider, data.fetch('uid'))
19
+ end
20
+ end
21
+
22
+ if options['model'].nil?
23
+ options['model'] = Morpho::User.create_from_provider(provider, data.fetch('uid'), {
24
+ email: data.fetch('email')
25
+ })
26
+ end
27
+ end
28
+
29
+ def present!(options, model:, **)
30
+ model.generate_refresh_token!
31
+ model.register_last_login_activity!(options['ip_address'])
32
+ token = Morpho::JWT::Payload.new(model)
33
+ options['response'] = options['presenter.class'].represent(token)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class GenerateToken < Morpho::Operations::Base::Find
5
+ pass :data!, after: :params!
6
+ pass :validate!, before: :build_model!
7
+ pass :check!, before: :present!
8
+
9
+ def data!(options, params:, **)
10
+ options['data'] = params['data'].is_a?(Hash) ? params.fetch('data') : {}
11
+ end
12
+
13
+ def validate!(options, data:, **)
14
+ options['contract'] = options['contract.class'].new(OpenStruct.new)
15
+
16
+ unless options['contract'].validate(data)
17
+ raise Morpho::Exceptions::StandardError.new(
18
+ errors: options['contract'].errors
19
+ )
20
+ end
21
+ end
22
+
23
+ def build_model!(_options, **)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def check!(_options, **)
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class RefreshToken < Morpho::Operations::User::GenerateToken
5
+ def build_model!(options, data:, **)
6
+ options['model'] = options['model.class'].find_by(refresh_token: data.fetch('refresh_token'))
7
+
8
+ if options['model'].nil?
9
+ raise Morpho::Exceptions::StandardError.new(
10
+ message: I18n.t('morpho.api.messages.refresh_token.invalid'),
11
+ status: 404
12
+ )
13
+ end
14
+ end
15
+
16
+ def check!(options, model:, data:, **)
17
+ unless model.active?
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ message: I18n.t('morpho.api.messages.refresh_token.account_not_confirmed'),
20
+ status: 403
21
+ )
22
+ end
23
+
24
+ unless model.unlocked?
25
+ raise Morpho::Exceptions::StandardError.new(
26
+ message: I18n.t('morpho.api.messages.refresh_token.account_locked'),
27
+ status: 423
28
+ )
29
+ end
30
+ end
31
+
32
+ def present!(options, model:, **)
33
+ model.generate_refresh_token!
34
+ model.register_last_login_activity!(options['ip_address'])
35
+ token = Morpho::JWT::Payload.new(model)
36
+ options['response'] = options['presenter.class'].represent(token)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class ResendActivationEmail < Morpho::Operations::User::DeliverEmail
5
+ def build_model!(options, data:, **)
6
+ options['model'] = options['model.class'].find_by(email: data.fetch('email'))
7
+
8
+ if options['model'].nil?
9
+ raise Morpho::Exceptions::StandardError.new(
10
+ message: I18n.t('morpho.api.messages.activate.email_not_exists'),
11
+ status: 404
12
+ )
13
+ end
14
+ end
15
+
16
+ def check!(options, model:, **)
17
+ if model.active?
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ message: I18n.t('morpho.api.messages.activate.account_already_confirmed'),
20
+ status: 405
21
+ )
22
+ end
23
+ end
24
+
25
+ def deliver!(options, model:, **)
26
+ model.resend_activation_needed_email!
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class ResendUnlockEmail < Morpho::Operations::User::DeliverEmail
5
+ def build_model!(options, data:, **)
6
+ options['model'] = options['model.class'].find_by(email: data.fetch('email'))
7
+
8
+ if options['model'].nil?
9
+ raise Morpho::Exceptions::StandardError.new(
10
+ message: I18n.t('morpho.api.messages.unlock.email_not_exists'),
11
+ status: 404
12
+ )
13
+ end
14
+ end
15
+
16
+ def check!(options, model:, **)
17
+ unless options['model'].login_locked?
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ message: I18n.t('morpho.api.messages.unlock.account_not_locked'),
20
+ status: 405
21
+ )
22
+ end
23
+ end
24
+
25
+ def deliver!(options, model:, **)
26
+ model.resend_unlock_token_email!
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class SendResetPasswordEmail < Morpho::Operations::User::DeliverEmail
5
+ def build_model!(options, data:, **)
6
+ options['model'] = options['model.class'].find_by(email: data.fetch('email'))
7
+
8
+ if options['model'].nil?
9
+ raise Morpho::Exceptions::StandardError.new(
10
+ message: I18n.t('morpho.api.messages.unlock.email_not_exists'),
11
+ status: 404
12
+ )
13
+ end
14
+ end
15
+
16
+ def check!(options, model:, **)
17
+ if options['model'].external?
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ status: 405
20
+ )
21
+ end
22
+ end
23
+
24
+ def deliver!(options, model:, **)
25
+ model.deliver_reset_password_instructions!
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ module Morpho
2
+ module Operations
3
+ module User
4
+ class SignIn < Morpho::Operations::User::GenerateToken
5
+ def build_model!(options, data:, **)
6
+ options['model'] = options['model.class'].find_by(email: data.fetch('email'))
7
+
8
+ if options['model'].nil?
9
+ raise Morpho::Exceptions::StandardError.new(
10
+ message: I18n.t('morpho.api.messages.sign_in.email_not_exists'),
11
+ status: 404
12
+ )
13
+ end
14
+ end
15
+
16
+ def check!(options, model:, data:, **)
17
+ unless model.active?
18
+ raise Morpho::Exceptions::StandardError.new(
19
+ message: I18n.t('morpho.api.messages.sign_in.account_not_confirmed'),
20
+ status: 403
21
+ )
22
+ end
23
+
24
+ unless model.unlocked?
25
+ raise Morpho::Exceptions::StandardError.new(
26
+ message: I18n.t('morpho.api.messages.sign_in.account_locked'),
27
+ status: 423
28
+ )
29
+ end
30
+
31
+ unless model.valid_password?(data.fetch('password'))
32
+ model.register_failed_login!
33
+ raise Morpho::Exceptions::StandardError.new(
34
+ message: I18n.t('morpho.api.messages.sign_in.bad_credentials'),
35
+ status: 401
36
+ )
37
+ end
38
+ end
39
+
40
+ def present!(options, model:, **)
41
+ model.generate_refresh_token!
42
+ model.register_last_login_activity!(options['ip_address'])
43
+ token = Morpho::JWT::Payload.new(model)
44
+ options['response'] = options['presenter.class'].represent(token)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end