parse-stack-next 5.5.3 → 5.5.5

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 +13 -4
  3. data/README.md +26 -13
  4. data/bin/parse-console +9 -1
  5. data/docs/TEST_SERVER.md +115 -238
  6. data/docs/mcp_guide.md +1 -1
  7. data/docs/mongodb_index_optimization_guide.md +3 -2
  8. data/docs/usage_guide.md +1 -1
  9. data/docs/yard-template/default/fulldoc/html/css/common.css +52 -9
  10. data/docs/yard-template/default/fulldoc/html/css/full_list.css +40 -13
  11. data/lib/parse/agent/constraint_translator.rb +18 -18
  12. data/lib/parse/agent/errors.rb +29 -7
  13. data/lib/parse/agent/metadata_dsl.rb +6 -6
  14. data/lib/parse/agent/tools.rb +250 -59
  15. data/lib/parse/agent.rb +42 -30
  16. data/lib/parse/api/aggregate.rb +3 -3
  17. data/lib/parse/api/cloud_functions.rb +19 -10
  18. data/lib/parse/api/objects.rb +8 -8
  19. data/lib/parse/api/users.rb +9 -9
  20. data/lib/parse/atlas_search/session.rb +34 -34
  21. data/lib/parse/atlas_search.rb +243 -110
  22. data/lib/parse/client/body_builder.rb +10 -10
  23. data/lib/parse/client/logging.rb +5 -2
  24. data/lib/parse/client/profiling.rb +5 -2
  25. data/lib/parse/client/protocol.rb +1 -1
  26. data/lib/parse/client/url_redaction.rb +94 -0
  27. data/lib/parse/client.rb +43 -28
  28. data/lib/parse/embeddings/image_fetch.rb +6 -1
  29. data/lib/parse/embeddings/voyage.rb +16 -17
  30. data/lib/parse/live_query/client.rb +7 -7
  31. data/lib/parse/live_query/subscription.rb +1 -1
  32. data/lib/parse/lock.rb +1 -1
  33. data/lib/parse/lock_backend.rb +118 -2
  34. data/lib/parse/model/acl.rb +24 -24
  35. data/lib/parse/model/classes/job_schedule.rb +8 -8
  36. data/lib/parse/model/classes/job_status.rb +9 -9
  37. data/lib/parse/model/classes/role.rb +49 -49
  38. data/lib/parse/model/classes/session.rb +2 -2
  39. data/lib/parse/model/classes/user.rb +66 -66
  40. data/lib/parse/model/core/builder.rb +7 -7
  41. data/lib/parse/model/core/create_lock.rb +1 -1
  42. data/lib/parse/model/core/properties.rb +4 -4
  43. data/lib/parse/model/file.rb +57 -16
  44. data/lib/parse/model/model.rb +19 -19
  45. data/lib/parse/model/object.rb +38 -38
  46. data/lib/parse/model/pointer.rb +4 -4
  47. data/lib/parse/model/push.rb +5 -5
  48. data/lib/parse/mongodb.rb +84 -26
  49. data/lib/parse/pipeline_security.rb +2 -2
  50. data/lib/parse/query/constraints.rb +38 -38
  51. data/lib/parse/query.rb +151 -75
  52. data/lib/parse/retrieval/reranker/cohere.rb +30 -0
  53. data/lib/parse/schema.rb +1 -1
  54. data/lib/parse/stack/version.rb +1 -1
  55. data/lib/parse/stack.rb +23 -10
  56. data/lib/parse/two_factor_auth/user_extension.rb +25 -25
  57. data/lib/parse/webhooks/payload.rb +35 -35
  58. data/lib/parse/webhooks/registration.rb +2 -2
  59. data/lib/parse/webhooks/replay_protection.rb +16 -16
  60. data/lib/parse/webhooks.rb +11 -11
  61. data/parse-stack-next.gemspec +19 -1
  62. metadata +2 -38
  63. data/.bundle/config +0 -5
  64. data/.env.sample +0 -138
  65. data/.env.test +0 -10
  66. data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
  67. data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
  68. data/.github/dependabot.yml +0 -13
  69. data/.github/workflows/codeql.yml +0 -44
  70. data/.github/workflows/docs.yml +0 -39
  71. data/.github/workflows/release.yml +0 -43
  72. data/.github/workflows/ruby.yml +0 -38
  73. data/.gitignore +0 -56
  74. data/.ruby-version +0 -1
  75. data/.solargraph.yml +0 -22
  76. data/.vscode/settings.json +0 -3
  77. data/.yardopts +0 -19
  78. data/Gemfile +0 -43
  79. data/Gemfile.lock +0 -198
  80. data/Makefile +0 -63
  81. data/Rakefile +0 -825
  82. data/config/parse-config.json +0 -12
  83. data/scripts/debug-ips.js +0 -35
  84. data/scripts/docker/Dockerfile.parse +0 -17
  85. data/scripts/docker/atlas-init.js +0 -284
  86. data/scripts/docker/docker-compose.atlas.yml +0 -80
  87. data/scripts/docker/docker-compose.test.yml +0 -159
  88. data/scripts/docker/docker-compose.verifyemail.yml +0 -4
  89. data/scripts/docker/mongo-init.js +0 -21
  90. data/scripts/docker/preflight.sh +0 -76
  91. data/scripts/eval_mcp_with_lm_studio.rb +0 -274
  92. data/scripts/start-parse.sh +0 -154
  93. data/scripts/start_mcp_server.rb +0 -78
  94. data/scripts/test_server_connection.rb +0 -82
  95. data/scripts/vector_prototype/create_vector_index.js +0 -105
  96. data/scripts/vector_prototype/fetch_embeddings.py +0 -241
  97. data/scripts/vector_prototype/fixture_manifest.json +0 -9
  98. data/scripts/vector_prototype/query_prototype.rb +0 -84
  99. data/scripts/vector_prototype/run.sh +0 -34
data/lib/parse/agent.rb CHANGED
@@ -739,7 +739,7 @@ module Parse
739
739
 
740
740
  # Built-in mutation tools that route through session-token REST and
741
741
  # are therefore enforceable by Parse Server's native ACL/CLP. Gated
742
- # additionally by the per-agent +allow_mutations:+ kwarg in client
742
+ # additionally by the per-agent `allow_mutations:` kwarg in client
743
743
  # mode (default false) and by the existing process-level env vars
744
744
  # (PARSE_AGENT_ALLOW_WRITE_TOOLS + PARSE_AGENT_ALLOW_RAW_CRUD).
745
745
  CLIENT_SAFE_MUTATION_TOOLS = %i[
@@ -806,7 +806,7 @@ module Parse
806
806
  # configure this on load.
807
807
  attr_accessor :allowed_llm_endpoints
808
808
 
809
- # Validate +endpoint+ against {allowed_llm_endpoints}. No-op
809
+ # Validate `endpoint` against {allowed_llm_endpoints}. No-op
810
810
  # when the allowlist is unset. Raises `ArgumentError` on miss so
811
811
  # the caller's `ask` / `ask_streaming` invocation fails before
812
812
  # any HTTP request is sent.
@@ -887,29 +887,29 @@ module Parse
887
887
  attr_reader :session_token
888
888
 
889
889
  # @return [Parse::User, Parse::Pointer, nil] the User identity the
890
- # agent was constructed with via +acl_user:+. The agent's
890
+ # agent was constructed with via `acl_user:`. The agent's
891
891
  # {#acl_scope} resolves this user's permission_strings
892
892
  # (objectId + roles, expanded) at construction. nil for
893
893
  # session_token / acl_role / master-key construction.
894
894
  attr_reader :acl_user_scope
895
895
 
896
896
  # @return [Parse::Role, String, Symbol, nil] the Role identity the
897
- # agent was constructed with via +acl_role:+. Used for
897
+ # agent was constructed with via `acl_role:`. Used for
898
898
  # service-account-style scoping ("see as if a user with this
899
899
  # role were asking") without a specific user. nil for
900
900
  # session_token / acl_user / master-key construction.
901
901
  attr_reader :acl_role_scope
902
902
 
903
903
  # @return [Parse::ACLScope::Resolution, nil] the resolved ACL scope
904
- # for this agent. Frozen at construction. +nil+ means master-key
904
+ # for this agent. Frozen at construction. `nil` means master-key
905
905
  # posture — the agent runs every tool call with the application
906
906
  # master key, bypassing per-row ACL/CLP enforcement. Non-nil
907
- # carries a +permission_strings+ allow-set that built-in tools
907
+ # carries a `permission_strings` allow-set that built-in tools
908
908
  # forward to mongo-direct / Atlas Search via {#acl_scope_kwargs}.
909
909
  attr_reader :acl_scope
910
910
 
911
911
  # @return [Boolean] whether this agent may run Atlas Search tools
912
- # in master-key-equivalent mode when no +session_token+ is set.
912
+ # in master-key-equivalent mode when no `session_token` is set.
913
913
  # See {#master_atlas?} for the gate semantics applied by the
914
914
  # Atlas Search tool handlers in {Parse::Agent::Tools}.
915
915
  attr_reader :master_atlas
@@ -921,7 +921,7 @@ module Parse
921
921
  # Parse::Client has no master_key). In client mode the dispatchable
922
922
  # tool set is restricted to {CLIENT_SAFE_READ_TOOLS},
923
923
  # {CLIENT_SAFE_MUTATION_TOOLS} (gated on {#allow_mutations?}), and
924
- # any registered tool declared +client_safe: true+.
924
+ # any registered tool declared `client_safe: true`.
925
925
  def client_mode?
926
926
  @client_mode == true
927
927
  end
@@ -930,7 +930,7 @@ module Parse
930
930
  # tools (create_object/update_object/delete_object). Layered with
931
931
  # the process-level PARSE_AGENT_ALLOW_WRITE_TOOLS +
932
932
  # PARSE_AGENT_ALLOW_RAW_CRUD env vars (all three must be true).
933
- # Default: +false+ in client mode, +true+ in master-key mode.
933
+ # Default: `false` in client mode, `true` in master-key mode.
934
934
  def allow_mutations?
935
935
  @allow_mutations == true
936
936
  end
@@ -1050,10 +1050,10 @@ module Parse
1050
1050
  tok.cancelled?
1051
1051
  end
1052
1052
 
1053
- # @return [Boolean] +true+ when this agent has been explicitly
1054
- # constructed with +master_atlas: true+. Used by the Atlas
1053
+ # @return [Boolean] `true` when this agent has been explicitly
1054
+ # constructed with `master_atlas: true`. Used by the Atlas
1055
1055
  # Search tool handlers in {Parse::Agent::Tools} to gate calls
1056
- # that would otherwise refuse because no +session_token+ is
1056
+ # that would otherwise refuse because no `session_token` is
1057
1057
  # available — see {Parse::AtlasSearch} for the reasoning behind
1058
1058
  # the dedicated opt-in (Atlas Search bypasses Parse Server
1059
1059
  # entirely, so the agent's normal master-key posture is not a
@@ -1098,7 +1098,7 @@ module Parse
1098
1098
  # The agent's resolved identity claim set — the
1099
1099
  # `["*", userObjectId, "role:Foo", ...]` array that gets matched
1100
1100
  # against a document's `_rperm` (for read) or `_wperm` (for
1101
- # write). Returns +nil+ for master-key posture (unrestricted reach
1101
+ # write). Returns `nil` for master-key posture (unrestricted reach
1102
1102
  # — no filtering applied).
1103
1103
  #
1104
1104
  # The set is identity-based and identical for read and write
@@ -1113,7 +1113,7 @@ module Parse
1113
1113
  # A ready-to-prepend `$match` stage filtering an aggregation
1114
1114
  # pipeline to documents the agent's scope is allowed to READ.
1115
1115
  # Mirrors what the built-in read tools inject automatically via
1116
- # {Parse::ACLScope.match_stage_for}. Returns +nil+ for master-key
1116
+ # {Parse::ACLScope.match_stage_for}. Returns `nil` for master-key
1117
1117
  # posture.
1118
1118
  #
1119
1119
  # @return [Hash, nil]
@@ -1129,7 +1129,7 @@ module Parse
1129
1129
  # perform writes (e.g., a custom `agent_method` that batch-updates
1130
1130
  # rows under the agent's scope) prepend this stage themselves so
1131
1131
  # the update only sees rows whose `_wperm` includes the agent's
1132
- # identity. Returns +nil+ for master-key posture.
1132
+ # identity. Returns `nil` for master-key posture.
1133
1133
  #
1134
1134
  # @return [Hash, nil]
1135
1135
  def acl_write_match_stage
@@ -1138,11 +1138,11 @@ module Parse
1138
1138
  { "$match" => Parse::ACL.write_predicate(perms) }
1139
1139
  end
1140
1140
 
1141
- # +true+ when the agent carries any non-master-key scope
1141
+ # `true` when the agent carries any non-master-key scope
1142
1142
  # (session_token, acl_user, or acl_role). Use this when deciding
1143
1143
  # whether a Parse Server endpoint that DOES NOT enforce ACL
1144
1144
  # (notably the REST `aggregate` endpoint) is safe to route through:
1145
- # any +true+ here means the REST path would silently bypass the
1145
+ # any `true` here means the REST path would silently bypass the
1146
1146
  # agent's declared scope, so the tool must use the mongo-direct
1147
1147
  # path (which runs Parse::ACLScope's `_rperm` injection).
1148
1148
  #
@@ -1151,11 +1151,11 @@ module Parse
1151
1151
  !@acl_scope.nil?
1152
1152
  end
1153
1153
 
1154
- # +true+ when the agent's ACL scope cannot be honored by Parse
1154
+ # `true` when the agent's ACL scope cannot be honored by Parse
1155
1155
  # Server's REST surface at all (no "act as role" affordance) and
1156
1156
  # the SDK must auto-route every built-in tool through mongo-direct
1157
1157
  # (Parse::MongoDB.aggregate / Parse::Query#results_direct). Fires
1158
- # ONLY for +acl_user:+ and +acl_role:+ scopes; session_token
1158
+ # ONLY for `acl_user:` and `acl_role:` scopes; session_token
1159
1159
  # agents can keep the REST find_objects path because Parse Server
1160
1160
  # validates the token natively for find / get endpoints.
1161
1161
  #
@@ -1170,7 +1170,7 @@ module Parse
1170
1170
  !(@acl_user_scope.nil? && @acl_role_scope.nil?)
1171
1171
  end
1172
1172
 
1173
- # +true+ when an AGGREGATE operation for this agent MUST run through
1173
+ # `true` when an AGGREGATE operation for this agent MUST run through
1174
1174
  # the SDK's mongo-direct path (Parse::MongoDB.aggregate) instead of
1175
1175
  # Parse Server's REST aggregate endpoint. The REST aggregate endpoint
1176
1176
  # enforces NEITHER ACL, CLP, nor protectedFields and requires the
@@ -1183,7 +1183,7 @@ module Parse
1183
1183
  # REST find/get DOES enforce a session token — but REST aggregate does
1184
1184
  # not). Fires for acl_user / acl_role scopes AND for any session-token
1185
1185
  # identity, including a runtime-impersonated agent whose @acl_scope has
1186
- # been cleared. Master-key agents return +false+.
1186
+ # been cleared. Master-key agents return `false`.
1187
1187
  #
1188
1188
  # @return [Boolean]
1189
1189
  def requires_mongo_direct?
@@ -1766,7 +1766,7 @@ module Parse
1766
1766
  # false default would silently disable
1767
1767
  # writes for every existing master-key
1768
1768
  # agent).
1769
- # When +parent:+ is supplied, the child cannot widen the parent's
1769
+ # When `parent:` is supplied, the child cannot widen the parent's
1770
1770
  # gate: if parent.allow_mutations? is false, child must also be
1771
1771
  # false. Default-on-nil inherits the parent's value verbatim so
1772
1772
  # the safe path (omit kwarg) is trivially correct.
@@ -2543,6 +2543,18 @@ module Parse
2543
2543
  details = e.respond_to?(:to_details) ? e.to_details : {}
2544
2544
  response = error_response(e.message, error_code: :access_denied, details: details.any? ? details : nil)
2545
2545
 
2546
+ # Recognized-but-unimplemented tool (the built-in write/admin
2547
+ # CRUD tools ship without a handler). Surface the actionable
2548
+ # message rather than collapsing to the opaque internal-error
2549
+ # path, and tag a distinct :not_implemented code so callers can
2550
+ # branch on "tool doesn't exist here" vs a real failure.
2551
+ rescue Parse::Agent::NotImplemented => e
2552
+ trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
2553
+ payload[:success] = false
2554
+ payload[:error_class] = e.class.name
2555
+ payload[:error_code] = :not_implemented
2556
+ response = error_response(e.message, error_code: :not_implemented)
2557
+
2546
2558
  # Validation errors (e.g. from registered tool handlers or get_objects)
2547
2559
  rescue Parse::Agent::ValidationError => e
2548
2560
  trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
@@ -3012,7 +3024,7 @@ module Parse
3012
3024
  # {#import_conversation}.
3013
3025
  IMPORT_MAX_CONTENT_LEN = 32 * 1024
3014
3026
  # @!visibility private
3015
- # Roles permitted on imported conversation entries. +system+ and +tool+
3027
+ # Roles permitted on imported conversation entries. `system` and `tool`
3016
3028
  # are explicitly excluded — without this guard, an attacker who
3017
3029
  # controls a saved transcript can plant fabricated tool results
3018
3030
  # (which the next LLM turn treats as authentic prior retrievals) or
@@ -3024,10 +3036,10 @@ module Parse
3024
3036
  # conversation history and token usage. Permissions are NEVER
3025
3037
  # restored from the export — they belong to the Agent constructor.
3026
3038
  #
3027
- # Only +role: "user"+ and +role: "assistant"+ entries with
3028
- # String/nil +content+ are accepted. Disallowed roles, oversized
3039
+ # Only `role: "user"` and `role: "assistant"` entries with
3040
+ # String/nil `content` are accepted. Disallowed roles, oversized
3029
3041
  # content, or message counts above {IMPORT_MAX_MESSAGES} raise
3030
- # +ArgumentError+; a malformed JSON payload returns +false+ with a
3042
+ # `ArgumentError`; a malformed JSON payload returns `false` with a
3031
3043
  # warning.
3032
3044
  #
3033
3045
  # @param json_string [String] JSON string from {#export_conversation}.
@@ -3761,11 +3773,11 @@ module Parse
3761
3773
 
3762
3774
  # Get the current authentication context.
3763
3775
  #
3764
- # @return [Hash] +:type+ is one of +:session_token+, +:acl_user+,
3765
- # +:acl_role+, or +:master_key+. +:using_master_key+ is +true+
3766
- # ONLY for +:master_key+; scoped agents (session_token / acl_user /
3776
+ # @return [Hash] `:type` is one of `:session_token`, `:acl_user`,
3777
+ # `:acl_role`, or `:master_key`. `:using_master_key` is `true`
3778
+ # ONLY for `:master_key`; scoped agents (session_token / acl_user /
3767
3779
  # acl_role) run with explicit ACL enforcement and never set the
3768
- # master-key flag. The +:identity+ slot carries a posture-specific
3780
+ # master-key flag. The `:identity` slot carries a posture-specific
3769
3781
  # identifier (user_id for session/acl_user, role name for
3770
3782
  # acl_role, nil for master_key) so the AUDIT log can attribute
3771
3783
  # tool calls accurately.
@@ -25,7 +25,7 @@ module Parse
25
25
  module ClassMethods
26
26
  # Get the aggregate API path for this class.
27
27
  #
28
- # +className+ is validated to prevent path-smuggling — aggregate
28
+ # `className` is validated to prevent path-smuggling — aggregate
29
29
  # endpoints are master-key-only on Parse Server, so any traversal
30
30
  # here pivots a master-key request to a different endpoint.
31
31
  #
@@ -66,9 +66,9 @@ module Parse
66
66
  # @param pipeline [Array] the MongoDB aggregation pipeline stages.
67
67
  # @param opts [Hash] additional options to pass to the {Parse::Client} request.
68
68
  # @param headers [Hash] additional HTTP headers to send with the request.
69
- # @param raw_values [Boolean] when true, adds +rawValues: true+ to the request
69
+ # @param raw_values [Boolean] when true, adds `rawValues: true` to the request
70
70
  # so Parse Server returns un-decoded field values (PS 9.9.0+, #10438).
71
- # @param raw_field_names [Boolean] when true, adds +rawFieldNames: true+ to the
71
+ # @param raw_field_names [Boolean] when true, adds `rawFieldNames: true` to the
72
72
  # request so Parse Server returns original (un-decoded) field names (PS 9.9.0+).
73
73
  # @return [Parse::Response]
74
74
  # @see Parse::Query
@@ -9,31 +9,40 @@ module Parse
9
9
  # Call a cloud function.
10
10
  # @param name [String] the name of the cloud function.
11
11
  # @param body [Hash] the parameters to forward to the function.
12
- # @param opts [Hash] additional options for the request.
12
+ # @param opts [Hash] additional options for the request. Accepts the
13
+ # explicit `opts:` hash for back-compat, but request options may
14
+ # also be passed as plain keyword arguments (e.g.
15
+ # `call_function("f", {}, session_token: t)`) to match the rest of
16
+ # the API surface — those are merged into `opts` (explicit `opts:`
17
+ # keys win on conflict).
13
18
  # @option opts [String] :session_token The session token for authenticated requests.
14
19
  # @option opts [String] :master_key Whether to use the master key for this request.
15
20
  # @param context [Hash, nil] an optional caller context forwarded as the
16
- # +X-Parse-Cloud-Context+ header. Parse Server maps it to
17
- # +req.info.context+ in the cloud function handler.
18
- # Omit or pass +nil+ to leave behavior unchanged.
21
+ # `X-Parse-Cloud-Context` header. Parse Server maps it to
22
+ # `req.info.context` in the cloud function handler.
23
+ # Omit or pass `nil` to leave behavior unchanged.
19
24
  # @return [Parse::Response]
20
- def call_function(name, body = {}, opts: {}, context: nil)
25
+ def call_function(name, body = {}, opts: {}, context: nil, **kwopts)
21
26
  safe = Parse::API::PathSegment.identifier!(name, kind: "function name")
22
27
  headers = {}
23
28
  headers[Parse::Protocol::CLOUD_CONTEXT] = context.to_json unless context.nil?
24
- request :post, "functions/#{safe}", body: body, headers: headers, opts: opts
29
+ merged = kwopts.empty? ? opts : kwopts.merge(opts)
30
+ request :post, "functions/#{safe}", body: body, headers: headers, opts: merged
25
31
  end
26
32
 
27
33
  # Trigger a job.
28
34
  # @param name [String] the name of the job to trigger.
29
35
  # @param body [Hash] the parameters to forward to the job.
30
- # @param opts [Hash] additional options for the request.
36
+ # @param opts [Hash] additional options for the request. Like
37
+ # {#call_function}, request options may be passed either as the
38
+ # explicit `opts:` hash or as plain keyword arguments.
31
39
  # @option opts [String] :session_token The session token for authenticated requests.
32
40
  # @option opts [String] :master_key Whether to use the master key for this request.
33
41
  # @return [Parse::Response]
34
- def trigger_job(name, body = {}, opts: {})
42
+ def trigger_job(name, body = {}, opts: {}, **kwopts)
35
43
  safe = Parse::API::PathSegment.identifier!(name, kind: "job name")
36
- request :post, "jobs/#{safe}", body: body, opts: opts
44
+ merged = kwopts.empty? ? opts : kwopts.merge(opts)
45
+ request :post, "jobs/#{safe}", body: body, opts: merged
37
46
  end
38
47
 
39
48
  # Call a cloud function with a specific session token.
@@ -42,7 +51,7 @@ module Parse
42
51
  # @param body [Hash] the parameters to forward to the function.
43
52
  # @param session_token [String] the session token for authenticated requests.
44
53
  # @param context [Hash, nil] an optional caller context forwarded as the
45
- # +X-Parse-Cloud-Context+ header.
54
+ # `X-Parse-Cloud-Context` header.
46
55
  # @return [Parse::Response]
47
56
  def call_function_with_session(name, body = {}, session_token, context: nil)
48
57
  opts = {}
@@ -36,9 +36,9 @@ module Parse
36
36
  module ClassMethods
37
37
  # Get the API path for this class.
38
38
  #
39
- # Both +className+ and +id+ are validated to prevent path-smuggling
39
+ # Both `className` and `id` are validated to prevent path-smuggling
40
40
  # attacks where an attacker-controlled string traverses to a
41
- # different REST endpoint (e.g. +"../sessions/me"+) with whatever
41
+ # different REST endpoint (e.g. `"../sessions/me"`) with whatever
42
42
  # auth the outer request carries — typically the master key.
43
43
  #
44
44
  # @param className [String] the name of the Parse collection.
@@ -85,9 +85,9 @@ module Parse
85
85
  # @param opts [Hash] additional options to pass to the {Parse::Client} request.
86
86
  # @param headers [Hash] additional HTTP headers to send with the request.
87
87
  # @param context [Hash, nil] an optional caller context forwarded as the
88
- # +X-Parse-Cloud-Context+ header. Parse Server maps it to
89
- # +req.info.context+ inside beforeSave/afterSave cloud triggers.
90
- # Omit or pass +nil+ to leave behavior unchanged.
88
+ # `X-Parse-Cloud-Context` header. Parse Server maps it to
89
+ # `req.info.context` inside beforeSave/afterSave cloud triggers.
90
+ # Omit or pass `nil` to leave behavior unchanged.
91
91
  # @return [Parse::Response]
92
92
  def create_object(className, body = {}, headers: {}, context: nil, **opts)
93
93
  unless context.nil?
@@ -143,9 +143,9 @@ module Parse
143
143
  # @param opts [Hash] additional options to pass to the {Parse::Client} request.
144
144
  # @param headers [Hash] additional HTTP headers to send with the request.
145
145
  # @param context [Hash, nil] an optional caller context forwarded as the
146
- # +X-Parse-Cloud-Context+ header. Parse Server maps it to
147
- # +req.info.context+ inside beforeSave/afterSave cloud triggers.
148
- # Omit or pass +nil+ to leave behavior unchanged.
146
+ # `X-Parse-Cloud-Context` header. Parse Server maps it to
147
+ # `req.info.context` inside beforeSave/afterSave cloud triggers.
148
+ # Omit or pass `nil` to leave behavior unchanged.
149
149
  # @return [Parse::Response]
150
150
  def update_object(className, id, body = {}, headers: {}, context: nil, **opts)
151
151
  unless context.nil?
@@ -108,9 +108,9 @@ module Parse
108
108
  #
109
109
  # Client-side rate limited on a per-email basis using the same
110
110
  # tracker that backs {#login} (entries are namespaced under a
111
- # +pwreset:+ prefix so the two limiters don't collide on usernames
111
+ # `pwreset:` prefix so the two limiters don't collide on usernames
112
112
  # that happen to equal an email). Every request counts toward the
113
- # backoff — Parse Server's +requestPasswordReset+ response does
113
+ # backoff — Parse Server's `requestPasswordReset` response does
114
114
  # not differentiate "email exists" from "email does not exist"
115
115
  # (and rightly so, to avoid account enumeration), so the SDK
116
116
  # cannot distinguish a legitimate retry from an attacker probing
@@ -212,23 +212,23 @@ module Parse
212
212
  # that the username + password combination is correct without producing a
213
213
  # new session token on success.
214
214
  #
215
- # Uses the +POST /parse/verifyPassword+ endpoint (credentials in the request
216
- # BODY, mirroring +login+) rather than the +GET+ form. Parse Server accepts
215
+ # Uses the `POST /parse/verifyPassword` endpoint (credentials in the request
216
+ # BODY, mirroring `login`) rather than the `GET` form. Parse Server accepts
217
217
  # both (same handler, neither master-key gated; the POST variant landed in
218
218
  # 7.1.0), but POST keeps the plaintext password out of the URL — and
219
- # therefore out of server access logs, reverse-proxy logs, the +Referer+
219
+ # therefore out of server access logs, reverse-proxy logs, the `Referer`
220
220
  # header, and the SDK's URL-keyed response cache.
221
221
  #
222
222
  # On success Parse Server returns the user object (HTTP 200) with the same
223
- # shape as a login response (minus +sessionToken+). On failure it returns a
223
+ # shape as a login response (minus `sessionToken`). On failure it returns a
224
224
  # 4xx with an error body, most commonly:
225
- # - code 101 (+ERROR_OBJECT_NOT_FOUND+) for an unknown username or wrong password.
226
- # - code 205 (+ERROR_EMAIL_NOT_FOUND+) when +preventLoginWithUnverifiedEmail+
225
+ # - code 101 (`ERROR_OBJECT_NOT_FOUND`) for an unknown username or wrong password.
226
+ # - code 205 (`ERROR_EMAIL_NOT_FOUND`) when `preventLoginWithUnverifiedEmail`
227
227
  # is enabled and the account's email has not been verified.
228
228
  #
229
229
  # Client-side rate limited per username using the SAME bucket as {#login}
230
230
  # (bare username, no namespace) — failures across both credential oracles
231
- # accumulate, so an attacker cannot bypass a +login+ lockout by pivoting to
231
+ # accumulate, so an attacker cannot bypass a `login` lockout by pivoting to
232
232
  # this endpoint. The trade-off: a run of failed step-up re-auth calls counts
233
233
  # toward (and can trigger) the primary login lockout for that username.
234
234
  # Client-side limiting is a convenience, not a boundary — the server is the
@@ -11,46 +11,46 @@ module Parse
11
11
  #
12
12
  # Atlas Search runs aggregations directly against MongoDB and
13
13
  # therefore bypasses Parse Server's per-request ACL enforcement.
14
- # To compile a +_rperm+ +$match+ stage (see {Parse::ACL.read_predicate})
14
+ # To compile a `_rperm` `$match` stage (see {Parse::ACL.read_predicate})
15
15
  # the caller needs to know two things about the requesting
16
16
  # session:
17
17
  #
18
- # 1. The +_User.objectId+ that owns the session.
18
+ # 1. The `_User.objectId` that owns the session.
19
19
  # 2. The transitive upward closure of role names that user
20
20
  # inherits permissions from (cf. {Parse::Role.all_for_user}).
21
21
  #
22
22
  # Both lookups can be expensive — token → user requires a
23
- # +/users/me+ round-trip, and user → roles can require multiple
24
- # +_Role+ queries to walk the inheritance graph. Both are cached
23
+ # `/users/me` round-trip, and user → roles can require multiple
24
+ # `_Role` queries to walk the inheritance graph. Both are cached
25
25
  # separately so a single agent turn that runs several Atlas Search
26
26
  # tools amortizes the cost.
27
27
  #
28
28
  # Two distinct caches:
29
29
  #
30
- # * +session_cache+: maps +session_token+ to +user_id+. Long
30
+ # * `session_cache`: maps `session_token` to `user_id`. Long
31
31
  # TTL (1 hour default), invalidation profile is logout. Apps
32
32
  # that need sub-TTL revocation must call {.invalidate}
33
33
  # explicitly from their logout path.
34
34
  #
35
- # * +role_cache+: maps +user_id+ to a +Set+ of role names. Short
35
+ # * `role_cache`: maps `user_id` to a `Set` of role names. Short
36
36
  # TTL (2 minutes default), invalidation profile is role-graph
37
37
  # mutation. Stale role data here yields incorrect ACL
38
38
  # decisions, so the default is conservatively short.
39
39
  #
40
40
  # The default cache implementation is process-local
41
- # ({MemoryCache}) and guarded by a +Mutex+. Apps that need shared
41
+ # ({MemoryCache}) and guarded by a `Mutex`. Apps that need shared
42
42
  # cross-process caching (Redis, Memcached) may install a
43
43
  # replacement via {AtlasSearch.session_cache=} /
44
44
  # {AtlasSearch.role_cache=}; the replacement must respond to
45
- # +get(key)+, +set(key, value, ttl:)+, and +invalidate(key)+.
45
+ # `get(key)`, `set(key, value, ttl:)`, and `invalidate(key)`.
46
46
  module Session
47
- # Raised when a +session_token+ cannot be resolved — invalid
48
- # token, expired session, or +/users/me+ returned an error.
47
+ # Raised when a `session_token` cannot be resolved — invalid
48
+ # token, expired session, or `/users/me` returned an error.
49
49
  # Atlas Search callers should treat this as a 401-equivalent.
50
50
  class InvalidSession < StandardError; end
51
51
 
52
52
  # Default cache: in-memory hash with per-entry TTL, guarded by a
53
- # +Mutex+. Suitable for single-process apps. Apps running
53
+ # `Mutex`. Suitable for single-process apps. Apps running
54
54
  # multi-process (Puma workers, Sidekiq processes) get a per-
55
55
  # process cache — install a shared cache through
56
56
  # {AtlasSearch.session_cache=} for cross-process sharing.
@@ -61,7 +61,7 @@ module Parse
61
61
  end
62
62
 
63
63
  # @param key [String]
64
- # @return [Object, nil] the cached value, or +nil+ when the key
64
+ # @return [Object, nil] the cached value, or `nil` when the key
65
65
  # is missing or its TTL has elapsed. Expired entries are
66
66
  # evicted lazily on read.
67
67
  def get(key)
@@ -97,15 +97,15 @@ module Parse
97
97
  end
98
98
  end
99
99
 
100
- # Value returned by {Session.resolve}. +user_id+ is the
101
- # +_User.objectId+ owning the session, or +nil+ for an anonymous
102
- # caller. +role_names+ is a +Set+ of role names (no +role:+
100
+ # Value returned by {Session.resolve}. `user_id` is the
101
+ # `_User.objectId` owning the session, or `nil` for an anonymous
102
+ # caller. `role_names` is a `Set` of role names (no `role:`
103
103
  # prefix) the user inherits permissions from, computed via
104
104
  # {Parse::Role.all_for_user}.
105
105
  Resolved = Struct.new(:user_id, :role_names) do
106
- # Build the canonical +_rperm+/+_wperm+ permission-string set
107
- # for this session. Always includes +"*"+ (public). Includes
108
- # +user_id+ when present. Includes +"role:#{name}"+ for each
106
+ # Build the canonical `_rperm`/`_wperm` permission-string set
107
+ # for this session. Always includes `"*"` (public). Includes
108
+ # `user_id` when present. Includes `"role:#{name}"` for each
109
109
  # inherited role.
110
110
  # @return [Array<String>]
111
111
  def permission_strings
@@ -115,33 +115,33 @@ module Parse
115
115
  out.uniq
116
116
  end
117
117
 
118
- # @return [Boolean] +true+ for the anonymous-session case.
118
+ # @return [Boolean] `true` for the anonymous-session case.
119
119
  def anonymous?
120
120
  user_id.nil? || user_id.empty?
121
121
  end
122
122
  end
123
123
 
124
124
  class << self
125
- # Resolve a +session_token+ to the requesting user and the
126
- # transitive set of role names whose +role:NAME+ permission
127
- # strings should be checked against +_rperm+.
125
+ # Resolve a `session_token` to the requesting user and the
126
+ # transitive set of role names whose `role:NAME` permission
127
+ # strings should be checked against `_rperm`.
128
128
  #
129
- # +nil+ or empty +session_token+ → anonymous {Resolved} with
130
- # +user_id: nil+ and an empty +role_names+ set. The caller
129
+ # `nil` or empty `session_token` → anonymous {Resolved} with
130
+ # `user_id: nil` and an empty `role_names` set. The caller
131
131
  # decides whether to refuse the request (the
132
- # +require_session_token+ toggle on {Parse::AtlasSearch}) or
132
+ # `require_session_token` toggle on {Parse::AtlasSearch}) or
133
133
  # treat as public-only.
134
134
  #
135
135
  # Cache layering: token-to-user_id is checked first; on hit
136
- # the slower +/users/me+ round-trip is skipped. User-to-roles
136
+ # the slower `/users/me` round-trip is skipped. User-to-roles
137
137
  # is then checked independently (a single user shared across
138
138
  # sessions amortizes the role lookup).
139
139
  #
140
- # @param session_token [String, nil] the +X-Parse-Session-Token+
140
+ # @param session_token [String, nil] the `X-Parse-Session-Token`
141
141
  # value from the requesting session.
142
142
  # @return [Resolved]
143
143
  # @raise [InvalidSession] when the token cannot be resolved by
144
- # +/users/me+ (404 / 209 invalid session token / 401).
144
+ # `/users/me` (404 / 209 invalid session token / 401).
145
145
  def resolve(session_token)
146
146
  return Resolved.new(nil, Set.new) if session_token.nil? || session_token.to_s.empty?
147
147
 
@@ -150,12 +150,12 @@ module Parse
150
150
  Resolved.new(user_id, role_names)
151
151
  end
152
152
 
153
- # Forget a +session_token+ entry from the session-token cache.
153
+ # Forget a `session_token` entry from the session-token cache.
154
154
  # Apps that revoke sessions out-of-band (logout, password
155
155
  # reset, admin revoke) should call this from the same path so
156
156
  # subsequent Atlas Search requests don't act on the stale
157
- # +user_id+ mapping. The +role_names+ cache is keyed on
158
- # +user_id+ and is not affected — call {.invalidate_user_roles}
157
+ # `user_id` mapping. The `role_names` cache is keyed on
158
+ # `user_id` and is not affected — call {.invalidate_user_roles}
159
159
  # to clear that separately.
160
160
  # @param session_token [String]
161
161
  def invalidate(session_token)
@@ -163,8 +163,8 @@ module Parse
163
163
  Parse::AtlasSearch.session_cache.invalidate(session_token.to_s)
164
164
  end
165
165
 
166
- # Forget cached role membership for a +user_id+. Call after any
167
- # +_Role.users+ mutation that affects this user (role grant /
166
+ # Forget cached role membership for a `user_id`. Call after any
167
+ # `_Role.users` mutation that affects this user (role grant /
168
168
  # revoke, role-graph reshape).
169
169
  # @param user_id [String]
170
170
  def invalidate_user_roles(user_id)
@@ -184,7 +184,7 @@ module Parse
184
184
 
185
185
  # @!visibility private
186
186
  # Resolve session_token → user_id via cache, falling through
187
- # to +/users/me+. Raises {InvalidSession} on lookup failure;
187
+ # to `/users/me`. Raises {InvalidSession} on lookup failure;
188
188
  # the caller is responsible for refusing the request.
189
189
  def lookup_user_id(session_token)
190
190
  cache = Parse::AtlasSearch.session_cache