kiosk-server 0.5.20

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 +7 -0
  2. data/CHANGELOG.md +549 -0
  3. data/LICENSE.txt +202 -0
  4. data/README.md +635 -0
  5. data/app/channels/kiosk_events.rb +138 -0
  6. data/app/controllers/kiosk/server/application_controller.rb +17 -0
  7. data/app/controllers/kiosk/server/assistants_controller.rb +96 -0
  8. data/app/controllers/kiosk/server/auth_controller.rb +187 -0
  9. data/app/controllers/kiosk/server/device_verify_controller.rb +101 -0
  10. data/app/controllers/kiosk/server/discovery_controller.rb +66 -0
  11. data/app/controllers/kiosk/server/jwks_controller.rb +15 -0
  12. data/app/controllers/kiosk/server/kyc_attestation_controller.rb +69 -0
  13. data/app/controllers/kiosk/server/kyc_callback_controller.rb +15 -0
  14. data/app/controllers/kiosk/server/oauth_device_authorization_controller.rb +73 -0
  15. data/app/controllers/kiosk/server/oauth_token_controller.rb +59 -0
  16. data/app/controllers/kiosk/server/open_api_controller.rb +20 -0
  17. data/app/controllers/kiosk/server/payment_setup_controller.rb +31 -0
  18. data/app/controllers/kiosk/server/verb_controller.rb +61 -0
  19. data/app/controllers/kiosk/server/wire_controller.rb +43 -0
  20. data/app/models/kiosk/cart_mandate.rb +21 -0
  21. data/app/models/kiosk/server/application_record.rb +10 -0
  22. data/app/models/kiosk/settlement.rb +12 -0
  23. data/app/models/kiosk/visit.rb +13 -0
  24. data/app/views/kiosk/server/assistants/show.html.erb +94 -0
  25. data/app/views/kiosk/server/device_verify/decided.html.erb +14 -0
  26. data/app/views/kiosk/server/device_verify/show.html.erb +53 -0
  27. data/lib/generators/kiosk/install/install_generator.rb +125 -0
  28. data/lib/generators/kiosk/install/templates/create_kiosk_auth_challenges.rb.tt +16 -0
  29. data/lib/generators/kiosk/install/templates/create_kiosk_device_authorizations.rb.tt +17 -0
  30. data/lib/generators/kiosk/install/templates/create_kiosk_events.rb.tt +22 -0
  31. data/lib/generators/kiosk/install/templates/create_kiosk_identity_tables.rb.tt +16 -0
  32. data/lib/generators/kiosk/install/templates/create_kiosk_kyc_attributes.rb.tt +20 -0
  33. data/lib/generators/kiosk/install/templates/create_kiosk_mandates.rb.tt +21 -0
  34. data/lib/generators/kiosk/install/templates/create_kiosk_pow_spent.rb.tt +16 -0
  35. data/lib/generators/kiosk/install/templates/create_kiosk_refunds.rb.tt +16 -0
  36. data/lib/generators/kiosk/install/templates/create_kiosk_revocations.rb.tt +16 -0
  37. data/lib/generators/kiosk/install/templates/create_kiosk_schema.rb.tt +17 -0
  38. data/lib/generators/kiosk/install/templates/create_kiosk_visits.rb.tt +16 -0
  39. data/lib/generators/kiosk/install/templates/initializer.rb.tt +53 -0
  40. data/lib/kiosk/handler.rb +43 -0
  41. data/lib/kiosk/owned.rb +17 -0
  42. data/lib/kiosk/payable.rb +90 -0
  43. data/lib/kiosk/server/account_binding.rb +188 -0
  44. data/lib/kiosk/server/account_holder_gate.rb +58 -0
  45. data/lib/kiosk/server/action_event.rb +60 -0
  46. data/lib/kiosk/server/actions.rb +14 -0
  47. data/lib/kiosk/server/agent_identity_providers/default_agent_idp.rb +89 -0
  48. data/lib/kiosk/server/agent_login.rb +49 -0
  49. data/lib/kiosk/server/agent_registration.rb +88 -0
  50. data/lib/kiosk/server/argument_decoder.rb +260 -0
  51. data/lib/kiosk/server/audit_sink.rb +42 -0
  52. data/lib/kiosk/server/auth_challenge.rb +33 -0
  53. data/lib/kiosk/server/auth_challenge_stores.rb +74 -0
  54. data/lib/kiosk/server/auth_toll.rb +44 -0
  55. data/lib/kiosk/server/binding_module_gate.rb +25 -0
  56. data/lib/kiosk/server/caller_timezone.rb +50 -0
  57. data/lib/kiosk/server/column_spending_cap.rb +26 -0
  58. data/lib/kiosk/server/configuration_extension.rb +328 -0
  59. data/lib/kiosk/server/controller_verb.rb +63 -0
  60. data/lib/kiosk/server/current_request.rb +31 -0
  61. data/lib/kiosk/server/cursor.rb +33 -0
  62. data/lib/kiosk/server/device_authorization.rb +136 -0
  63. data/lib/kiosk/server/device_authorization_stores.rb +184 -0
  64. data/lib/kiosk/server/device_code_grant.rb +145 -0
  65. data/lib/kiosk/server/device_verification.rb +46 -0
  66. data/lib/kiosk/server/engine.rb +203 -0
  67. data/lib/kiosk/server/errors.rb +339 -0
  68. data/lib/kiosk/server/event_stores.rb +124 -0
  69. data/lib/kiosk/server/events.rb +86 -0
  70. data/lib/kiosk/server/events_cable.rb +102 -0
  71. data/lib/kiosk/server/events_connection.rb +170 -0
  72. data/lib/kiosk/server/executor/repeat.rb +42 -0
  73. data/lib/kiosk/server/executor/spending_cap.rb +102 -0
  74. data/lib/kiosk/server/executor.rb +397 -0
  75. data/lib/kiosk/server/failure_log.rb +28 -0
  76. data/lib/kiosk/server/handler_mixin.rb +418 -0
  77. data/lib/kiosk/server/handler_registrations.rb +57 -0
  78. data/lib/kiosk/server/handler_serving.rb +197 -0
  79. data/lib/kiosk/server/headers.rb +82 -0
  80. data/lib/kiosk/server/headers_middleware.rb +31 -0
  81. data/lib/kiosk/server/identity_resolution.rb +20 -0
  82. data/lib/kiosk/server/issuer_middleware.rb +22 -0
  83. data/lib/kiosk/server/jwks.rb +14 -0
  84. data/lib/kiosk/server/jwt_issuer.rb +98 -0
  85. data/lib/kiosk/server/kyc.rb +261 -0
  86. data/lib/kiosk/server/kyc_verifier.rb +106 -0
  87. data/lib/kiosk/server/link_code.rb +90 -0
  88. data/lib/kiosk/server/linked_assistants.rb +116 -0
  89. data/lib/kiosk/server/mandate_verifier.rb +221 -0
  90. data/lib/kiosk/server/open_api.rb +543 -0
  91. data/lib/kiosk/server/page.rb +19 -0
  92. data/lib/kiosk/server/payment_claim.rb +293 -0
  93. data/lib/kiosk/server/payment_setup.rb +102 -0
  94. data/lib/kiosk/server/pop_verifier.rb +87 -0
  95. data/lib/kiosk/server/pow_gate.rb +121 -0
  96. data/lib/kiosk/server/pow_spent_stores.rb +96 -0
  97. data/lib/kiosk/server/pow_toll.rb +136 -0
  98. data/lib/kiosk/server/queries.rb +14 -0
  99. data/lib/kiosk/server/reputation_factors.rb +38 -0
  100. data/lib/kiosk/server/request_validation.rb +171 -0
  101. data/lib/kiosk/server/response_validation.rb +45 -0
  102. data/lib/kiosk/server/result.rb +25 -0
  103. data/lib/kiosk/server/revocation_stores.rb +57 -0
  104. data/lib/kiosk/server/routing.rb +76 -0
  105. data/lib/kiosk/server/schema_definitions.rb +351 -0
  106. data/lib/kiosk/server/schema_document.rb +116 -0
  107. data/lib/kiosk/server/schema_slots.rb +119 -0
  108. data/lib/kiosk/server/schemas/auth.schema.json +135 -0
  109. data/lib/kiosk/server/schemas/binding.schema.json +145 -0
  110. data/lib/kiosk/server/schemas/kyc.schema.json +114 -0
  111. data/lib/kiosk/server/schemas/mandates.schema.json +75 -0
  112. data/lib/kiosk/server/schemas/pow.schema.json +94 -0
  113. data/lib/kiosk/server/session_context.rb +88 -0
  114. data/lib/kiosk/server/settled_spend.rb +21 -0
  115. data/lib/kiosk/server/signing_key.rb +82 -0
  116. data/lib/kiosk/server/verb_registry.rb +77 -0
  117. data/lib/kiosk/server/verb_vocabulary.rb +163 -0
  118. data/lib/kiosk/server/verb_wire.rb +85 -0
  119. data/lib/kiosk/server/version.rb +7 -0
  120. data/lib/kiosk/server/well_known.rb +416 -0
  121. data/lib/kiosk/server/wire_rendering.rb +116 -0
  122. data/lib/kiosk/server/wrong_door.rb +42 -0
  123. data/lib/kiosk/server.rb +42 -0
  124. data/lib/kiosk/skill_link.rb +16 -0
  125. data/listen.py +278 -0
  126. data/requirements.txt +1 -0
  127. metadata +349 -0
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'digest'
6
+ require 'json'
7
+
8
+ module Kiosk
9
+ module Server
10
+ # The RSA key that signs this deployment's JWTs, and its public JWK.
11
+ class SigningKey
12
+ MIN_KEY_BITS = 2048
13
+
14
+ ALGORITHM = 'RS256'
15
+
16
+ attr_reader :rsa
17
+
18
+ def initialize(rsa)
19
+ raise ArgumentError, "expected OpenSSL::PKey::RSA, got #{rsa.class}" unless rsa.is_a?(OpenSSL::PKey::RSA)
20
+ if rsa.n.num_bits < MIN_KEY_BITS
21
+ raise ArgumentError,
22
+ "RSA key is #{rsa.n.num_bits} bits; minimum is #{MIN_KEY_BITS}"
23
+ end
24
+ @rsa = rsa
25
+ end
26
+
27
+ def self.generate(bits: MIN_KEY_BITS)
28
+ new(OpenSSL::PKey::RSA.generate(bits))
29
+ end
30
+
31
+ # A public-only PEM verifies; a full keypair also signs.
32
+ def self.from_pem(pem)
33
+ new(OpenSSL::PKey::RSA.new(pem))
34
+ end
35
+
36
+ delegate :public_key, to: :rsa
37
+
38
+ delegate :private?, to: :rsa
39
+
40
+ def to_pem
41
+ raise 'cannot export PEM: signing key is public-only' unless private?
42
+
43
+ rsa.to_pem
44
+ end
45
+
46
+ # The RFC 7638 thumbprint.
47
+ def kid
48
+ @kid ||= compute_thumbprint
49
+ end
50
+
51
+ # RFC 7517 §4; never the private parameters.
52
+ def to_jwk
53
+ {
54
+ kty: 'RSA',
55
+ use: 'sig',
56
+ alg: ALGORITHM,
57
+ kid: kid,
58
+ n: b64url(rsa.n.to_s(2)),
59
+ e: b64url(rsa.e.to_s(2))
60
+ }
61
+ end
62
+
63
+ private
64
+
65
+ def compute_thumbprint
66
+ # RFC 7638 §3.2: the required members, in alphabetical order.
67
+ canonical = JSON.generate(
68
+ {
69
+ e: b64url(rsa.e.to_s(2)),
70
+ kty: 'RSA',
71
+ n: b64url(rsa.n.to_s(2))
72
+ }
73
+ )
74
+ b64url(Digest::SHA256.digest(canonical))
75
+ end
76
+
77
+ def b64url(bytes)
78
+ Base64.urlsafe_encode64(bytes, padding: false)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kiosk
4
+ module Server
5
+ # A verb registry: name → handler plus the descriptor an origin publishes.
6
+ # {Queries} and {Actions} are this module plus a `SCOPE`. Verbs are declared
7
+ # through {Kiosk::Handler}; `input_schema` and `output_schema` are required (§8.3).
8
+ module VerbRegistry
9
+ Entry = Data.define(:handler, :reach, :description, :input_schema, :output_schema,
10
+ :example_params, :example_row)
11
+
12
+ class << self
13
+ # Undeclared optional keys are omitted rather than null.
14
+ def add_declared_slots(descriptor, found)
15
+ descriptor[:input_schema] = found.input_schema unless found.input_schema.nil?
16
+ descriptor[:output_schema] = found.output_schema unless found.output_schema.nil?
17
+ descriptor[:example_params] = found.example_params unless found.example_params.nil?
18
+ descriptor[:example_row] = found.example_row unless found.example_row.nil?
19
+ descriptor
20
+ end
21
+ end
22
+
23
+ # @api private — the {HandlerMixin} is the only caller.
24
+ # `slots`: any of {SchemaSlots::RESOLVABLE_SLOTS}; an undeclared one is nil.
25
+ def declare(name, handler, reach: HandlerMixin::DEFAULT_REACH, description: nil, **slots)
26
+ slots = SchemaSlots::RESOLVABLE_SLOTS.index_with(nil).merge(slots)
27
+ entry = Entry.new(handler: handler, reach: reach, description: description, **slots)
28
+ SchemaSlots.note_declaration(slots)
29
+ registry[name.to_s] = entry
30
+ end
31
+
32
+ def fetch(name)
33
+ entry(name).handler
34
+ end
35
+
36
+ def describe(name)
37
+ found = entry(name)
38
+ SchemaSlots.descriptor(self::SCOPE, name, found) do
39
+ descriptor = { name: name.to_s, description: found.description, reach: found.reach.to_s }
40
+ VerbRegistry.add_declared_slots(descriptor, found)
41
+ end
42
+ end
43
+
44
+ def catalog
45
+ registry.keys.sort.map { |name| describe(name) }
46
+ end
47
+
48
+ def known
49
+ registry.keys
50
+ end
51
+
52
+ def unregister(name)
53
+ registry.delete(name.to_s)
54
+ end
55
+
56
+ def reset!
57
+ @registry = nil
58
+ end
59
+
60
+ private
61
+
62
+ # The 404's hint lists the registered names, already public via `/schema`.
63
+ def entry(name)
64
+ registry.fetch(name.to_s) do
65
+ raise Errors::VerbNotFound.new(
66
+ "Unknown #{self::SCOPE}: #{name.inspect}",
67
+ hint: Errors.unknown_name_hint(name, self::SCOPE.to_s, registry.keys.sort)
68
+ )
69
+ end
70
+ end
71
+
72
+ def registry
73
+ @registry ||= {}
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kiosk
4
+ module Server
5
+ # A policy hook is asked about writes as `:run` (§13), never `:action`, and
6
+ # a branch on `:action` would silently toll nothing. Refuses one at load by
7
+ # parsing the hook's source; returns `:unreadable` when it cannot.
8
+ module VerbVocabulary
9
+ WRITE_KIND = :run
10
+
11
+ DECLARATION_ALIAS = :action
12
+
13
+ COMPARISONS = %i[== != eql? equal?].freeze
14
+
15
+ def self.assert!(target, method_name, label)
16
+ location = HookSource.location_for(target, method_name)
17
+ return :unreadable if location.nil?
18
+
19
+ node = HookSource.node_at(*location)
20
+ return :unreadable if node.nil?
21
+
22
+ return :clean unless alias_branch?(node)
23
+
24
+ raise Errors::ConfigurationError, alias_branch_message(label, *location)
25
+ end
26
+
27
+ def self.alias_branch_message(label, file, line)
28
+ "Kiosk::Server: #{label} branches on :#{DECLARATION_ALIAS}, which this hook never " \
29
+ "receives (#{file}:#{line}). The gate hands it one of " \
30
+ "Kiosk::Server::Executor::VERBS — #{Executor::VERBS.map(&:inspect).join(', ')} — in which a " \
31
+ "handler declared `kind :#{DECLARATION_ALIAS}` arrives as :#{WRITE_KIND}. Branch on " \
32
+ ":#{WRITE_KIND}. (Protocol Section 13, Reputation.) A branch on :#{DECLARATION_ALIAS} " \
33
+ 'would match nothing and silently decline to toll every write.'
34
+ end
35
+ private_class_method :alias_branch_message
36
+
37
+ def self.alias_branch?(node)
38
+ found = false
39
+ HookSource.walk(node) do |candidate|
40
+ next if found
41
+
42
+ found = true if comparison_against_alias?(candidate) ||
43
+ when_clause_against_alias?(candidate) ||
44
+ inclusion_against_alias?(candidate)
45
+ end
46
+ found
47
+ end
48
+ private_class_method :alias_branch?
49
+
50
+ def self.comparison_against_alias?(node)
51
+ return false unless node.is_a?(Prism::CallNode)
52
+ return false unless COMPARISONS.include?(node.name)
53
+
54
+ operands = [node.receiver, *(node.arguments&.arguments || [])]
55
+ operands.any? { |operand| alias_literal?(operand) }
56
+ end
57
+ private_class_method :comparison_against_alias?
58
+
59
+ def self.when_clause_against_alias?(node)
60
+ return false unless node.is_a?(Prism::WhenNode)
61
+
62
+ node.conditions.any? { |condition| alias_literal?(condition) }
63
+ end
64
+ private_class_method :when_clause_against_alias?
65
+
66
+ def self.inclusion_against_alias?(node)
67
+ return false unless node.is_a?(Prism::CallNode)
68
+ return false unless %i[include? member? exclude?].include?(node.name)
69
+
70
+ inclusion_candidates(node).any? { |candidate| alias_literal?(candidate) }
71
+ end
72
+ private_class_method :inclusion_against_alias?
73
+
74
+ # The receiver's array elements, then each argument and its array elements.
75
+ def self.inclusion_candidates(node)
76
+ candidates = node.receiver.is_a?(Prism::ArrayNode) ? node.receiver.elements.dup : []
77
+ (node.arguments&.arguments || []).each do |argument|
78
+ candidates.concat(argument.elements) if argument.is_a?(Prism::ArrayNode)
79
+ candidates << argument
80
+ end
81
+ candidates
82
+ end
83
+ private_class_method :inclusion_candidates
84
+
85
+ def self.alias_literal?(node)
86
+ case node
87
+ when Prism::SymbolNode, Prism::StringNode
88
+ node.unescaped == DECLARATION_ALIAS.to_s
89
+ else
90
+ false
91
+ end
92
+ end
93
+ private_class_method :alias_literal?
94
+
95
+ # Finds a hook's source and parses it; every answer is nil when it cannot.
96
+ module HookSource
97
+ module_function
98
+
99
+ def location_for(target, method_name)
100
+ callable = callable_for(target, method_name)
101
+ return nil unless callable.respond_to?(:source_location)
102
+
103
+ readable_location(callable.source_location)
104
+ end
105
+
106
+ def callable_for(target, method_name)
107
+ target.method(method_name) if target.respond_to?(method_name)
108
+ end
109
+
110
+ # `[file, line]` when the file can be read.
111
+ def readable_location(location)
112
+ return nil unless location.is_a?(Array) && location[0].is_a?(String) && location[1].is_a?(Integer)
113
+
114
+ location if File.readable?(location[0])
115
+ end
116
+
117
+ # The widest construct starting on the hook's source line.
118
+ def node_at(file, line)
119
+ tree = parse(file)
120
+ return nil if tree.nil?
121
+
122
+ best = nil
123
+ walk(tree) do |node|
124
+ next unless node.location.start_line == line
125
+ next if best && node.location.end_line <= best.location.end_line
126
+
127
+ best = node
128
+ end
129
+ best
130
+ end
131
+
132
+ def parse(file)
133
+ @parsed ||= {}
134
+ key = [file, mtime_of(file)]
135
+ return @parsed[key] if @parsed.key?(key)
136
+
137
+ @parsed[key] = parse_tree(file)
138
+ end
139
+
140
+ def mtime_of(file)
141
+ File.mtime(file)
142
+ rescue StandardError
143
+ nil
144
+ end
145
+
146
+ def parse_tree(file)
147
+ require 'prism'
148
+ result = Prism.parse_file(file)
149
+ result.success? ? result.value : nil
150
+ rescue LoadError, StandardError
151
+ nil
152
+ end
153
+
154
+ def walk(node, &block)
155
+ return if node.nil?
156
+
157
+ block.call(node)
158
+ node.compact_child_nodes.each { |child| walk(child, &block) }
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Kiosk
6
+ module Server
7
+ # The gates of a call on the wire, in order: identity (401), the arguments
8
+ # (400), the caller's timezone (400) and the toll (402). Then the call runs
9
+ # inside a session. {WireController} and every controller that includes
10
+ # {Kiosk::Handler} serve the wire with them.
11
+ module VerbWire
12
+ include WireRendering
13
+
14
+ BODY_HINT = "an action's arguments are a JSON object in the request body; " \
15
+ "a query's are in the query string."
16
+
17
+ private
18
+
19
+ def resolve_identity!
20
+ identity = IdentityResolution.resolve(request)
21
+ raise Errors::Unauthenticated, 'no identity resolved from request' if identity.nil?
22
+
23
+ identity
24
+ end
25
+
26
+ # Query string for a query, JSON body for an action; never both.
27
+ def arguments_for(command, name, descriptor)
28
+ args = if command == :query
29
+ ArgumentDecoder.decode(request.query_string, input_schema: descriptor[:input_schema])
30
+ else
31
+ parse_body!
32
+ end
33
+
34
+ # Unconditional: §8.1 item 5 makes coerce-then-validate a MUST.
35
+ RequestValidation.validate_arguments!(
36
+ args, input_schema: descriptor[:input_schema], verb: name, query: command == :query
37
+ )
38
+
39
+ args
40
+ end
41
+
42
+ def parse_body!
43
+ raw = request.raw_post
44
+ return {} if raw.to_s.empty?
45
+
46
+ parsed = JSON.parse(raw, symbolize_names: true)
47
+ raise Errors::BadRequest, 'request body must be a JSON object' unless parsed.is_a?(Hash)
48
+
49
+ parsed
50
+ rescue JSON::ParserError
51
+ raise Errors.malformed_json(hint: BODY_HINT)
52
+ end
53
+
54
+ # A {Result}. `command` is the policy verb ({Executor::VERBS}); `name` the wire name;
55
+ # `handler` answers in place of the registered one.
56
+ def call_wire(command:, args:, identity:, name:, handler: nil)
57
+ # Refused with the arguments, before the toll.
58
+ timezone = CallerTimezone.from_env(request.env)
59
+ toll!(identity: identity, command: command, name: name, body: args)
60
+
61
+ CurrentRequest.with(identity: identity, timezone: timezone) do
62
+ Executor.new(connection: connection_for(identity), identity: identity)
63
+ .call(kind: command, args: args, name: name, handler: handler)
64
+ end
65
+ end
66
+
67
+ def toll!(identity:, command:, name:, body:)
68
+ pow = PowGate.proofs_from_header(request.get_header('HTTP_KIOSK_POW'))
69
+
70
+ # A malformed proof is a 400, not a silent fresh 402.
71
+ RequestValidation.validate_proofs!(pow) if Kiosk.configuration.validate_requests && pow
72
+
73
+ PowGate.gate(
74
+ identity: identity, command: command, method: request.request_method,
75
+ verb: name, body: body, pow: pow
76
+ )
77
+ end
78
+
79
+ # A lease: the GUCs and `pay`'s three transactions need one connection per request.
80
+ def connection_for(_identity)
81
+ ::ActiveRecord::Base.lease_connection
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kiosk
4
+ module Server
5
+ VERSION = '0.5.20'
6
+ end
7
+ end