morpho 1.2.0 → 1.2.1

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