card-mod-account 0.11.0
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 +7 -0
- data/README.md +6 -0
- data/set/abstract/account_field.rb +17 -0
- data/set/abstract/accountable.rb +49 -0
- data/set/all/account.rb +97 -0
- data/set/right/account.rb +58 -0
- data/set/right/account/events.rb +99 -0
- data/set/right/account/views.rb +65 -0
- data/set/right/api_key.rb +48 -0
- data/set/right/email.rb +46 -0
- data/set/right/password.rb +51 -0
- data/set/right/roles.rb +27 -0
- data/set/right/salt.rb +13 -0
- data/set/right/status.rb +18 -0
- data/set/self/captcha.rb +5 -0
- data/set/self/signin.rb +226 -0
- data/set/type/role.rb +21 -0
- data/set/type/signup.rb +54 -0
- data/set/type/signup/core.haml +4 -0
- data/set/type/signup/views.rb +93 -0
- data/set/type/user.rb +67 -0
- data/set/type/user/setup_help.haml +10 -0
- data/set/type_plus_right/user/email.rb +13 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 446ac1cbd57b9473bffdf6fdb91303bc86cbcdcf584e2fafe48fc306da050580
|
4
|
+
data.tar.gz: eecb9da6a9312154ec015ac20353061b8d2839a6c61d2dacabe9c2d3377603b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9472b1ed791997464de8a57adad65d83b9021df74750f76edaab8fddfb51df493801f659c861a870ae29994e806add8add7e8aa8f07c73954b159f9e288ed050
|
7
|
+
data.tar.gz: 8525e80cacd92f2d5c7ce960bbdda0be8fc63441fb3cb408080bbf03a0d9fe2c302da16ab60b117c923d5141bd1a62611e3bc48ad61cc677c6ac62729b1bce36
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
# allow account owner to update account field content
|
3
|
+
def ok_to_update
|
4
|
+
return true if own_account? && !name_changed? && !type_id_changed?
|
5
|
+
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
# force inherit permission on create
|
10
|
+
# (cannot be done with rule, because sets are not addressable)
|
11
|
+
def permission_rule_id action
|
12
|
+
if action == :create
|
13
|
+
left_permission_rule_id action
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
def account
|
2
|
+
fetch :account, new: {}
|
3
|
+
end
|
4
|
+
|
5
|
+
def default_account_status
|
6
|
+
"active"
|
7
|
+
end
|
8
|
+
|
9
|
+
def current_account?
|
10
|
+
id && Auth.current_id == id
|
11
|
+
end
|
12
|
+
|
13
|
+
format :html do
|
14
|
+
def default_bridge_tab
|
15
|
+
card.current_account? ? :account_tab : super
|
16
|
+
end
|
17
|
+
|
18
|
+
view :account_tab do
|
19
|
+
bridge_pill_sections "Account" do
|
20
|
+
[["Settings", account_details_items],
|
21
|
+
["Content", account_content_items]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def show_account_tab?
|
26
|
+
card.account.real?
|
27
|
+
end
|
28
|
+
|
29
|
+
def account_formgroups
|
30
|
+
Auth.as_bot do
|
31
|
+
subformat(card.account)._render :content_formgroups, structure: true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def account_details_items
|
36
|
+
[
|
37
|
+
["Email and Password", :account,
|
38
|
+
{ path: { slot: { hide: %i[help_link bridge_link] } } }],
|
39
|
+
["Roles", :roles,
|
40
|
+
{ path: { view: :content_with_edit_button } }],
|
41
|
+
["Notifications", :follow]
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
def account_content_items
|
46
|
+
[["Created", :created],
|
47
|
+
["Edited", :edited]]
|
48
|
+
end
|
49
|
+
end
|
data/set/all/account.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
module ClassMethods
|
2
|
+
def default_accounted_type_id
|
3
|
+
UserID
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
def account
|
8
|
+
fetch :account
|
9
|
+
end
|
10
|
+
|
11
|
+
def parties
|
12
|
+
@parties ||= (all_enabled_roles << id).flatten.reject(&:blank?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def among? ok_ids
|
16
|
+
ok_ids.any? do |ok_id|
|
17
|
+
ok_id == AnyoneID ||
|
18
|
+
(ok_id == AnyoneWithRoleID && all_enabled_roles.size > 1) ||
|
19
|
+
parties.member?(ok_id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def own_account?
|
24
|
+
# card is +*account card of signed_in user.
|
25
|
+
name.part_names[0].key == Auth.as_card.key &&
|
26
|
+
name.part_names[1].key == Card[:account].key
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_rules
|
30
|
+
@read_rules ||= fetch_read_rules
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_rules_hash
|
34
|
+
@read_rules_hash ||= read_rules.each_with_object({}) { |id, h| h[id] = true }
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_read_rules
|
38
|
+
return [] if id == WagnBotID # always_ok, so not needed
|
39
|
+
|
40
|
+
([AnyoneID] + parties).each_with_object([]) do |party_id, rule_ids|
|
41
|
+
next unless (cache = Card::Rule.read_rule_cache[party_id])
|
42
|
+
rule_ids.concat cache
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_roles
|
47
|
+
@parties = @all_roles = @all_active_roles = @read_rules = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_clear_roles
|
51
|
+
a, b, c, d = @parties, @all_roles, @all_active_roles, @read_rules
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
@parties, @all_roles, @all_active_roles, @read_rules = a, b, c, d
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_enabled_roles
|
58
|
+
@all_active_roles ||= (id == AnonymousID ? [] : enabled_role_ids)
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_roles
|
62
|
+
@all_roles ||= (id == AnonymousID ? [] : fetch_roles)
|
63
|
+
end
|
64
|
+
|
65
|
+
def enabled_role_ids
|
66
|
+
Auth.as_bot do
|
67
|
+
# workaround for broken migrations
|
68
|
+
return fetch_roles unless Card::Codename.exists? :enabled_roles
|
69
|
+
|
70
|
+
enabled = enabled_roles_card
|
71
|
+
enabled.virtual? ? enabled.item_ids : fetch_roles
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def enabled_roles_card
|
76
|
+
fetch :enabled_roles, eager_cache: true, new: { type_id: SessionID }
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_roles
|
80
|
+
[AnyoneSignedInID] + role_ids_from_roles_trait
|
81
|
+
end
|
82
|
+
|
83
|
+
def role_ids_from_roles_trait
|
84
|
+
Auth.as_bot do
|
85
|
+
role_trait = fetch :roles
|
86
|
+
role_trait ? role_trait.item_ids : []
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
event :generate_token do
|
91
|
+
Digest::SHA1.hexdigest "--#{Time.zone.now.to_f}--#{rand 10}--"
|
92
|
+
end
|
93
|
+
|
94
|
+
event :set_stamper, :prepare_to_validate do
|
95
|
+
self.updater_id = Auth.current_id
|
96
|
+
self.creator_id = updater_id if new_card?
|
97
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
card_accessor :email
|
4
|
+
card_accessor :password
|
5
|
+
card_accessor :salt
|
6
|
+
card_accessor :status
|
7
|
+
card_accessor :api_key
|
8
|
+
|
9
|
+
require_field :email
|
10
|
+
|
11
|
+
def accounted
|
12
|
+
left
|
13
|
+
end
|
14
|
+
|
15
|
+
def accounted_id
|
16
|
+
left_id
|
17
|
+
end
|
18
|
+
|
19
|
+
def ok_to_read
|
20
|
+
own_account? ? true : super
|
21
|
+
end
|
22
|
+
|
23
|
+
# allow account owner to update account field content
|
24
|
+
def ok_to_update
|
25
|
+
return true if own_account? && !name_changed? && !type_id_changed?
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def changes_visible? act
|
31
|
+
act.actions_affecting(act.card).each do |action|
|
32
|
+
return true if action.card.ok? :read
|
33
|
+
end
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def send_account_email email_template
|
38
|
+
ecard = Card[email_template]
|
39
|
+
unless ecard&.type_id == EmailTemplateID
|
40
|
+
raise Card::Error, "invalid email template: #{email_template}"
|
41
|
+
end
|
42
|
+
|
43
|
+
ecard.deliver self, to: email
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_api_key! api_key
|
47
|
+
api_key_card.validate! api_key
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing method, *args
|
51
|
+
super unless args.empty? && (matches = method.match(/^(?<status>.*)\?$/))
|
52
|
+
|
53
|
+
status == matches[:status]
|
54
|
+
end
|
55
|
+
|
56
|
+
def respond_to_missing? method, _include_private=false
|
57
|
+
method.match?(/\?/) ? true : super
|
58
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#### ON CREATE
|
2
|
+
|
3
|
+
event :set_default_salt, :prepare_to_validate, on: :create do
|
4
|
+
add_subfield(:salt).generate
|
5
|
+
end
|
6
|
+
|
7
|
+
event :set_default_status, :prepare_to_validate, on: :create do
|
8
|
+
add_subfield :status, content: (accounted&.try(:default_account_status) || "active")
|
9
|
+
end
|
10
|
+
|
11
|
+
# ON UPDATE
|
12
|
+
|
13
|
+
# reset password emails contain a link to update the +*account card
|
14
|
+
# and trigger this event
|
15
|
+
event :reset_password, :prepare_to_validate, on: :update, trigger: :required do
|
16
|
+
verifying_token :reset_password_success, :reset_password_failure
|
17
|
+
end
|
18
|
+
|
19
|
+
event :verify_and_activate, :prepare_to_validate, on: :update, trigger: :required do
|
20
|
+
activatable do
|
21
|
+
verifying_token :verify_and_activate_success, :verify_and_activate_failure
|
22
|
+
add_subcard(accounted)&.try :activate_accounted
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
event :password_redirect, :finalize, on: :update, when: :password_redirect? do
|
27
|
+
success << { id: name, view: "edit" }
|
28
|
+
end
|
29
|
+
|
30
|
+
# INTEGRATION
|
31
|
+
|
32
|
+
%i[password_reset_email verification_email welcome_email].each do |email|
|
33
|
+
event "send_#{email}".to_sym, :integrate, trigger: :required do
|
34
|
+
send_account_email email
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
## EVENT HELPERS
|
39
|
+
|
40
|
+
def activatable
|
41
|
+
abort :failure, "no field manipulation mid-activation" if subcards.present?
|
42
|
+
# above is necessary because activation uses super user (Decko Bot),
|
43
|
+
# so allowing subcards would be unsafe
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
|
47
|
+
# note: this only works in the context of an action.
|
48
|
+
# if run independently, it will not activate an account
|
49
|
+
event :activate_account do
|
50
|
+
add_subfield :status, content: "active"
|
51
|
+
trigger_event! :send_welcome_email
|
52
|
+
end
|
53
|
+
|
54
|
+
def verifying_token success, failure
|
55
|
+
requiring_token do |token|
|
56
|
+
result = Auth::Token.decode token
|
57
|
+
if result.is_a?(String)
|
58
|
+
send failure, result
|
59
|
+
else
|
60
|
+
send success
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def requiring_token
|
66
|
+
if !(token = Env.params[:token])
|
67
|
+
errors.add :token, "is required"
|
68
|
+
else
|
69
|
+
yield token
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def password_redirect?
|
74
|
+
Auth.current_id == accounted_id && password.blank?
|
75
|
+
end
|
76
|
+
|
77
|
+
def verify_and_activate_success
|
78
|
+
Auth.signin accounted_id
|
79
|
+
Auth.as_bot # use admin permissions for rest of action
|
80
|
+
activate_account
|
81
|
+
success << ""
|
82
|
+
end
|
83
|
+
|
84
|
+
def verify_and_activate_failure error_message
|
85
|
+
send_verification_email
|
86
|
+
errors.add :content,
|
87
|
+
"Sorry, #{error_message}. Please check your email for a new activation link."
|
88
|
+
end
|
89
|
+
|
90
|
+
def reset_password_success
|
91
|
+
Auth.signin accounted_id
|
92
|
+
success << { id: name, view: :edit }
|
93
|
+
abort :success
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset_password_failure error_message
|
97
|
+
Auth.as_bot { send_password_reset_email }
|
98
|
+
errors.add :content, tr(:sorry_email_reset, error_msg: error_message)
|
99
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
format do
|
2
|
+
view :verify_url, cache: :never do
|
3
|
+
raise Error::PermissionDenied unless card.ok?(:create) || card.action
|
4
|
+
|
5
|
+
token_url :verify_and_activate, anonymous: true
|
6
|
+
end
|
7
|
+
|
8
|
+
view :reset_password_url do
|
9
|
+
raise Error::PermissionDenied unless card.password_card.ok? :update
|
10
|
+
|
11
|
+
token_url :reset_password
|
12
|
+
end
|
13
|
+
|
14
|
+
view :token_expiry do
|
15
|
+
"(#{token_expiry_sentence}"
|
16
|
+
end
|
17
|
+
|
18
|
+
view :token_days do
|
19
|
+
Card.config.token_expiry / 1.day
|
20
|
+
end
|
21
|
+
|
22
|
+
# DEPRECATED
|
23
|
+
view :verify_days, :token_days
|
24
|
+
view :reset_password_days, :token_days
|
25
|
+
|
26
|
+
def token_url trigger, extra_payload={}
|
27
|
+
card_url path(action: :update,
|
28
|
+
card: { trigger: trigger },
|
29
|
+
token: new_token(extra_payload))
|
30
|
+
end
|
31
|
+
|
32
|
+
def token_expiry_sentence
|
33
|
+
"Link will expire in #{render_token_days} days"
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_token extra_payload
|
37
|
+
Auth::Token.encode card.accounted_id, extra_payload
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
format :html do
|
42
|
+
view :core do
|
43
|
+
[account_field_nest(:email, "email"),
|
44
|
+
account_field_nest(:password, "password")]
|
45
|
+
end
|
46
|
+
|
47
|
+
def account_field_nest field, title
|
48
|
+
field_nest field, title: title, view: :labeled
|
49
|
+
# edit: :inline, hide: [:help_link, :bridge_link]
|
50
|
+
end
|
51
|
+
|
52
|
+
before :content_formgroups do
|
53
|
+
voo.edit_structure = [[:email, "email"], [:password, "password"]]
|
54
|
+
end
|
55
|
+
|
56
|
+
view :token_expiry do
|
57
|
+
"<p><em>#{token_expiry_sentence}</em></p>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
format :email_html do
|
62
|
+
def mail context, fields
|
63
|
+
super context, fields.reverse_merge(to: card.email)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
include_set Abstract::AccountField
|
2
|
+
|
3
|
+
# DURATIONS = "second|minute|hour|day|week|month|year".freeze
|
4
|
+
|
5
|
+
def history?
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
view :raw do
|
10
|
+
tr :private_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate! api_key
|
14
|
+
error =
|
15
|
+
case
|
16
|
+
when !real? then [:token_not_found, tr(:error_token_not_found)]
|
17
|
+
# when expired? then [:token_expired, tr(:error_token_expired)]
|
18
|
+
when content != api_key then [:incorrect_token, tr(:error_incorrect_token)]
|
19
|
+
end
|
20
|
+
errors.add(*error) if error
|
21
|
+
error.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
# def expired?
|
25
|
+
# !permanent? && updated_at <= term.ago
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def permanent?
|
29
|
+
# term == "permanent"
|
30
|
+
# end
|
31
|
+
|
32
|
+
# def term
|
33
|
+
# @term ||=
|
34
|
+
# if expiration.present?
|
35
|
+
# term_from_string expiration
|
36
|
+
# else
|
37
|
+
# Card.config.token_expiry
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
|
41
|
+
# def term_from_string string
|
42
|
+
# string.strip!
|
43
|
+
# return "permanent" if string == "none"
|
44
|
+
# re_match = /^(\d+)[\.\s]*(#{DURATIONS})s?$/.match(string)
|
45
|
+
# number, unit = re_match.captures if re_match
|
46
|
+
# raise Card::Open::Error, tr(:exception_bad_expiration, example: '2 days') unless unit
|
47
|
+
# number.to_i.send unit
|
48
|
+
# end
|
data/set/right/email.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
include_set Abstract::AccountField
|
4
|
+
|
5
|
+
event :validate_email, :validate, on: :save do
|
6
|
+
return unless content?
|
7
|
+
|
8
|
+
self.content = content.strip
|
9
|
+
return if content.match?(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
|
10
|
+
|
11
|
+
errors.add :content, tr(:error_invalid_address)
|
12
|
+
end
|
13
|
+
|
14
|
+
event :validate_unique_email, after: :validate_email, on: :save do
|
15
|
+
if content.present?
|
16
|
+
Auth.as_bot do
|
17
|
+
cql = { right_id: EmailID, eq: content, return: :id }
|
18
|
+
cql[:not] = { id: id } if id
|
19
|
+
cql_comment = tr(:search_email_duplicate, content: content)
|
20
|
+
if Card.search(cql, cql_comment).first
|
21
|
+
errors.add :content, tr(:error_not_unique)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
event :downcase_email, :prepare_to_validate, on: :save do
|
28
|
+
return if !content || content == content.downcase
|
29
|
+
self.content = content.downcase
|
30
|
+
end
|
31
|
+
|
32
|
+
def email_required?
|
33
|
+
!left.system?
|
34
|
+
end
|
35
|
+
|
36
|
+
def ok_to_read
|
37
|
+
if own_email? || Auth.always_ok?
|
38
|
+
true
|
39
|
+
else
|
40
|
+
deny_because tr(:deny_email_restricted)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def own_email?
|
45
|
+
name.part_names[0].key == Auth.as_card.key
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
include_set Abstract::AccountField
|
2
|
+
|
3
|
+
def history?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def ok_to_read
|
8
|
+
own_account? ? true : super
|
9
|
+
end
|
10
|
+
|
11
|
+
event :encrypt_password, :store,
|
12
|
+
on: :save, changed: :content,
|
13
|
+
when: proc { !Card::Env[:no_password_encryptions] } do
|
14
|
+
# no_password_encryptions = hack for import - fix with api for ignoring events
|
15
|
+
salt = left&.salt
|
16
|
+
self.content = Auth.encrypt content, salt
|
17
|
+
|
18
|
+
# errors.add :password, 'need a valid salt'
|
19
|
+
# turns out we have a lot of existing account without a salt.
|
20
|
+
# not sure when that broke??
|
21
|
+
end
|
22
|
+
|
23
|
+
event :validate_password, :validate, on: :save do
|
24
|
+
return if content.length > 3
|
25
|
+
|
26
|
+
errors.add :password, tr(:password_length)
|
27
|
+
end
|
28
|
+
|
29
|
+
event :validate_password_present, :prepare_to_validate, on: :update do
|
30
|
+
abort :success if content.blank?
|
31
|
+
end
|
32
|
+
|
33
|
+
view :raw do
|
34
|
+
tr :encrypted
|
35
|
+
end
|
36
|
+
|
37
|
+
format :html do
|
38
|
+
view :core, wrap: :em do
|
39
|
+
render_raw
|
40
|
+
end
|
41
|
+
|
42
|
+
view :input do
|
43
|
+
card.content = ""
|
44
|
+
password_field :content, class: "d0-card-content", autocomplete: autocomplete?
|
45
|
+
end
|
46
|
+
|
47
|
+
def autocomplete?
|
48
|
+
return "on" if @parent && @parent.card.name == "*signin+*account" # HACK
|
49
|
+
"off"
|
50
|
+
end
|
51
|
+
end
|
data/set/right/roles.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
event :validate_permission_to_assign_roles, :validate, on: :save do
|
2
|
+
return unless (fr = forbidden_roles).present?
|
3
|
+
|
4
|
+
errors.add :permission_denied,
|
5
|
+
"You don't have permission to assign the role#{'s' if fr.size > 1} "\
|
6
|
+
"#{fr.map(&:name).to_sentence}" # LOCALIZE
|
7
|
+
end
|
8
|
+
|
9
|
+
def forbidden_roles
|
10
|
+
# restore old roles for permission check
|
11
|
+
with_old_role_permissions do |new_roles|
|
12
|
+
new_roles.select do |card|
|
13
|
+
!Card.fetch(card, "*members").ok? :update
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_old_role_permissions
|
19
|
+
new_roles = item_cards
|
20
|
+
new_content = content
|
21
|
+
left.clear_roles
|
22
|
+
Auth.update_always_cache Card::Auth.as_id, nil
|
23
|
+
self.content = db_content_before_act
|
24
|
+
yield new_roles
|
25
|
+
ensure
|
26
|
+
self.content = new_content
|
27
|
+
end
|
data/set/right/salt.rb
ADDED
data/set/right/status.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
include_set Abstract::AccountField
|
2
|
+
include_set Abstract::Pointer
|
3
|
+
|
4
|
+
def input_type
|
5
|
+
:radio
|
6
|
+
end
|
7
|
+
|
8
|
+
def option_names
|
9
|
+
%w[unapproved unverified active blocked system]
|
10
|
+
end
|
11
|
+
|
12
|
+
def ok_to_update
|
13
|
+
if own_account? && !Auth.always_ok?
|
14
|
+
deny_because you_cant(tr(:deny_not_change_own_account))
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
data/set/self/captcha.rb
ADDED
data/set/self/signin.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
# The Sign In card manages logging in and out of the site.
|
2
|
+
#
|
3
|
+
# /:signin (core view) gives the login ui
|
4
|
+
# /:signin?view=edit gives the forgot password ui
|
5
|
+
|
6
|
+
# /update/:signin is the login action
|
7
|
+
# /delete/:signin is the logout action
|
8
|
+
|
9
|
+
# authentication event
|
10
|
+
event :signin, :validate, on: :update do
|
11
|
+
email = subfield :email
|
12
|
+
email &&= email.content
|
13
|
+
pword = subfield :password
|
14
|
+
pword &&= pword.content
|
15
|
+
|
16
|
+
authenticate_or_abort email, pword
|
17
|
+
end
|
18
|
+
|
19
|
+
# abort after successful signin (do not save card)
|
20
|
+
event :signin_success, after: :signin do
|
21
|
+
abort :success
|
22
|
+
end
|
23
|
+
|
24
|
+
event :signout, :validate, on: :delete do
|
25
|
+
Env.reset_session
|
26
|
+
Auth.signin AnonymousID
|
27
|
+
abort :success
|
28
|
+
end
|
29
|
+
|
30
|
+
# triggered by clicking "Reset my Password", this sends out the verification password
|
31
|
+
# and aborts (does not sign in)
|
32
|
+
event :send_reset_password_token, before: :signin, on: :update, trigger: :required do
|
33
|
+
email = subfield(:email)&.content
|
34
|
+
send_reset_password_email_or_fail email
|
35
|
+
end
|
36
|
+
|
37
|
+
def ok_to_read
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def recaptcha_on?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def i18n_signin key
|
46
|
+
I18n.t key, scope: "mod.card-mod-account.set.self.signin"
|
47
|
+
end
|
48
|
+
|
49
|
+
def authenticate_or_abort email, pword
|
50
|
+
abort_unless email, :email_missing
|
51
|
+
abort_unless pword, :password_missing
|
52
|
+
authenticate_and_signin(email, pword) || failed_signin(email)
|
53
|
+
end
|
54
|
+
|
55
|
+
def authenticate_and_signin email, pword
|
56
|
+
return unless (account = Auth.authenticate email, pword)
|
57
|
+
|
58
|
+
Auth.signin account.left_id
|
59
|
+
end
|
60
|
+
|
61
|
+
def failed_signin email
|
62
|
+
errors.add :signin, signin_error_message(account_for(email))
|
63
|
+
abort :failure
|
64
|
+
end
|
65
|
+
|
66
|
+
def abort_unless value, error_key
|
67
|
+
abort :failure, i18n_signin(error_key) unless value
|
68
|
+
end
|
69
|
+
|
70
|
+
def signin_error_message account
|
71
|
+
case
|
72
|
+
when account.nil? then i18n_signin(:error_unknown_email)
|
73
|
+
when !account.active? then i18n_signin(:error_not_active)
|
74
|
+
else i18n_signin(:error_wrong_password)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def error_on field, error_key
|
79
|
+
errors.add field, i18n_signin(error_key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def account_for email
|
83
|
+
Auth.find_account_by_email email
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_reset_password_email_or_fail email
|
87
|
+
aborting do
|
88
|
+
break errors.add :email, i18n_signin(:error_blank) if email.blank?
|
89
|
+
|
90
|
+
if (account = Auth.find_account_by_email(email))&.active?
|
91
|
+
Auth.as_bot { account.send_password_reset_email }
|
92
|
+
elsif account
|
93
|
+
errors.add :account, i18n_signin(:error_not_active)
|
94
|
+
else
|
95
|
+
errors.add :email, i18n_signin(:error_not_recognized)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def send_reset_password_email_or_fail email
|
101
|
+
aborting do
|
102
|
+
break if blank_email? email
|
103
|
+
|
104
|
+
if (account = account_for email)&.active?
|
105
|
+
send_reset_password_email account
|
106
|
+
else
|
107
|
+
reset_password_fail account
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def blank_email? email
|
113
|
+
return false if email.present?
|
114
|
+
|
115
|
+
error_on :email, :error_blank
|
116
|
+
end
|
117
|
+
|
118
|
+
def send_reset_password_email account
|
119
|
+
Auth.as_bot { account.send_password_reset_email }
|
120
|
+
end
|
121
|
+
|
122
|
+
def reset_password_fail account
|
123
|
+
if account
|
124
|
+
error_on :account, :error_not_active
|
125
|
+
else
|
126
|
+
error_on :email, :error_not_recognized
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
format :html do
|
131
|
+
view :core, cache: :never do
|
132
|
+
voo.edit_structure = [signin_field(:email), signin_field(:password)]
|
133
|
+
with_nest_mode :edit do
|
134
|
+
card_form :update, recaptcha: :off, success: signin_success do
|
135
|
+
[
|
136
|
+
_render_content_formgroups,
|
137
|
+
_render_signin_buttons
|
138
|
+
]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
view :open do
|
144
|
+
voo.show :help
|
145
|
+
voo.hide :menu
|
146
|
+
super()
|
147
|
+
end
|
148
|
+
|
149
|
+
# FIXME: need a generic solution for this
|
150
|
+
view :title do
|
151
|
+
voo.title ||= I18n.t(:sign_in_title, scope: "mod.card-mod-account.set.self.signin")
|
152
|
+
super()
|
153
|
+
end
|
154
|
+
|
155
|
+
view :open_content do
|
156
|
+
# annoying step designed to avoid table of contents. sigh
|
157
|
+
_render_core
|
158
|
+
end
|
159
|
+
|
160
|
+
view :one_line_content do
|
161
|
+
""
|
162
|
+
end
|
163
|
+
|
164
|
+
view :reset_password_success do
|
165
|
+
# 'Check your email for a link to reset your password'
|
166
|
+
frame { I18n.t(:check_email, scope: "mod.card-mod-account.set.self.signin") }
|
167
|
+
end
|
168
|
+
|
169
|
+
view :signin_buttons do
|
170
|
+
button_formgroup do
|
171
|
+
[signin_button, signup_link, reset_password_link]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# FORGOT PASSWORD
|
176
|
+
view :edit do
|
177
|
+
reset_password_voo
|
178
|
+
Auth.as_bot { super() }
|
179
|
+
end
|
180
|
+
|
181
|
+
def reset_password_voo
|
182
|
+
voo.title ||= card.i18n_signin(:forgot_password)
|
183
|
+
voo.edit_structure = [signin_field(:email)]
|
184
|
+
voo.hide :help
|
185
|
+
end
|
186
|
+
|
187
|
+
view :edit_buttons do
|
188
|
+
text = I18n.t :reset_my_password, scope: "mod.card-mod-account.set.self.signin"
|
189
|
+
button_tag text, situation: "primary", class: "_close-modal-on-success"
|
190
|
+
end
|
191
|
+
|
192
|
+
def signin_success
|
193
|
+
"REDIRECT: #{Env.interrupted_action || '*previous'}"
|
194
|
+
end
|
195
|
+
|
196
|
+
def signin_button
|
197
|
+
text = I18n.t :sign_in, scope: "mod.card-mod-account.set.self.signin"
|
198
|
+
button_tag text, situation: "primary"
|
199
|
+
end
|
200
|
+
|
201
|
+
def signup_link
|
202
|
+
text = I18n.t :or_sign_up, scope: "mod.card-mod-account.set.self.signin"
|
203
|
+
subformat(Card[:account_links]).render! :sign_up, title: text
|
204
|
+
end
|
205
|
+
|
206
|
+
def reset_password_link
|
207
|
+
text = I18n.t :reset_password, scope: "mod.card-mod-account.set.self.signin"
|
208
|
+
link = link_to_view :edit, text, path: { slot: { hide: :bridge_link } }
|
209
|
+
# FIXME: inline styling
|
210
|
+
raw("<div style='float:right'>#{link}</div>")
|
211
|
+
end
|
212
|
+
|
213
|
+
def edit_view_hidden
|
214
|
+
hidden_tags card: { trigger: :send_reset_password_token }
|
215
|
+
end
|
216
|
+
|
217
|
+
def edit_success
|
218
|
+
{ view: :reset_password_success }
|
219
|
+
end
|
220
|
+
|
221
|
+
def signin_field name
|
222
|
+
nest_name = "".to_name.trait(name)
|
223
|
+
[nest_name, { title: name.to_s, view: "titled",
|
224
|
+
nest_name: nest_name, skip_perms: true }]
|
225
|
+
end
|
226
|
+
end
|
data/set/type/role.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
def disabled?
|
2
|
+
Auth.current&.fetch(:disabled_roles)&.item_ids&.include? id
|
3
|
+
end
|
4
|
+
|
5
|
+
format :html do
|
6
|
+
view :link_with_checkbox, cache: :never do
|
7
|
+
role_checkbox
|
8
|
+
end
|
9
|
+
|
10
|
+
def role_checkbox
|
11
|
+
name = card.disabled? ? "add_item" : "drop_item"
|
12
|
+
subformat(Auth.current.field(:disabled_roles, new: {})).card_form :update do
|
13
|
+
[check_box_tag(name, card.id, !card.disabled?, class: "_edit-item"),
|
14
|
+
render_link]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def related_by_content_items
|
19
|
+
super.unshift ["members", :members]
|
20
|
+
end
|
21
|
+
end
|
data/set/type/signup.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
include_set Abstract::Accountable
|
2
|
+
|
3
|
+
require_field :account
|
4
|
+
|
5
|
+
def default_account_status
|
6
|
+
can_approve? ? "unverified" : "unapproved"
|
7
|
+
end
|
8
|
+
|
9
|
+
def can_approve?
|
10
|
+
Card.new(type_id: Card.default_accounted_type_id).ok? :create
|
11
|
+
end
|
12
|
+
|
13
|
+
def activate_accounted
|
14
|
+
self.type_id = Card.default_accounted_type_id
|
15
|
+
end
|
16
|
+
|
17
|
+
event :auto_approve_with_verification, :validate, on: :create, when: :can_approve? do
|
18
|
+
request_verification
|
19
|
+
end
|
20
|
+
|
21
|
+
event :approve_with_verification, :validate, on: :update, trigger: :required do
|
22
|
+
approvable { request_verification }
|
23
|
+
end
|
24
|
+
|
25
|
+
event :approve_without_verification, :validate, on: :update, trigger: :required do
|
26
|
+
# TODO: if validated here, add trigger and activate in storage phase
|
27
|
+
approvable do
|
28
|
+
activate_accounted
|
29
|
+
account_subfield.activate_account
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
event :act_as_current_for_integrate_stage, :integrate, on: :create do
|
34
|
+
# needs justification!
|
35
|
+
Auth.current_id = id
|
36
|
+
end
|
37
|
+
|
38
|
+
def account_subfield
|
39
|
+
subfield(:account) || add_subfield(:account)
|
40
|
+
end
|
41
|
+
|
42
|
+
def request_verification
|
43
|
+
acct = account_subfield
|
44
|
+
acct.add_subfield :status, content: "unverified"
|
45
|
+
acct.trigger_event! :send_verification_email
|
46
|
+
end
|
47
|
+
|
48
|
+
def approvable
|
49
|
+
if can_approve?
|
50
|
+
yield
|
51
|
+
else
|
52
|
+
abort :failure, "illegal approval" # raise permission denied?
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
format :html do
|
2
|
+
def invitation?
|
3
|
+
Auth.signed_in? && card.can_approve?
|
4
|
+
end
|
5
|
+
|
6
|
+
view :new do
|
7
|
+
voo.title = invitation? ? tr(:invite) : tr(:sign_up)
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
view :content_formgroups do
|
12
|
+
[account_formgroups, (card.structure ? edit_slot : "")].join
|
13
|
+
end
|
14
|
+
|
15
|
+
view :new_buttons do
|
16
|
+
button_formgroup do
|
17
|
+
[standard_create_button, invite_button].compact
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def invite_button
|
22
|
+
return unless invitation?
|
23
|
+
button_tag "Send Invitation", situation: "primary"
|
24
|
+
end
|
25
|
+
|
26
|
+
view :core, template: :haml do
|
27
|
+
@lines = [signup_line] + account_lines
|
28
|
+
@body = process_content _render_raw
|
29
|
+
end
|
30
|
+
|
31
|
+
def signup_line
|
32
|
+
["<strong>#{safe_name}</strong>",
|
33
|
+
("was" if invited?),
|
34
|
+
"signed up on #{format_date card.created_at}"].compact.join " "
|
35
|
+
end
|
36
|
+
|
37
|
+
def invited?
|
38
|
+
!self_signup?
|
39
|
+
end
|
40
|
+
|
41
|
+
def self_signup?
|
42
|
+
card.creator_id == AnonymousID
|
43
|
+
end
|
44
|
+
|
45
|
+
def account_lines
|
46
|
+
if card.account
|
47
|
+
verification_lines
|
48
|
+
else
|
49
|
+
[tr(:missing_account)]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def verification_lines
|
54
|
+
[verification_sent_line, verification_link_line].compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def verification_sent_line
|
58
|
+
account = card.account
|
59
|
+
return unless account.email_card.ok?(:read)
|
60
|
+
"A verification email has been sent to #{account.email}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def verification_link_line
|
64
|
+
links = verification_links
|
65
|
+
return if links.empty?
|
66
|
+
links.join " "
|
67
|
+
end
|
68
|
+
|
69
|
+
def verification_links
|
70
|
+
[approve_with_token_link, approve_without_token_link, deny_link].compact
|
71
|
+
end
|
72
|
+
|
73
|
+
def approve_with_token_link
|
74
|
+
action = card.account.status == "unverified" ? "Resend" : "Send"
|
75
|
+
approval_link "#{action} verification email", :with
|
76
|
+
end
|
77
|
+
|
78
|
+
def approve_without_token_link
|
79
|
+
approval_link "Approve without verification", :without
|
80
|
+
end
|
81
|
+
|
82
|
+
def approval_link text, with_or_without
|
83
|
+
return unless card.can_approve?
|
84
|
+
link_to_card card, text,
|
85
|
+
path: { action: :update,
|
86
|
+
card: { trigger: "approve_#{with_or_without}_verification" } }
|
87
|
+
end
|
88
|
+
|
89
|
+
def deny_link
|
90
|
+
return unless card.ok? :delete
|
91
|
+
link_to_card card, "Deny and delete", path: { action: :delete }
|
92
|
+
end
|
93
|
+
end
|
data/set/type/user.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
include_set Abstract::Accountable
|
2
|
+
|
3
|
+
attr_accessor :email
|
4
|
+
|
5
|
+
format :html do
|
6
|
+
view :setup, unknown: true, perms: ->(_fmt) { Auth.needs_setup? } do
|
7
|
+
with_nest_mode :edit do
|
8
|
+
voo.title = "Your deck is ready to go!" # LOCALIZE
|
9
|
+
voo.show! :help
|
10
|
+
voo.hide! :menu
|
11
|
+
voo.help = haml :setup_help
|
12
|
+
Auth.as_bot { setup_form }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_form
|
17
|
+
frame_and_form :create do
|
18
|
+
[
|
19
|
+
setup_hidden_fields,
|
20
|
+
_render_name_formgroup,
|
21
|
+
account_formgroups,
|
22
|
+
setup_form_buttons
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup_form_buttons
|
28
|
+
button_formgroup { setup_button }
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_button
|
32
|
+
submit_button text: "Set up", disable_with: "Setting up"
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_hidden_fields
|
36
|
+
hidden_tags(
|
37
|
+
setup: true,
|
38
|
+
success: "REDIRECT: #{path mark: ''}",
|
39
|
+
"card[type_id]" => Card.default_accounted_type_id
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup?
|
45
|
+
Card::Env.params[:setup]
|
46
|
+
end
|
47
|
+
|
48
|
+
event :setup_as_bot, before: :check_permissions, on: :create, when: :setup? do
|
49
|
+
abort :failure unless Auth.needs_setup?
|
50
|
+
Auth.as_bot
|
51
|
+
# we need bot authority to set the initial administrator roles
|
52
|
+
# this is granted and inspected here as a separate event for
|
53
|
+
# flexibility and security when configuring initial setups
|
54
|
+
end
|
55
|
+
|
56
|
+
event :setup_first_user, :prepare_to_store, on: :create, when: :setup? do
|
57
|
+
add_subcard "signup alert email+*to", content: name
|
58
|
+
add_subfield :roles, content: roles_for_first_user
|
59
|
+
end
|
60
|
+
|
61
|
+
def roles_for_first_user
|
62
|
+
%i[help_desk shark administrator].map(&:cardname)
|
63
|
+
end
|
64
|
+
|
65
|
+
event :signin_after_setup, :integrate, on: :create, when: :setup? do
|
66
|
+
Auth.signin id
|
67
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
%h3 First, set up an account.
|
2
|
+
.pb-2
|
3
|
+
As the first account holder for this deck, you will automatically have several
|
4
|
+
permissioned roles. You can configure these roles at any time.
|
5
|
+
|
6
|
+
|
7
|
+
- if Card.config.action_mailer.perform_deliveries == false
|
8
|
+
.bg-warning.p-3
|
9
|
+
WARNING: Email delivery is turned off.
|
10
|
+
Change settings in config/application.rb to send sign up notifications.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# supports legacy references to <User>+*email
|
2
|
+
# (standard representation is now <User>+*account+*email)
|
3
|
+
view :raw do
|
4
|
+
card.content_email || card.account_email || ""
|
5
|
+
end
|
6
|
+
|
7
|
+
def content_email
|
8
|
+
content if real?
|
9
|
+
end
|
10
|
+
|
11
|
+
def account_email
|
12
|
+
left&.account&.email
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-account
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.11.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan McCutchen
|
8
|
+
- Philipp Kühl
|
9
|
+
- Gerry Gleason
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: card
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.101.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - '='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 1.101.0
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: card-mod-email
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - '='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.11.0
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - '='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.11.0
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: card-mod-permissions
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.11.0
|
50
|
+
type: :runtime
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.11.0
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: card-mod-list
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.11.0
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 0.11.0
|
71
|
+
description: ''
|
72
|
+
email:
|
73
|
+
- info@decko.org
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- README.md
|
79
|
+
- set/abstract/account_field.rb
|
80
|
+
- set/abstract/accountable.rb
|
81
|
+
- set/all/account.rb
|
82
|
+
- set/right/account.rb
|
83
|
+
- set/right/account/events.rb
|
84
|
+
- set/right/account/views.rb
|
85
|
+
- set/right/api_key.rb
|
86
|
+
- set/right/email.rb
|
87
|
+
- set/right/password.rb
|
88
|
+
- set/right/roles.rb
|
89
|
+
- set/right/salt.rb
|
90
|
+
- set/right/status.rb
|
91
|
+
- set/self/captcha.rb
|
92
|
+
- set/self/signin.rb
|
93
|
+
- set/type/role.rb
|
94
|
+
- set/type/signup.rb
|
95
|
+
- set/type/signup/core.haml
|
96
|
+
- set/type/signup/views.rb
|
97
|
+
- set/type/user.rb
|
98
|
+
- set/type/user/setup_help.haml
|
99
|
+
- set/type_plus_right/user/email.rb
|
100
|
+
homepage: http://decko.org
|
101
|
+
licenses:
|
102
|
+
- GPL-3.0
|
103
|
+
metadata:
|
104
|
+
card-mod: account
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '2.5'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubygems_version: 3.0.3
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Email-based account handling for decko cards
|
124
|
+
test_files: []
|