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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -0
- data/README.md +134 -102
- data/app/channels/kiosk_events.rb +30 -17
- data/app/controllers/kiosk/server/application_controller.rb +0 -1
- data/app/controllers/kiosk/server/assistants_controller.rb +13 -14
- data/app/controllers/kiosk/server/auth_controller.rb +3 -3
- data/app/controllers/kiosk/server/device_verify_controller.rb +0 -5
- data/app/controllers/kiosk/server/discovery_controller.rb +2 -7
- data/app/controllers/kiosk/server/kyc_callback_controller.rb +3 -1
- data/app/controllers/kiosk/server/oauth_device_authorization_controller.rb +8 -7
- data/app/controllers/kiosk/server/oauth_token_controller.rb +4 -1
- data/app/controllers/kiosk/server/payment_setup_controller.rb +3 -4
- data/app/controllers/kiosk/server/wire_controller.rb +2 -2
- data/app/models/kiosk/checkout_mandate.rb +0 -2
- data/app/models/kiosk/settlement.rb +0 -2
- data/app/models/kiosk/visit.rb +0 -2
- data/app/views/kiosk/server/assistants/show.html.erb +20 -24
- data/app/views/kiosk/server/device_verify/decided.html.erb +1 -0
- data/app/views/kiosk/server/device_verify/show.html.erb +1 -0
- data/app/views/layouts/kiosk/server/application.html.erb +14 -0
- data/lib/generators/kiosk/install/install_generator.rb +25 -64
- data/lib/generators/kiosk/install/templates/create_kiosk_device_authorizations.rb.tt +21 -12
- data/lib/generators/kiosk/install/templates/create_kiosk_events.rb.tt +15 -17
- data/lib/generators/kiosk/install/templates/create_kiosk_identity_tables.rb.tt +39 -10
- data/lib/generators/kiosk/install/templates/create_kiosk_kyc_attributes.rb.tt +16 -14
- data/lib/generators/kiosk/install/templates/create_kiosk_mandates.rb.tt +50 -14
- data/lib/generators/kiosk/install/templates/create_kiosk_pow_spent.rb.tt +7 -11
- data/lib/generators/kiosk/install/templates/create_kiosk_refunds.rb.tt +11 -11
- data/lib/generators/kiosk/install/templates/create_kiosk_revocations.rb.tt +7 -11
- data/lib/generators/kiosk/install/templates/create_kiosk_visits.rb.tt +10 -11
- data/lib/generators/kiosk/install/templates/initializer.rb.tt +13 -4
- data/lib/kiosk/payable.rb +1 -1
- data/lib/kiosk/server/account_binding.rb +14 -5
- data/lib/kiosk/server/account_holder_gate.rb +3 -1
- data/lib/kiosk/server/agent_identity_providers/default_agent_idp.rb +1 -1
- data/lib/kiosk/server/agent_rotation.rb +1 -1
- data/lib/kiosk/server/argument_decoder.rb +2 -2
- data/lib/kiosk/server/assistant_accounts.rb +38 -0
- data/lib/kiosk/server/auth_toll.rb +5 -5
- data/lib/kiosk/server/client.rb +1 -1
- data/lib/kiosk/server/client_assertion.rb +3 -2
- data/lib/kiosk/server/client_registration.rb +6 -2
- data/lib/kiosk/server/column_spending_cap.rb +1 -1
- data/lib/kiosk/server/configuration_extension.rb +15 -38
- data/lib/kiosk/server/current_request.rb +1 -1
- data/lib/kiosk/server/device_authorization.rb +1 -1
- data/lib/kiosk/server/device_authorization_stores.rb +1 -1
- data/lib/kiosk/server/device_code_grant.rb +2 -2
- data/lib/kiosk/server/dpop.rb +6 -1
- data/lib/kiosk/server/engine.rb +2 -2
- data/lib/kiosk/server/errors.rb +21 -7
- data/lib/kiosk/server/event_stores.rb +2 -2
- data/lib/kiosk/server/events.rb +4 -3
- data/lib/kiosk/server/events_connection.rb +23 -5
- data/lib/kiosk/server/events_replay.rb +79 -0
- data/lib/kiosk/server/executor/repeat.rb +1 -1
- data/lib/kiosk/server/executor/spending_cap.rb +8 -66
- data/lib/kiosk/server/executor.rb +10 -10
- data/lib/kiosk/server/failure_log.rb +3 -3
- data/lib/kiosk/server/handler_mixin.rb +9 -9
- data/lib/kiosk/server/handler_registrations.rb +1 -1
- data/lib/kiosk/server/handler_serving.rb +11 -11
- data/lib/kiosk/server/headers.rb +3 -3
- data/lib/kiosk/server/identity_resolution.rb +1 -1
- data/lib/kiosk/server/kyc.rb +5 -7
- data/lib/kiosk/server/kyc_verifier.rb +26 -27
- data/lib/kiosk/server/linked_assistants.rb +21 -16
- data/lib/kiosk/server/money.rb +41 -0
- data/lib/kiosk/server/oauth_answers.rb +1 -0
- data/lib/kiosk/server/oauth_error.rb +3 -0
- data/lib/kiosk/server/open_api.rb +9 -9
- data/lib/kiosk/server/page.rb +1 -1
- data/lib/kiosk/server/payment_claim.rb +3 -3
- data/lib/kiosk/server/pow_gate.rb +12 -33
- data/lib/kiosk/server/pow_spent_stores.rb +1 -1
- data/lib/kiosk/server/receipts.rb +4 -3
- data/lib/kiosk/server/reputation_factors.rb +12 -5
- data/lib/kiosk/server/request_validation.rb +2 -1
- data/lib/kiosk/server/result.rb +1 -1
- data/lib/kiosk/server/revocation_stores.rb +1 -1
- data/lib/kiosk/server/routing.rb +1 -1
- data/lib/kiosk/server/schemas/auth.schema.json +2 -2
- data/lib/kiosk/server/schemas/binding.schema.json +3 -3
- data/lib/kiosk/server/schemas/kyc.schema.json +22 -3
- data/lib/kiosk/server/schemas/mandates.schema.json +5 -5
- data/lib/kiosk/server/schemas/pow.schema.json +2 -2
- data/lib/kiosk/server/session_context.rb +2 -2
- data/lib/kiosk/server/tolled_calls.rb +46 -0
- data/lib/kiosk/server/verb_wire.rb +3 -3
- data/lib/kiosk/server/version.rb +1 -1
- data/lib/kiosk/server/well_known.rb +14 -11
- data/lib/kiosk/server/wire_rendering.rb +1 -1
- data/lib/kiosk/server.rb +6 -2
- metadata +9 -8
- data/lib/generators/kiosk/install/templates/create_kiosk_schema.rb.tt +0 -17
- data/lib/kiosk/server/schema_definitions.rb +0 -320
- data/lib/kiosk/server/settled_spend.rb +0 -21
- 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.
|
|
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
|
|
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
|
|
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
|
|
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
|