graphql-armory 0.1.0.pre → 0.1.0.pre3

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE +0 -0
  4. data/MIGRATING.md +1 -1
  5. data/README.md +26 -266
  6. data/docs/authorization.md +264 -0
  7. data/docs/encrypted-ids.md +136 -0
  8. data/lib/graphql/armory/authorization/adapters/cancan.rb +0 -0
  9. data/lib/graphql/armory/authorization/adapters/null.rb +0 -0
  10. data/lib/graphql/armory/authorization/adapters/pundit.rb +0 -0
  11. data/lib/graphql/armory/authorization/adapters/substitute.rb +0 -0
  12. data/lib/graphql/armory/authorization/audit/command.rb +0 -0
  13. data/lib/graphql/armory/authorization/audit/report.rb +0 -0
  14. data/lib/graphql/armory/authorization/audit.rb +53 -11
  15. data/lib/graphql/armory/authorization/authorize.rb +0 -0
  16. data/lib/graphql/armory/authorization/configuration.rb +0 -0
  17. data/lib/graphql/armory/authorization/controls/actor.rb +0 -0
  18. data/lib/graphql/armory/authorization/controls/context.rb +0 -0
  19. data/lib/graphql/armory/authorization/controls/rule.rb +0 -0
  20. data/lib/graphql/armory/authorization/controls/subject.rb +0 -0
  21. data/lib/graphql/armory/authorization/errors.rb +0 -0
  22. data/lib/graphql/armory/authorization/integration/argument.rb +0 -0
  23. data/lib/graphql/armory/authorization/integration/base.rb +3 -1
  24. data/lib/graphql/armory/authorization/integration/field.rb +1 -1
  25. data/lib/graphql/armory/authorization/integration/find_by.rb +0 -0
  26. data/lib/graphql/armory/authorization/integration/input_object.rb +31 -0
  27. data/lib/graphql/armory/authorization/integration/object.rb +0 -0
  28. data/lib/graphql/armory/authorization/integration/resolver.rb +1 -1
  29. data/lib/graphql/armory/authorization/integration/schema.rb +0 -0
  30. data/lib/graphql/armory/authorization/integration.rb +32 -0
  31. data/lib/graphql/armory/authorization/memo.rb +0 -0
  32. data/lib/graphql/armory/authorization/rspec.rb +0 -0
  33. data/lib/graphql/armory/authorization/rule.rb +0 -0
  34. data/lib/graphql/armory/authorization/scope.rb +0 -0
  35. data/lib/graphql/armory/authorization/telemetry.rb +0 -0
  36. data/lib/graphql/armory/authorization.rb +0 -0
  37. data/lib/graphql/armory/encoder/default.rb +59 -0
  38. data/lib/graphql/armory/encoder/legacy.rb +34 -0
  39. data/lib/graphql/armory/encoder/versioned.rb +67 -0
  40. data/lib/graphql/armory/encoder.rb +137 -0
  41. data/lib/graphql/armory/ids.rb +76 -0
  42. data/lib/graphql/armory/version.rb +1 -1
  43. data/lib/graphql/armory.rb +2 -0
  44. data/lib/graphql-armory.rb +0 -0
  45. data/lib/tasks/graphql_armory.rake +0 -0
  46. metadata +13 -9
@@ -6,6 +6,7 @@ require "graphql/armory/authorization/integration/find_by"
6
6
  require "graphql/armory/authorization/integration/field"
7
7
  require "graphql/armory/authorization/integration/argument"
8
8
  require "graphql/armory/authorization/integration/resolver"
9
+ require "graphql/armory/authorization/integration/input_object"
9
10
  require "graphql/armory/authorization/integration/base"
10
11
 
11
12
  module GraphQL
@@ -62,6 +63,37 @@ module GraphQL
62
63
  record if record && allowed?(record, rule, context)
63
64
  end
64
65
 
66
+ # Loads a `loads:` argument inside the user's scope. When the schema defines `object_from_id` (global
67
+ # IDs), it decodes the id; the record must be of the type's model, and is then looked up again
68
+ # through the scope, so a foreign id still looks like a missing one. Otherwise the id is the primary key.
69
+ def self.load(type, id, context)
70
+ schema = context.schema
71
+ return find(type, nil, id, context) unless decodes_ids?(schema) && type.respond_to?(:armory_model)
72
+
73
+ model = type.armory_model
74
+ record = decode(schema, id, context)
75
+ return nil unless record.is_a?(model)
76
+
77
+ find(type, nil, record.public_send(model.primary_key), context)
78
+ end
79
+
80
+ # `GlobalID.find`, graphql-ruby's documented `object_from_id`, raises for a missing record, and a
81
+ # decoder may raise for a malformed id (ArgumentError, or the Encoder's InvalidError). All must answer like a
82
+ # record out of scope: nil.
83
+ def self.decode(schema, id, context)
84
+ schema.object_from_id(id, context)
85
+ rescue StandardError => error
86
+ raise unless error.is_a?(ArgumentError) || error.is_a?(Encoder::InvalidError) || error.class.name == "ActiveRecord::RecordNotFound"
87
+
88
+ nil
89
+ end
90
+ private_class_method :decode
91
+
92
+ # graphql-ruby's default `object_from_id` only raises, so an override is the app decoding its IDs.
93
+ def self.decodes_ids?(schema)
94
+ schema.method(:object_from_id).owner != GraphQL::Schema.singleton_class
95
+ end
96
+
65
97
  # An interface field with no rule of its own cannot name the type it is resolved on, so it is only
66
98
  # covered when no implementer is a bypass: a field on a bypassed type is denied, and so is this one.
67
99
  def self.bypassed_implementer?(interface, schema)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Armory
5
+ class Encoder
6
+ ENV_KEY = "GRAPHQL_ARMORY_KEY"
7
+ RAILS_PURPOSE = "graphql-armory/ids"
8
+
9
+ # An encoder whose key comes from the app, never from the gem: `GRAPHQL_ARMORY_KEY`, or else a key derived
10
+ # with HKDF from Rails' secret_key_base for this purpose alone. Without either it raises, so the app fails
11
+ # when the schema loads instead of serving unencrypted IDs.
12
+ #
13
+ # It reads `secret_key_base` instead of `Rails.application.key_generator`: that generator is built once per
14
+ # process with whatever digest Rails has configured at that moment, which changes during boot, so the same
15
+ # secret could give different IDs in a web server and in a rake task (and calling it early would also fix
16
+ # Rails' own cookie keys to the wrong digest).
17
+ def self.default(env: ENV, rails: rails_application)
18
+ secret = env_key(env)
19
+ secret = rails_key(rails) if secret.nil? && rails
20
+ if secret.nil?
21
+ raise ConfigurationError,
22
+ "no key for the default encoder: set #{ENV_KEY} (try `ruby -rsecurerandom -e 'puts SecureRandom.hex(32)'`), " \
23
+ "or run inside a Rails app with a secret_key_base"
24
+ end
25
+
26
+ Class.new(self) { key(secret) }
27
+ end
28
+
29
+ def self.env_key(env)
30
+ value = env[ENV_KEY].to_s
31
+ return nil if value.empty?
32
+ if value.strip != value || value.strip.empty?
33
+ raise ConfigurationError, "#{ENV_KEY} has leading or trailing whitespace, or only whitespace: remove it"
34
+ end
35
+
36
+ value
37
+ end
38
+ private_class_method :env_key
39
+
40
+ def self.rails_application
41
+ ::Rails.application if defined?(::Rails) && ::Rails.respond_to?(:application)
42
+ end
43
+ private_class_method :rails_application
44
+
45
+ def self.rails_key(rails)
46
+ secret = begin
47
+ rails.secret_key_base
48
+ rescue StandardError => error
49
+ # Rails' message may come from app code; name only its class so nothing secret is echoed.
50
+ raise ConfigurationError, "the default encoder could not read Rails' secret_key_base (#{error.class})"
51
+ end
52
+ raise ConfigurationError, "this Rails app has no secret_key_base for the default encoder" if secret.to_s.empty?
53
+
54
+ OpenSSL::KDF.hkdf(secret.to_s, salt: "graphql-armory", info: RAILS_PURPOSE, length: 32, hash: "SHA256")
55
+ end
56
+ private_class_method :rails_key
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module GraphQL
6
+ module Armory
7
+ class Encoder
8
+ # graphql-ruby's default base64, to keep reading the cursors and IDs clients already hold while migrating to
9
+ # encryption: put it last in `Encoder.versioned`. It reads base64 padded or not, url-safe or standard, and
10
+ # only when it holds printable text, so a tampered encrypted value never comes back as garbage. It is not
11
+ # encryption: anyone can forge what it reads. Remove it from the chain once old values have expired.
12
+ module Legacy
13
+ PRINTABLE = /\A[[:print:]]*\z/
14
+
15
+ def self.encode(text, nonce: false)
16
+ Base64.urlsafe_encode64(text.to_s, padding: false)
17
+ end
18
+
19
+ def self.decode(text, nonce: false)
20
+ raise InvalidError unless text.is_a?(String) && !text.empty?
21
+
22
+ plain = Base64.urlsafe_decode64(text.tr("+/", "-_").delete_suffix("==").delete_suffix("="))
23
+ raise InvalidError if plain.empty?
24
+ plain.force_encoding(Encoding::UTF_8)
25
+ raise InvalidError unless plain.valid_encoding? && plain.match?(PRINTABLE)
26
+
27
+ plain
28
+ rescue StandardError
29
+ raise InvalidError
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Armory
5
+ class Encoder
6
+ # A chain of encoders, newest first: encodes with the first, decodes with whichever accepts the value.
7
+ # Build it with `Encoder.versioned(NewEncoder, OldEncoder, Encoder::Legacy)`.
8
+ class Versioned
9
+ attr_reader :encoders
10
+
11
+ def initialize(encoders)
12
+ raise ConfigurationError, "a versioned encoder needs at least one encoder" if encoders.empty?
13
+
14
+ encoders.each { |encoder| check(encoder) }
15
+ @encoders = encoders.freeze
16
+ end
17
+
18
+ def encode(text, nonce: false)
19
+ encoders.first.encode(text, nonce: nonce)
20
+ end
21
+
22
+ def decode(text, nonce: false)
23
+ data, encoder = decode_versioned(text, nonce: nonce)
24
+ raise InvalidError unless encoder
25
+
26
+ data
27
+ end
28
+
29
+ # The data and the encoder that decoded it, or `[nil, nil]`, so the app can treat old values differently.
30
+ def decode_versioned(text, nonce: false)
31
+ encoders.each do |encoder|
32
+ data = encoder.decode(text, nonce: nonce)
33
+ return [data, encoder] if data.is_a?(String)
34
+ rescue ConfigurationError
35
+ raise
36
+ rescue StandardError
37
+ next
38
+ end
39
+ [nil, nil]
40
+ end
41
+
42
+ private
43
+
44
+ # Fails at boot for a member that would otherwise refuse every value at request time.
45
+ def check(encoder)
46
+ unless %i[encode decode].all? { |name| encoder.respond_to?(name) }
47
+ raise ConfigurationError, "#{encoder.inspect} cannot be versioned: it must respond to encode and decode"
48
+ end
49
+ unless %i[encode decode].all? { |name| accepts_nonce?(encoder.method(name)) }
50
+ raise ConfigurationError, "#{encoder.inspect} cannot be versioned: encode and decode must accept `nonce:`"
51
+ end
52
+ return unless encoder.respond_to?(:key) && encoder.respond_to?(:tag) && encoder.key.nil?
53
+
54
+ raise ConfigurationError, "#{encoder.inspect} cannot be versioned: it has no key"
55
+ end
56
+
57
+ def accepts_nonce?(method)
58
+ method.parameters.any? { |kind, name| kind == :keyrest || (%i[key keyreq].include?(kind) && name == :nonce) }
59
+ end
60
+ end
61
+
62
+ def self.versioned(*encoders)
63
+ Versioned.new(encoders)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ module GraphQL
7
+ module Armory
8
+ # Encrypts and authenticates IDs and cursors with AES-256-GCM. Subclass it and set a key:
9
+ #
10
+ # class AppEncoder < GraphQL::Armory::Encoder
11
+ # key ENV.fetch("GRAPHQL_ARMORY_KEY")
12
+ # end
13
+ #
14
+ # Cursors (`nonce: true`, as graphql-ruby's connections ask) get a random nonce, so the same position
15
+ # never gives the same cursor. IDs must be stable, so their nonce is derived from the ID itself with an
16
+ # HMAC: the same ID always encrypts the same way, and two different IDs never share a nonce.
17
+ # IDs and cursors use separate keys, so one is never accepted as the other.
18
+ class Encoder
19
+ # Raised for anything that does not decode: tampered, foreign, malformed. The message never says which.
20
+ class InvalidError < GraphQL::ExecutionError
21
+ def initialize(message = "Invalid ID or cursor")
22
+ super
23
+ end
24
+ end
25
+
26
+ class ConfigurationError < Armory::Error; end
27
+
28
+ # graphql-ruby's default text form for cursors: base64url without padding.
29
+ module URLSafeBase64
30
+ def self.encode(bytes)
31
+ Base64.urlsafe_encode64(bytes, padding: false)
32
+ end
33
+
34
+ def self.decode(text)
35
+ Base64.urlsafe_decode64(text)
36
+ end
37
+ end
38
+
39
+ MINIMUM_KEY_BYTES = 32
40
+ NONCE_BYTES = 12
41
+ AUTH_TAG_BYTES = 16
42
+ CIPHER = "aes-256-gcm"
43
+
44
+ class << self
45
+ # The key, at least 32 bytes. With no argument, answers it (inherited from the parent class).
46
+ def key(*value)
47
+ return setting(:@key, nil) if value.empty?
48
+
49
+ secret = value.first
50
+ unless secret.is_a?(String) && secret.bytesize >= MINIMUM_KEY_BYTES
51
+ raise ConfigurationError, "an Encoder key must be a String of at least #{MINIMUM_KEY_BYTES} bytes (try SecureRandom.hex(32))"
52
+ end
53
+
54
+ @key = secret
55
+ end
56
+
57
+ # Authenticated data: an ID made with another tag does not decode. Use it to separate apps or versions.
58
+ def tag(*value)
59
+ return setting(:@tag, "") if value.empty?
60
+ raise ConfigurationError, "an Encoder tag cannot be nil" if value.first.nil?
61
+
62
+ @tag = value.first.to_s
63
+ end
64
+
65
+ # Turns the encrypted bytes into text and back. Anything with `encode(bytes)` and `decode(text)`.
66
+ def codec(*value)
67
+ return setting(:@codec, URLSafeBase64) if value.empty?
68
+ raise ConfigurationError, "an Encoder codec must respond to encode and decode" unless %i[encode decode].all? { |name| value.first.respond_to?(name) }
69
+
70
+ @codec = value.first
71
+ end
72
+
73
+ def encode(text, nonce: false)
74
+ purpose = nonce ? :cursor : :id
75
+ bytes = text.to_s.b
76
+ iv = nonce ? OpenSSL::Random.random_bytes(NONCE_BYTES) : derived_nonce(bytes)
77
+ cipher = OpenSSL::Cipher.new(CIPHER).encrypt
78
+ cipher.key = subkey(purpose, :cipher)
79
+ cipher.iv = iv
80
+ cipher.auth_data = tag
81
+ sealed = cipher.update(bytes) + cipher.final
82
+ codec.encode(iv + sealed + cipher.auth_tag)
83
+ end
84
+
85
+ def decode(text, nonce: false)
86
+ purpose = nonce ? :cursor : :id
87
+ bytes = codec.decode(text.to_s)
88
+ # One text form only: a base64 variant with other padding bits is not the same ID.
89
+ raise InvalidError unless codec.encode(bytes) == text.to_s
90
+ raise InvalidError if bytes.bytesize < NONCE_BYTES + AUTH_TAG_BYTES
91
+
92
+ iv = bytes[0, NONCE_BYTES]
93
+ cipher = OpenSSL::Cipher.new(CIPHER).decrypt
94
+ cipher.key = subkey(purpose, :cipher)
95
+ cipher.iv = iv
96
+ cipher.auth_tag = bytes[-AUTH_TAG_BYTES..]
97
+ cipher.auth_data = tag
98
+ plain = cipher.update(bytes[NONCE_BYTES...-AUTH_TAG_BYTES]) + cipher.final
99
+ # An ID has one valid form only: its nonce must be the one derived from it.
100
+ raise InvalidError if !nonce && !OpenSSL.fixed_length_secure_compare(iv, derived_nonce(plain))
101
+
102
+ plain.force_encoding(Encoding::UTF_8)
103
+ rescue ConfigurationError
104
+ raise
105
+ rescue StandardError
106
+ # Whatever the codec, OpenSSL or the input raise, the client learns only that it did not decode.
107
+ raise InvalidError
108
+ end
109
+
110
+ private
111
+
112
+ def setting(ivar, default)
113
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
114
+
115
+ superclass.respond_to?(:key) ? superclass.send(:setting, ivar, default) : default
116
+ end
117
+
118
+ def derived_nonce(bytes)
119
+ OpenSSL::HMAC.digest("SHA256", subkey(:id, :nonce), tag.b + "\0".b + bytes)[0, NONCE_BYTES]
120
+ end
121
+
122
+ # Separate keys per purpose and use, derived with HKDF and cached by the secret they come from, so a key
123
+ # changed later (here or in a parent class) is used at once.
124
+ def subkey(purpose, use)
125
+ secret = key or raise ConfigurationError, "#{name || "Encoder"} has no key: call `key` in its class body"
126
+ @subkeys ||= {}
127
+ @subkeys[[secret, purpose, use]] ||= OpenSSL::KDF.hkdf(secret, salt: "graphql-armory", info: "#{purpose}/#{use}",
128
+ length: 32, hash: "SHA256")
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ require "graphql/armory/encoder/versioned"
136
+ require "graphql/armory/encoder/legacy"
137
+ require "graphql/armory/encoder/default"
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Armory
5
+ # Encrypted global IDs in one line:
6
+ #
7
+ # class AppSchema < GraphQL::Schema
8
+ # use GraphQL::Armory::IDs # the default encoder
9
+ # use GraphQL::Armory::IDs, encoder: AppEncoder # or your own
10
+ # end
11
+ #
12
+ # Defines `id_from_object` (the type name and the record's id, encrypted) and `object_from_id`, which only
13
+ # honours visible object types that implement Node. A type that
14
+ # includes the authorization module's Base is looked up inside the user's scope; any other type only through
15
+ # `find: ->(type, key, context) { ... }`. Every ID that cannot be honoured answers nil, like a missing record.
16
+ module IDs
17
+ SEPARATOR = "/"
18
+
19
+ def self.use(schema, encoder: nil, find: nil)
20
+ lookup = Lookup.new(encoder: encoder || Encoder.default, find: find)
21
+ schema.define_singleton_method(:id_from_object) { |object, type, _context| lookup.id_for(object, type) }
22
+ schema.define_singleton_method(:object_from_id) { |id, context| lookup.object_for(id, context) }
23
+ end
24
+
25
+ # Turns records into IDs and IDs back into records.
26
+ class Lookup
27
+ attr_reader :encoder, :find
28
+
29
+ def initialize(encoder:, find: nil)
30
+ @encoder = encoder
31
+ @find = find
32
+ end
33
+
34
+ def id_for(object, type)
35
+ key = object.id
36
+ raise Encoder::ConfigurationError, "#{type.graphql_name} object has no id to build a global ID from" if key.nil?
37
+
38
+ encoder.encode("#{type.graphql_name}#{SEPARATOR}#{key}")
39
+ end
40
+
41
+ def object_for(id, context)
42
+ type_name, key = encoder.decode(id).split(SEPARATOR, 2)
43
+ return nil if key.nil? || key.empty?
44
+
45
+ type = visible_type(type_name, context)
46
+ return nil unless node_type?(type)
47
+
48
+ load(type, key, context)
49
+ rescue Encoder::InvalidError
50
+ nil
51
+ end
52
+
53
+ private
54
+
55
+ # Through the query's own view of the schema: `Schema.get_type(name, context)` does not apply `visible?`
56
+ # when graphql-ruby runs without `Schema::Visibility`, `context.types` does in both modes.
57
+ def visible_type(type_name, context)
58
+ context.respond_to?(:types) ? context.types.type(type_name) : context.schema.get_type(type_name, context)
59
+ end
60
+
61
+ # Only object types that implement Node can have handed out an ID: root types, payloads and introspection
62
+ # types never did, so an ID naming them is not honoured (and their model is never looked up).
63
+ def node_type?(type)
64
+ type.respond_to?(:kind) && type.kind.object? &&
65
+ type.interfaces.any? { |interface| interface.graphql_name == "Node" }
66
+ end
67
+
68
+ def load(type, key, context)
69
+ return Authorization::Integration.find(type, nil, key, context) if type.respond_to?(:armory_model)
70
+
71
+ find&.call(type, key, context)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Armory
5
- VERSION = "0.1.0.pre"
5
+ VERSION = "0.1.0.pre3"
6
6
  end
7
7
  end
@@ -10,4 +10,6 @@ module GraphQL
10
10
  end
11
11
  end
12
12
 
13
+ require "graphql/armory/encoder"
13
14
  require "graphql/armory/authorization"
15
+ require "graphql/armory/ids"
File without changes
File without changes
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-armory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.0.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Israel
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-29 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: graphql
@@ -34,7 +33,6 @@ description: 'Open-source extensions for graphql-ruby built on its public extens
34
33
  points. The first module is authorization: deny-by-default coverage, scoped lists
35
34
  and connections, IDOR-safe lookups, mutations, interfaces and unions, visibility
36
35
  driven by the policy, and a schema audit for CI.'
37
- email:
38
36
  executables: []
39
37
  extensions: []
40
38
  extra_rdoc_files: []
@@ -43,6 +41,8 @@ files:
43
41
  - LICENSE
44
42
  - MIGRATING.md
45
43
  - README.md
44
+ - docs/authorization.md
45
+ - docs/encrypted-ids.md
46
46
  - lib/graphql-armory.rb
47
47
  - lib/graphql/armory.rb
48
48
  - lib/graphql/armory/authorization.rb
@@ -65,6 +65,7 @@ files:
65
65
  - lib/graphql/armory/authorization/integration/base.rb
66
66
  - lib/graphql/armory/authorization/integration/field.rb
67
67
  - lib/graphql/armory/authorization/integration/find_by.rb
68
+ - lib/graphql/armory/authorization/integration/input_object.rb
68
69
  - lib/graphql/armory/authorization/integration/object.rb
69
70
  - lib/graphql/armory/authorization/integration/resolver.rb
70
71
  - lib/graphql/armory/authorization/integration/schema.rb
@@ -73,6 +74,11 @@ files:
73
74
  - lib/graphql/armory/authorization/rule.rb
74
75
  - lib/graphql/armory/authorization/scope.rb
75
76
  - lib/graphql/armory/authorization/telemetry.rb
77
+ - lib/graphql/armory/encoder.rb
78
+ - lib/graphql/armory/encoder/default.rb
79
+ - lib/graphql/armory/encoder/legacy.rb
80
+ - lib/graphql/armory/encoder/versioned.rb
81
+ - lib/graphql/armory/ids.rb
76
82
  - lib/graphql/armory/version.rb
77
83
  - lib/tasks/graphql_armory.rake
78
84
  homepage: https://github.com/israelb/graphql-armory
@@ -84,7 +90,6 @@ metadata:
84
90
  changelog_uri: https://github.com/israelb/graphql-armory/blob/main/CHANGELOG.md
85
91
  bug_tracker_uri: https://github.com/israelb/graphql-armory/issues
86
92
  rubygems_mfa_required: 'true'
87
- post_install_message:
88
93
  rdoc_options: []
89
94
  require_paths:
90
95
  - lib
@@ -95,12 +100,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
100
  version: '3.1'
96
101
  required_rubygems_version: !ruby/object:Gem::Requirement
97
102
  requirements:
98
- - - ">"
103
+ - - ">="
99
104
  - !ruby/object:Gem::Version
100
- version: 1.3.1
105
+ version: '0'
101
106
  requirements: []
102
- rubygems_version: 3.4.19
103
- signing_key:
107
+ rubygems_version: 3.6.9
104
108
  specification_version: 4
105
109
  summary: 'Authorization for graphql-ruby: Pundit and CanCanCan, deny-by-default, with
106
110
  a schema audit.'