kiosk-server 0.6.1 → 0.6.28

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -0
  3. data/README.md +134 -102
  4. data/app/channels/kiosk_events.rb +30 -17
  5. data/app/controllers/kiosk/server/application_controller.rb +0 -1
  6. data/app/controllers/kiosk/server/assistants_controller.rb +13 -14
  7. data/app/controllers/kiosk/server/auth_controller.rb +3 -3
  8. data/app/controllers/kiosk/server/device_verify_controller.rb +0 -5
  9. data/app/controllers/kiosk/server/discovery_controller.rb +2 -7
  10. data/app/controllers/kiosk/server/kyc_callback_controller.rb +3 -1
  11. data/app/controllers/kiosk/server/oauth_device_authorization_controller.rb +8 -7
  12. data/app/controllers/kiosk/server/oauth_token_controller.rb +4 -1
  13. data/app/controllers/kiosk/server/payment_setup_controller.rb +3 -4
  14. data/app/controllers/kiosk/server/wire_controller.rb +2 -2
  15. data/app/models/kiosk/checkout_mandate.rb +0 -2
  16. data/app/models/kiosk/settlement.rb +0 -2
  17. data/app/models/kiosk/visit.rb +0 -2
  18. data/app/views/kiosk/server/assistants/show.html.erb +20 -24
  19. data/app/views/kiosk/server/device_verify/decided.html.erb +1 -0
  20. data/app/views/kiosk/server/device_verify/show.html.erb +1 -0
  21. data/app/views/layouts/kiosk/server/application.html.erb +14 -0
  22. data/lib/generators/kiosk/install/install_generator.rb +25 -64
  23. data/lib/generators/kiosk/install/templates/create_kiosk_device_authorizations.rb.tt +21 -12
  24. data/lib/generators/kiosk/install/templates/create_kiosk_events.rb.tt +15 -17
  25. data/lib/generators/kiosk/install/templates/create_kiosk_identity_tables.rb.tt +39 -10
  26. data/lib/generators/kiosk/install/templates/create_kiosk_kyc_attributes.rb.tt +16 -14
  27. data/lib/generators/kiosk/install/templates/create_kiosk_mandates.rb.tt +50 -14
  28. data/lib/generators/kiosk/install/templates/create_kiosk_pow_spent.rb.tt +7 -11
  29. data/lib/generators/kiosk/install/templates/create_kiosk_refunds.rb.tt +11 -11
  30. data/lib/generators/kiosk/install/templates/create_kiosk_revocations.rb.tt +7 -11
  31. data/lib/generators/kiosk/install/templates/create_kiosk_visits.rb.tt +10 -11
  32. data/lib/generators/kiosk/install/templates/initializer.rb.tt +13 -4
  33. data/lib/kiosk/payable.rb +1 -1
  34. data/lib/kiosk/server/account_binding.rb +14 -5
  35. data/lib/kiosk/server/account_holder_gate.rb +3 -1
  36. data/lib/kiosk/server/agent_identity_providers/default_agent_idp.rb +1 -1
  37. data/lib/kiosk/server/agent_rotation.rb +1 -1
  38. data/lib/kiosk/server/argument_decoder.rb +2 -2
  39. data/lib/kiosk/server/assistant_accounts.rb +38 -0
  40. data/lib/kiosk/server/auth_toll.rb +5 -5
  41. data/lib/kiosk/server/client.rb +1 -1
  42. data/lib/kiosk/server/client_assertion.rb +3 -2
  43. data/lib/kiosk/server/client_registration.rb +6 -2
  44. data/lib/kiosk/server/column_spending_cap.rb +1 -1
  45. data/lib/kiosk/server/configuration_extension.rb +15 -38
  46. data/lib/kiosk/server/current_request.rb +1 -1
  47. data/lib/kiosk/server/device_authorization.rb +1 -1
  48. data/lib/kiosk/server/device_authorization_stores.rb +1 -1
  49. data/lib/kiosk/server/device_code_grant.rb +2 -2
  50. data/lib/kiosk/server/dpop.rb +6 -1
  51. data/lib/kiosk/server/engine.rb +2 -2
  52. data/lib/kiosk/server/errors.rb +21 -7
  53. data/lib/kiosk/server/event_stores.rb +2 -2
  54. data/lib/kiosk/server/events.rb +4 -3
  55. data/lib/kiosk/server/events_connection.rb +23 -5
  56. data/lib/kiosk/server/events_replay.rb +79 -0
  57. data/lib/kiosk/server/executor/repeat.rb +1 -1
  58. data/lib/kiosk/server/executor/spending_cap.rb +8 -66
  59. data/lib/kiosk/server/executor.rb +10 -10
  60. data/lib/kiosk/server/failure_log.rb +3 -3
  61. data/lib/kiosk/server/handler_mixin.rb +9 -9
  62. data/lib/kiosk/server/handler_registrations.rb +1 -1
  63. data/lib/kiosk/server/handler_serving.rb +11 -11
  64. data/lib/kiosk/server/headers.rb +3 -3
  65. data/lib/kiosk/server/identity_resolution.rb +1 -1
  66. data/lib/kiosk/server/kyc.rb +5 -7
  67. data/lib/kiosk/server/kyc_verifier.rb +26 -27
  68. data/lib/kiosk/server/linked_assistants.rb +21 -16
  69. data/lib/kiosk/server/money.rb +41 -0
  70. data/lib/kiosk/server/oauth_answers.rb +1 -0
  71. data/lib/kiosk/server/oauth_error.rb +3 -0
  72. data/lib/kiosk/server/open_api.rb +9 -9
  73. data/lib/kiosk/server/page.rb +1 -1
  74. data/lib/kiosk/server/payment_claim.rb +3 -3
  75. data/lib/kiosk/server/pow_gate.rb +12 -33
  76. data/lib/kiosk/server/pow_spent_stores.rb +1 -1
  77. data/lib/kiosk/server/receipts.rb +4 -3
  78. data/lib/kiosk/server/reputation_factors.rb +12 -5
  79. data/lib/kiosk/server/request_validation.rb +2 -1
  80. data/lib/kiosk/server/result.rb +1 -1
  81. data/lib/kiosk/server/revocation_stores.rb +1 -1
  82. data/lib/kiosk/server/routing.rb +1 -1
  83. data/lib/kiosk/server/schemas/auth.schema.json +2 -2
  84. data/lib/kiosk/server/schemas/binding.schema.json +3 -3
  85. data/lib/kiosk/server/schemas/kyc.schema.json +22 -3
  86. data/lib/kiosk/server/schemas/mandates.schema.json +5 -5
  87. data/lib/kiosk/server/schemas/pow.schema.json +2 -2
  88. data/lib/kiosk/server/session_context.rb +2 -2
  89. data/lib/kiosk/server/tolled_calls.rb +46 -0
  90. data/lib/kiosk/server/verb_wire.rb +3 -3
  91. data/lib/kiosk/server/version.rb +1 -1
  92. data/lib/kiosk/server/well_known.rb +14 -11
  93. data/lib/kiosk/server/wire_rendering.rb +1 -1
  94. data/lib/kiosk/server.rb +6 -2
  95. metadata +9 -8
  96. data/lib/generators/kiosk/install/templates/create_kiosk_schema.rb.tt +0 -17
  97. data/lib/kiosk/server/schema_definitions.rb +0 -320
  98. data/lib/kiosk/server/settled_spend.rb +0 -21
  99. data/lib/kiosk/server/wrong_door.rb +0 -42
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kiosk-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Pirozhkov
@@ -164,7 +164,7 @@ dependencies:
164
164
  description: |
165
165
  kiosk-server is the host-side surface for Kiosk. The full surface ships:
166
166
 
167
- - The controllers — the per-verb wire (GET <endpoint>/<query-name>,
167
+ - The controllers — the per-verb endpoints (GET <endpoint>/<query-name>,
168
168
  POST <endpoint>/<action-name>) and the reserved endpoints
169
169
  (/kiosk/schema, /kiosk/pay), a derived OpenAPI description of both,
170
170
  OAuth client registration (RFC 7591) and the token endpoint
@@ -176,7 +176,7 @@ description: |
176
176
  - Kiosk::Server::Executor — dispatches a resolved command to the
177
177
  queries and actions the host registered.
178
178
  - Client registration and authentication, with a pluggable agent-IdP.
179
- - The PoW gate that enforces a kiosk-reputation policy's challenge
179
+ - The proof-of-work toll that a kiosk-reputation policy sets
180
180
  (soft dependency; zero overhead when no policy is set).
181
181
  - A Rack middleware injecting the Kiosk-Server-Version /
182
182
  Kiosk-API-Version / Kiosk-Min-Client response headers, a pure-Ruby
@@ -186,7 +186,7 @@ description: |
186
186
 
187
187
  The Rails engine serves that whole surface from one `kiosk` block in
188
188
  config/routes.rb, which mounts it at Kiosk.configuration.mount_path —
189
- drawing the wire/auth/JWKS/KYC/binding routes under the mount and
189
+ drawing the verb/auth/JWKS/KYC/binding routes under the mount and
190
190
  installing the root-relative discovery routes (agents.txt, .well-known)
191
191
  into the host — then one route per query and action the block names. It
192
192
  also auto-injects the headers middleware.
@@ -222,6 +222,7 @@ files:
222
222
  - app/views/kiosk/server/assistants/show.html.erb
223
223
  - app/views/kiosk/server/device_verify/decided.html.erb
224
224
  - app/views/kiosk/server/device_verify/show.html.erb
225
+ - app/views/layouts/kiosk/server/application.html.erb
225
226
  - lib/generators/kiosk/install/install_generator.rb
226
227
  - lib/generators/kiosk/install/templates/create_kiosk_device_authorizations.rb.tt
227
228
  - lib/generators/kiosk/install/templates/create_kiosk_events.rb.tt
@@ -231,7 +232,6 @@ files:
231
232
  - lib/generators/kiosk/install/templates/create_kiosk_pow_spent.rb.tt
232
233
  - lib/generators/kiosk/install/templates/create_kiosk_refunds.rb.tt
233
234
  - lib/generators/kiosk/install/templates/create_kiosk_revocations.rb.tt
234
- - lib/generators/kiosk/install/templates/create_kiosk_schema.rb.tt
235
235
  - lib/generators/kiosk/install/templates/create_kiosk_visits.rb.tt
236
236
  - lib/generators/kiosk/install/templates/initializer.rb.tt
237
237
  - lib/kiosk/handler.rb
@@ -245,6 +245,7 @@ files:
245
245
  - lib/kiosk/server/agent_identity_providers/default_agent_idp.rb
246
246
  - lib/kiosk/server/agent_rotation.rb
247
247
  - lib/kiosk/server/argument_decoder.rb
248
+ - lib/kiosk/server/assistant_accounts.rb
248
249
  - lib/kiosk/server/assistant_key.rb
249
250
  - lib/kiosk/server/audit_sink.rb
250
251
  - lib/kiosk/server/auth_toll.rb
@@ -271,6 +272,7 @@ files:
271
272
  - lib/kiosk/server/events.rb
272
273
  - lib/kiosk/server/events_cable.rb
273
274
  - lib/kiosk/server/events_connection.rb
275
+ - lib/kiosk/server/events_replay.rb
274
276
  - lib/kiosk/server/executor.rb
275
277
  - lib/kiosk/server/executor/repeat.rb
276
278
  - lib/kiosk/server/executor/spending_cap.rb
@@ -290,6 +292,7 @@ files:
290
292
  - lib/kiosk/server/link_code.rb
291
293
  - lib/kiosk/server/linked_assistants.rb
292
294
  - lib/kiosk/server/mandate_verifier.rb
295
+ - lib/kiosk/server/money.rb
293
296
  - lib/kiosk/server/oauth_answers.rb
294
297
  - lib/kiosk/server/oauth_error.rb
295
298
  - lib/kiosk/server/open_api.rb
@@ -307,7 +310,6 @@ files:
307
310
  - lib/kiosk/server/result.rb
308
311
  - lib/kiosk/server/revocation_stores.rb
309
312
  - lib/kiosk/server/routing.rb
310
- - lib/kiosk/server/schema_definitions.rb
311
313
  - lib/kiosk/server/schema_document.rb
312
314
  - lib/kiosk/server/schema_slots.rb
313
315
  - lib/kiosk/server/schemas/auth.schema.json
@@ -317,15 +319,14 @@ files:
317
319
  - lib/kiosk/server/schemas/pow.schema.json
318
320
  - lib/kiosk/server/sd_jwt.rb
319
321
  - lib/kiosk/server/session_context.rb
320
- - lib/kiosk/server/settled_spend.rb
321
322
  - lib/kiosk/server/signing_key.rb
323
+ - lib/kiosk/server/tolled_calls.rb
322
324
  - lib/kiosk/server/verb_registry.rb
323
325
  - lib/kiosk/server/verb_vocabulary.rb
324
326
  - lib/kiosk/server/verb_wire.rb
325
327
  - lib/kiosk/server/version.rb
326
328
  - lib/kiosk/server/well_known.rb
327
329
  - lib/kiosk/server/wire_rendering.rb
328
- - lib/kiosk/server/wrong_door.rb
329
330
  - lib/kiosk/skill_link.rb
330
331
  - listen.py
331
332
  - requirements.txt
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Migration 001 — Kiosk schema + the four current_*() helper functions.
4
- #
5
- # `ActiveRecord::Migration[…]` resolves at host-app load time, so this
6
- # file picks up whatever Rails version the host is on.
7
- class CreateKioskSchema < ActiveRecord::Migration[ActiveRecord::Migration.current_version]
8
- def up
9
- execute <<~'SQL'
10
- <%= ddl(:helper_functions, guc_namespace: options[:guc_namespace], user_id_type: user_id_type) %>
11
- SQL
12
- end
13
-
14
- def down
15
- execute %(DROP SCHEMA IF EXISTS "<%= options[:schema] %>" CASCADE)
16
- end
17
- end
@@ -1,320 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kiosk
4
- module Server
5
- # The SQL of the migrations `kiosk:install` emits, one method per
6
- # migration, each creating its tables in their final shape. No database
7
- # connection; the host's migrations `execute` it.
8
- # rubocop:disable-next Metrics/ModuleLength, Metrics/MethodLength -- each method returns one migration's DDL as one SQL heredoc
9
- module SchemaDefinitions
10
- module_function
11
-
12
- # 001: the schema and the `current_*()` GUC readers.
13
- def helper_functions_sql(schema: nil, guc_namespace: nil, user_id_type: nil)
14
- schema ||= Kiosk.configuration.schema
15
- guc_namespace ||= Kiosk.configuration.guc_namespace
16
- user_id_type ||= Kiosk.configuration.user_id_type
17
- cast = user_id_cast(user_id_type)
18
-
19
- <<~DDL.strip
20
- CREATE SCHEMA IF NOT EXISTS "#{schema}";
21
-
22
- CREATE OR REPLACE FUNCTION "#{schema}".current_user_id() RETURNS #{cast} LANGUAGE sql STABLE AS $$
23
- SELECT NULLIF(current_setting('#{guc_namespace}.current_user_id', true), '')::#{cast}
24
- $$;
25
-
26
- CREATE OR REPLACE FUNCTION "#{schema}".current_role() RETURNS text LANGUAGE sql STABLE AS $$
27
- SELECT NULLIF(current_setting('#{guc_namespace}.current_role', true), '')
28
- $$;
29
-
30
- CREATE OR REPLACE FUNCTION "#{schema}".current_actor() RETURNS text LANGUAGE sql STABLE AS $$
31
- SELECT NULLIF(current_setting('#{guc_namespace}.current_actor', true), '')
32
- $$;
33
-
34
- CREATE OR REPLACE FUNCTION "#{schema}".current_agent_id() RETURNS uuid LANGUAGE sql STABLE AS $$
35
- SELECT NULLIF(current_setting('#{guc_namespace}.current_agent_id', true), '')::uuid
36
- $$;
37
- DDL
38
- end
39
-
40
- # 002: `agents` (`spending_cap_cents`: NULL unlimited, 0 disabled; a cap
41
- # has its `spending_cap_currency`), `agent_tokens`, `agent_mappings`.
42
- def identity_tables_sql(schema: nil, user_id_type: nil, user_table: 'users')
43
- schema ||= Kiosk.configuration.schema
44
- user_id_type ||= Kiosk.configuration.user_id_type
45
- col_type = user_id_cast(user_id_type)
46
-
47
- <<~DDL.strip
48
- CREATE TABLE IF NOT EXISTS "#{schema}".agents (
49
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
50
- user_id #{col_type} NOT NULL REFERENCES "#{user_table}"(id) ON DELETE CASCADE,
51
- allowed_roles text[] NOT NULL DEFAULT '{}'::text[],
52
- public_key text,
53
- human_label text,
54
- spending_cap_cents bigint,
55
- spending_cap_currency text CHECK (spending_cap_currency ~ '^[a-z]{3}$'),
56
- created_at timestamptz NOT NULL DEFAULT now(),
57
- revoked_at timestamptz,
58
- issuer text NOT NULL,
59
- CONSTRAINT agents_spending_cap_currency_set
60
- CHECK ((spending_cap_cents IS NULL) = (spending_cap_currency IS NULL))
61
- );
62
-
63
- CREATE INDEX IF NOT EXISTS idx_agents_user_id ON "#{schema}".agents (user_id) WHERE revoked_at IS NULL;
64
- -- Dedupe at the DB, not via SELECT-then-INSERT (TOCTOU): two LIVE
65
- -- rows for one public key on one origin cannot coexist. Partial
66
- -- (WHERE revoked_at IS NULL) so a revoked key can re-register.
67
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_issuer_public_key_live
68
- ON "#{schema}".agents (issuer, public_key) WHERE revoked_at IS NULL;
69
-
70
- CREATE TABLE IF NOT EXISTS "#{schema}".agent_tokens (
71
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
72
- agent_id uuid NOT NULL REFERENCES "#{schema}".agents(id) ON DELETE CASCADE,
73
- token_hash text NOT NULL,
74
- issued_at timestamptz NOT NULL DEFAULT now(),
75
- expires_at timestamptz NOT NULL,
76
- revoked_at timestamptz
77
- );
78
- CREATE INDEX IF NOT EXISTS idx_agent_tokens_agent_id ON "#{schema}".agent_tokens (agent_id);
79
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_tokens_hash ON "#{schema}".agent_tokens (token_hash);
80
-
81
- CREATE TABLE IF NOT EXISTS "#{schema}".agent_mappings (
82
- provider text NOT NULL,
83
- external_id text NOT NULL,
84
- agent_id uuid NOT NULL REFERENCES "#{schema}".agents(id) ON DELETE CASCADE,
85
- PRIMARY KEY (provider, external_id)
86
- );
87
- DDL
88
- end
89
-
90
- # 004: account-binding requests; codes are stored hashed only.
91
- # `requested_role` is the approving human's role, never a client's.
92
- def device_authorizations_sql(schema: nil, user_id_type: nil)
93
- schema ||= Kiosk.configuration.schema
94
- user_id_type ||= Kiosk.configuration.user_id_type
95
- col_type = user_id_cast(user_id_type)
96
-
97
- <<~DDL.strip
98
- CREATE TABLE IF NOT EXISTS "#{schema}".device_authorizations (
99
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
100
- device_code_hash text NOT NULL,
101
- user_code_hash text NOT NULL,
102
- public_key_pem text,
103
- kind text NOT NULL DEFAULT 'claim',
104
- client_id text NOT NULL,
105
- requested_role text,
106
- status text NOT NULL,
107
- user_id #{col_type},
108
- expires_at timestamptz NOT NULL,
109
- consumed_at timestamptz,
110
- created_at timestamptz NOT NULL DEFAULT now(),
111
- CONSTRAINT device_authorizations_status_check
112
- CHECK (status IN ('pending', 'approved', 'denied', 'consumed', 'expired')),
113
- CONSTRAINT device_authorizations_kind_check
114
- CHECK (kind IN ('claim', 'link'))
115
- );
116
- CREATE UNIQUE INDEX IF NOT EXISTS idx_device_authorizations_code_hash
117
- ON "#{schema}".device_authorizations (device_code_hash);
118
- -- Only `pending` rows need a unique user_code; approved/consumed
119
- -- rows may share codes from past flows without collision.
120
- CREATE UNIQUE INDEX IF NOT EXISTS idx_device_authorizations_user_code_pending
121
- ON "#{schema}".device_authorizations (user_code_hash)
122
- WHERE status = 'pending';
123
- CREATE INDEX IF NOT EXISTS idx_device_authorizations_expiry
124
- ON "#{schema}".device_authorizations (expires_at)
125
- WHERE status IN ('pending', 'approved');
126
- DDL
127
- end
128
-
129
- # 005: the AP2 trail. `id` is server-generated; `reference` is the hash of
130
- # the presented SD-JWT, unique per principal. One checkout holds one live
131
- # checkout mandate: `declined_at` records a charge refused with no money
132
- # moved, and frees the checkout. A settlement is server-minted.
133
- def mandates_sql(schema: nil, user_id_type: nil)
134
- schema ||= Kiosk.configuration.schema
135
- user_id_type ||= Kiosk.configuration.user_id_type
136
- col_type = user_id_cast(user_id_type)
137
-
138
- <<~DDL.strip
139
- CREATE TABLE IF NOT EXISTS "#{schema}".checkout_mandates (
140
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
141
- reference text NOT NULL,
142
- checkout_hash text NOT NULL,
143
- user_id #{col_type} NOT NULL,
144
- agent_id uuid NOT NULL,
145
- issuer text NOT NULL,
146
- line_items jsonb NOT NULL,
147
- total_amount_cents bigint NOT NULL,
148
- currency text NOT NULL,
149
- expires_at timestamptz NOT NULL,
150
- created_at timestamptz NOT NULL DEFAULT now(),
151
- sd_jwt text NOT NULL,
152
- declined_at timestamptz,
153
- UNIQUE (user_id, reference)
154
- );
155
- CREATE INDEX IF NOT EXISTS idx_checkout_mandates_user_id ON "#{schema}".checkout_mandates (user_id);
156
- CREATE UNIQUE INDEX IF NOT EXISTS idx_checkout_mandates_live_checkout
157
- ON "#{schema}".checkout_mandates (user_id, checkout_hash) WHERE declined_at IS NULL;
158
-
159
- CREATE TABLE IF NOT EXISTS "#{schema}".payment_mandates (
160
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
161
- reference text NOT NULL,
162
- checkout_mandate_id uuid NOT NULL REFERENCES "#{schema}".checkout_mandates(id) ON DELETE CASCADE,
163
- user_id #{col_type} NOT NULL,
164
- agent_id uuid NOT NULL,
165
- issuer text NOT NULL,
166
- payment_instrument text NOT NULL,
167
- amount_cents bigint NOT NULL,
168
- currency text NOT NULL,
169
- expires_at timestamptz,
170
- created_at timestamptz NOT NULL DEFAULT now(),
171
- sd_jwt text NOT NULL,
172
- UNIQUE (user_id, reference)
173
- );
174
- CREATE INDEX IF NOT EXISTS idx_payment_mandates_user_id ON "#{schema}".payment_mandates (user_id);
175
- CREATE INDEX IF NOT EXISTS idx_payment_mandates_checkout ON "#{schema}".payment_mandates (checkout_mandate_id);
176
-
177
- CREATE TABLE IF NOT EXISTS "#{schema}".settlements (
178
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
179
- checkout_mandate_id uuid NOT NULL REFERENCES "#{schema}".checkout_mandates(id) ON DELETE CASCADE,
180
- user_id #{col_type} NOT NULL,
181
- agent_id uuid NOT NULL,
182
- issuer text NOT NULL,
183
- psp_reference text NOT NULL,
184
- settled_amount_cents bigint NOT NULL,
185
- currency text NOT NULL,
186
- network_confirmation_id text,
187
- settled_at timestamptz NOT NULL,
188
- UNIQUE (checkout_mandate_id)
189
- );
190
- CREATE INDEX IF NOT EXISTS idx_settlements_user_id ON "#{schema}".settlements (user_id);
191
- CREATE INDEX IF NOT EXISTS idx_settlements_checkout ON "#{schema}".settlements (checkout_mandate_id);
192
- DDL
193
- end
194
-
195
- # 006: KYC grants and open verifications, keyed on the person.
196
- def kyc_attributes_sql(schema: nil, user_id_type: nil, user_table: nil)
197
- schema ||= Kiosk.configuration.schema
198
- user_id_type ||= Kiosk.configuration.user_id_type
199
- user_table ||= configured_user_table
200
- col_type = user_id_cast(user_id_type)
201
- person = %(#{col_type} NOT NULL REFERENCES "#{user_table}"(id) ON DELETE CASCADE)
202
-
203
- <<~DDL.strip
204
- CREATE TABLE IF NOT EXISTS "#{schema}".kyc_attributes (
205
- user_id #{person},
206
- name text NOT NULL,
207
- granted_at timestamptz NOT NULL DEFAULT now(),
208
- PRIMARY KEY (user_id, name)
209
- );
210
-
211
- CREATE TABLE IF NOT EXISTS "#{schema}".kyc_requests (
212
- id text PRIMARY KEY,
213
- user_id #{person},
214
- nonce text NOT NULL,
215
- approved_at timestamptz,
216
- created_at timestamptz NOT NULL DEFAULT now()
217
- );
218
- CREATE INDEX IF NOT EXISTS idx_kyc_requests_user_id ON "#{schema}".kyc_requests (user_id, created_at);
219
- DDL
220
- end
221
-
222
- # 007: the event tail; `id` is the origin's one monotonic cursor.
223
- def events_sql(schema: nil)
224
- schema ||= Kiosk.configuration.schema
225
-
226
- <<~DDL.strip
227
- CREATE TABLE IF NOT EXISTS "#{schema}".events (
228
- id bigserial PRIMARY KEY,
229
- identity_key text NOT NULL,
230
- topic text NOT NULL,
231
- subject text,
232
- occurred_at timestamptz NOT NULL,
233
- data jsonb NOT NULL,
234
- created_at timestamptz NOT NULL DEFAULT now()
235
- );
236
- -- `since` for one subscriber: the cursor scan, in id order.
237
- CREATE INDEX IF NOT EXISTS idx_events_identity_key_id
238
- ON "#{schema}".events (identity_key, id);
239
- -- The retention sweep only.
240
- CREATE INDEX IF NOT EXISTS idx_events_created_at
241
- ON "#{schema}".events (created_at);
242
- DDL
243
- end
244
-
245
- # 008: spent ids of proof-of-work challenges and client assertions; the primary key is the single-use gate.
246
- def pow_spent_sql(schema: nil)
247
- schema ||= Kiosk.configuration.schema
248
-
249
- <<~DDL.strip
250
- CREATE TABLE IF NOT EXISTS "#{schema}".pow_spent (
251
- id text PRIMARY KEY,
252
- expires_at timestamptz NOT NULL
253
- );
254
- -- Supports the TTL sweep only; the PK above is what enforces
255
- -- single-use.
256
- CREATE INDEX IF NOT EXISTS idx_pow_spent_expires_at
257
- ON "#{schema}".pow_spent (expires_at);
258
- DDL
259
- end
260
-
261
- # 009: each agent's revocation watermark: a token issued before it stops verifying.
262
- def revocations_sql(schema: nil)
263
- schema ||= Kiosk.configuration.schema
264
-
265
- <<~DDL.strip
266
- CREATE TABLE IF NOT EXISTS "#{schema}".revocations (
267
- agent_id uuid PRIMARY KEY REFERENCES "#{schema}".agents(id) ON DELETE CASCADE,
268
- revoked_before timestamptz NOT NULL
269
- );
270
- DDL
271
- end
272
-
273
- # 011: the visits an operator records as fulfilled; reputation counts their value.
274
- def visits_sql(schema: nil, user_id_type: nil)
275
- schema ||= Kiosk.configuration.schema
276
- col_type = user_id_cast(user_id_type || Kiosk.configuration.user_id_type)
277
-
278
- <<~DDL.strip
279
- CREATE TABLE IF NOT EXISTS "#{schema}".visits (
280
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
281
- user_id #{col_type} NOT NULL,
282
- value_cents bigint NOT NULL CHECK (value_cents >= 0),
283
- visited_at timestamptz NOT NULL DEFAULT now()
284
- );
285
- CREATE INDEX IF NOT EXISTS idx_visits_user_id ON "#{schema}".visits (user_id, visited_at);
286
- DDL
287
- end
288
-
289
- # 012: the refunds of settled charges, one row per PSP refund; recent
290
- # spend counts a settlement less what was refunded of it.
291
- def refunds_sql(schema: nil)
292
- schema ||= Kiosk.configuration.schema
293
-
294
- <<~DDL.strip
295
- CREATE TABLE IF NOT EXISTS "#{schema}".refunds (
296
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
297
- settlement_id uuid NOT NULL REFERENCES "#{schema}".settlements(id) ON DELETE CASCADE,
298
- psp_reference text NOT NULL UNIQUE,
299
- refunded_amount_cents bigint NOT NULL CHECK (refunded_amount_cents > 0),
300
- refunded_at timestamptz NOT NULL
301
- );
302
- CREATE INDEX IF NOT EXISTS idx_refunds_settlement_id ON "#{schema}".refunds (settlement_id);
303
- DDL
304
- end
305
-
306
- def configured_user_table = Kiosk.configuration.user_model.to_s.constantize.table_name
307
-
308
- def user_id_cast(user_id_type)
309
- case user_id_type.to_sym
310
- when :uuid then 'uuid'
311
- when :bigint, :integer then user_id_type.to_s
312
- when :text then 'text'
313
- else
314
- raise ArgumentError,
315
- "user_id_type must be one of :uuid, :bigint, :integer, :text — got #{user_id_type.inspect}"
316
- end
317
- end
318
- end
319
- end
320
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kiosk
4
- module Server
5
- # What an assistant spent: each settlement less what was refunded of it,
6
- # never below zero, one tally per currency. The «Link an assistant» page
7
- # shows this figure. The spending cap counts it plus the carts in flight.
8
- module SettledSpend
9
- module_function
10
-
11
- # The net cents of the current `settlements` row.
12
- def net_cents_sql(schema)
13
- <<~SQL.squish
14
- GREATEST(settlements.settled_amount_cents - (
15
- SELECT COALESCE(SUM(r.refunded_amount_cents), 0) FROM #{schema}.refunds r WHERE r.settlement_id = settlements.id
16
- ), 0)
17
- SQL
18
- end
19
- end
20
- end
21
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
-
5
- module Kiosk
6
- module Server
7
- # The account holder's browser pages. A JSON caller without a CSRF token is
8
- # an assistant at the wrong door: the answer points it at the wire. Browser
9
- # requests re-raise untouched. Each page's controller sets WRONG_DOOR_MESSAGE.
10
- module WrongDoor
11
- extend ActiveSupport::Concern
12
-
13
- included do
14
- rescue_from ::ActionController::InvalidAuthenticityToken do |error|
15
- raise error unless json_request?
16
-
17
- render json: wrong_door_envelope, status: :unprocessable_entity
18
- end
19
- end
20
-
21
- private
22
-
23
- # An explicit JSON Accept or a JSON body; anything ambiguous counts as a browser.
24
- def json_request?
25
- return true if request.format.json?
26
-
27
- !!request.content_mime_type&.json?
28
- rescue StandardError
29
- false
30
- end
31
-
32
- # Not a wire problem document or wire error code: this page is not the wire.
33
- def wrong_door_envelope
34
- hint = "assistants use the wire: GET #{request.base_url}/.well-known/kiosk.json " \
35
- 'for the register/login endpoints, then GET <endpoint>/schema ' \
36
- '(public) for the verbs this origin serves'
37
- { ok: false,
38
- error: { code: 'invalid_authenticity_token', message: self.class::WRONG_DOOR_MESSAGE, hint: hint } }
39
- end
40
- end
41
- end
42
- end