card-mod-account 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|