fastapi-openrpc 0.1.3__py3-none-any.whl
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.
- fastapi_openrpc/__init__.py +104 -0
- fastapi_openrpc/authentication.py +248 -0
- fastapi_openrpc/authorization.py +79 -0
- fastapi_openrpc/constants.py +43 -0
- fastapi_openrpc/decorator.py +517 -0
- fastapi_openrpc/document.py +329 -0
- fastapi_openrpc/errors.py +125 -0
- fastapi_openrpc/registry.py +132 -0
- fastapi_openrpc/router.py +905 -0
- fastapi_openrpc/types.py +99 -0
- fastapi_openrpc-0.1.3.dist-info/METADATA +25 -0
- fastapi_openrpc-0.1.3.dist-info/RECORD +13 -0
- fastapi_openrpc-0.1.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""fastapi-openrpc: FastAPI JSON-RPC 2.0 + OpenRPC 1.0 plugin.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi_openrpc import OpenRpcRouter, method, RpcContext
|
|
7
|
+
|
|
8
|
+
app = FastAPI()
|
|
9
|
+
|
|
10
|
+
openrpc_router = OpenRpcRouter(
|
|
11
|
+
title="My API",
|
|
12
|
+
version="1.0.0",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
@method(name="getUser")
|
|
16
|
+
async def get_user(ctx: RpcContext, user_id: str) -> dict:
|
|
17
|
+
return {"id": user_id, "name": "Alice"}
|
|
18
|
+
|
|
19
|
+
app.include_router(openrpc_router)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from fastapi_openrpc.authentication import (
|
|
25
|
+
AuthenticationMiddleware,
|
|
26
|
+
HMACAuthentication,
|
|
27
|
+
)
|
|
28
|
+
from fastapi_openrpc.authorization import (
|
|
29
|
+
AuthorizationPolicy,
|
|
30
|
+
DefaultAuthorizationPolicy,
|
|
31
|
+
Require,
|
|
32
|
+
)
|
|
33
|
+
from fastapi_openrpc.constants import (
|
|
34
|
+
JSONRPC_FORBIDDEN,
|
|
35
|
+
JSONRPC_INTERNAL_ERROR,
|
|
36
|
+
JSONRPC_INVALID_PARAMS,
|
|
37
|
+
JSONRPC_INVALID_REQUEST,
|
|
38
|
+
JSONRPC_METHOD_NOT_FOUND,
|
|
39
|
+
JSONRPC_PARSE_ERROR,
|
|
40
|
+
JSONRPC_REPLAY_DETECTED,
|
|
41
|
+
OPENRPC_VERSION,
|
|
42
|
+
)
|
|
43
|
+
from fastapi_openrpc.decorator import method
|
|
44
|
+
from fastapi_openrpc.document import build_openrpc_document
|
|
45
|
+
from fastapi_openrpc.errors import (
|
|
46
|
+
AuthenticationError,
|
|
47
|
+
Forbidden,
|
|
48
|
+
InternalError,
|
|
49
|
+
InvalidParams,
|
|
50
|
+
InvalidRequest,
|
|
51
|
+
JsonRpcError,
|
|
52
|
+
MethodNotFound,
|
|
53
|
+
ParseError,
|
|
54
|
+
ReplayDetected,
|
|
55
|
+
RpcError,
|
|
56
|
+
)
|
|
57
|
+
from fastapi_openrpc.registry import add, clear, get, list_methods
|
|
58
|
+
from fastapi_openrpc.router import OpenRpcRouter
|
|
59
|
+
from fastapi_openrpc.types import MethodMeta, RpcContext
|
|
60
|
+
|
|
61
|
+
__version__ = "0.1.3"
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
# Version
|
|
65
|
+
"__version__",
|
|
66
|
+
"OPENRPC_VERSION",
|
|
67
|
+
# Core API
|
|
68
|
+
"method",
|
|
69
|
+
"OpenRpcRouter",
|
|
70
|
+
"build_openrpc_document",
|
|
71
|
+
# Functional Registration API
|
|
72
|
+
"add",
|
|
73
|
+
"get",
|
|
74
|
+
"list_methods",
|
|
75
|
+
"clear",
|
|
76
|
+
# Types
|
|
77
|
+
"MethodMeta",
|
|
78
|
+
"RpcContext",
|
|
79
|
+
"Require",
|
|
80
|
+
"AuthorizationPolicy",
|
|
81
|
+
"DefaultAuthorizationPolicy",
|
|
82
|
+
# Error Classes
|
|
83
|
+
"JsonRpcError",
|
|
84
|
+
"ParseError",
|
|
85
|
+
"InvalidRequest",
|
|
86
|
+
"MethodNotFound",
|
|
87
|
+
"InvalidParams",
|
|
88
|
+
"InternalError",
|
|
89
|
+
"Forbidden",
|
|
90
|
+
"ReplayDetected",
|
|
91
|
+
"RpcError",
|
|
92
|
+
# Constants
|
|
93
|
+
"JSONRPC_PARSE_ERROR",
|
|
94
|
+
"JSONRPC_INVALID_REQUEST",
|
|
95
|
+
"JSONRPC_METHOD_NOT_FOUND",
|
|
96
|
+
"JSONRPC_INVALID_PARAMS",
|
|
97
|
+
"JSONRPC_INTERNAL_ERROR",
|
|
98
|
+
"JSONRPC_FORBIDDEN",
|
|
99
|
+
"JSONRPC_REPLAY_DETECTED",
|
|
100
|
+
# Authentication
|
|
101
|
+
"AuthenticationMiddleware",
|
|
102
|
+
"AuthenticationError",
|
|
103
|
+
"HMACAuthentication",
|
|
104
|
+
]
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""fastapi-openrpc authentication component."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import redis.asyncio
|
|
14
|
+
from typing_extensions import Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
from fastapi_openrpc.errors import AuthenticationError, ReplayDetected
|
|
17
|
+
from fastapi_openrpc.types import RpcContext
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from starlette.requests import Request
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@runtime_checkable
|
|
26
|
+
class AuthenticationMiddleware(Protocol):
|
|
27
|
+
"""Authentication middleware Protocol.
|
|
28
|
+
|
|
29
|
+
Applications can implement custom authentication logic and inject it via
|
|
30
|
+
OpenRpcRouter(authentication=...).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
async def authenticate(self, request: Request) -> RpcContext:
|
|
34
|
+
"""Authenticate the requester.
|
|
35
|
+
|
|
36
|
+
Raises AuthenticationError on failure, returns RpcContext on success.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HMACAuthentication:
|
|
42
|
+
"""HMAC-SHA256 signature verification authentication.
|
|
43
|
+
|
|
44
|
+
Suitable for scenarios where requests are forwarded through a reverse proxy
|
|
45
|
+
(e.g., winwin-proxy). Supports two header formats:
|
|
46
|
+
|
|
47
|
+
- JSON object: ``{"user_id": "...", "roles": ["admin"]}``
|
|
48
|
+
- JSON array (direct): ``["admin", "developer"]``
|
|
49
|
+
|
|
50
|
+
Behavior:
|
|
51
|
+
- If ``secret_key`` is configured: verifies HMAC-SHA256 signature integrity;
|
|
52
|
+
raises AuthenticationError if signature is missing or invalid
|
|
53
|
+
- If ``secret_key=None``: skips signature verification and directly parses
|
|
54
|
+
header content (suitable for trusted internal network scenarios)
|
|
55
|
+
|
|
56
|
+
Optional Nonce replay attack protection:
|
|
57
|
+
- Requires ``enable_nonce=True`` and ``redis_url`` when enabled
|
|
58
|
+
- Uses Redis SETEX NX command for atomic deduplication
|
|
59
|
+
- Degrades according to ``redis_required`` config when Redis is unavailable
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
roles_header: str = "X-Forwarded-User-Info",
|
|
65
|
+
signature_header: str = "X-Forwarded-User-Info-Signature",
|
|
66
|
+
secret_key: str | None = None,
|
|
67
|
+
enable_nonce: bool = False,
|
|
68
|
+
redis_url: str | None = None,
|
|
69
|
+
nonce_ttl: int = 3600,
|
|
70
|
+
redis_required: bool = True,
|
|
71
|
+
connect_timeout: float = 2.0,
|
|
72
|
+
operation_timeout: float = 5.0,
|
|
73
|
+
cleanup_timeout: float = 2.0,
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Args:
|
|
77
|
+
roles_header: Header name for user info, defaults to ``X-Forwarded-User-Info``.
|
|
78
|
+
Header value can be JSON object (``{"roles": [...]}``) or JSON array.
|
|
79
|
+
signature_header: Header name for signature, defaults to
|
|
80
|
+
``X-Forwarded-User-Info-Signature``.
|
|
81
|
+
secret_key: HMAC signature key. ``None`` skips signature verification.
|
|
82
|
+
enable_nonce: Enable Nonce replay attack protection. Defaults to ``False``.
|
|
83
|
+
redis_url: Redis connection URL (e.g., ``redis://localhost:6379``).
|
|
84
|
+
Required when ``enable_nonce=True``.
|
|
85
|
+
nonce_ttl: Nonce TTL in Redis (seconds). Defaults to 3600 (1 hour).
|
|
86
|
+
redis_required: Whether to reject requests when Redis is unavailable.
|
|
87
|
+
When True (default) or False: Redis errors always result in
|
|
88
|
+
request rejection (fail-close). This parameter is kept for
|
|
89
|
+
behavioral configuration; Redis exceptions reject regardless.
|
|
90
|
+
connect_timeout: Redis connection timeout (seconds). Defaults to 2.
|
|
91
|
+
operation_timeout: Redis operation timeout (seconds). Defaults to 5.
|
|
92
|
+
cleanup_timeout: Redis connection cleanup timeout (seconds). Defaults to 2.
|
|
93
|
+
"""
|
|
94
|
+
if enable_nonce and not redis_url:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"redis_url is required when enable_nonce=True. "
|
|
97
|
+
"Example: redis_url='redis://localhost:6379'"
|
|
98
|
+
)
|
|
99
|
+
self.roles_header = roles_header
|
|
100
|
+
self.signature_header = signature_header
|
|
101
|
+
self.secret_key = secret_key
|
|
102
|
+
self.enable_nonce = enable_nonce
|
|
103
|
+
self.redis_url = redis_url
|
|
104
|
+
self.nonce_ttl = nonce_ttl
|
|
105
|
+
self.redis_required = redis_required
|
|
106
|
+
self.connect_timeout = connect_timeout
|
|
107
|
+
self.operation_timeout = operation_timeout
|
|
108
|
+
self.cleanup_timeout = cleanup_timeout
|
|
109
|
+
|
|
110
|
+
async def authenticate(self, request: Request) -> RpcContext:
|
|
111
|
+
"""Verify HMAC signature and parse role list.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
request: FastAPI/Starlette request object.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
RpcContext containing the parsed role list.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
AuthenticationError: Signature verification failed, invalid role format,
|
|
121
|
+
or Nonce replay.
|
|
122
|
+
"""
|
|
123
|
+
user_info_raw = request.headers.get(self.roles_header)
|
|
124
|
+
signature_raw = request.headers.get(self.signature_header)
|
|
125
|
+
|
|
126
|
+
# Step 1: Verify signature (if secret_key is configured)
|
|
127
|
+
if self.secret_key:
|
|
128
|
+
if not signature_raw:
|
|
129
|
+
raise AuthenticationError("Missing signature")
|
|
130
|
+
# user_info_raw may be None; use empty string to avoid .encode() TypeError
|
|
131
|
+
expected = hmac.new(
|
|
132
|
+
self.secret_key.encode(),
|
|
133
|
+
(user_info_raw or "").encode(),
|
|
134
|
+
hashlib.sha256,
|
|
135
|
+
).hexdigest()
|
|
136
|
+
if not hmac.compare_digest(expected, signature_raw):
|
|
137
|
+
raise AuthenticationError("Invalid signature")
|
|
138
|
+
|
|
139
|
+
# Step 2: Parse roles and user_id (runs even when secret_key=None;
|
|
140
|
+
# independent of signature verification)
|
|
141
|
+
user_info_dict: dict | list | None = None
|
|
142
|
+
if user_info_raw:
|
|
143
|
+
try:
|
|
144
|
+
user_info_dict = json.loads(user_info_raw)
|
|
145
|
+
except json.JSONDecodeError as err:
|
|
146
|
+
raise AuthenticationError("Invalid user info format") from err
|
|
147
|
+
# Supports two formats: JSON object ({"roles": [...], "user_id": "..."})
|
|
148
|
+
# or direct JSON array ([...])
|
|
149
|
+
if isinstance(user_info_dict, dict):
|
|
150
|
+
roles = user_info_dict.get("roles", [])
|
|
151
|
+
user_id = user_info_dict.get("user_id")
|
|
152
|
+
elif isinstance(user_info_dict, list):
|
|
153
|
+
roles = user_info_dict
|
|
154
|
+
user_id = None
|
|
155
|
+
else:
|
|
156
|
+
roles = []
|
|
157
|
+
user_id = None
|
|
158
|
+
else:
|
|
159
|
+
roles = []
|
|
160
|
+
user_id = None
|
|
161
|
+
|
|
162
|
+
# Step 3: Nonce verification (if enabled)
|
|
163
|
+
if self.enable_nonce:
|
|
164
|
+
nonce: str | None = None
|
|
165
|
+
if isinstance(user_info_dict, dict):
|
|
166
|
+
nonce = user_info_dict.get("nonce")
|
|
167
|
+
|
|
168
|
+
if not nonce:
|
|
169
|
+
raise AuthenticationError(
|
|
170
|
+
"Missing nonce. When enable_nonce=True, "
|
|
171
|
+
"the user_info header must contain a 'nonce' field. "
|
|
172
|
+
'Example: {"roles": ["admin"], "nonce": "uuid-here"}'
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
await self._verify_nonce(nonce)
|
|
176
|
+
|
|
177
|
+
return RpcContext(roles=roles, user_id=user_id)
|
|
178
|
+
|
|
179
|
+
async def _verify_nonce(self, nonce: str) -> None:
|
|
180
|
+
"""Verify whether the Nonce has already been used.
|
|
181
|
+
|
|
182
|
+
Uses Redis SET NX EX command for atomic operations:
|
|
183
|
+
- If nonce does not exist: SET succeeds, returns True, continue processing
|
|
184
|
+
- If nonce already exists: SET returns None, raises AuthenticationError
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
nonce: Client-provided unique identifier.
|
|
188
|
+
Must be 8-128 hexadecimal characters, may contain dashes.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
AuthenticationError: Nonce already used (replay attack) or Redis unavailable.
|
|
192
|
+
"""
|
|
193
|
+
# Nonce validation: length check first (cheap check first)
|
|
194
|
+
if len(nonce) < 8:
|
|
195
|
+
raise AuthenticationError("Nonce too short (minimum 8 characters)")
|
|
196
|
+
if len(nonce) > 128:
|
|
197
|
+
raise AuthenticationError("Nonce too long (maximum 128 characters)")
|
|
198
|
+
# Format validation: hexadecimal character set, supports UUID with or without dashes
|
|
199
|
+
if not re.match(r"^[a-fA-F0-9-]+$", nonce):
|
|
200
|
+
raise AuthenticationError(
|
|
201
|
+
"Invalid nonce format (must be hexadecimal with optional dashes)"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
redis_client = None
|
|
205
|
+
try:
|
|
206
|
+
# redis_url already validated in __init__; must exist when enable_nonce=True
|
|
207
|
+
assert self.redis_url is not None
|
|
208
|
+
redis_client = redis.asyncio.from_url(
|
|
209
|
+
self.redis_url,
|
|
210
|
+
socket_connect_timeout=self.connect_timeout,
|
|
211
|
+
socket_timeout=self.operation_timeout,
|
|
212
|
+
)
|
|
213
|
+
key = f"fastapi_openrpc:nonce:{nonce}"
|
|
214
|
+
# SET key value EX seconds NX: set only if key does not exist, with expiration
|
|
215
|
+
result = await redis_client.set(key, "1", ex=self.nonce_ttl, nx=True)
|
|
216
|
+
if result is None:
|
|
217
|
+
raise ReplayDetected("Replay detected: nonce has already been used")
|
|
218
|
+
except redis.asyncio.RedisError as err:
|
|
219
|
+
logger.error(
|
|
220
|
+
"Redis error during nonce verification: %s",
|
|
221
|
+
err,
|
|
222
|
+
)
|
|
223
|
+
# fail-close: reject request when Redis is unavailable
|
|
224
|
+
raise AuthenticationError("Redis unavailable, cannot verify nonce") from err
|
|
225
|
+
finally:
|
|
226
|
+
# Always clean up Redis connection, whether success or failure
|
|
227
|
+
await self._cleanup_redis(redis_client, nonce)
|
|
228
|
+
|
|
229
|
+
async def _cleanup_redis(self, redis_client: redis.asyncio.Redis | None, nonce: str) -> None:
|
|
230
|
+
"""Clean up Redis connection with timeout protection."""
|
|
231
|
+
if redis_client is None:
|
|
232
|
+
return
|
|
233
|
+
try:
|
|
234
|
+
await asyncio.wait_for(redis_client.aclose(), timeout=self.cleanup_timeout)
|
|
235
|
+
except TimeoutError:
|
|
236
|
+
logger.warning(
|
|
237
|
+
"Redis aclose() timed out after %ss for key: fastapi_openrpc:nonce:%s",
|
|
238
|
+
self.cleanup_timeout,
|
|
239
|
+
nonce[:8] + "...",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
__all__ = [
|
|
244
|
+
"AuthenticationError",
|
|
245
|
+
"AuthenticationMiddleware",
|
|
246
|
+
"HMACAuthentication",
|
|
247
|
+
"ReplayDetected",
|
|
248
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Authorization abstraction and default implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from fastapi_openrpc.types import Require
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from fastapi_openrpc.types import RpcContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class AuthorizationPolicy(Protocol):
|
|
19
|
+
"""Authorization policy interface.
|
|
20
|
+
|
|
21
|
+
Applications can implement a custom Policy and inject it via
|
|
22
|
+
OpenRpcRouter(authorization_policy=...).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def authorize(
|
|
26
|
+
self,
|
|
27
|
+
method_name: str,
|
|
28
|
+
required_roles: list[str],
|
|
29
|
+
ctx: RpcContext,
|
|
30
|
+
) -> bool:
|
|
31
|
+
"""Determine whether the caller is authorized to invoke the specified method.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
method_name: The name of the RPC method being invoked.
|
|
35
|
+
required_roles: List of roles required by the method.
|
|
36
|
+
ctx: Caller context containing roles and require fields.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if authorization is granted, False otherwise.
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DefaultAuthorizationPolicy:
|
|
45
|
+
"""Default authorization policy.
|
|
46
|
+
|
|
47
|
+
ANY semantics: True if any required_roles match.
|
|
48
|
+
ALL semantics: True only if all required_roles are present in ctx.roles.
|
|
49
|
+
Role comparison is case-insensitive (normalized to lowercase).
|
|
50
|
+
Policy exceptions are treated as authorization denial (fail-close).
|
|
51
|
+
Returns True when required_roles is empty (no authorization check).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def authorize(
|
|
55
|
+
self,
|
|
56
|
+
method_name: str,
|
|
57
|
+
required_roles: list[str],
|
|
58
|
+
ctx: RpcContext,
|
|
59
|
+
) -> bool:
|
|
60
|
+
# Empty role list -> skip check (backward compatible, R3)
|
|
61
|
+
if not required_roles:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
ctx_roles = {r.lower() for r in ctx.roles}
|
|
66
|
+
needed = {r.lower() for r in required_roles}
|
|
67
|
+
|
|
68
|
+
if ctx.require == Require.ALL:
|
|
69
|
+
return needed <= ctx_roles # All required_roles present in ctx.roles
|
|
70
|
+
else:
|
|
71
|
+
# Require.ANY (default)
|
|
72
|
+
return bool(ctx_roles & needed) # Any match == True
|
|
73
|
+
except Exception:
|
|
74
|
+
# Policy exception -> fail-close
|
|
75
|
+
logger.exception("Authorization policy error for %s", method_name)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ["Require", "AuthorizationPolicy", "DefaultAuthorizationPolicy"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""JSON-RPC 2.0 error code constants.
|
|
2
|
+
|
|
3
|
+
Standard error codes (JSON-RPC 2.0 spec):
|
|
4
|
+
-32700 Parse error
|
|
5
|
+
-32600 Invalid Request
|
|
6
|
+
-32601 Method not found
|
|
7
|
+
-32602 Invalid params
|
|
8
|
+
-32603 Internal error
|
|
9
|
+
|
|
10
|
+
Library-reserved application error codes (-32000 ~ -32099).
|
|
11
|
+
Custom application error codes should start from -32100.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# ----------------------------------------------------------------------
|
|
17
|
+
# JSON-RPC 2.0 Standard Error Codes
|
|
18
|
+
# ----------------------------------------------------------------------
|
|
19
|
+
JSONRPC_PARSE_ERROR = -32700
|
|
20
|
+
JSONRPC_INVALID_REQUEST = -32600
|
|
21
|
+
JSONRPC_METHOD_NOT_FOUND = -32601
|
|
22
|
+
JSONRPC_INVALID_PARAMS = -32602
|
|
23
|
+
JSONRPC_INTERNAL_ERROR = -32603
|
|
24
|
+
|
|
25
|
+
# ----------------------------------------------------------------------
|
|
26
|
+
# Library-reserved Application Error Codes (-32000 ~ -32099)
|
|
27
|
+
# ----------------------------------------------------------------------
|
|
28
|
+
# 0x7D00 = -32000: Forbidden / insufficient permissions
|
|
29
|
+
JSONRPC_FORBIDDEN = -32000
|
|
30
|
+
# 0x7D01 = -32001: Reserved (formerly used for insufficient permissions)
|
|
31
|
+
# 0x7D02 = -32002: Replay attack detected
|
|
32
|
+
JSONRPC_REPLAY_DETECTED = -32002
|
|
33
|
+
|
|
34
|
+
# ----------------------------------------------------------------------
|
|
35
|
+
# Security Limit Constants
|
|
36
|
+
# ----------------------------------------------------------------------
|
|
37
|
+
MAX_BATCH_SIZE = 50
|
|
38
|
+
MAX_BODY_BYTES = 1 * 1024 * 1024 # 1 MB
|
|
39
|
+
|
|
40
|
+
# ----------------------------------------------------------------------
|
|
41
|
+
# OpenRPC Version
|
|
42
|
+
# ----------------------------------------------------------------------
|
|
43
|
+
OPENRPC_VERSION = "1.0.0"
|