card-mod-account 0.15.6 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,4 +3,108 @@
3
3
  -->
4
4
  # Account mod
5
5
 
6
- Create and manage accounts with cards.
6
+ _Create and manage accounts with cards._
7
+
8
+ Like everything else in Decko, accounts are encoded using cards. A high-level
9
+ introduction to account handling in Decko is available at https://decko.org/account
10
+
11
+ ## Sets
12
+
13
+ | name | important fields |
14
+ |:-------------------------:|:-------------------------------------:|
15
+ | [accounted card]+:account | :email, :password, :salt, and :status |
16
+
17
+ Accounts themselves are stored in `+:account` cards. While it is most common
18
+ (and best supported) for `User` cards to have accounts, it is possible for any
19
+ card to have an account, if they include the `Card::Set::Abstract::Accountable` set.
20
+ This can be useful for decks on which it is desirable
21
+ to assign accounts to, for example, companies or robots, but it is not desirable
22
+ to treat such entities as "users."
23
+
24
+ Because (in the wiki tradition) Decko attributes changes to community members who make
25
+ them, it is typically important not to delete cards of former users, lest their changes
26
+ be unattributed, creating confusing, misleading, or potentially even malicious gaps in
27
+ the record.
28
+
29
+ However, it is possible to delete the _account_ without deleting the _accounted_ card.
30
+ For example, if `Malik` is a user and wishes to have his account deleted, this can be
31
+ achieved by deleting `Malik+:account`. The email and passwords associated with the
32
+ account will be deleted, but Malik's name will remain in the system so that his
33
+ edits can still be attributed.
34
+
35
+ A new default deck comes with the following two cards:
36
+
37
+ - **Anonymous** - Edits made by people who are not signed in are credited to Anonymous
38
+ - **Decko Bot** - A fully permissioned card to which many generic actions are attributed.
39
+
40
+ ### Account fields
41
+
42
+ All account fields include the `Card::Set::Abstract::AccountField` set, which
43
+ makes content editable by the account owner
44
+
45
+ | name | type | content |
46
+ |:-------:|:-------:|:------------:|
47
+ | +:email | phrase | email string |
48
+
49
+ Email cards are validated as valid email strings.
50
+
51
+
52
+ | name | type | content |
53
+ |:----------:|:-------:|:---------------:|
54
+ | +:password | phrase | password string |
55
+
56
+ Password cards are validated as valid password strings.
57
+
58
+
59
+ ### Other types
60
+
61
+ | name | type | important fields |
62
+ |:----:|:--------:|:--------------------------:|
63
+ | Role | Cardtype | :members (those with role) |
64
+
65
+ Roles are tools for grouping accounted cards for the sake of assigning permissions.
66
+ Each Role card can have a *List* of members.
67
+
68
+ The account mod comes with several built-in roles:
69
+
70
+ Two have implicit member handling:
71
+
72
+ - **Anyone** - Every accounted card automatically has this role. A permission given to
73
+ *Anyone* is unrestricted.
74
+ - **Anyone Signed In** - Everyone signed in has this role
75
+
76
+ Another has implicit permission handling:
77
+ - **Administrator** - Can create, read, or update any card
78
+
79
+ And others have no special code attached to them, but they come with handy default
80
+ settings:
81
+ - **Eagle** - typically used for content editors
82
+ - **Shark** - typically used for those editing rules, like structure, defaults, styling,
83
+ etc.
84
+ - **Help Desk** - someone who can assign roles and edit user permissions.
85
+
86
+
87
+ | name | codename | type | important fields |
88
+ |:-------:|:--------:|:---------:|:----------------:|
89
+ | Sign Up | signup | Cardtype | :account |
90
+ | User | user | Cardtype | :account |
91
+
92
+ When you sign up for Decko, you create a new `Sign Up` card. A successful signup is
93
+ then converted into a User card (as in, its type changes from `Sign Up` to `User`.)
94
+
95
+ Permissions on these cards determines how accounts are created:
96
+
97
+ - If Anyone can create a Sign Up card, then anyone can sign up.
98
+ - If Anyone can create a User card, then users can verify their own accounts (via email)
99
+ - If NOT Anyone can create a User card, then accounts must be approved by an existing user
100
+ who has the permission to create a User card.
101
+
102
+ ### Other special cards
103
+
104
+ - Signing in and out is performed using the `:signin` card. Signing in works by initiating
105
+ an update action on the card, and signing out works by initiating a delete action. (In
106
+ neither case is the `:signin` card actually altered; the action is aborted once
107
+ the authentication takes place)
108
+
109
+ - The `:account_settings` card can be appended to any accounted card to provide UI
110
+ for various account-related content.
@@ -0,0 +1,11 @@
1
+ $ ->
2
+ $("body").on "click", "._toggle-pw-visibility", (event) ->
3
+ passwordField = $("._pw-input")
4
+ togglePassword = $("._pw-text-area")
5
+
6
+ if passwordField.prop("type") is "password"
7
+ passwordField.prop "type", "text"
8
+ togglePassword.find("i").text "visibility"
9
+ else
10
+ passwordField.prop "type", "password"
11
+ togglePassword.find("i").text "visibility_off"
@@ -50,6 +50,14 @@
50
50
  display: inline;
51
51
  }
52
52
 
53
+ ._toggle-pw-visibility {
54
+ float: right;
55
+ margin-right: 0.6rem;
56
+ margin-top: -1.95rem;
57
+ position: relative;
58
+ z-index: 2;
59
+ }
60
+
53
61
  // FIXME - decko should handle better
54
62
  //#my-card-link {
55
63
  // padding-right: 0.6em;
data/config/admin.yml ADDED
@@ -0,0 +1,4 @@
1
+ cardtypes:
2
+ admin:
3
+ - role
4
+ - signup
@@ -98,7 +98,13 @@ de:
98
98
  account_invite: Einladen
99
99
  account_missing_account: "FEHLER: Registrierkarte ohne Konto"
100
100
  account_or_sign_up: '...oder registrieren!'
101
- account_password_length: muss mindestens 4 Zeichen lang sein
101
+ account_password_length: muss mindestens %{num_char} Zeichen lang sein
102
+ account_password_requirements: muss %{char_type} enthalten
103
+ account_password_requirement_upper: einen Grossbuchstaben
104
+ account_password_requirement_lower: einene Kleinbuchstaben
105
+ account_password_requirement_special_char: ein Sonderzeichen (wie !@$%&#)
106
+ account_password_requirement_number: eine Zahl
107
+ account_password_requirement_letter: einen Buchstaben
102
108
  account_password_missing: Passwort muss angegeben werden
103
109
  account_private_data: Private Daten
104
110
  account_reset_password: Password Zurücksetzen
@@ -17,7 +17,13 @@ en:
17
17
  account_invite: Invite
18
18
  account_missing_account: "ERROR: signup card missing account"
19
19
  account_or_sign_up: '...or sign up!'
20
- account_password_length: must be at least 4 characters
20
+ account_password_length: must be %{num_char} characters or longer
21
+ account_password_requirements: must contain %{char_type}
22
+ account_password_requirement_upper: an upper case letter
23
+ account_password_requirement_lower: a lower case letter
24
+ account_password_requirement_special_char: a special character (like !@$%&#)
25
+ account_password_requirement_number: a number
26
+ account_password_requirement_letter: a letter
21
27
  account_password_missing: password can't be blank
22
28
  account_private_data: Private data
23
29
  account_reset_password: Reset password
@@ -27,4 +33,4 @@ en:
27
33
  account_sign_out: Sign out
28
34
  account_sign_up: Sign up
29
35
  account_sorry_email_reset:
30
- Sorry, %{error_msg}. Please check your email for a new password reset link.
36
+ Sorry, %{error_msg}. Please check your email for a new password reset link.
@@ -0,0 +1,2 @@
1
+ // toggle_visibility.js.coffee
2
+ (function(){$(function(){return $("body").on("click","._toggle-pw-visibility",function(){var i,t;return i=$("._pw-input"),t=$("._pw-text-area"),"password"===i.prop("type")?(i.prop("type","text"),t.find("i").text("visibility")):(i.prop("type","password"),t.find("i").text("visibility_off"))})})}).call(this);
data/data/real.yml CHANGED
@@ -77,17 +77,21 @@
77
77
  - :type_plus_right
78
78
  - :update
79
79
  :content:
80
- Help Desk
80
+ - :help_desk
81
81
 
82
82
  - :name: "*account"
83
83
  :codename: account
84
84
  :fields:
85
85
  :right:
86
86
  :fields:
87
- :create: Administrator
88
- :read: Help Desk
89
- :update: Help Desk
90
- :delete: Help Desk
87
+ :create:
88
+ - :administrator
89
+ :read:
90
+ - :help_desk
91
+ :update:
92
+ - :help_desk
93
+ :delete:
94
+ - :help_desk
91
95
  :content: |-
92
96
  <div>When someone signs up, they create a [[Sign up]] card...</div>
93
97
  <blockquote>
@@ -119,31 +123,41 @@
119
123
  :fields:
120
124
  :right:
121
125
  :fields:
122
- :update: Help Desk
123
- :delete: Help Desk
124
- :read: Help Desk
126
+ :read:
127
+ - :help_desk
128
+ :update:
129
+ - :help_desk
130
+ :delete:
131
+ - :help_desk
125
132
  - :name: "*password"
126
133
  :codename: password
127
134
  :fields:
128
135
  :right:
129
136
  :fields:
130
- :update: Help Desk
131
- :delete: Help Desk
132
- :read: Help Desk
137
+ :read:
138
+ - :help_desk
139
+ :update:
140
+ - :help_desk
141
+ :delete:
142
+ - :help_desk
133
143
  - :name: "*salt"
134
144
  :codename: salt
135
145
  :fields:
136
146
  :right:
137
147
  :fields:
138
- :read: Help Desk
148
+ :read:
149
+ - :help_desk
139
150
  - :name: "*status"
140
151
  :codename: status
141
152
  :fields:
142
153
  :right:
143
154
  :fields:
144
- :update: Help Desk
145
- :delete: Help Desk
146
- :read: Help Desk
155
+ :read:
156
+ - :help_desk
157
+ :update:
158
+ - :help_desk
159
+ :delete:
160
+ - :help_desk
147
161
 
148
162
  - :name: "*account settings"
149
163
  :codename: account_settings
@@ -1,6 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- class MoveRoles < Cardio::Migration
3
+ class MoveRoles < Cardio::Migration::Transform
4
4
  def up
5
5
  remove_member_structure_rule
6
6
  change_existing_member_cards_to_lists
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ class MemberIds < Cardio::Migration::Transform
4
+ def up
5
+ Card.search(right: :members).each &:save!
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ Cardio::Railtie.config.tap do |config|
2
+ config.account_password_length = 8
3
+ config.account_password_requirements = %i[lower upper special_char number]
4
+ end
@@ -1,11 +1,19 @@
1
- delegate :accounted, to: :account_card
1
+ # -*- encoding : utf-8 -*-
2
2
 
3
3
  def account_card
4
- left
4
+ left.tap { |l| return nil unless l.right_id == AccountID }
5
+ end
6
+
7
+ def accounted
8
+ account_card&.accounted
9
+ end
10
+
11
+ def own_account?
12
+ account_card&.own_account?
5
13
  end
6
14
 
7
15
  # allow account owner to update account field content
8
- def ok_to_update
16
+ def ok_to_update?
9
17
  (own_account? && !name_changed? && !type_id_changed?) || super
10
18
  end
11
19
 
@@ -0,0 +1,152 @@
1
+ event :validate_account_holder_name_change, :validate, on: :update, changed: :name do
2
+ return unless account? && !Card::Auth.as_card.account_manager?
3
+
4
+ errors.add :name, "cannot rename Account Holder"
5
+ end
6
+
7
+ def account
8
+ fetch :account, new: {}
9
+ end
10
+
11
+ def account?
12
+ account.real?
13
+ end
14
+
15
+ def own_account?
16
+ key == Auth.as_card.key
17
+ end
18
+
19
+ def default_account_status
20
+ "active"
21
+ end
22
+
23
+ def current_account?
24
+ id && Auth.current_id == id
25
+ end
26
+
27
+ def parties
28
+ @parties ||= (all_enabled_roles << id).flatten.reject(&:blank?)
29
+ end
30
+
31
+ def among? ok_ids
32
+ ok_ids.any? do |ok_id|
33
+ ok_id == AnyoneID ||
34
+ # (ok_id == AnyoneWithRoleID && all_enabled_roles.size > 1) ||
35
+ parties.member?(ok_id)
36
+ end
37
+ end
38
+
39
+ def all_enabled_roles
40
+ @all_enabled_roles ||= (id == AnonymousID ? [] : enabled_role_ids)
41
+ end
42
+
43
+ def all_roles
44
+ @all_roles ||= (id == AnonymousID ? [] : fetch_roles)
45
+ end
46
+
47
+ def read_rules
48
+ @read_rules ||= fetch_read_rules
49
+ end
50
+
51
+ def read_rules_hash
52
+ @read_rules_hash ||= read_rules.each_with_object({}) { |id, h| h[id] = true }
53
+ end
54
+
55
+ def clear_roles
56
+ @parties = @all_roles = @all_enabled_roles = @read_rules = nil
57
+ end
58
+
59
+ def ok_to_update?
60
+ (own_account? && !type_id_changed?) || super
61
+ end
62
+
63
+ def admin?
64
+ role? AdministratorID
65
+ end
66
+
67
+ def role? role_mark
68
+ all_enabled_roles.include? role_mark.card_id
69
+ end
70
+
71
+ def account_manager?
72
+ own_account? || parties.member?(HelpDeskID)
73
+ end
74
+
75
+ private
76
+
77
+ def enabled_role_ids
78
+ with_enabled_roles do |enabled|
79
+ enabled.virtual? ? enabled.item_ids : fetch_roles
80
+ end
81
+ end
82
+
83
+ def with_enabled_roles
84
+ Auth.as_bot do
85
+ Card::Codename.exists?(:enabled_roles) ? yield(enabled_roles_card) : fetch_roles
86
+ end
87
+ end
88
+
89
+ def enabled_roles_card
90
+ fetch :enabled_roles, eager_cache: true, new: { type_id: SessionID }
91
+ end
92
+
93
+ def role_ids_from_role_member_cards
94
+ Self::Role.role_ids id
95
+ end
96
+
97
+ def fetch_roles
98
+ [AnyoneSignedInID] + role_ids_from_role_member_cards
99
+ end
100
+
101
+ def fetch_read_rules
102
+ return [] if id == WagnBotID # always_ok, so not needed
103
+
104
+ ([AnyoneID] + parties).each_with_object([]) do |party_id, rule_ids|
105
+ next unless (cache = Card::Rule.read_rule_cache[party_id])
106
+
107
+ rule_ids.concat cache
108
+ end
109
+ end
110
+
111
+ format :html do
112
+ def default_board_tab
113
+ card.current_account? ? :account_tab : super
114
+ end
115
+
116
+ view :account_tab do
117
+ board_pill_sections "Account" do
118
+ [["Settings", account_details_items],
119
+ ["Content", account_content_items]]
120
+ end
121
+ end
122
+
123
+ def show_account_tab?
124
+ card.account?
125
+ end
126
+
127
+ def account_formgroups
128
+ Auth.as_bot do
129
+ subformat(card.account)._render :content_formgroups, structure: true
130
+ end
131
+ end
132
+
133
+ def account_details_items
134
+ [
135
+ ["Email and Password", :account,
136
+ { path: { slot: { hide: %i[help_link board_link] } } }],
137
+ ["Roles", :roles,
138
+ { path: { view: :content } }],
139
+ ["Notifications", :follow],
140
+ # FIXME: this should be added in api_key mod!
141
+ ["API", :account,
142
+ { path: { view: :api_key,
143
+ items: { view: :content },
144
+ slot: { hide: %i[help_link board_link] } } }]
145
+ ]
146
+ end
147
+
148
+ def account_content_items
149
+ [["Created", :created],
150
+ ["Edited", :edited]]
151
+ end
152
+ end
data/set/all/account.rb CHANGED
@@ -5,92 +5,11 @@ module ClassMethods
5
5
  end
6
6
 
7
7
  def account
8
- fetch :account
8
+ nil
9
9
  end
10
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
-
43
- rule_ids.concat cache
44
- end
45
- end
46
-
47
- def clear_roles
48
- @parties = @all_roles = @all_active_roles = @read_rules = nil
49
- end
50
-
51
- # def with_clear_roles
52
- # a = @parties
53
- # b = @all_roles
54
- # c = @all_active_roles
55
- # d = @read_rules
56
- # yield
57
- # ensure
58
- # @parties = a
59
- # @all_roles = b
60
- # @all_active_roles = c
61
- # @read_rules = d
62
- # end
63
-
64
- def all_enabled_roles
65
- @all_active_roles ||= (id == AnonymousID ? [] : enabled_role_ids)
66
- end
67
-
68
- def all_roles
69
- @all_roles ||= (id == AnonymousID ? [] : fetch_roles)
70
- end
71
-
72
- def enabled_role_ids
73
- with_enabled_roles do |enabled|
74
- enabled.virtual? ? enabled.item_ids : fetch_roles
75
- end
76
- end
77
-
78
- def with_enabled_roles
79
- Auth.as_bot do
80
- Card::Codename.exists?(:enabled_roles) ? yield(enabled_roles_card) : fetch_roles
81
- end
82
- end
83
-
84
- def enabled_roles_card
85
- fetch :enabled_roles, eager_cache: true, new: { type_id: SessionID }
86
- end
87
-
88
- def fetch_roles
89
- [AnyoneSignedInID] + role_ids_from_role_member_cards
90
- end
91
-
92
- def role_ids_from_role_member_cards
93
- Self::Role.role_ids id
11
+ def account?
12
+ false
94
13
  end
95
14
 
96
15
  event :generate_token do
@@ -0,0 +1,6 @@
1
+
2
+ = password_field :content, class: "d0-card-content _pw-input",
3
+ placeholder: "Password",
4
+ autocomplete: "off"
5
+ %span._toggle-password.visibility-icon._pw-text-area
6
+ = icon_tag :hide_password, class: "_toggle-pw-visibility"
@@ -0,0 +1,5 @@
1
+ format :html do
2
+ def password_input
3
+ haml :password_input
4
+ end
5
+ end
@@ -54,7 +54,7 @@ end
54
54
  def verifying_token success, failure
55
55
  requiring_token do |token|
56
56
  result = Auth::Token.decode token
57
- if result.is_a?(String)
57
+ if result.is_a?(String) || (result[:user_id] != accounted_id)
58
58
  send failure, result
59
59
  else
60
60
  send success
data/set/right/account.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
+ delegate :own_account?, to: :accounted
4
+
3
5
  card_accessor :email
4
6
  card_accessor :password
5
7
  card_accessor :salt
@@ -15,12 +17,12 @@ def accounted_id
15
17
  left_id
16
18
  end
17
19
 
18
- def ok_to_read
20
+ def ok_to_read?
19
21
  own_account? || super
20
22
  end
21
23
 
22
24
  # allow account owner to update account field content
23
- def ok_to_update
25
+ def ok_to_update?
24
26
  (own_account? && !name_changed? && !type_id_changed?) || super
25
27
  end
26
28
 
data/set/right/email.rb CHANGED
@@ -36,7 +36,7 @@ def email_required?
36
36
  !left&.left&.codename == :wagn_bot
37
37
  end
38
38
 
39
- def ok_to_read
39
+ def ok_to_read?
40
40
  if own_email? || Auth.always_ok?
41
41
  true
42
42
  else