devora-fastapi 0.1.0__tar.gz → 0.1.2__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.
- devora_fastapi-0.1.2/PKG-INFO +159 -0
- devora_fastapi-0.1.2/README.md +130 -0
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/pyproject.toml +2 -2
- devora_fastapi-0.1.0/PKG-INFO +0 -122
- devora_fastapi-0.1.0/README.md +0 -93
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/.gitignore +0 -0
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/LICENSE +0 -0
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/__init__.py +0 -0
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/adapter.py +0 -0
- {devora_fastapi-0.1.0 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/py.typed +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: devora-fastapi
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: FastAPI adapter for the Devora Python backend SDK
|
|
5
|
+
Project-URL: Documentation, https://docs.devora.sh
|
|
6
|
+
Project-URL: Repository, https://github.com/getdevora/devora-sdks
|
|
7
|
+
Project-URL: Issues, https://github.com/getdevora/devora-sdks/issues
|
|
8
|
+
Author: Devora
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: devora,fastapi,impersonation,sdk,security
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: <4.0,>=3.10
|
|
23
|
+
Requires-Dist: anyio>=4.14.2
|
|
24
|
+
Requires-Dist: devora-python<0.2.0,>=0.1.2
|
|
25
|
+
Requires-Dist: fastapi<1.0.0,>=0.133.0
|
|
26
|
+
Requires-Dist: idna>=3.15
|
|
27
|
+
Requires-Dist: starlette>=1.3.1
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# devora-fastapi
|
|
31
|
+
|
|
32
|
+
Recording, masking and capture policy are configured in the Devora dashboard and
|
|
33
|
+
authorized server-side for each session. This backend SDK takes no capture
|
|
34
|
+
settings, and `devora_sdk()` ignores keyword arguments it does not recognize,
|
|
35
|
+
so check option names carefully. See
|
|
36
|
+
[capture settings](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
|
|
37
|
+
|
|
38
|
+
FastAPI adapter for the Devora Python backend SDK.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Python `>=3.10,<4.0`
|
|
43
|
+
- FastAPI `>=0.133.0,<1.0.0`
|
|
44
|
+
- Outbound HTTPS access from your backend to the Devora API
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install devora-python devora-fastapi
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import os
|
|
56
|
+
|
|
57
|
+
from fastapi import FastAPI
|
|
58
|
+
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
59
|
+
from devora_sdk_fastapi import fastapi_router
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
sdk = devora_sdk(
|
|
63
|
+
api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
|
|
64
|
+
secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
|
|
65
|
+
org_id=os.environ["DEVORA_ORG_ID"],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
70
|
+
async def search_users(req):
|
|
71
|
+
# Match name, email and the exact user ID with a parameterised query.
|
|
72
|
+
term = req.query.get("term", "")
|
|
73
|
+
limit = int(req.query.get("limit", 10))
|
|
74
|
+
users = await search_customer_users(term, limit)
|
|
75
|
+
return {
|
|
76
|
+
"users": [
|
|
77
|
+
{
|
|
78
|
+
"id": u.id,
|
|
79
|
+
"name": u.name,
|
|
80
|
+
"email": u.email,
|
|
81
|
+
"attributes": {"company": u.company, "role": u.role, "plan": u.plan},
|
|
82
|
+
}
|
|
83
|
+
for u in users
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@sdk.register(DEVORA_ENDPOINTS.TERMINATE)
|
|
89
|
+
async def terminate(req):
|
|
90
|
+
session_id = req.params["id"]
|
|
91
|
+
reason = (req.body or {}).get("reason")
|
|
92
|
+
# YOU IMPLEMENT: mark this Devora session revoked so every credential issued for it
|
|
93
|
+
# is rejected, including one issued after this call. Must be idempotent: Devora
|
|
94
|
+
# retries with a new request id (up to 5 attempts over 6 hours).
|
|
95
|
+
await auth.revoke_impersonation_session(session_id=session_id, reason=reason)
|
|
96
|
+
return {"success": True}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
app = FastAPI()
|
|
100
|
+
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
User search should match name, email and the exact user ID. `attributes` are optional display fields such as company, role or plan: up to 12 per user, lowercase keys like `last_login`, and string, number, boolean or `null` values. Devora drops invalid entries silently; see [Search results and templates](https://docs.devora.sh/guide/search-results) for the limits and how your team lays out results.
|
|
104
|
+
|
|
105
|
+
Register every handler before calling `fastapi_router(sdk)`; routes registered
|
|
106
|
+
later are not mounted. Handlers may be sync or async; sync handlers run in a
|
|
107
|
+
worker thread.
|
|
108
|
+
|
|
109
|
+
Each verified request is claimed once from Devora before your handler runs, so
|
|
110
|
+
replay protection needs no storage on your side; your backend only needs
|
|
111
|
+
outbound HTTPS access to the Devora API. See
|
|
112
|
+
[SIGNING.md](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#request-claims).
|
|
113
|
+
|
|
114
|
+
The terminate body is `{"reason": ..., "terminatedBy": ...}`; Devora retries a
|
|
115
|
+
failed call (up to 5 attempts over 6 hours) with a new request id, so keep the
|
|
116
|
+
handler idempotent. Return a 2xx, or 429/503 with `Retry-After` when overloaded; any
|
|
117
|
+
other 4xx stops the retries. See
|
|
118
|
+
[Session lifecycle & cleanup](https://docs.devora.sh/guide/session-lifecycle).
|
|
119
|
+
|
|
120
|
+
For protected application routes, install the guard middleware and return the
|
|
121
|
+
context dict your `IMPERSONATE` handler stored, read back from your
|
|
122
|
+
authenticated request state.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_impersonation_context(request) -> dict | None:
|
|
129
|
+
# YOU IMPLEMENT: return the dict your IMPERSONATE handler stored, read from
|
|
130
|
+
# your auth middleware (never from headers or JSON the browser can set), or None.
|
|
131
|
+
return getattr(request.state, "devora", None)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
app.add_middleware(
|
|
135
|
+
DevoraImpersonationGuardMiddleware,
|
|
136
|
+
sdk=sdk,
|
|
137
|
+
get_impersonation_context=get_impersonation_context,
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Register your own authentication middleware **after** this call — Starlette
|
|
142
|
+
runs middleware in the reverse of its registration order, so it must be the
|
|
143
|
+
outer layer that runs first for `request.state` to carry verified claims by
|
|
144
|
+
the time the guard reads them.
|
|
145
|
+
|
|
146
|
+
Resolve method overrides and route rewrites **before** the guard as well
|
|
147
|
+
(register that middleware after this call too, so it runs first). The guard
|
|
148
|
+
also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
|
|
149
|
+
`X-Method-Override` value and any `_method` query parameter, but it cannot see a
|
|
150
|
+
`_method` field inside a request body, and it judges the path it receives.
|
|
151
|
+
|
|
152
|
+
`expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
|
|
153
|
+
seconds, multiply by `1000` when building the impersonation context. `actor`,
|
|
154
|
+
`subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
|
|
155
|
+
all required — the guard rejects the context as invalid without them, even
|
|
156
|
+
though the dataclass marks them optional for construction convenience.
|
|
157
|
+
|
|
158
|
+
Options, error codes and the browser-session bridge are documented in the
|
|
159
|
+
[Python reference](https://docs.devora.sh/reference/python).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# devora-fastapi
|
|
2
|
+
|
|
3
|
+
Recording, masking and capture policy are configured in the Devora dashboard and
|
|
4
|
+
authorized server-side for each session. This backend SDK takes no capture
|
|
5
|
+
settings, and `devora_sdk()` ignores keyword arguments it does not recognize,
|
|
6
|
+
so check option names carefully. See
|
|
7
|
+
[capture settings](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
|
|
8
|
+
|
|
9
|
+
FastAPI adapter for the Devora Python backend SDK.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Python `>=3.10,<4.0`
|
|
14
|
+
- FastAPI `>=0.133.0,<1.0.0`
|
|
15
|
+
- Outbound HTTPS access from your backend to the Devora API
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install devora-python devora-fastapi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
from fastapi import FastAPI
|
|
29
|
+
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
30
|
+
from devora_sdk_fastapi import fastapi_router
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
sdk = devora_sdk(
|
|
34
|
+
api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
|
|
35
|
+
secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
|
|
36
|
+
org_id=os.environ["DEVORA_ORG_ID"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
41
|
+
async def search_users(req):
|
|
42
|
+
# Match name, email and the exact user ID with a parameterised query.
|
|
43
|
+
term = req.query.get("term", "")
|
|
44
|
+
limit = int(req.query.get("limit", 10))
|
|
45
|
+
users = await search_customer_users(term, limit)
|
|
46
|
+
return {
|
|
47
|
+
"users": [
|
|
48
|
+
{
|
|
49
|
+
"id": u.id,
|
|
50
|
+
"name": u.name,
|
|
51
|
+
"email": u.email,
|
|
52
|
+
"attributes": {"company": u.company, "role": u.role, "plan": u.plan},
|
|
53
|
+
}
|
|
54
|
+
for u in users
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@sdk.register(DEVORA_ENDPOINTS.TERMINATE)
|
|
60
|
+
async def terminate(req):
|
|
61
|
+
session_id = req.params["id"]
|
|
62
|
+
reason = (req.body or {}).get("reason")
|
|
63
|
+
# YOU IMPLEMENT: mark this Devora session revoked so every credential issued for it
|
|
64
|
+
# is rejected, including one issued after this call. Must be idempotent: Devora
|
|
65
|
+
# retries with a new request id (up to 5 attempts over 6 hours).
|
|
66
|
+
await auth.revoke_impersonation_session(session_id=session_id, reason=reason)
|
|
67
|
+
return {"success": True}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
app = FastAPI()
|
|
71
|
+
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
User search should match name, email and the exact user ID. `attributes` are optional display fields such as company, role or plan: up to 12 per user, lowercase keys like `last_login`, and string, number, boolean or `null` values. Devora drops invalid entries silently; see [Search results and templates](https://docs.devora.sh/guide/search-results) for the limits and how your team lays out results.
|
|
75
|
+
|
|
76
|
+
Register every handler before calling `fastapi_router(sdk)`; routes registered
|
|
77
|
+
later are not mounted. Handlers may be sync or async; sync handlers run in a
|
|
78
|
+
worker thread.
|
|
79
|
+
|
|
80
|
+
Each verified request is claimed once from Devora before your handler runs, so
|
|
81
|
+
replay protection needs no storage on your side; your backend only needs
|
|
82
|
+
outbound HTTPS access to the Devora API. See
|
|
83
|
+
[SIGNING.md](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#request-claims).
|
|
84
|
+
|
|
85
|
+
The terminate body is `{"reason": ..., "terminatedBy": ...}`; Devora retries a
|
|
86
|
+
failed call (up to 5 attempts over 6 hours) with a new request id, so keep the
|
|
87
|
+
handler idempotent. Return a 2xx, or 429/503 with `Retry-After` when overloaded; any
|
|
88
|
+
other 4xx stops the retries. See
|
|
89
|
+
[Session lifecycle & cleanup](https://docs.devora.sh/guide/session-lifecycle).
|
|
90
|
+
|
|
91
|
+
For protected application routes, install the guard middleware and return the
|
|
92
|
+
context dict your `IMPERSONATE` handler stored, read back from your
|
|
93
|
+
authenticated request state.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_impersonation_context(request) -> dict | None:
|
|
100
|
+
# YOU IMPLEMENT: return the dict your IMPERSONATE handler stored, read from
|
|
101
|
+
# your auth middleware (never from headers or JSON the browser can set), or None.
|
|
102
|
+
return getattr(request.state, "devora", None)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
app.add_middleware(
|
|
106
|
+
DevoraImpersonationGuardMiddleware,
|
|
107
|
+
sdk=sdk,
|
|
108
|
+
get_impersonation_context=get_impersonation_context,
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Register your own authentication middleware **after** this call — Starlette
|
|
113
|
+
runs middleware in the reverse of its registration order, so it must be the
|
|
114
|
+
outer layer that runs first for `request.state` to carry verified claims by
|
|
115
|
+
the time the guard reads them.
|
|
116
|
+
|
|
117
|
+
Resolve method overrides and route rewrites **before** the guard as well
|
|
118
|
+
(register that middleware after this call too, so it runs first). The guard
|
|
119
|
+
also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
|
|
120
|
+
`X-Method-Override` value and any `_method` query parameter, but it cannot see a
|
|
121
|
+
`_method` field inside a request body, and it judges the path it receives.
|
|
122
|
+
|
|
123
|
+
`expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
|
|
124
|
+
seconds, multiply by `1000` when building the impersonation context. `actor`,
|
|
125
|
+
`subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
|
|
126
|
+
all required — the guard rejects the context as invalid without them, even
|
|
127
|
+
though the dataclass marks them optional for construction convenience.
|
|
128
|
+
|
|
129
|
+
Options, error codes and the browser-session bridge are documented in the
|
|
130
|
+
[Python reference](https://docs.devora.sh/reference/python).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devora-fastapi"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "FastAPI adapter for the Devora Python backend SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<4.0"
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Typing :: Typed",
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
28
|
-
"devora-python>=0.1.
|
|
28
|
+
"devora-python>=0.1.2,<0.2.0",
|
|
29
29
|
# Security floors: the lowest versions without known advisories (audited 2026-09-28).
|
|
30
30
|
"fastapi>=0.133.0,<1.0.0",
|
|
31
31
|
"starlette>=1.3.1",
|
devora_fastapi-0.1.0/PKG-INFO
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: devora-fastapi
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: FastAPI adapter for the Devora Python backend SDK
|
|
5
|
-
Project-URL: Documentation, https://docs.devora.sh
|
|
6
|
-
Project-URL: Repository, https://github.com/getdevora/devora-sdks
|
|
7
|
-
Project-URL: Issues, https://github.com/getdevora/devora-sdks/issues
|
|
8
|
-
Author: Devora
|
|
9
|
-
License-Expression: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: devora,fastapi,impersonation,sdk,security
|
|
12
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
-
Classifier: Framework :: FastAPI
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
-
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: <4.0,>=3.10
|
|
23
|
-
Requires-Dist: anyio>=4.14.2
|
|
24
|
-
Requires-Dist: devora-python<0.2.0,>=0.1.0
|
|
25
|
-
Requires-Dist: fastapi<1.0.0,>=0.133.0
|
|
26
|
-
Requires-Dist: idna>=3.15
|
|
27
|
-
Requires-Dist: starlette>=1.3.1
|
|
28
|
-
Description-Content-Type: text/markdown
|
|
29
|
-
|
|
30
|
-
# devora-fastapi
|
|
31
|
-
|
|
32
|
-
Recording, masking and activity preferences are configured in Devora Settings.
|
|
33
|
-
SDK initialization overrides are ignored. New sessions retain the server's policy
|
|
34
|
-
snapshot across exchange and resume. Developer privacy labels take effect only
|
|
35
|
-
when selected in Settings; sensitive-field protection remains mandatory.
|
|
36
|
-
See [migration details](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
|
|
37
|
-
|
|
38
|
-
FastAPI adapter for the Devora Python backend SDK.
|
|
39
|
-
|
|
40
|
-
## Requirements
|
|
41
|
-
|
|
42
|
-
- Python `>=3.10,<4.0`
|
|
43
|
-
- FastAPI `>=0.100.0,<1.0.0`
|
|
44
|
-
|
|
45
|
-
## Install
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
pip install devora-python devora-fastapi
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Quick Start
|
|
52
|
-
|
|
53
|
-
```python
|
|
54
|
-
from fastapi import FastAPI
|
|
55
|
-
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
56
|
-
from devora_sdk_fastapi import fastapi_router
|
|
57
|
-
|
|
58
|
-
sdk = devora_sdk(
|
|
59
|
-
api_key="pk_server_live_...",
|
|
60
|
-
secret_key="sk_server_live_...",
|
|
61
|
-
org_id="org_...",
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
66
|
-
async def search_users(req):
|
|
67
|
-
return {"users": await search_customer_users(req.query.get("term", ""))}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
app = FastAPI()
|
|
71
|
-
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
For protected application routes, install the guard middleware and extract a
|
|
75
|
-
trusted impersonation context from authenticated request state.
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from devora_sdk import ImpersonationContext
|
|
79
|
-
from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def get_impersonation_context(request) -> ImpersonationContext | None:
|
|
83
|
-
devora = getattr(request.state, "devora", None)
|
|
84
|
-
if not devora:
|
|
85
|
-
return None
|
|
86
|
-
return ImpersonationContext(
|
|
87
|
-
is_impersonation=devora["isImpersonation"],
|
|
88
|
-
scope=devora["scope"],
|
|
89
|
-
session_id=devora["sessionId"],
|
|
90
|
-
expires_at=devora["expiresAt"],
|
|
91
|
-
actor=devora["actor"],
|
|
92
|
-
subject=devora["subject"],
|
|
93
|
-
auth_method=devora["authMethod"],
|
|
94
|
-
authorization_source=devora["authorizationSource"],
|
|
95
|
-
recording_allowed=devora["recordingAllowed"],
|
|
96
|
-
impersonator=devora.get("impersonator"),
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
app.add_middleware(
|
|
101
|
-
DevoraImpersonationGuardMiddleware,
|
|
102
|
-
sdk=sdk,
|
|
103
|
-
get_impersonation_context=get_impersonation_context,
|
|
104
|
-
)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Register your own authentication middleware **after** this call — Starlette
|
|
108
|
-
runs middleware in the reverse of its registration order, so it must be the
|
|
109
|
-
outer layer that runs first for `request.state` to carry verified claims by
|
|
110
|
-
the time the guard reads them.
|
|
111
|
-
|
|
112
|
-
Resolve method overrides and route rewrites **before** the guard as well
|
|
113
|
-
(register that middleware after this call too, so it runs first). The guard
|
|
114
|
-
also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
|
|
115
|
-
`X-Method-Override` value and any `_method` query parameter, but it cannot see a
|
|
116
|
-
`_method` field inside a request body, and it judges the path it receives.
|
|
117
|
-
|
|
118
|
-
`expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
|
|
119
|
-
seconds, multiply by `1000` when building the impersonation context. `actor`,
|
|
120
|
-
`subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
|
|
121
|
-
all required — the guard rejects the context as invalid without them, even
|
|
122
|
-
though the dataclass marks them optional for construction convenience.
|
devora_fastapi-0.1.0/README.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# devora-fastapi
|
|
2
|
-
|
|
3
|
-
Recording, masking and activity preferences are configured in Devora Settings.
|
|
4
|
-
SDK initialization overrides are ignored. New sessions retain the server's policy
|
|
5
|
-
snapshot across exchange and resume. Developer privacy labels take effect only
|
|
6
|
-
when selected in Settings; sensitive-field protection remains mandatory.
|
|
7
|
-
See [migration details](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
|
|
8
|
-
|
|
9
|
-
FastAPI adapter for the Devora Python backend SDK.
|
|
10
|
-
|
|
11
|
-
## Requirements
|
|
12
|
-
|
|
13
|
-
- Python `>=3.10,<4.0`
|
|
14
|
-
- FastAPI `>=0.100.0,<1.0.0`
|
|
15
|
-
|
|
16
|
-
## Install
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
pip install devora-python devora-fastapi
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
```python
|
|
25
|
-
from fastapi import FastAPI
|
|
26
|
-
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
27
|
-
from devora_sdk_fastapi import fastapi_router
|
|
28
|
-
|
|
29
|
-
sdk = devora_sdk(
|
|
30
|
-
api_key="pk_server_live_...",
|
|
31
|
-
secret_key="sk_server_live_...",
|
|
32
|
-
org_id="org_...",
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
37
|
-
async def search_users(req):
|
|
38
|
-
return {"users": await search_customer_users(req.query.get("term", ""))}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
app = FastAPI()
|
|
42
|
-
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
For protected application routes, install the guard middleware and extract a
|
|
46
|
-
trusted impersonation context from authenticated request state.
|
|
47
|
-
|
|
48
|
-
```python
|
|
49
|
-
from devora_sdk import ImpersonationContext
|
|
50
|
-
from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def get_impersonation_context(request) -> ImpersonationContext | None:
|
|
54
|
-
devora = getattr(request.state, "devora", None)
|
|
55
|
-
if not devora:
|
|
56
|
-
return None
|
|
57
|
-
return ImpersonationContext(
|
|
58
|
-
is_impersonation=devora["isImpersonation"],
|
|
59
|
-
scope=devora["scope"],
|
|
60
|
-
session_id=devora["sessionId"],
|
|
61
|
-
expires_at=devora["expiresAt"],
|
|
62
|
-
actor=devora["actor"],
|
|
63
|
-
subject=devora["subject"],
|
|
64
|
-
auth_method=devora["authMethod"],
|
|
65
|
-
authorization_source=devora["authorizationSource"],
|
|
66
|
-
recording_allowed=devora["recordingAllowed"],
|
|
67
|
-
impersonator=devora.get("impersonator"),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
app.add_middleware(
|
|
72
|
-
DevoraImpersonationGuardMiddleware,
|
|
73
|
-
sdk=sdk,
|
|
74
|
-
get_impersonation_context=get_impersonation_context,
|
|
75
|
-
)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Register your own authentication middleware **after** this call — Starlette
|
|
79
|
-
runs middleware in the reverse of its registration order, so it must be the
|
|
80
|
-
outer layer that runs first for `request.state` to carry verified claims by
|
|
81
|
-
the time the guard reads them.
|
|
82
|
-
|
|
83
|
-
Resolve method overrides and route rewrites **before** the guard as well
|
|
84
|
-
(register that middleware after this call too, so it runs first). The guard
|
|
85
|
-
also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
|
|
86
|
-
`X-Method-Override` value and any `_method` query parameter, but it cannot see a
|
|
87
|
-
`_method` field inside a request body, and it judges the path it receives.
|
|
88
|
-
|
|
89
|
-
`expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
|
|
90
|
-
seconds, multiply by `1000` when building the impersonation context. `actor`,
|
|
91
|
-
`subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
|
|
92
|
-
all required — the guard rejects the context as invalid without them, even
|
|
93
|
-
though the dataclass marks them optional for construction convenience.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|