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