masks-server 0.8.0 → 0.10.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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/masks/server/client_authentication.rb +4 -3
  3. data/app/controllers/concerns/masks/server/resource_token.rb +12 -3
  4. data/app/controllers/masks/server/addresses_controller.rb +6 -1
  5. data/app/controllers/masks/server/application_controller.rb +36 -6
  6. data/app/controllers/masks/server/authorize_controller.rb +24 -19
  7. data/app/controllers/masks/server/device_verifications_controller.rb +1 -1
  8. data/app/controllers/masks/server/logins_controller.rb +3 -1
  9. data/app/controllers/masks/server/notifications_controller.rb +11 -1
  10. data/app/controllers/masks/server/profiles_controller.rb +5 -0
  11. data/app/controllers/masks/server/registrations_controller.rb +9 -1
  12. data/app/controllers/masks/server/saml_controller.rb +1 -7
  13. data/app/controllers/masks/server/scim/metadata_controller.rb +1 -1
  14. data/app/controllers/masks/server/tokens_controller.rb +9 -1
  15. data/app/graphql/masks/server/manage/mutations/add_app.rb +1 -1
  16. data/app/graphql/masks/server/manage/mutations/create_actor.rb +1 -0
  17. data/app/graphql/masks/server/manage/mutations/update_actor.rb +1 -0
  18. data/app/graphql/masks/server/manage/mutations/update_client.rb +4 -0
  19. data/app/graphql/masks/server/manage/types/client_type.rb +2 -0
  20. data/app/graphql/masks/server/manage/types/query_type.rb +7 -0
  21. data/app/helpers/masks/server/account_helper.rb +1 -1
  22. data/app/jobs/masks/server/cleanup_job.rb +1 -0
  23. data/app/ldap/masks/server/ldap/listener.rb +16 -3
  24. data/app/logins/masks/server/login_states/confirmation.rb +9 -3
  25. data/app/logins/masks/server/login_states/signup.rb +1 -0
  26. data/app/models/masks/server/actor.rb +28 -5
  27. data/app/models/masks/server/client.rb +61 -6
  28. data/app/models/masks/server/client_keys.rb +12 -1
  29. data/app/models/masks/server/declared.rb +2 -1
  30. data/app/models/masks/server/event.rb +1 -0
  31. data/app/models/masks/server/handshake.rb +12 -6
  32. data/app/models/masks/server/login.rb +3 -6
  33. data/app/models/masks/server/notifications.rb +8 -0
  34. data/app/models/masks/server/pending_handshake.rb +2 -1
  35. data/app/models/masks/server/pending_request.rb +8 -0
  36. data/app/models/masks/server/refresh_token.rb +8 -0
  37. data/app/models/masks/server/released_nickname.rb +26 -0
  38. data/app/models/masks/server/scim/filter.rb +45 -5
  39. data/app/models/masks/server/scim/user.rb +1 -0
  40. data/app/models/masks/server/session.rb +6 -5
  41. data/app/policies/masks/server/exchange_policy.rb +1 -1
  42. data/app/policies/masks/server/grant_checks.rb +1 -1
  43. data/app/views/masks/server/account/_info.html.erb +13 -5
  44. data/app/views/masks/server/account/_organizations.html.erb +4 -4
  45. data/app/views/masks/server/account/index.html.erb +1 -1
  46. data/app/views/masks/server/application/_actor.html.erb +27 -0
  47. data/app/views/masks/server/authorize/error.html.erb +2 -2
  48. data/app/views/masks/server/handshakes/refused.html.erb +1 -1
  49. data/app/views/masks/server/handshakes/show.html.erb +1 -1
  50. data/app/views/masks/server/logins/_choose_organization.html.erb +1 -1
  51. data/app/views/masks/server/logins/_consent.html.erb +1 -1
  52. data/app/views/masks/server/sessions/confirm.html.erb +1 -1
  53. data/config/locales/de/account.yml +6 -2
  54. data/config/locales/de/actors.yml +2 -0
  55. data/config/locales/de/application.yml +1 -1
  56. data/config/locales/de/authorize.yml +2 -0
  57. data/config/locales/de/events.yml +7 -5
  58. data/config/locales/de/flashes.yml +1 -0
  59. data/config/locales/de/logins.yml +1 -0
  60. data/config/locales/de/manage/client.yml +3 -0
  61. data/config/locales/de/manage/groups.yml +1 -1
  62. data/config/locales/de/manage/ldap.yml +1 -0
  63. data/config/locales/de/manage/organization.yml +1 -1
  64. data/config/locales/de/notifications.yml +1 -0
  65. data/config/locales/en/account.yml +6 -2
  66. data/config/locales/en/actors.yml +2 -0
  67. data/config/locales/en/application.yml +1 -1
  68. data/config/locales/en/authorize.yml +2 -0
  69. data/config/locales/en/events.yml +7 -5
  70. data/config/locales/en/flashes.yml +1 -0
  71. data/config/locales/en/logins.yml +1 -0
  72. data/config/locales/en/manage/client.yml +3 -0
  73. data/config/locales/en/manage/groups.yml +1 -1
  74. data/config/locales/en/manage/ldap.yml +1 -0
  75. data/config/locales/en/manage/organization.yml +1 -1
  76. data/config/locales/en/notifications.yml +1 -0
  77. data/config/locales/es/account.yml +6 -2
  78. data/config/locales/es/actors.yml +2 -0
  79. data/config/locales/es/application.yml +1 -1
  80. data/config/locales/es/authorize.yml +2 -0
  81. data/config/locales/es/events.yml +7 -5
  82. data/config/locales/es/flashes.yml +1 -0
  83. data/config/locales/es/logins.yml +1 -0
  84. data/config/locales/es/manage/client.yml +3 -0
  85. data/config/locales/es/manage/groups.yml +1 -1
  86. data/config/locales/es/manage/ldap.yml +1 -0
  87. data/config/locales/es/manage/organization.yml +1 -1
  88. data/config/locales/es/notifications.yml +1 -0
  89. data/config/locales/fr/account.yml +6 -2
  90. data/config/locales/fr/actors.yml +2 -0
  91. data/config/locales/fr/application.yml +1 -1
  92. data/config/locales/fr/authorize.yml +2 -0
  93. data/config/locales/fr/events.yml +7 -5
  94. data/config/locales/fr/flashes.yml +1 -0
  95. data/config/locales/fr/logins.yml +1 -0
  96. data/config/locales/fr/manage/client.yml +3 -0
  97. data/config/locales/fr/manage/groups.yml +1 -1
  98. data/config/locales/fr/manage/ldap.yml +1 -0
  99. data/config/locales/fr/manage/organization.yml +1 -1
  100. data/config/locales/fr/notifications.yml +1 -0
  101. data/config/routes.rb +1 -0
  102. data/db/migrate/20261013010000_add_handshake_return_to_to_clients.rb +5 -0
  103. data/db/migrate/20261018100000_create_released_nicknames.rb +22 -0
  104. data/db/migrate/20261018110000_add_refresh_token_absolute_lifetime_to_clients.rb +5 -0
  105. data/lib/generators/masks/server/install/install_generator.rb +2 -2
  106. data/lib/masks/server/browser_access.rb +35 -0
  107. data/lib/masks/server/configuration.rb +11 -3
  108. data/lib/masks/server/engine.rb +1 -0
  109. data/lib/masks/server/schema_reference.rb +17 -4
  110. data/lib/masks/server/tenancy/middleware.rb +9 -1
  111. data/lib/masks/server.rb +1 -0
  112. data/public/masks-assets/.vite/manifest.json +7 -7
  113. data/public/masks-assets/assets/app-D7W0gAyT.css +2 -0
  114. data/public/masks-assets/assets/{initial-D99AzFOd.js → initial-C1GpCiEh.js} +2 -2
  115. data/public/masks-assets/assets/{initial-D99AzFOd.js.map → initial-C1GpCiEh.js.map} +1 -1
  116. data/public/masks-assets/assets/login-C_6igRZ3.js +4 -0
  117. data/public/masks-assets/assets/login-C_6igRZ3.js.map +1 -0
  118. data/public/masks-assets/assets/manage-DR9qQiBU.js +569 -0
  119. data/public/masks-assets/assets/manage-DR9qQiBU.js.map +1 -0
  120. data/version.txt +1 -1
  121. metadata +14 -9
  122. data/app/views/masks/server/application/_person.html.erb +0 -27
  123. data/public/masks-assets/assets/app-DoQZrM_J.css +0 -2
  124. data/public/masks-assets/assets/login-CNs1UpxJ.js +0 -4
  125. data/public/masks-assets/assets/login-CNs1UpxJ.js.map +0 -1
  126. data/public/masks-assets/assets/manage-isLCrWiU.js +0 -567
  127. data/public/masks-assets/assets/manage-isLCrWiU.js.map +0 -1
@@ -155,7 +155,7 @@ fr:
155
155
  lede_owner: "En tant que propriétaire, vous décidez qui fait partie de l’organisation et quel rôle chaque personne occupe. Les applications qui demandent votre organisation voient ce rôle."
156
156
  lede_member: "Les applications qui demandent votre organisation voient que vous en faites partie, ainsi que votre rôle."
157
157
  members: "Membres"
158
- people:
158
+ member_count:
159
159
  one: "1 membre"
160
160
  other: "%{count} membres"
161
161
  owners:
@@ -182,7 +182,7 @@ fr:
182
182
  decline: "Refuser"
183
183
  confirm_decline: "Refuser l’invitation à %{organization} ? Un propriétaire devra vous inviter à nouveau."
184
184
  set_role: "Définir le rôle"
185
- role_of: "Rôle de %{person}"
185
+ role_of: "Rôle de %{name}"
186
186
  remove: "Retirer"
187
187
  withdraw: "Annuler"
188
188
  resend: "Renvoyer"
@@ -251,6 +251,10 @@ fr:
251
251
  lede: "Tout ce qui figure ici est activé jusqu’à ce que vous le désactiviez. Ces e-mails arrivent au fil des événements, et il n’est pas possible d’y répondre."
252
252
  silent: "Rien n’est envoyé, car ces e-mails ne partent que vers une adresse e-mail confirmée."
253
253
  submit: "Enregistrer"
254
+ kept: "Un mot de passe modifié ou réinitialisé et une connexion à risque sont toujours signalés."
255
+ always: "Toujours"
256
+ again: "Votre dernière connexion remonte à un moment, vous devrez donc d’abord vous reconnecter."
257
+ submit_again: "Se reconnecter"
254
258
 
255
259
  export:
256
260
  heading: "Télécharger votre compte"
@@ -6,3 +6,5 @@ fr:
6
6
  attributes:
7
7
  base:
8
8
  unnamed: "Un compte doit avoir un pseudo ou une adresse e-mail."
9
+ nickname:
10
+ held: "a été libéré récemment et ne peut pas encore être repris"
@@ -1,6 +1,6 @@
1
1
  fr:
2
2
  application:
3
- person:
3
+ actor:
4
4
  manager: "Gestionnaire"
5
5
  unconfirmed: "non confirmé"
6
6
  sign_out: "Se déconnecter"
@@ -5,3 +5,5 @@ fr:
5
5
  stopped: "Interrompu"
6
6
  heading: "L’application a demandé quelque chose que ce serveur refuse de faire."
7
7
  contact: "Rien n’a été partagé. C’est à l’application de corriger le problème."
8
+ refused: "Cette connexion a été refusée."
9
+ nothing_shared: "Rien n’a été partagé avec l’application. Vous pouvez fermer cette page."
@@ -6,6 +6,8 @@ fr:
6
6
  ended: "Déconnexion"
7
7
  revoked: "Session révoquée"
8
8
  expired: "Session expirée"
9
+ notifications:
10
+ changed: "Notifications modifiées"
9
11
  logout:
10
12
  undelivered: "Déconnexion non transmise à une application"
11
13
  login:
@@ -94,14 +96,14 @@ fr:
94
96
  refused: "Échange de jeton refusé"
95
97
  actor:
96
98
  approved: "Compte approuvé"
97
- created: "Personne ajoutée"
98
- provisioned: "Personne ajoutée par un fournisseur"
99
+ created: "Compte ajouté"
100
+ provisioned: "Compte ajouté par un fournisseur"
99
101
  updated: "Profil modifié"
100
- deleted: "Personne supprimée"
102
+ deleted: "Compte supprimé"
101
103
  scopes_changed: "Portées modifiées"
102
104
  signed_out: "Déconnecté partout"
103
- suspended: "Personne suspendue"
104
- restored: "Personne rétablie"
105
+ suspended: "Compte suspendu"
106
+ restored: "Compte rétabli"
105
107
  idle_warned: "Compte inactif averti"
106
108
  client:
107
109
  created: "Application ajoutée"
@@ -129,6 +129,7 @@ fr:
129
129
  directed: "L’annuaire de votre organisation gère votre nom."
130
130
  invalid: "%{errors}"
131
131
  updated: "Vos informations ont été enregistrées."
132
+ again: "Reconnectez-vous pour changer votre pseudo. Votre dernière connexion remonte à un moment."
132
133
 
133
134
  verifications:
134
135
  too_many_requests: "Trop de demandes. Attendez quelques minutes."
@@ -238,6 +238,7 @@ fr:
238
238
  too-many-attempts: "Trop de tentatives. Patientez quelques minutes."
239
239
  too-many-attempts-for-account: "Trop de tentatives pour ce compte."
240
240
  too-many-codes: "Trop de codes ont été envoyés à cette adresse. Patientez quelques minutes."
241
+ too-many-texts: "Trop de codes ont été envoyés à ce numéro. Patientez quelques minutes."
241
242
  prove-email-first: "Saisissez d’abord le code envoyé à cette adresse."
242
243
  no-account-for-email: "Aucun compte ici n’utilise cette adresse."
243
244
  invalid-setup-token: "Ce jeton de configuration est incorrect."
@@ -115,6 +115,8 @@ fr:
115
115
  sector_placeholder: "nécessaire quand ses URI de redirection couvrent plusieurs hôtes"
116
116
  unattended: "Se connecte en son propre nom (client_credentials)"
117
117
  rotate: "Renouveler le secret"
118
+ secret_expires: "Le secret expire le %{day}."
119
+ secret_expired: "Le secret a expiré le %{day}. Renouvelez-le, sinon rien ne peut se connecter avec."
118
120
  archive: "Archiver"
119
121
  saml:
120
122
  title: "SAML"
@@ -146,6 +148,7 @@ fr:
146
148
  consent: "Demander le consentement des acteurs"
147
149
  access: "Durée des jetons d’accès (minutes, vide pour une heure)"
148
150
  refresh: "Durée des jetons d’actualisation sans utilisation (jours, vide pour 30)"
151
+ refresh_absolute: "Fin des jetons d’actualisation après la connexion (jours, vide pour 90)"
149
152
  consent_lifetime: "Durée du consentement (jours, vide jusqu’à révocation)"
150
153
  logout: "URI de déconnexion back-channel"
151
154
  logout_session: "Son jeton de déconnexion indique la session (sid)"
@@ -6,7 +6,7 @@ fr:
6
6
  added: "%{name} est prêt. Ajoutez des acteurs depuis leur propre page."
7
7
  delete_confirm: "Supprimer %{name} ? Les clients limités à ce groupe refusent ses acteurs."
8
8
  deleted: "%{name} est supprimé."
9
- people:
9
+ actors:
10
10
  one: "%{count} acteur"
11
11
  other: "%{count} acteurs"
12
12
  delete: "Supprimer %{name}"
@@ -17,6 +17,7 @@ fr:
17
17
  address: "Adresse"
18
18
  port: "Port %{port}"
19
19
  ldaps: "Port %{port}, LDAPS"
20
+ local_only: "Sans certificat, l’annuaire ne répond que sur la machine de ce serveur, pour qu’aucun mot de passe ne traverse le réseau en clair. Définissez MASKS_LDAP_CERT et MASKS_LDAP_KEY pour servir LDAPS sur toutes les adresses, ou MASKS_LDAP_PLAINTEXT=true sur un réseau privé de confiance."
20
21
  label: "Libellé"
21
22
  issue: "Émettre un identifiant"
22
23
  shown_once: "Ce secret n'est affiché qu'une fois. Copiez-le maintenant."
@@ -66,7 +66,7 @@ fr:
66
66
  invite: "Inviter"
67
67
  cancel: "Annuler"
68
68
  hint: "L’invité rejoint l’organisation dès qu’il accepte, depuis l’adresse que vous invitez. Quelqu’un qui n’est pas encore un acteur reçoit une invitation à s’inscrire."
69
- person: "Acteur"
69
+ actor: "Acteur"
70
70
  empty:
71
71
  invited: "Aucune invitation en attente."
72
72
  owners: "Cette organisation n’a aucun propriétaire. Faites d’un membre un propriétaire pour que quelqu’un puisse la gérer."
@@ -5,6 +5,7 @@ fr:
5
5
 
6
6
  notifications:
7
7
  saved: "Enregistré."
8
+ again: "Reconnectez-vous pour changer les e-mails que vous recevez. Votre dernière connexion remonte à un moment."
8
9
 
9
10
  groups:
10
11
  password: "Mot de passe"
data/config/routes.rb CHANGED
@@ -18,6 +18,7 @@ Masks::Server::Engine.routes.draw do
18
18
  post "/ssf/events", to: "received_signals#create", as: :received_signals
19
19
 
20
20
  match "/authorize", to: "authorize#show", via: %i[get post], as: :authorize
21
+ get "/authorize/refused", to: "authorize#refused", as: :authorize_refused
21
22
  post "/par", to: "pushed_authorizations#create", as: :pushed_authorization
22
23
  post "/device_authorization", to: "device_authorizations#create", as: :device_authorization
23
24
 
@@ -0,0 +1,5 @@
1
+ class AddHandshakeReturnToToClients < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :clients, :handshake_return_to, :string
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ class CreateReleasedNicknames < ActiveRecord::Migration[8.1]
2
+ include Masks::Server::TenantIsolation
3
+
4
+ def up
5
+ create_table :released_nicknames do |t|
6
+ t.references :tenant, null: false, foreign_key: { on_delete: :cascade }, index: false
7
+ t.references :actor, foreign_key: { on_delete: :nullify }, index: false
8
+ t.string :nickname, null: false
9
+ t.datetime :released_at, null: false
10
+ end
11
+
12
+ add_index :released_nicknames, %i[tenant_id nickname], unique: true
13
+ add_index :released_nicknames, :released_at
14
+
15
+ enable_row_level_security(:released_nicknames)
16
+ end
17
+
18
+ def down
19
+ disable_row_level_security(:released_nicknames)
20
+ drop_table :released_nicknames
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ class AddRefreshTokenAbsoluteLifetimeToClients < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :clients, :refresh_token_absolute_lifetime, :integer
4
+ end
5
+ end
@@ -95,8 +95,8 @@ module Masks
95
95
  say " migrations_paths: <%= Masks::Server::Engine.root.join(\"db/migrate\") %>"
96
96
  say " schema_dump: false"
97
97
  say ""
98
- say "Its role must hold neither SUPERUSER nor BYPASSRLS, or masks refuses to start: row-level"
99
- say "security is what keeps one tenant's accounts and keys away from another's."
98
+ say "Its role must hold neither SUPERUSER nor BYPASSRLS, or masks refuses every request that needs"
99
+ say "a tenant: row-level security is what keeps one tenant's accounts and keys away from another's."
100
100
  say ""
101
101
  say "Controllers that need the signed-in account include Masks::Server::Host, which gives them"
102
102
  say "masks_actor and require_masks_actor!." unless subdomain?
@@ -0,0 +1,35 @@
1
+ module Masks
2
+ module Server
3
+ class BrowserAccess
4
+ OPEN = %r{\A/(?:\.well-known/(?:openid-configuration|oauth-authorization-server|jwks\.json|oauth-protected-resource(?:/.*)?)|token|revoke|userinfo|avatars/.+)\z}
5
+ ALLOWED_HEADERS = "Authorization, Content-Type, DPoP".freeze
6
+ EXPOSED_HEADERS = "DPoP-Nonce, WWW-Authenticate".freeze
7
+ PREFLIGHT_LIFETIME = 1.hour.to_i.to_s
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) unless env["HTTP_ORIGIN"] && OPEN.match?(env["PATH_INFO"].to_s)
15
+ return preflight if env["REQUEST_METHOD"] == "OPTIONS"
16
+
17
+ status, headers, body = @app.call(env)
18
+ headers["access-control-allow-origin"] = "*"
19
+ headers["access-control-expose-headers"] = EXPOSED_HEADERS
20
+
21
+ [ status, headers, body ]
22
+ end
23
+
24
+ private
25
+ def preflight
26
+ [ 204, {
27
+ "access-control-allow-origin" => "*",
28
+ "access-control-allow-methods" => "GET, POST",
29
+ "access-control-allow-headers" => ALLOWED_HEADERS,
30
+ "access-control-max-age" => PREFLIGHT_LIFETIME
31
+ }, [] ]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -42,8 +42,10 @@ module Masks
42
42
  config.email_verification_lifetime = ENV.fetch("MASKS_EMAIL_VERIFICATION_LIFETIME", 2 * 24 * 60 * 60).to_i.seconds
43
43
  config.outbound_allowed = outbound_allowed(ENV["MASKS_OUTBOUND_ALLOWED"])
44
44
  config.ldap_port = ENV.fetch("MASKS_LDAP_PORT", 3389).to_i
45
+ config.on_demand_tls = ENV.fetch("MASKS_ON_DEMAND_TLS", "false")
45
46
  config.ldap_cert = ENV["MASKS_LDAP_CERT"].presence
46
47
  config.ldap_key = ENV["MASKS_LDAP_KEY"].presence
48
+ config.ldap_plaintext = ENV.fetch("MASKS_LDAP_PLAINTEXT", "false") == "true"
47
49
  config
48
50
  end
49
51
 
@@ -93,15 +95,21 @@ module Masks
93
95
  if config.named_by && !%w[nickname email either].include?(config.named_by)
94
96
  raise "MASKS_NAMED_BY is #{config.named_by.inspect}, and the only things an " \
95
97
  "account can be named by are a nickname, an email address, or either one. " \
96
- "Set it to nickname, email or either, or leave it unset and let the first-run " \
97
- "screen decide."
98
+ "Set it to nickname, email or either, or leave it unset so each tenant's managers " \
99
+ "choose in /manage."
98
100
  end
99
101
 
100
102
  if config.dynamic_registration && !%w[off anything bounded].include?(config.dynamic_registration)
101
103
  raise "MASKS_DYNAMIC_REGISTRATION is #{config.dynamic_registration.inspect}. " \
102
104
  "Dynamic registration is off, on for anything a manager does not have to grant, " \
103
105
  "or on and bounded by MASKS_DYNAMIC_CLIENT_SCOPES. Set it to off, anything or " \
104
- "bounded, or leave it unset and let the first-run screen decide."
106
+ "bounded, or leave it unset so each tenant's managers choose in /manage."
107
+ end
108
+
109
+ unless [ true, false, "true", "false" ].include?(config.on_demand_tls)
110
+ raise "MASKS_ON_DEMAND_TLS is #{config.on_demand_tls.inspect}. Set it to true to answer a " \
111
+ "proxy's certificate check at /tls/allowed, or to false, or leave it unset, to keep " \
112
+ "that path closed."
105
113
  end
106
114
 
107
115
  return if local || building
@@ -7,6 +7,7 @@ module Masks
7
7
  app.config.masks = Configuration.defaults
8
8
  end
9
9
 
10
+ middleware.use BrowserAccess
10
11
  middleware.use Tenancy::Middleware
11
12
 
12
13
  initializer "masks_server.forwarded_app" do |app|
@@ -26,9 +26,12 @@ module Masks
26
26
  <<~HEAD
27
27
  ---
28
28
  title: Manage API
29
- description: The GraphQL schema served at /manage/graphql.
29
+ description: The GraphQL schema served at /manage/graphql, with an explorer to browse it.
30
30
  ---
31
31
 
32
+ import sdl from "../../../assets/manage.graphql?raw";
33
+ import Explorer from "../../../components/Explorer.tsx";
34
+
32
35
  Everything `/manage` can ask for and everything it can change. `/manage/graphql` accepts a
33
36
  bearer carrying `masks:manage` or one of the narrower
34
37
  [manage roles](/guides/security/#manage-roles), issued for this tenant's manage resource and
@@ -37,6 +40,17 @@ module Masks
37
40
 
38
41
  Generated from `ManageSchema` by `./dev reference`. CI fails when this page and the
39
42
  schema disagree.
43
+
44
+ ## Explorer
45
+
46
+ The explorer shows this schema in GraphiQL, an in-browser tool for writing GraphQL
47
+ queries. Autocomplete, the docs pane, and validation all work on this page. The explorer
48
+ answers only introspection queries, which ask about the schema itself. It sends nothing
49
+ to a server.
50
+
51
+ To run a query, send it to your own server's `/manage/graphql`.
52
+
53
+ <Explorer client:only="react" sdl={sdl} />
40
54
  HEAD
41
55
  end
42
56
 
@@ -50,7 +64,7 @@ module Masks
50
64
  body = if type.kind.enum?
51
65
  values(type)
52
66
  elsif type.kind.scalar?
53
- "A scalar. Serialized as a string unless a client says otherwise.\n"
67
+ "A scalar.\n"
54
68
  else
55
69
  fields(type)
56
70
  end
@@ -97,8 +111,7 @@ module Masks
97
111
  "#{named}#{taken.join}"
98
112
  end
99
113
 
100
- # Every description in this schema is empty, because the code carries no
101
- # comments. An empty column on every row is noise, so it only appears when
114
+ # An empty column on every row is noise, so it only appears when
102
115
  # something in that table actually filled it in.
103
116
  def cells(values, described)
104
117
  kept = described.any?(&:present?) ? values : values[0..-2]
@@ -8,6 +8,14 @@ module Masks
8
8
  TENANTLESS = %w[/up /metrics].freeze
9
9
  TLS_ASK = "/tls/allowed".freeze
10
10
 
11
+ def self.excluded_from_host_check?(request)
12
+ TENANTLESS.include?(request.path) || certificate_check?(request) || Tenant.serving(request.host).present?
13
+ end
14
+
15
+ def self.certificate_check?(request)
16
+ request.path == TLS_ASK && ::Rails.configuration.masks.on_demand_tls.to_s == "true"
17
+ end
18
+
11
19
  def initialize(app)
12
20
  @app = app
13
21
  end
@@ -16,7 +24,7 @@ module Masks
16
24
  request = ActionDispatch::Request.new(env)
17
25
 
18
26
  return @app.call(env) if TENANTLESS.include?(request.path)
19
- return allowed(request) if request.path == TLS_ASK
27
+ return allowed(request) if self.class.certificate_check?(request)
20
28
 
21
29
  tenant = Tenant.serving(request.host)
22
30
 
data/lib/masks/server.rb CHANGED
@@ -105,6 +105,7 @@ end
105
105
 
106
106
  require_relative "server/version"
107
107
  require_relative "server/configuration"
108
+ require_relative "server/browser_access"
108
109
  require_relative "server/isolation"
109
110
  require_relative "server/tenant_key_provider"
110
111
  require_relative "server/forwarding"
@@ -1,6 +1,6 @@
1
1
  {
2
- "_initial-D99AzFOd.js": {
3
- "file": "assets/initial-D99AzFOd.js",
2
+ "_initial-C1GpCiEh.js": {
3
+ "file": "assets/initial-C1GpCiEh.js",
4
4
  "name": "initial"
5
5
  },
6
6
  "_passkey-DIj_5LCY.js": {
@@ -17,7 +17,7 @@
17
17
  ]
18
18
  },
19
19
  "entrypoints/app.css": {
20
- "file": "assets/app-DoQZrM_J.css",
20
+ "file": "assets/app-D7W0gAyT.css",
21
21
  "name": "entrypoints/app.css",
22
22
  "names": [
23
23
  "entrypoints/app.css"
@@ -41,22 +41,22 @@
41
41
  "isEntry": true
42
42
  },
43
43
  "entrypoints/login.js": {
44
- "file": "assets/login-CNs1UpxJ.js",
44
+ "file": "assets/login-C_6igRZ3.js",
45
45
  "name": "entrypoints/login.js",
46
46
  "src": "entrypoints/login.js",
47
47
  "isEntry": true,
48
48
  "imports": [
49
49
  "_passkey-DIj_5LCY.js",
50
- "_initial-D99AzFOd.js"
50
+ "_initial-C1GpCiEh.js"
51
51
  ]
52
52
  },
53
53
  "entrypoints/manage.js": {
54
- "file": "assets/manage-isLCrWiU.js",
54
+ "file": "assets/manage-DR9qQiBU.js",
55
55
  "name": "entrypoints/manage.js",
56
56
  "src": "entrypoints/manage.js",
57
57
  "isEntry": true,
58
58
  "imports": [
59
- "_initial-D99AzFOd.js"
59
+ "_initial-C1GpCiEh.js"
60
60
  ]
61
61
  }
62
62
  }
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial}}}@layer theme{:root,:host{--font-sans:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-xl:36rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--radius-lg:.5rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-base-100:#fff;--color-base-200:#fcf9f7;--color-base-300:#e5ded9;--color-base-content:#211c18;--color-primary:#b04000;--color-primary-content:#fff;--color-secondary:#87611e;--color-accent:#6d4702;--color-neutral:#211c18;--color-neutral-content:#fcf9f7;--color-info:#0077b2;--color-success:#008450;--color-warning:#8b6200;--color-error:#b53530;--color-error-content:#fff;--radius-box:5px;--radius-field:3px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.btn{border-radius:var(--radius-box);border:1px solid var(--edge);background:var(--color-base-100);color:inherit;font:inherit;white-space:nowrap;cursor:pointer;justify-content:center;align-items:center;gap:.4375rem;padding:.5625rem .9375rem;font-size:.875rem;font-weight:500;line-height:1.2;text-decoration:none;transition:border-color .15s,background-color .15s,color .15s;display:inline-flex}.btn:hover:not(:disabled){border-color:#211c18}@supports (color:color-mix(in lab, red, red)){.btn:hover:not(:disabled){border-color:color-mix(in oklab, var(--color-base-content) 30%, var(--edge))}}.btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.btn:disabled,.btn[aria-disabled=true]{opacity:.45;cursor:not-allowed}.btn-sm{padding:.375rem .75rem;font-size:.8125rem}.btn-xs{padding:.25rem .5rem;font-size:.75rem}.btn-primary{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-content)}.btn-primary:hover:not(:disabled){background:#973700}@supports (color:color-mix(in lab, red, red)){.btn-primary:hover:not(:disabled){background:color-mix(in oklab, var(--color-primary) 86%, black)}}.btn-primary:hover:not(:disabled){border-color:#973700}@supports (color:color-mix(in lab, red, red)){.btn-primary:hover:not(:disabled){border-color:color-mix(in oklab, var(--color-primary) 86%, black)}}.btn-error{background:var(--color-error);border-color:var(--color-error);color:var(--color-error-content)}.btn-error:hover:not(:disabled){background:#9c2e29}@supports (color:color-mix(in lab, red, red)){.btn-error:hover:not(:disabled){background:color-mix(in oklab, var(--color-error) 86%, black)}}.btn-error:hover:not(:disabled){border-color:#9c2e29}@supports (color:color-mix(in lab, red, red)){.btn-error:hover:not(:disabled){border-color:color-mix(in oklab, var(--color-error) 86%, black)}}.btn-ghost{background:0 0;border-color:#0000}.btn-ghost:hover:not(:disabled){background:var(--sunk);border-color:#0000}.btn-outline{background:0 0}.btn-error.btn-outline{color:var(--color-error);border-color:#b5353073}@supports (color:color-mix(in lab, red, red)){.btn-error.btn-outline{border-color:color-mix(in oklab, var(--color-error) 45%, transparent)}}.btn-error.btn-outline:hover:not(:disabled){background:#b535301f}@supports (color:color-mix(in lab, red, red)){.btn-error.btn-outline:hover:not(:disabled){background:color-mix(in oklab, var(--color-error) 12%, transparent)}}.btn-error.btn-outline:hover:not(:disabled){border-color:var(--color-error)}.link{color:inherit;text-underline-offset:2px;cursor:pointer;text-decoration:underline;text-decoration-thickness:1px}.link-hover{text-decoration:none}.link-hover:hover,.link:hover{text-decoration:underline}.badge{border:1px solid var(--edge);background:var(--color-base-100);white-space:nowrap;border-radius:999px;align-items:center;gap:.25rem;padding:.125rem .4375rem;font-size:.75rem;font-weight:500;line-height:1.5;display:inline-flex}.badge-sm{padding:.0625rem .375rem;font-size:.6875rem}.badge-xs{padding:0 .3125rem;font-size:.625rem}.badge-ghost{background:var(--sunk);border-color:#0000}.badge-success{color:var(--color-success);border-color:#00845066}@supports (color:color-mix(in lab, red, red)){.badge-success{border-color:color-mix(in oklab, var(--color-success) 40%, transparent)}}.badge-success{background:#0084501f}@supports (color:color-mix(in lab, red, red)){.badge-success{background:color-mix(in oklab, var(--color-success) 12%, transparent)}}.badge-warning{color:var(--color-warning);border-color:#8b620066}@supports (color:color-mix(in lab, red, red)){.badge-warning{border-color:color-mix(in oklab, var(--color-warning) 40%, transparent)}}.badge-warning{background:#8b62001f}@supports (color:color-mix(in lab, red, red)){.badge-warning{background:color-mix(in oklab, var(--color-warning) 12%, transparent)}}.badge-error{color:var(--color-error);border-color:#b5353066}@supports (color:color-mix(in lab, red, red)){.badge-error{border-color:color-mix(in oklab, var(--color-error) 40%, transparent)}}.badge-error{background:#b535301f}@supports (color:color-mix(in lab, red, red)){.badge-error{background:color-mix(in oklab, var(--color-error) 12%, transparent)}}.badge-info{color:var(--color-info);border-color:#0077b266}@supports (color:color-mix(in lab, red, red)){.badge-info{border-color:color-mix(in oklab, var(--color-info) 40%, transparent)}}.badge-info{background:#0077b21f}@supports (color:color-mix(in lab, red, red)){.badge-info{background:color-mix(in oklab, var(--color-info) 12%, transparent)}}.alert{border-radius:var(--radius-box);border:1px solid var(--edge);background:var(--color-base-100);align-items:flex-start;gap:.625rem;padding:.75rem .875rem;font-size:.875rem;line-height:1.45;display:flex}.alert-soft{background:var(--sunk);border-color:#0000}.alert-error{color:var(--color-error);border-color:#b5353059}@supports (color:color-mix(in lab, red, red)){.alert-error{border-color:color-mix(in oklab, var(--color-error) 35%, transparent)}}.alert-error{background:#b535301a}@supports (color:color-mix(in lab, red, red)){.alert-error{background:color-mix(in oklab, var(--color-error) 10%, transparent)}}.alert-warning{color:var(--color-warning);border-color:#8b620059}@supports (color:color-mix(in lab, red, red)){.alert-warning{border-color:color-mix(in oklab, var(--color-warning) 35%, transparent)}}.alert-warning{background:#8b62001a}@supports (color:color-mix(in lab, red, red)){.alert-warning{background:color-mix(in oklab, var(--color-warning) 10%, transparent)}}.alert-success{color:var(--color-success);border-color:#00845059}@supports (color:color-mix(in lab, red, red)){.alert-success{border-color:color-mix(in oklab, var(--color-success) 35%, transparent)}}.alert-success{background:#0084501a}@supports (color:color-mix(in lab, red, red)){.alert-success{background:color-mix(in oklab, var(--color-success) 10%, transparent)}}.alert-info{color:var(--color-info);border-color:#0077b259}@supports (color:color-mix(in lab, red, red)){.alert-info{border-color:color-mix(in oklab, var(--color-info) 35%, transparent)}}.alert-info{background:#0077b21a}@supports (color:color-mix(in lab, red, red)){.alert-info{background:color-mix(in oklab, var(--color-info) 10%, transparent)}}.input,.select,.textarea{border-radius:var(--radius-box);border:1px solid var(--edge);background:var(--color-base-100);width:100%;color:inherit;font:inherit;padding:.5625rem .75rem;font-size:.875rem;line-height:1.4}.input:focus,.select:focus,.textarea:focus{outline:2px solid var(--color-primary);outline-offset:-1px}.input-sm,.select-sm,.textarea-sm{padding:.375rem .5625rem;font-size:.8125rem}.textarea{resize:vertical;min-height:5rem}.select{appearance:none;background-image:linear-gradient(45deg,#0000 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,#0000 50%);background-position:calc(100% - .9375rem) 55%,calc(100% - .625rem) 55%;background-repeat:no-repeat;background-size:.3125rem .3125rem,.3125rem .3125rem;padding-right:1.75rem}.checkbox{width:1rem;height:1rem;accent-color:var(--color-primary);cursor:pointer;flex:none;margin:0}.toggle{appearance:none;border:1px solid var(--edge);background:var(--sunk);cursor:pointer;border-radius:999px;flex:none;width:2.25rem;height:1.3125rem;transition:background-color .15s;position:relative}.toggle:after{content:"";background:var(--color-base-content);border-radius:50%;width:.9375rem;height:.9375rem;transition:transform .15s;position:absolute;top:50%;left:.125rem;transform:translateY(-50%)}.toggle:checked{background:var(--color-primary);border-color:var(--color-primary)}.toggle:checked:after{background:var(--color-primary-content);transform:translate(.9375rem,-50%)}.toggle-sm{width:1.875rem;height:1.125rem}.toggle-sm:after{width:.75rem;height:.75rem}.toggle-sm:checked:after{transform:translate(.75rem,-50%)}.range{width:100%;accent-color:var(--color-primary)}.join{display:inline-flex}.join-item:not(:first-child){border-start-start-radius:0;border-end-start-radius:0;margin-inline-start:-1px}.join-item:not(:last-child){border-start-end-radius:0;border-end-end-radius:0}.card{border-radius:var(--radius-box);border:1px solid var(--edge);background:var(--color-base-100);flex-direction:column;display:flex}.table{border-collapse:collapse;text-align:left;width:100%;font-size:.875rem}.table :where(th){color:#211c1899;padding:.625rem .75rem;font-size:.75rem;font-weight:500}@supports (color:color-mix(in lab, red, red)){.table :where(th){color:color-mix(in oklab, var(--color-base-content) 60%, transparent)}}.table :where(th){border-bottom:1px solid var(--edge)}.table :where(td){border-bottom:1px solid var(--edge);vertical-align:top;padding:.6875rem .75rem}.table :where(tbody tr:last-child td){border-bottom:0}.table-sm :where(th,td){padding:.4375rem .625rem}.loading{border:2px solid;border-top-color:#0000;border-radius:50%;width:1.125rem;height:1.125rem;animation:.62s linear infinite spin;display:inline-block}.loading-spinner{border-style:solid}.auth-page{--ground:#ececed;--surface:#f7f7f8;--line:#d9d9dc;--line-firm:#c4c4c8;--ink:#18181b;--ink-soft:#5c5c63;--brass:#8a5a1c;--brass-lit:#9b6a26;--signal:#b3380f;--signal-ink:#fff;--ring:#b3380f4d;--bad:#b0183f;--bad-field:#fbeef1;--warn:#8b6200;--warn-field:#fbf5e8;--info:#0077b2;--info-field:#edf5fa;--glow:#b3380f0d;--window:url(/masks-public/icon.svg);--window-strength:.2;--pane:var(--surface)}@supports (color:color-mix(in lab, red, red)){.auth-page{--pane:color-mix(in oklab, var(--surface) 58%, transparent)}}.auth-page{--pane-rim:var(--brass)}@supports (color:color-mix(in lab, red, red)){.auth-page{--pane-rim:color-mix(in oklab, var(--brass) 30%, var(--line))}}.auth-page{--pane-shine:#ffffffb3;--pane-sheen:#ffffff73;--pane-shadow:0 1px 1px #14141812, 0 18px 40px -24px #14141866;--bend:8px;--swing:cubic-bezier(.2, .8, .2, 1)}@media (prefers-color-scheme:dark){.auth-page{--ground:#0b0b0c;--surface:#1c1c1f;--line:#2a2a2e;--line-firm:#3e3e44;--ink:#ededef;--ink-soft:#9a9a9f;--brass:#c9a14a;--brass-lit:#e8c27a;--signal:#ff7a4a;--signal-ink:#1f0c06;--ring:#ff7a4a66;--bad:#ff7396;--bad-field:#ff73961a;--warn:#d4ad03;--warn-field:#d4ad031a;--info:#00c2fe;--info-field:#00c2fe1a;--glow:#ff7a4a0f;--window-strength:.36;--pane:var(--surface)}@supports (color:color-mix(in lab, red, red)){.auth-page{--pane:color-mix(in oklab, var(--surface) 52%, transparent)}}.auth-page{--pane-shine:#ffffff1a;--pane-sheen:#ffffff0b;--pane-shadow:0 1px 0 #0009, 0 22px 50px -24px #000c}}.auth-page{background:var(--ground);min-height:100svh;color:var(--ink);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:0;padding:2.5rem 1.25rem 3rem;display:flex}.auth-page:before{--window-size:min(64rem, 150vw);content:"";top:calc(var(--window-size) * -.42);right:calc(var(--window-size) * -.2);width:var(--window-size);aspect-ratio:1;pointer-events:none;background:var(--window) center / contain no-repeat;filter:blur(56px)saturate(1.2);opacity:var(--window-strength);position:fixed}.auth-col{flex-direction:column;gap:1.5rem;width:100%;max-width:24rem;margin:auto;display:flex;position:relative}.surface-grant{max-width:30rem}.setup-ready{--signal:var(--glass-emerald);--signal-ink:#fff;--ring:var(--glass-emerald)}@supports (color:color-mix(in lab, red, red)){.setup-ready{--ring:color-mix(in oklab, var(--glass-emerald) 26%, transparent)}}@media (prefers-color-scheme:dark){.setup-ready{--signal-ink:#06231a}}.surface-terminus{max-width:21rem}.disclosure{flex-direction:column;gap:.5rem;display:flex}.disclosure>summary{cursor:pointer;width:fit-content;color:var(--signal);text-underline-offset:3px;font-size:.9375rem;text-decoration:underline;list-style:none}.disclosure>summary::-webkit-details-marker{display:none}.surface-terminus .prompt-head{text-align:left;align-items:flex-start}.surface-terminus .prompt-title{letter-spacing:-.012em;font-size:1.125rem;font-weight:500;line-height:1.4}.surface-terminus .prompt-lede{max-width:none;font-size:.9375rem}.state{letter-spacing:.14em;text-transform:uppercase;color:var(--brass);align-items:center;gap:.5rem;font-size:.6875rem;font-weight:700;display:flex}.state:before{content:"";background:currentColor;border-radius:1px;flex:none;width:1.5rem;height:2px}.state-bad{color:var(--bad)}.state-done{color:var(--signal)}.said{border-radius:var(--bend);background:var(--surface);border:1px solid var(--line);font-family:var(--font-mono);color:var(--ink-soft);overflow-wrap:anywhere;margin:0;padding:.6875rem .8125rem;font-size:.75rem;line-height:1.5}.auth-id{text-align:center;flex-direction:column;align-items:center;gap:.75rem;display:flex}.auth-mark{border-radius:var(--bend);background:var(--surface);border:1px solid var(--brass);place-items:center;width:2.75rem;height:2.75rem;display:grid}@supports (color:color-mix(in lab, red, red)){.auth-mark{border:1px solid color-mix(in oklab, var(--brass) 55%, var(--line))}}.auth-mark{color:var(--signal);letter-spacing:-.02em;font-size:1.0625rem;font-weight:700}.avatar{border-radius:var(--bend);border:1px solid var(--line);background:var(--surface);object-fit:cover;display:block}.avatar-photo{border-radius:50%}.avatar-identicon,.avatar-initials{border-radius:22%}.auth-tenant{letter-spacing:.12em;text-transform:uppercase;color:var(--brass);overflow-wrap:anywhere;font-size:.75rem;font-weight:700}.auth-pair{justify-content:center;align-items:center;gap:.875rem;display:flex}.auth-pair .auth-mark{width:2.5rem;height:2.5rem;font-size:1rem}.auth-mark-client{color:var(--ink);border-color:var(--line-firm)}.auth-mark-logo{object-fit:cover;background:var(--surface)}.item-logo{border-radius:.3rem}.app-tiles{grid-template-columns:repeat(auto-fill,minmax(7rem,1fr));gap:.5rem;margin:0;padding:0;list-style:none;display:grid}.app-tile{border:1px solid var(--pane-rim);background:var(--pane);box-shadow:var(--pane-shadow);color:inherit;text-align:center;border-radius:1rem;flex-direction:column;align-items:center;gap:.5rem;padding:1rem .5rem .875rem;text-decoration:none;display:flex}.app-tile:hover,.app-tile:focus-visible{border-color:var(--brass)}.app-tile-logo{border-radius:.45rem;width:2rem;height:2rem}.app-tile-initial{background:var(--brass);place-items:center;display:grid}@supports (color:color-mix(in lab, red, red)){.app-tile-initial{background:color-mix(in oklab, var(--brass) 14%, transparent)}}.app-tile-initial{color:var(--brass);font-weight:600}.app-tile-name{overflow-wrap:anywhere;font-size:.875rem;font-weight:500}.client-links{flex-wrap:wrap;gap:.25rem 1rem;display:flex}.auth-mark-pending{color:var(--brass);border-style:dashed}.auth-mark-rose{background:0 0;border:0;border-radius:50%}.auth-wire{background:var(--line-firm);flex:0 0 3.5rem;height:1px;position:relative}.auth-wire:after{content:"";border-top:1px solid var(--line-firm);border-right:1px solid var(--line-firm);width:5px;height:5px;position:absolute;top:50%;right:0;transform:translateY(-50%)rotate(45deg)}.auth-wire-live{background:var(--signal)}.auth-wire-live:after{background:var(--signal);border:0;border-radius:50%;width:6px;height:6px;transform:translate(50%,-50%)}.slab,.entry,.actor{background:linear-gradient(160deg, var(--pane-sheen), transparent 45%), var(--pane);border:1px solid var(--pane-rim);box-shadow:inset 0 1px 0 var(--pane-shine), var(--pane-shadow);-webkit-backdrop-filter:blur(22px)saturate(1.5);backdrop-filter:blur(22px)saturate(1.5)}.slab{border-radius:var(--bend);flex-direction:column;gap:1rem;padding:1.125rem;display:flex}.grant-scope-hot,.grant-scope-hot .chip-key{color:var(--warn)}.grant-scope-split{background:var(--line-firm);height:1px;margin:.25rem 0;list-style:none}.record-cap{letter-spacing:.16em;text-transform:uppercase;color:var(--brass);font-size:.6875rem;font-weight:700}.place{--accent:var(--glass-gold);flex-direction:column;gap:1.75rem;display:flex}.plaque{isolation:isolate;text-align:center;flex-direction:column;align-items:center;gap:.25rem;padding:0 1rem .5rem;display:flex;position:relative}.plaque-top{text-align:left;align-self:stretch;align-items:center;gap:.5rem;display:flex}.plaque-top .auth-mark{width:1.75rem;height:1.75rem;font-size:.75rem}.plaque-title{border-bottom:1px solid var(--line);letter-spacing:-.02em;text-align:left;align-self:stretch;margin:.5rem 0 1.75rem;padding-bottom:1rem;font-size:1.25rem;font-weight:600}.plaque-notes{text-align:left;flex-direction:column;align-self:stretch;gap:.5rem;margin:-.75rem 0 1.75rem;display:flex}.plaque:before{content:"";z-index:-1;pointer-events:none;background:radial-gradient(44% 58% at 50% 0%, var(--glass-amber), transparent 72%);height:18rem;position:absolute;inset:-3rem 0 auto}@supports (color:color-mix(in lab, red, red)){.plaque:before{background:radial-gradient(44% 58% at 50% 0%, color-mix(in oklab, var(--glass-amber) 20%, transparent), transparent 72%)}}.plaque-shot{display:inline-block;position:relative}.plaque-pick{cursor:pointer;display:block}.plaque-badge-form{z-index:1;position:absolute;bottom:-.1875rem;right:-.1875rem}.plaque-badge{background:var(--surface);border:1px solid var(--line-firm);width:1.75rem;height:1.75rem;color:var(--ink-soft);cursor:pointer;border-radius:50%;place-items:center;padding:0;display:grid}.plaque-badge svg{width:.875rem;height:.875rem}.plaque-badge:hover{border-color:var(--bad);color:var(--bad)}.plaque-done{z-index:2;background:var(--glass-emerald);width:1.75rem;height:1.75rem;color:var(--surface);pointer-events:none;opacity:0;border-radius:50%;place-items:center;animation:2.4s ease-out plaque-done;display:grid;position:absolute;bottom:-.1875rem;right:-.1875rem}.plaque-done svg{stroke-width:2.2px;width:.875rem;height:.875rem}.plaque-shot-done .plaque-avatar{animation:2.4s ease-out plaque-ring}@keyframes plaque-done{0%,65%{opacity:1}to{opacity:0}}@keyframes plaque-ring{0%,65%{box-shadow:0 0 0 2px var(--glass-emerald), 0 16px 34px -18px color-mix(in oklab, var(--glass-amber) 55%, transparent)}}.plaque-file{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.plaque-file-label{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;margin:-1px;position:absolute;overflow:hidden}.plaque-pick:focus-within .plaque-avatar{outline:2px solid var(--accent);outline-offset:3px}.plaque-avatar{object-fit:cover;width:5.5rem;height:5.5rem;box-shadow:0 0 0 1px var(--line-firm), 0 16px 34px -18px var(--glass-amber);flex:none;display:block}@supports (color:color-mix(in lab, red, red)){.plaque-avatar{box-shadow:0 0 0 1px var(--line-firm), 0 16px 34px -18px color-mix(in oklab, var(--glass-amber) 55%, transparent)}}.plaque-name{letter-spacing:-.03em;margin:.75rem 0 0;font-size:1.625rem;font-weight:700;line-height:1.15}.plaque-sub{color:var(--ink-soft);margin:0;font-size:1rem}.plaque-pills{flex-wrap:wrap;justify-content:center;align-items:flex-start;gap:.5rem;margin-top:1rem;display:flex}.signout{display:contents}.pill{border:1px solid var(--line-firm);background:var(--glow);color:var(--ink-soft);cursor:pointer;border-radius:999px;align-items:center;gap:.4375rem;padding:.5rem 1rem;font-family:inherit;font-size:.875rem;font-weight:600;text-decoration:none;display:inline-flex}.pill:hover{border-color:var(--brass);color:var(--ink)}.pill-accent{color:var(--accent);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.pill-accent{border-color:color-mix(in oklab, var(--accent) 40%, var(--line))}}.pill-accent:hover{color:var(--accent);border-color:var(--accent)}.pill svg{width:1rem;height:1rem}.group{flex-direction:column;gap:.5rem;scroll-margin-top:1.25rem;display:flex}.group>h2{color:var(--brass);margin:0 0 .125rem .25rem;font-size:.875rem;font-weight:500}.entries{flex-direction:column;gap:.5rem;display:flex}.entry{border-radius:1rem;scroll-margin-top:1.25rem}.entry[open]{border-color:var(--brass)}@supports (color:color-mix(in lab, red, red)){.entry[open]{border-color:color-mix(in oklab, var(--brass) 45%, var(--line-firm))}}.entry>summary{cursor:pointer;align-items:center;gap:.875rem;padding:.9375rem 1rem;list-style:none;display:flex}.entry>summary::-webkit-details-marker{display:none}.entry-icon{border:1px solid var(--line-firm);width:2.25rem;height:2.25rem;color:var(--ink-soft);border-radius:50%;flex:none;place-items:center;display:grid}.entry-icon svg{width:1.125rem;height:1.125rem}.entry-label{overflow-wrap:anywhere;letter-spacing:-.012em;min-width:0;font-size:1rem;font-weight:500}.entry-state{color:var(--brass);text-align:right;flex:none;margin-left:auto;font-size:.875rem}.entry-state-warn{color:var(--accent)}.entry-state-bad{color:var(--bad)}.entry-mark{border-right:1.5px solid var(--line-firm);border-bottom:1.5px solid var(--line-firm);width:.4375rem;height:.4375rem;transition:transform .2s var(--swing);flex:none;transform:rotate(-45deg)}.entry[open] .entry-mark{transform:rotate(45deg)}@media (prefers-reduced-motion:reduce){.entry-mark{transition:none}}.entry-body{flex-direction:column;gap:.875rem;padding:0 1rem 1.125rem;display:flex}.entry-body>.aside{max-width:52ch}.items{flex-direction:column;display:flex}.item{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:.375rem 1rem;padding:.875rem 0;display:grid}.item+.item{border-top:1px solid var(--line)}.item:first-child{padding-top:.25rem}.item:last-child{padding-bottom:.25rem}.item-name{overflow-wrap:anywhere;letter-spacing:-.01em;flex-wrap:wrap;grid-column:1;align-items:center;gap:.5rem;min-width:0;font-size:.9375rem;font-weight:600;display:flex}.item-meta{color:var(--ink-soft);grid-column:1;font-size:.8125rem;line-height:1.5}.item-list{color:var(--ink-soft);flex-direction:column;grid-column:1;gap:.1875rem;margin:0;padding:0;font-size:.8125rem;line-height:1.4;list-style:none;display:flex}.item-more{color:var(--brass)}.item-delegations{color:var(--ink-soft);flex-direction:column;grid-column:1/-1;gap:.375rem;margin:.25rem 0 0;padding:0;font-size:.8125rem;list-style:none;display:flex}.item-delegations li{border-left:2px solid var(--line);justify-content:space-between;align-items:center;gap:1rem;padding-left:.75rem;display:flex}.item-act{grid-area:1/2;justify-self:end}.item-act .action{white-space:nowrap}.item-acts{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.5rem;display:flex}.item-acts .control{width:auto}.item-member .item-act{grid-row:1/span 2;align-self:center}@media (width<=34rem){.item-member{grid-template-columns:minmax(0,1fr)}.item-member .item-act{grid-area:auto/1;justify-self:start}.item-member .item-acts{justify-content:flex-start}}.entry-invited{border-color:var(--warn)}@supports (color:color-mix(in lab, red, red)){.entry-invited{border-color:color-mix(in oklab, var(--warn) 55%, var(--line-firm))}}.member-add{border-top:1px solid var(--line);grid-template-columns:minmax(0,1fr) minmax(7rem,auto) auto;align-items:end;gap:.625rem .75rem;padding-top:1rem;display:grid}.member-add-hint{grid-column:1/-1}.member-add .control{height:3.25rem;padding-block:0}.member-add .member-add-go{height:3.25rem}@media (width<=34rem){.member-add{grid-template-columns:minmax(0,1fr) auto}.member-add-email{grid-column:1/-1}}.member-leave{border-top:1px solid var(--line);padding-top:1rem;display:flex}.switches{flex-direction:column;display:flex}.switch{cursor:pointer;justify-content:space-between;align-items:center;gap:1rem;padding:.6875rem 0;font-size:.9375rem;display:flex}.switch+.switch{border-top:1px solid var(--line)}.switch input[type=checkbox]{width:1.0625rem;height:1.0625rem;accent-color:var(--accent);flex:none;margin:0}.switch-kept{color:var(--brass);flex:none;font-size:.75rem;font-weight:500}.switch-set{flex-direction:column;gap:.25rem;display:flex}.switch-set>legend{color:var(--brass);padding:0;font-size:.75rem;font-weight:500}.flow{flex-direction:column;gap:1.25rem;display:flex}.flow-tight{flex-direction:column;gap:.625rem;display:flex}.prompt-head{text-align:center;flex-direction:column;align-items:center;gap:.4375rem;display:flex}.prompt-title{letter-spacing:-.03em;text-wrap:balance;margin:0;font-size:clamp(1.75rem,1.4rem + 1.5vw,2.125rem);font-weight:700;line-height:1.06}.prompt-lede{color:var(--ink-soft);max-width:38ch;margin:0;font-size:1rem;line-height:1.5}.prompt-lede strong{color:var(--ink);font-weight:500}.pulse{background:var(--brass);border-radius:1px;width:2.75rem;height:2px;margin-bottom:.625rem;position:relative}@supports (color:color-mix(in lab, red, red)){.pulse{background:color-mix(in oklab, var(--brass) 60%, transparent)}}.pulse{overflow:hidden}.pulse:after{content:"";background:var(--signal);inset-block:0;border-radius:1px;width:55%;position:absolute;left:0;translate:-100%}.pulse-on:after{animation:1.15s cubic-bezier(.66,0,.34,1) infinite sweep}.pulse-done{background:var(--signal)}.pulse-bad{background:var(--bad)}.ledger-row{border-top:1px solid var(--line);flex-direction:column;gap:.5rem;padding:.9375rem 0;display:flex}.ledger-row:first-child{border-top:0;padding-top:0}.ledger-row:last-child{padding-bottom:0}.ledger-step+.ledger-step{border-top:0}.setup .prompt-head{margin-bottom:.75rem}.tabset{gap:.25rem;display:flex}.tabset-tab{border-bottom:2px solid var(--line);letter-spacing:.06em;text-transform:uppercase;text-align:center;color:var(--ink-soft);flex:1;padding:0 0 .5rem;font-size:.6875rem;font-weight:700;transition:color .2s,border-color .2s}.tabset-done{border-bottom-color:var(--glass-emerald);color:var(--brass)}.tabset-now{border-bottom-color:var(--signal);color:var(--ink)}.ledger-row-aside{grid-template-rows:auto auto;grid-template-columns:1fr auto;align-items:center;column-gap:.75rem;display:grid}.ledger-aside{grid-area:1/2/span 2;justify-self:end}.ledger-label{letter-spacing:.08em;text-transform:uppercase;color:var(--brass);font-size:.75rem;font-weight:700}.ledger-value{overflow-wrap:anywhere;font-size:.9375rem;line-height:1.5}.ledger-row-tight{gap:.25rem;padding:.625rem 0}.ledger-row-tight .ledger-value{font-size:.8125rem}.ledger-step{--step:var(--line-firm)}.ledger-step-done{--step:var(--glass-emerald)}.ledger-step .ledger-label{color:var(--ink-soft);align-items:center;gap:.5rem;transition:color .2s;display:flex}.ledger-step .ledger-label:before{content:"";background:var(--step);border-radius:50%;width:.4375rem;height:.4375rem;transition:background-color .2s}.ledger-step-done .ledger-label{color:var(--step)}.ledger-step-done .control:not(:focus){border-color:var(--step)}@supports (color:color-mix(in lab, red, red)){.ledger-step-done .control:not(:focus){border-color:color-mix(in oklab, var(--step) 45%, var(--line))}}.enrol{flex-wrap:wrap;align-items:center;gap:1rem;display:flex}.enrol-qr{aspect-ratio:1;border-radius:var(--bend);border:1px solid var(--line);background:#fff;flex:none;width:10rem;max-width:100%;overflow:hidden}.enrol-qr svg{width:100%;height:100%;display:block}.enrol-key{flex-direction:column;flex:10rem;gap:.375rem;min-width:0;display:flex}.enrol-secret{overflow-wrap:anywhere;letter-spacing:.06em;-webkit-user-select:all;user-select:all}.codes{font-variant-numeric:tabular-nums;grid-template-columns:repeat(auto-fill,minmax(10rem,1fr));gap:.375rem 1rem;margin:0;padding:0;list-style:none;display:grid}.codes li{-webkit-user-select:all;user-select:all}.codes-actions{flex-wrap:wrap;align-self:flex-start;gap:.5rem 1.25rem;display:flex}.grant-scopes{flex-direction:column;gap:.5rem;margin:0;padding:0;list-style:none;display:flex}.grant-fields{color:var(--ink-soft);flex-basis:100%;grid-template-columns:auto 1fr;gap:.125rem .75rem;margin:.25rem 0 0;font-size:.8125rem;display:grid}.grant-fields dt{font-weight:600}.grant-fields dd{overflow-wrap:anywhere;margin:0}.grant-scope{flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:.1875rem .875rem;font-size:.9375rem;line-height:1.45;display:flex}.field{flex-direction:column;gap:.4375rem;display:flex}.field-label{letter-spacing:-.002em;font-size:.875rem;font-weight:500}.field-line{justify-content:space-between;align-items:baseline;gap:1rem;display:flex}.field-line .textlink{font-size:.875rem}.field-hint{color:var(--ink-soft);font-size:.8125rem;line-height:1.45}.field-hint-bad{color:var(--bad);font-weight:500}.control{width:100%;font:inherit;border-radius:var(--bend);border:1px solid var(--line);background:var(--surface);padding:.9375rem 1rem;font-size:1rem}@supports (color:color-mix(in lab, red, red)){.control{background:color-mix(in oklab, var(--surface) 72%, transparent)}}.control{color:var(--ink);caret-color:var(--signal);transition:border-color .15s,box-shadow .15s,background-color .15s}.control:autofill{-webkit-text-fill-color:var(--ink);caret-color:var(--signal);box-shadow:inset 0 0 0 100vmax var(--surface)}.control:autofill:focus{box-shadow:inset 0 0 0 1px var(--brass-lit), inset 0 0 0 100vmax var(--surface)}.control::placeholder{color:var(--ink-soft)}@supports (color:color-mix(in lab, red, red)){.control::placeholder{color:color-mix(in oklab, var(--ink-soft) 78%, transparent)}}.control:hover:not(:disabled):not([readonly]){border-color:var(--line-firm)}.control:focus{border-color:var(--brass-lit);box-shadow:inset 0 0 0 1px var(--brass-lit);outline:none}.control[readonly]{background:var(--ground);color:var(--ink-soft);border-color:var(--line)}.control-sm{padding:.5625rem .75rem;font-size:.9375rem}.control-mono{font-family:var(--font-mono);letter-spacing:.14em;font-size:1rem}.control-code{font-family:var(--font-mono);text-align:center;letter-spacing:.4em;text-indent:.4em;font-variant-numeric:tabular-nums;padding:.875rem .5rem;font-size:1.5rem;font-weight:500}.check{color:var(--ink-soft);cursor:pointer;align-items:center;gap:.625rem;font-size:.875rem;line-height:1.4;display:flex}.check input[type=checkbox],.check input[type=radio]{width:1rem;height:1rem;accent-color:var(--signal);flex:none;margin:0}.check input[type=checkbox]:focus-visible,.check input[type=radio]:focus-visible{outline:2px solid var(--signal);outline-offset:2px}.action{width:100%;font:inherit;letter-spacing:-.014em;text-align:center;border-radius:var(--bend);border:1px solid var(--signal);justify-content:center;align-items:center;gap:.5rem;padding:.9375rem 1.125rem;font-size:1rem;font-weight:500;display:inline-flex;position:relative}@supports (color:color-mix(in lab, red, red)){.action{border:1px solid color-mix(in oklab, var(--signal) 60%, transparent)}}.action{background:var(--signal)}@supports (color:color-mix(in lab, red, red)){.action{background:color-mix(in oklab, var(--signal) 14%, var(--ground))}}.action{color:var(--signal);cursor:pointer;transition:background-color .15s,color .15s,border-color .15s}.action:hover:not(:disabled){border-color:var(--signal);background:var(--signal)}@supports (color:color-mix(in lab, red, red)){.action:hover:not(:disabled){background:color-mix(in oklab, var(--signal) 24%, var(--ground))}}.action:focus-visible{outline:2px solid var(--brass-lit);outline-offset:2px}.action:disabled{border-color:var(--line-firm);color:var(--ink-soft);background:0 0}@supports (color:color-mix(in lab, red, red)){.action:disabled{color:color-mix(in oklab, var(--ink-soft) 66%, transparent)}}.action:disabled{cursor:not-allowed}.action-busy,.action-busy:disabled{background:var(--surface);border-color:var(--line-firm);color:var(--ink-soft);cursor:progress}.action-mark{position:absolute;top:50%;left:1.0625rem;translate:0 -50%}.action-quiet{background:var(--surface);color:var(--ink);border-color:var(--line)}.action-quiet:hover:not(:disabled){background:var(--surface);border-color:var(--line-firm)}.action-plain{color:var(--ink-soft);background:0 0;padding:.6875rem 1rem;font-size:.9375rem;font-weight:400}.action-plain:hover:not(:disabled){background:var(--surface);color:var(--ink)}.action-sm{align-self:flex-start;width:auto;padding:.5625rem .9375rem;font-size:.9375rem}.providers{flex-direction:column;gap:.625rem;display:flex}.providers>form{display:contents}.org-picks{flex-direction:column;gap:.5rem;margin:0;padding:0;list-style:none;display:flex}.org-pick{border:1px solid var(--line);border-radius:var(--bend);background:var(--surface);width:100%;color:var(--ink);font:inherit;text-align:left;cursor:pointer;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.875rem;padding:.75rem 1rem .75rem .75rem;transition:border-color .15s,background-color .15s;display:grid}.org-pick:hover:not(:disabled){border-color:var(--signal)}@supports (color:color-mix(in lab, red, red)){.org-pick:hover:not(:disabled){border-color:color-mix(in oklab, var(--signal) 60%, var(--line-firm))}}.org-pick:focus-visible{outline:2px solid var(--brass-lit);outline-offset:2px}.org-pick:disabled{cursor:not-allowed;opacity:.6}.org-pick[aria-busy=true]{opacity:1;cursor:progress;border-color:var(--signal)}.org-pick-mark{border-radius:calc(var(--bend) * .75);border:1px solid var(--line-firm);background:var(--ground);width:2.25rem;height:2.25rem;color:var(--signal);letter-spacing:.02em;place-items:center;font-size:.8125rem;font-weight:700;display:grid}.org-pick-name{overflow-wrap:anywhere;letter-spacing:-.014em;min-width:0;font-size:1rem;font-weight:600}.org-pick-role{color:var(--ink-soft);font-size:.8125rem}.credential-ways{grid-template-columns:1fr 1fr;gap:.5rem;display:grid}.credential-way{font:inherit;border-radius:var(--bend);border:1px solid var(--line);background:var(--surface);padding:.75rem 1rem;font-size:.9375rem}@supports (color:color-mix(in lab, red, red)){.credential-way{background:color-mix(in oklab, var(--surface) 72%, transparent)}}.credential-way{color:var(--ink-soft);cursor:pointer;transition:border-color .15s,color .15s}.credential-way[aria-checked=true]{border-color:var(--signal);color:var(--ink);font-weight:600}.credential-way:focus-visible{outline:2px solid var(--signal);outline-offset:2px}.providers-rule{color:var(--ink-soft);text-transform:lowercase;align-items:center;gap:.75rem;font-size:.8125rem;display:flex}.providers-rule:before,.providers-rule:after{content:"";background:var(--line);flex:auto;height:1px}.otherwise{flex-direction:column;gap:.625rem;display:flex}.otherwise>.providers-rule{margin-bottom:.3125rem}.otherwise .providers{gap:.625rem}.action-row{align-items:stretch;gap:.625rem;display:flex}.action-row>form{display:flex}.action-countdown{font-variant-numeric:tabular-nums}.action-grow{flex:auto}.action-row .action-sm{align-self:auto}.action-fit{flex:none;width:auto}.textlink{font:inherit;color:var(--signal);cursor:pointer;text-underline-offset:3px;text-decoration:underline;text-decoration-thickness:1px;-webkit-text-decoration-color:var(--signal);-webkit-text-decoration-color:var(--signal);-webkit-text-decoration-color:var(--signal);text-decoration-color:var(--signal);background:0 0;border:0;padding:0;font-size:.9375rem;font-weight:400}@supports (color:color-mix(in lab, red, red)){.textlink{-webkit-text-decoration-color:color-mix(in oklab, var(--signal) 38%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--signal) 38%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--signal) 38%, transparent);text-decoration-color:color-mix(in oklab, var(--signal) 38%, transparent)}}.textlink{transition:text-decoration-color .15s}.textlink:hover{text-decoration-color:currentColor}.textlink:focus-visible{outline:2px solid var(--signal);outline-offset:3px;border-radius:3px}.textlink:disabled{opacity:.5;cursor:not-allowed}.whom{border-bottom:1px solid var(--line);flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:.25rem 1rem;padding-bottom:.8125rem;display:flex}.whom-name{font-family:var(--font-mono);color:var(--ink-soft);overflow-wrap:anywhere;font-size:.8125rem;line-height:1.5}.whom .textlink{flex:none;font-size:.875rem}.handshake-warn{color:var(--warn)}.handshake-more>summary{color:var(--ink-soft);-webkit-text-decoration-color:var(--line-firm);-webkit-text-decoration-color:var(--line-firm);-webkit-text-decoration-color:var(--line-firm);text-decoration-color:var(--line-firm);align-self:center;font-size:.8125rem}.handshake-more[open]>summary{margin-bottom:.25rem}.handshake-origin{border:1px solid var(--line-firm);background:var(--surface);border-radius:999px;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:.25rem .5rem;max-width:100%;margin:.375rem 0 .25rem;padding:.375rem .875rem;display:flex}.handshake-origin-label{color:var(--ink-soft);font-size:.75rem}.handshake-origin-value{font-family:var(--font-mono);color:var(--ink);overflow-wrap:anywhere;font-size:.875rem;font-weight:600}.handshake-replacing strong{font-weight:600}.handshake-replacing span{color:var(--ink-soft);font-size:.875rem}.handshake-as{flex-direction:column;gap:.4375rem;display:flex}.handshake-caption{color:var(--ink-soft);padding-inline:.125rem;font-size:.8125rem}.handshake-as .actor{box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;border-color:var(--line);background:0 0}.handshake-as .actor-note{color:var(--ink-soft);font-style:italic}.handshake-as .actor-role{color:var(--brass);letter-spacing:.06em}.handshake-heeds{flex-direction:column;gap:.625rem;margin:0;padding:0;list-style:none;display:flex}.handshake-heed{border-left:2px solid var(--warn);flex-direction:column;gap:.1875rem;padding:.125rem 0 .125rem .75rem;display:flex}.handshake-heed-what{color:var(--warn);font-size:.9375rem;font-weight:600;line-height:1.4}.handshake-heed-why{color:var(--ink-soft);font-size:.875rem;line-height:1.45}.handshake-heed-alarm{border-left-color:var(--bad)}.handshake-heed-alarm .handshake-heed-what,.handshake-heed-alarm .handshake-heed-why{color:var(--bad)}.handshake-sees{flex-wrap:wrap;gap:.375rem;margin:0;padding:0;list-style:none;display:flex}.handshake-sees li{border:1px solid var(--line);background:var(--surface);border-radius:999px;padding:.25rem .625rem;font-size:.875rem;line-height:1.4}.handshake-say{margin:0;font-size:.9375rem;line-height:1.5}.handshake-say .chip-key{color:var(--ink);font-size:.8125rem}.actor{border-radius:.75rem;grid-template-columns:auto 1fr auto;align-items:center;gap:.875rem;padding:.75rem .875rem;display:grid}.actor-avatar{object-fit:cover;border-radius:999px;width:2.75rem;height:2.75rem}.actor-who{flex-direction:column;gap:.125rem;min-width:0;display:flex}.actor-name{overflow-wrap:anywhere;font-weight:600;line-height:1.3}.actor-sub{color:var(--ink-soft);overflow-wrap:anywhere;font-size:.8125rem;line-height:1.4}.actor-note{color:var(--brass)}.actor-role{letter-spacing:.08em;text-transform:uppercase;width:fit-content;color:var(--signal);margin-top:.25rem;font-size:.6875rem;font-weight:700}.actor .textlink{font-size:.875rem}.chip-key{font-family:var(--font-mono);color:var(--ink-soft);flex:none;font-size:.75rem}.tag{border:1px solid var(--line-firm);background:var(--ground);color:var(--ink-soft);border-radius:999px;flex:none;padding:.0625rem .4375rem;font-size:.75rem;font-weight:500;line-height:1.6}.tag-bad{background:var(--bad-field);color:var(--bad);border-color:currentColor}.aside{color:var(--ink-soft);overflow-wrap:anywhere;margin:0;font-size:.875rem;line-height:1.5}.aside-mono{font-family:var(--font-mono);font-size:.8125rem}.aside-bad{color:var(--bad)}.approval-code{letter-spacing:.12em;color:var(--ink);text-align:center;font-size:2rem}.note{border-radius:var(--bend);color:var(--info);background:var(--info-field);border:1px solid;flex-direction:column;gap:.1875rem;padding:.8125rem .875rem;font-size:.9375rem;line-height:1.45;display:flex}.note-bad{color:var(--bad);background:var(--bad-field)}.note-plain{color:var(--ink);background:var(--glow);border-color:var(--line-firm)}.note-warn{color:var(--warn);background:var(--warn-field)}.spinner{border:2px solid;border-top-color:#0000;border-radius:50%;flex:none;width:1.05em;height:1.05em;animation:.62s linear infinite spin}.waiting{color:var(--ink-soft);align-items:center;gap:.625rem;margin:0;font-size:1rem;display:flex}.over{text-align:center;flex-direction:column;align-items:center;gap:1.5rem;display:flex}.over .prompt-lede{max-width:30ch}.over .action{width:auto;min-width:11rem}.empty{color:var(--ink-soft);margin:0;padding:.375rem 0;font-size:.9375rem;line-height:1.5}.recover{flex-flow:wrap;justify-content:space-between;align-items:baseline;gap:.375rem 1rem;display:flex}.recover>span{flex:14rem}.recover .textlink{color:inherit;flex:none;font-size:.9375rem}.recover .textlink:focus-visible{outline-color:currentColor}}@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.top-0{top:0}.z-20{z-index:20}.m-0{margin:0}.mx-auto{margin-inline:auto}.ms-auto{margin-inline-start:auto}.-me-3{margin-inline-end:calc(var(--spacing) * -3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.table{display:table}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-14{width:calc(var(--spacing) * 14);height:calc(var(--spacing) * 14)}.size-16{width:calc(var(--spacing) * 16);height:calc(var(--spacing) * 16)}.min-h-screen{min-height:100vh}.w-0{width:0}.w-16{width:calc(var(--spacing) * 16)}.w-44{width:calc(var(--spacing) * 44)}.w-auto{width:auto}.w-full{width:100%}.max-w-0{max-width:0}.max-w-6xl{max-width:var(--container-6xl)}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-60{max-width:calc(var(--spacing) * 60)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-\[16rem\]{max-width:16rem}.max-w-\[18rem\]{max-width:18rem}.max-w-full{max-width:100%}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-28{min-width:calc(var(--spacing) * 28)}.min-w-44{min-width:calc(var(--spacing) * 44)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.basis-28{flex-basis:calc(var(--spacing) * 28)}.cursor-pointer{cursor:pointer}.scroll-mt-20{scroll-margin-top:calc(var(--spacing) * 20)}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-5{column-gap:calc(var(--spacing) * 5)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.self-center{align-self:center}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-\[var\(--line\)\]{border-color:var(--line)}.border-base-300{border-color:var(--color-base-300)}.bg-base-200{background-color:var(--color-base-200)}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-14{padding-block:calc(var(--spacing) * 14)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-1{padding-top:var(--spacing)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-error{color:var(--color-error)}.text-warning{color:var(--color-warning)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.last\:pb-0:last-child{padding-bottom:0}@media (hover:hover){.hover\:opacity-100:hover{opacity:1}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}@media (width>=40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:size-16{width:calc(var(--spacing) * 16);height:calc(var(--spacing) * 16)}.sm\:w-48{width:calc(var(--spacing) * 48)}.sm\:w-auto{width:auto}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[2fr_1fr\]{grid-template-columns:2fr 1fr}.sm\:grid-cols-\[minmax\(0\,1fr\)_10rem_auto\]{grid-template-columns:minmax(0,1fr) 10rem auto}.sm\:items-end{align-items:flex-end}.sm\:gap-y-2{row-gap:calc(var(--spacing) * 2)}.sm\:pb-0{padding-bottom:0}}@media (width>=48rem){.md\:ml-auto{margin-left:auto}.md\:hidden{display:none}.md\:table-cell{display:table-cell}.md\:w-56{width:calc(var(--spacing) * 56)}.md\:w-auto{width:auto}.md\:max-w-none{max-width:none}.md\:flex-1{flex:1}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}}@media (width>=64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}@media (prefers-color-scheme:dark){:root{--color-base-100:#15110e;--color-base-200:#1e1915;--color-base-300:#3d352f;--color-base-content:#f3efec;--color-primary:#ff8f53;--color-primary-content:#1a1614;--color-secondary:#b58a41;--color-secondary-content:#000;--color-accent:#deaf56;--color-accent-content:#000;--color-neutral:#1a1614;--color-neutral-content:#e9e5e2;--color-info:#00c2fe;--color-info-content:#000;--color-success:#1dcf98;--color-success-content:#000;--color-warning:#d4ad03;--color-warning-content:#000;--color-error:#ff867a;--color-error-content:#1a1614}}:root{--glass-ruby:#b53530;--glass-ember:#b04000;--glass-amber:#a44f00;--glass-gold:#8b6200;--glass-verdant:#267e05;--glass-emerald:#008450;--glass-cyan:#008386;--glass-sky:#0077b2;--glass-cobalt:#2065c5;--glass-violet:#6e50be}@media (prefers-color-scheme:dark){:root{--glass-ruby:#ff867a;--glass-ember:#ff8f53;--glass-amber:#f19c29;--glass-gold:#d4ad03;--glass-verdant:#78c966;--glass-emerald:#1dcf98;--glass-cyan:#00cecf;--glass-sky:#00c2fe;--glass-cobalt:#70b1ff;--glass-violet:#b49dff}}@keyframes rise{0%{opacity:0;translate:0 4px}to{opacity:1;translate:none}}@keyframes sweep{0%{translate:-100%}to{translate:240%}}@keyframes spin{to{transform:rotate(360deg)}}.gate{flex-direction:column;gap:1.25rem;display:flex}.gate-moving{overflow-clip-margin:8px;overflow:visible clip}.gate-enter>*{animation:rise .34s var(--swing) both}.gate-enter>:nth-child(2){animation-delay:40ms}.gate-enter>:nth-child(3){animation-delay:80ms}.gate-enter>:nth-child(4){animation-delay:.11s}.gate-enter>:nth-child(n+5){animation-delay:.13s}@media (prefers-reduced-motion:reduce){.gate-enter>*{animation:none}.pulse-on:after{animation-duration:2.4s}.spinner{animation-duration:2s}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}