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.
- checksums.yaml +4 -4
- data/app/api/morpho/entities/user.rb +0 -1
- data/app/api/morpho/resources/activations.rb +9 -4
- data/app/api/morpho/resources/externals.rb +8 -2
- data/app/api/morpho/resources/passwords.rb +9 -4
- data/app/api/morpho/resources/tokens.rb +19 -5
- data/app/api/morpho/resources/unlocks.rb +10 -5
- data/app/api/morpho/resources/users.rb +8 -3
- data/app/concepts/morpho/contracts/user/deliver_email.rb +13 -0
- data/app/concepts/morpho/contracts/user/external_sign_in.rb +17 -0
- data/app/concepts/morpho/contracts/user/refresh_token.rb +13 -0
- data/app/concepts/morpho/contracts/user/resend_activation_email.rb +8 -0
- data/app/concepts/morpho/contracts/user/resend_unlock_email.rb +8 -0
- data/app/concepts/morpho/contracts/user/send_reset_password_email.rb +8 -0
- data/app/concepts/morpho/contracts/user/sign_in.rb +15 -0
- data/app/concepts/morpho/contracts/user/sign_up.rb +25 -0
- data/app/concepts/morpho/operations/base/create.rb +14 -0
- data/app/concepts/morpho/operations/base/delete.rb +16 -0
- data/app/concepts/morpho/operations/base/fetch.rb +106 -0
- data/app/concepts/morpho/operations/base/find.rb +55 -0
- data/app/concepts/morpho/operations/base/form.rb +84 -0
- data/app/concepts/morpho/operations/base/update.rb +19 -0
- data/app/concepts/morpho/operations/user/deliver_email.rb +38 -0
- data/app/concepts/morpho/operations/user/external_sign_in.rb +38 -0
- data/app/concepts/morpho/operations/user/generate_token.rb +33 -0
- data/app/concepts/morpho/operations/user/refresh_token.rb +41 -0
- data/app/concepts/morpho/operations/user/resend_activation_email.rb +31 -0
- data/app/concepts/morpho/operations/user/resend_unlock_email.rb +31 -0
- data/app/concepts/morpho/operations/user/send_reset_password_email.rb +30 -0
- data/app/concepts/morpho/operations/user/sign_in.rb +49 -0
- data/app/concepts/morpho/operations/user/sign_up.rb +8 -0
- data/app/models/morpho/user.rb +6 -2
- data/config/locales/morpho.en.yml +2 -0
- data/config/locales/morpho.es.yml +2 -0
- data/lib/generators/morpho/install/templates/app/api/morpho/api.rb +12 -2
- data/lib/generators/morpho/install/templates/config/initializers/morpho.rb +2 -1
- data/lib/morpho/configuration.rb +2 -0
- data/lib/morpho/engine.rb +1 -6
- data/lib/morpho/version.rb +1 -1
- metadata +26 -17
- data/app/concepts/morpho/user/contract/activate.rb +0 -8
- data/app/concepts/morpho/user/contract/external_sign_in.rb +0 -12
- data/app/concepts/morpho/user/contract/refresh_token.rb +0 -8
- data/app/concepts/morpho/user/contract/reset_password.rb +0 -8
- data/app/concepts/morpho/user/contract/sign_in.rb +0 -10
- data/app/concepts/morpho/user/contract/sign_up.rb +0 -22
- data/app/concepts/morpho/user/contract/unlock.rb +0 -8
- data/app/concepts/morpho/user/operation/activate.rb +0 -42
- data/app/concepts/morpho/user/operation/external_sign_in.rb +0 -70
- data/app/concepts/morpho/user/operation/refresh_token.rb +0 -37
- data/app/concepts/morpho/user/operation/reset_password.rb +0 -40
- data/app/concepts/morpho/user/operation/sign_in.rb +0 -73
- data/app/concepts/morpho/user/operation/sign_up.rb +0 -32
- 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
|