rare-platform-sdk 0.1.0__tar.gz

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.
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: rare-platform-sdk
3
+ Version: 0.1.0
4
+ Summary: Rare platform integration kit for Python services
5
+ License-Expression: Apache-2.0
6
+ Project-URL: Homepage, https://rareid.cc
7
+ Project-URL: Repository, https://github.com/Rare-ID/Rare
8
+ Project-URL: Documentation, https://github.com/Rare-ID/Rare/tree/main/packages/platform/python/rare-platform-sdk-python
9
+ Project-URL: Issues, https://github.com/Rare-ID/Rare/issues
10
+ Keywords: rare,platform,identity,delegation,attestation
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: fastapi>=0.115.0
14
+ Requires-Dist: httpx>=0.27.0
15
+ Requires-Dist: rare-identity-protocol>=0.1.0
16
+ Requires-Dist: rare-identity-verifier>=0.1.0
17
+ Provides-Extra: redis
18
+ Requires-Dist: redis>=5.0.0; extra == "redis"
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=8.2.0; extra == "test"
21
+
22
+ # rare-platform-sdk
23
+
24
+ Python toolkit for third-party platforms integrating Rare with local verification-first defaults.
25
+
26
+ ## What It Is
27
+
28
+ `rare-platform-sdk` helps Python services issue Rare auth challenges, complete login, verify delegated signed actions, manage platform sessions, and optionally ingest signed negative-event signals back into Rare.
29
+
30
+ ## Who It Is For
31
+
32
+ - Python and FastAPI platforms adding Rare login
33
+ - Backend teams that want local identity/delegation verification
34
+ - Integrators that need Redis-backed replay protection and session storage
35
+
36
+ ## Quick Start
37
+
38
+ ```bash
39
+ pip install rare-platform-sdk
40
+ ```
41
+
42
+ ```python
43
+ from rare_platform_sdk import (
44
+ InMemoryChallengeStore,
45
+ InMemoryReplayStore,
46
+ InMemorySessionStore,
47
+ RareApiClient,
48
+ RarePlatformKitConfig,
49
+ create_rare_platform_kit,
50
+ )
51
+
52
+ rare = RareApiClient(rare_base_url="https://api.rareid.cc")
53
+ kit = create_rare_platform_kit(
54
+ RarePlatformKitConfig(
55
+ aud="platform",
56
+ rare_api_client=rare,
57
+ challenge_store=InMemoryChallengeStore(),
58
+ replay_store=InMemoryReplayStore(),
59
+ session_store=InMemorySessionStore(),
60
+ )
61
+ )
62
+ ```
63
+
64
+ FastAPI integration:
65
+
66
+ ```python
67
+ from fastapi import FastAPI
68
+ from rare_platform_sdk import create_fastapi_rare_router
69
+
70
+ app = FastAPI()
71
+ app.include_router(create_fastapi_rare_router(kit, prefix="/rare"))
72
+ ```
73
+
74
+ ## Production Notes
75
+
76
+ - Challenge nonces must be one-time use.
77
+ - Delegation replay protection must be atomic.
78
+ - Full identity mode requires `payload.aud == expected_aud`.
79
+ - Identity triad must match:
80
+ `auth_complete.agent_id == delegation.agent_id == attestation.sub`
81
+ - Public identity mode is capped to `L1` effective governance.
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ pip install -r requirements-test.lock
87
+ pip install -e .[test] --no-deps
88
+ pytest -q
89
+ python -m build
90
+ ```
@@ -0,0 +1,69 @@
1
+ # rare-platform-sdk
2
+
3
+ Python toolkit for third-party platforms integrating Rare with local verification-first defaults.
4
+
5
+ ## What It Is
6
+
7
+ `rare-platform-sdk` helps Python services issue Rare auth challenges, complete login, verify delegated signed actions, manage platform sessions, and optionally ingest signed negative-event signals back into Rare.
8
+
9
+ ## Who It Is For
10
+
11
+ - Python and FastAPI platforms adding Rare login
12
+ - Backend teams that want local identity/delegation verification
13
+ - Integrators that need Redis-backed replay protection and session storage
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ pip install rare-platform-sdk
19
+ ```
20
+
21
+ ```python
22
+ from rare_platform_sdk import (
23
+ InMemoryChallengeStore,
24
+ InMemoryReplayStore,
25
+ InMemorySessionStore,
26
+ RareApiClient,
27
+ RarePlatformKitConfig,
28
+ create_rare_platform_kit,
29
+ )
30
+
31
+ rare = RareApiClient(rare_base_url="https://api.rareid.cc")
32
+ kit = create_rare_platform_kit(
33
+ RarePlatformKitConfig(
34
+ aud="platform",
35
+ rare_api_client=rare,
36
+ challenge_store=InMemoryChallengeStore(),
37
+ replay_store=InMemoryReplayStore(),
38
+ session_store=InMemorySessionStore(),
39
+ )
40
+ )
41
+ ```
42
+
43
+ FastAPI integration:
44
+
45
+ ```python
46
+ from fastapi import FastAPI
47
+ from rare_platform_sdk import create_fastapi_rare_router
48
+
49
+ app = FastAPI()
50
+ app.include_router(create_fastapi_rare_router(kit, prefix="/rare"))
51
+ ```
52
+
53
+ ## Production Notes
54
+
55
+ - Challenge nonces must be one-time use.
56
+ - Delegation replay protection must be atomic.
57
+ - Full identity mode requires `payload.aud == expected_aud`.
58
+ - Identity triad must match:
59
+ `auth_complete.agent_id == delegation.agent_id == attestation.sub`
60
+ - Public identity mode is capped to `L1` effective governance.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ pip install -r requirements-test.lock
66
+ pip install -e .[test] --no-deps
67
+ pytest -q
68
+ python -m build
69
+ ```
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rare-platform-sdk"
7
+ version = "0.1.0"
8
+ description = "Rare platform integration kit for Python services"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.11"
12
+ dependencies = [
13
+ "fastapi>=0.115.0",
14
+ "httpx>=0.27.0",
15
+ "rare-identity-protocol>=0.1.0",
16
+ "rare-identity-verifier>=0.1.0",
17
+ ]
18
+ keywords = ["rare", "platform", "identity", "delegation", "attestation"]
19
+
20
+ [project.optional-dependencies]
21
+ redis = [
22
+ "redis>=5.0.0",
23
+ ]
24
+ test = [
25
+ "pytest>=8.2.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://rareid.cc"
30
+ Repository = "https://github.com/Rare-ID/Rare"
31
+ Documentation = "https://github.com/Rare-ID/Rare/tree/main/packages/platform/python/rare-platform-sdk-python"
32
+ Issues = "https://github.com/Rare-ID/Rare/issues"
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests"]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+ include = ["rare_platform_sdk*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,58 @@
1
+ from rare_platform_sdk.client import ApiError, RareApiClient, RareApiClientError
2
+ from rare_platform_sdk.fastapi import (
3
+ AuthChallengeRequest,
4
+ AuthChallengeResponse,
5
+ AuthCompleteRequest,
6
+ AuthCompleteResponse,
7
+ create_fastapi_rare_router,
8
+ )
9
+ from rare_platform_sdk.kit import create_rare_platform_kit, sign_platform_event_token
10
+ from rare_platform_sdk.stores import (
11
+ InMemoryChallengeStore,
12
+ InMemoryReplayStore,
13
+ InMemorySessionStore,
14
+ RedisChallengeStore,
15
+ RedisReplayStore,
16
+ RedisSessionStore,
17
+ )
18
+ from rare_platform_sdk.types import (
19
+ AuthChallenge,
20
+ AuthCompleteInput,
21
+ AuthCompleteResult,
22
+ IngestEventsInput,
23
+ IngestEventsResult,
24
+ PlatformSession,
25
+ RarePlatformEventItem,
26
+ RarePlatformKitConfig,
27
+ VerifiedActionContext,
28
+ VerifyActionInput,
29
+ )
30
+
31
+ __all__ = [
32
+ "ApiError",
33
+ "AuthChallenge",
34
+ "AuthChallengeRequest",
35
+ "AuthChallengeResponse",
36
+ "AuthCompleteInput",
37
+ "AuthCompleteRequest",
38
+ "AuthCompleteResponse",
39
+ "AuthCompleteResult",
40
+ "InMemoryChallengeStore",
41
+ "InMemoryReplayStore",
42
+ "InMemorySessionStore",
43
+ "IngestEventsInput",
44
+ "IngestEventsResult",
45
+ "PlatformSession",
46
+ "RareApiClient",
47
+ "RareApiClientError",
48
+ "RarePlatformEventItem",
49
+ "RarePlatformKitConfig",
50
+ "RedisChallengeStore",
51
+ "RedisReplayStore",
52
+ "RedisSessionStore",
53
+ "VerifiedActionContext",
54
+ "VerifyActionInput",
55
+ "create_fastapi_rare_router",
56
+ "create_rare_platform_kit",
57
+ "sign_platform_event_token",
58
+ ]
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+
9
+ class RareApiClientError(RuntimeError):
10
+ """Raised for Rare API failures."""
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ApiError(RareApiClientError):
15
+ status_code: int
16
+ detail: str
17
+
18
+ def __str__(self) -> str:
19
+ return f"rare api error {self.status_code}: {self.detail}"
20
+
21
+
22
+ class RareApiClient:
23
+ def __init__(
24
+ self,
25
+ *,
26
+ rare_base_url: str,
27
+ http_client: httpx.AsyncClient | None = None,
28
+ default_headers: dict[str, str] | None = None,
29
+ timeout_seconds: float = 10.0,
30
+ ) -> None:
31
+ self.rare_base_url = rare_base_url.rstrip("/")
32
+ self.default_headers = default_headers or {}
33
+ self._owns_http_client = http_client is None
34
+ self._http = http_client or httpx.AsyncClient(timeout=timeout_seconds)
35
+
36
+ async def aclose(self) -> None:
37
+ if self._owns_http_client:
38
+ await self._http.aclose()
39
+
40
+ async def get_jwks(self) -> dict[str, Any]:
41
+ return await self._request_json("GET", "/.well-known/rare-keys.json")
42
+
43
+ async def issue_platform_register_challenge(
44
+ self, *, platform_aud: str, domain: str
45
+ ) -> dict[str, Any]:
46
+ return await self._request_json(
47
+ "POST",
48
+ "/v1/platforms/register/challenge",
49
+ {"platform_aud": platform_aud, "domain": domain},
50
+ )
51
+
52
+ async def complete_platform_register(
53
+ self,
54
+ *,
55
+ challenge_id: str,
56
+ platform_id: str,
57
+ platform_aud: str,
58
+ domain: str,
59
+ keys: list[dict[str, str]],
60
+ ) -> dict[str, Any]:
61
+ return await self._request_json(
62
+ "POST",
63
+ "/v1/platforms/register/complete",
64
+ {
65
+ "challenge_id": challenge_id,
66
+ "platform_id": platform_id,
67
+ "platform_aud": platform_aud,
68
+ "domain": domain,
69
+ "keys": keys,
70
+ },
71
+ )
72
+
73
+ async def ingest_platform_events(self, event_token: str) -> dict[str, Any]:
74
+ return await self._request_json(
75
+ "POST",
76
+ "/v1/identity-library/events/ingest",
77
+ {"event_token": event_token},
78
+ )
79
+
80
+ async def _request_json(
81
+ self, method: str, path: str, body: dict[str, Any] | None = None
82
+ ) -> dict[str, Any]:
83
+ response = await self._http.request(
84
+ method,
85
+ f"{self.rare_base_url}{path}",
86
+ headers={
87
+ "Content-Type": "application/json",
88
+ **self.default_headers,
89
+ },
90
+ json=body,
91
+ )
92
+
93
+ payload: dict[str, Any] | str
94
+ try:
95
+ payload = response.json()
96
+ except Exception: # noqa: BLE001
97
+ payload = response.text
98
+
99
+ if response.status_code >= 400:
100
+ if isinstance(payload, dict):
101
+ detail = str(payload.get("detail") or payload)
102
+ else:
103
+ detail = payload
104
+ raise ApiError(status_code=response.status_code, detail=detail)
105
+
106
+ if not isinstance(payload, dict):
107
+ raise RareApiClientError("expected JSON object response")
108
+ return payload
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict
4
+ from typing import Any
5
+
6
+ from fastapi import APIRouter, HTTPException
7
+ from pydantic import BaseModel
8
+
9
+ from rare_identity_protocol import SignatureError, TokenValidationError
10
+
11
+ from rare_platform_sdk.types import AuthCompleteInput, RarePlatformKit
12
+
13
+
14
+ class AuthChallengeRequest(BaseModel):
15
+ aud: str | None = None
16
+
17
+
18
+ class AuthChallengeResponse(BaseModel):
19
+ nonce: str
20
+ aud: str
21
+ issued_at: int
22
+ expires_at: int
23
+
24
+
25
+ class AuthCompleteRequest(BaseModel):
26
+ nonce: str
27
+ agent_id: str
28
+ session_pubkey: str
29
+ delegation_token: str
30
+ signature_by_session: str
31
+ public_identity_attestation: str | None = None
32
+ full_identity_attestation: str | None = None
33
+
34
+
35
+ class AuthCompleteResponse(BaseModel):
36
+ session_token: str
37
+ agent_id: str
38
+ level: str
39
+ raw_level: str
40
+ identity_mode: str
41
+ display_name: str
42
+ session_pubkey: str
43
+
44
+
45
+ def _raise_http(exc: Exception) -> None:
46
+ if isinstance(exc, PermissionError):
47
+ raise HTTPException(status_code=401, detail=str(exc)) from exc
48
+ if isinstance(exc, (TokenValidationError, SignatureError, ValueError)):
49
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
50
+ raise HTTPException(status_code=500, detail="internal server error") from exc
51
+
52
+
53
+ def create_fastapi_rare_router(
54
+ kit: RarePlatformKit, prefix: str = ""
55
+ ) -> APIRouter:
56
+ router = APIRouter(prefix=prefix)
57
+
58
+ @router.post("/auth/challenge", response_model=AuthChallengeResponse)
59
+ async def auth_challenge(request: AuthChallengeRequest) -> AuthChallengeResponse:
60
+ try:
61
+ challenge = await kit.issue_challenge(request.aud)
62
+ return AuthChallengeResponse(**asdict(challenge))
63
+ except Exception as exc: # noqa: BLE001
64
+ _raise_http(exc)
65
+
66
+ @router.post("/auth/complete", response_model=AuthCompleteResponse)
67
+ async def auth_complete(request: AuthCompleteRequest) -> AuthCompleteResponse:
68
+ try:
69
+ result = await kit.complete_auth(
70
+ AuthCompleteInput(
71
+ nonce=request.nonce,
72
+ agent_id=request.agent_id,
73
+ session_pubkey=request.session_pubkey,
74
+ delegation_token=request.delegation_token,
75
+ signature_by_session=request.signature_by_session,
76
+ public_identity_attestation=request.public_identity_attestation,
77
+ full_identity_attestation=request.full_identity_attestation,
78
+ )
79
+ )
80
+ return AuthCompleteResponse(**asdict(result))
81
+ except Exception as exc: # noqa: BLE001
82
+ _raise_http(exc)
83
+
84
+ return router