vercel-connect-bundle 0.1.0__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.
@@ -0,0 +1,454 @@
1
+ """Vercel Connect SDK surface.
2
+
3
+ Connect is a credential broker. Your deployment proves which project and
4
+ environment it is with the Vercel OIDC token the platform injects, and Connect
5
+ returns a short-lived credential for a third-party service. Your project never
6
+ stores provider secrets, and Connect owns OAuth client registration, PKCE,
7
+ state, refresh, and revocation server-side.
8
+
9
+ A connector must be attached to your project for the target environment before
10
+ any of these calls succeed.
11
+ """
12
+
13
+ from collections.abc import Awaitable, Callable, Mapping, Sequence
14
+ from typing import Any
15
+
16
+ from vercel._internal.core.session import get_active_session
17
+ from vercel.connect._internal.async_runtime import (
18
+ get_connector_metadata as _get_connector_metadata,
19
+ get_token as _get_token,
20
+ get_token_response as _get_token_response,
21
+ revoke_token as _revoke_token,
22
+ start_authorization as _start_authorization,
23
+ verify_connect_webhook as _verify_connect_webhook,
24
+ )
25
+ from vercel.connect._internal.errors import (
26
+ AuthorizationDeniedError,
27
+ AuthorizationExpiredError,
28
+ AuthorizationPendingError,
29
+ ConnectApiError,
30
+ ConnectCredentialsError,
31
+ ConnectError,
32
+ ConnectNotFoundError,
33
+ ConnectorInstallationRequiredError,
34
+ ConnectResponseError,
35
+ ConnectValidationError,
36
+ ConnectWebhookVerificationError,
37
+ InvalidGrantError,
38
+ NoValidTokenError,
39
+ UserAuthorizationRequiredError,
40
+ )
41
+ from vercel.connect._internal.models import (
42
+ ConnectAppTokenSubject,
43
+ ConnectAuthorizationDetail,
44
+ ConnectAuthorizationResponse,
45
+ ConnectCustomAuthorizationDetail,
46
+ ConnectGitHubAppInstallationAuthorizationDetail,
47
+ ConnectJwtBearerTokenSubject,
48
+ ConnectorMetadata,
49
+ ConnectorRef,
50
+ ConnectTokenExchangeSubject,
51
+ ConnectTokenResponse,
52
+ ConnectTokenSubject,
53
+ ConnectUserTokenSubject,
54
+ ConnectWebhookClaims,
55
+ DurationInput,
56
+ StringContainer,
57
+ )
58
+ from vercel.connect._internal.options import (
59
+ ConnectOptions,
60
+ ConnectServiceOptions,
61
+ VercelTokenInput,
62
+ )
63
+ from vercel.connect._internal.service import ConnectService, get_connect_service
64
+ from vercel.connect.version import __version__
65
+
66
+ from . import sync
67
+
68
+
69
+ def _service() -> ConnectService:
70
+ return get_connect_service(get_active_session())
71
+
72
+
73
+ async def get_token(
74
+ connector: str,
75
+ *,
76
+ subject: ConnectTokenSubject,
77
+ scopes: StringContainer | None = None,
78
+ installation_id: str | None = None,
79
+ audience: StringContainer | None = None,
80
+ resources: StringContainer | None = None,
81
+ authorization_details: Sequence[ConnectAuthorizationDetail] | None = None,
82
+ options: ConnectOptions | None = None,
83
+ ) -> str:
84
+ """Mint a short-lived upstream credential.
85
+
86
+ Args:
87
+ connector: Connector id (`scl_...`) or UID (`slack/my-bot`).
88
+ subject: Whose authority the token carries.
89
+ scopes: Requested scopes. Omit for the connector's defaults for this
90
+ subject type.
91
+ installation_id: Select one installation when the connector has several.
92
+ audience: Optional token audience.
93
+ resources: Optional resource indicators (RFC 8707).
94
+ authorization_details: Optional rich authorization requests (RFC 9396).
95
+ options: Per-call overrides such as an explicit platform identity token
96
+ or a forced cache bypass.
97
+
98
+ Returns:
99
+ The credential to send to the upstream provider.
100
+
101
+ Raises:
102
+ UserAuthorizationRequiredError: If this user has not consented yet. Call
103
+ `start_authorization` and send them to the returned URL.
104
+ ConnectorInstallationRequiredError: If the connector is not installed
105
+ for the target tenant.
106
+ NoValidTokenError: If the grant exists but no usable credential can be
107
+ issued.
108
+ AuthorizationPendingError: If a device-code authorization is still
109
+ waiting on the user. Keep polling.
110
+ AuthorizationDeniedError: If the user refused the request.
111
+ AuthorizationExpiredError: If the device code expired before consent.
112
+ InvalidGrantError: If the upstream provider rejected the stored grant, so
113
+ the subject must authorize again.
114
+ ConnectNotFoundError: If the connector does not exist.
115
+ ConnectApiError: For any other Connect API failure.
116
+ """
117
+ return await _get_token(
118
+ _service(),
119
+ connector,
120
+ subject=subject,
121
+ scopes=scopes,
122
+ installation_id=installation_id,
123
+ audience=audience,
124
+ resources=resources,
125
+ authorization_details=authorization_details,
126
+ options=options,
127
+ )
128
+
129
+
130
+ async def get_token_response(
131
+ connector: str,
132
+ *,
133
+ subject: ConnectTokenSubject,
134
+ scopes: StringContainer | None = None,
135
+ installation_id: str | None = None,
136
+ audience: StringContainer | None = None,
137
+ resources: StringContainer | None = None,
138
+ authorization_details: Sequence[ConnectAuthorizationDetail] | None = None,
139
+ options: ConnectOptions | None = None,
140
+ ) -> ConnectTokenResponse:
141
+ """Mint a credential and return the full response envelope.
142
+
143
+ Identical to `get_token`, but also surfaces the per-issuance `token_id`
144
+ (`stk_...`) for correlating with Vercel observability, the expiry, the
145
+ issuing connector, the installation and tenant, and any driver metadata or
146
+ allow-listed upstream claims.
147
+
148
+ Args:
149
+ connector: Connector id (`scl_...`) or UID (`slack/my-bot`).
150
+ subject: Whose authority the token carries.
151
+ scopes: Requested scopes. Omit for the connector's defaults.
152
+ installation_id: Select one installation when the connector has several.
153
+ audience: Optional token audience.
154
+ resources: Optional resource indicators (RFC 8707).
155
+ authorization_details: Optional rich authorization requests (RFC 9396).
156
+ options: Per-call overrides.
157
+
158
+ Returns:
159
+ The credential and its issuance metadata.
160
+
161
+ Raises:
162
+ UserAuthorizationRequiredError: If this user has not consented yet.
163
+ ConnectorInstallationRequiredError: If the connector is not installed.
164
+ NoValidTokenError: If no usable credential can be issued.
165
+ ConnectApiError: For any other Connect API failure.
166
+ """
167
+ return await _get_token_response(
168
+ _service(),
169
+ connector,
170
+ subject=subject,
171
+ scopes=scopes,
172
+ installation_id=installation_id,
173
+ audience=audience,
174
+ resources=resources,
175
+ authorization_details=authorization_details,
176
+ options=options,
177
+ )
178
+
179
+
180
+ async def revoke_token(
181
+ connector: str,
182
+ *,
183
+ subject: ConnectTokenSubject,
184
+ installation_id: str | None = None,
185
+ options: ConnectOptions | None = None,
186
+ ) -> None:
187
+ """Revoke a grant upstream and drop its cached credentials.
188
+
189
+ This removes the stored authorization server-side, not just the local cache
190
+ entry. Use it when a user disconnects an integration or a tenant is
191
+ offboarded.
192
+
193
+ Args:
194
+ connector: Connector id or UID.
195
+ subject: The subject whose grant should be revoked.
196
+ installation_id: Restrict revocation to one installation.
197
+ options: Per-call overrides.
198
+
199
+ Raises:
200
+ ConnectApiError: If the revocation request fails.
201
+ """
202
+ await _revoke_token(
203
+ _service(),
204
+ connector,
205
+ subject=subject,
206
+ installation_id=installation_id,
207
+ options=options,
208
+ )
209
+
210
+
211
+ async def start_authorization(
212
+ connector: str,
213
+ *,
214
+ subject: ConnectTokenSubject,
215
+ scopes: StringContainer | None = None,
216
+ installation_id: str | None = None,
217
+ return_url: str | None = None,
218
+ webhook: str | None = None,
219
+ device_code: bool | None = None,
220
+ expires_in: DurationInput | None = None,
221
+ options: ConnectOptions | None = None,
222
+ ) -> ConnectAuthorizationResponse:
223
+ """Start an end-user consent flow and get a URL to send them to.
224
+
225
+ Connect owns the OAuth client, PKCE, state, and the callback handshake, so
226
+ you never implement the dance. Pass `return_url` for a web app and Connect
227
+ redirects back after consent. Pass `device_code=True` for a CLI or headless
228
+ process: Connect returns a short code the user approves elsewhere, and you
229
+ poll `get_token(..., options=ConnectOptions(force_refresh=True))` until it
230
+ succeeds, because nothing is delivered back to your process.
231
+
232
+ Setting `VERCEL_CONNECT_INTERACTIVE_AUTH_MODE=detached` makes device-code the
233
+ default; a warning is emitted if that silently discards a `return_url` you
234
+ supplied.
235
+
236
+ Args:
237
+ connector: Connector id or UID.
238
+ subject: The subject to authorize, usually a user subject.
239
+ scopes: Requested scopes. Omit for the connector's defaults.
240
+ installation_id: Target one installation.
241
+ return_url: Where Connect should redirect after consent. Must be
242
+ `https://`, or `http://` on localhost or 127.0.0.1.
243
+ webhook: An `https://` URL to notify.
244
+ device_code: Return a device code instead of redirecting.
245
+ expires_in: How long the authorization request stays valid.
246
+ options: Per-call overrides.
247
+
248
+ Returns:
249
+ The consent URL, plus the opaque `request` and `verifier` values and an
250
+ optional device code.
251
+
252
+ Raises:
253
+ ConnectValidationError: If `return_url` or `webhook` is not an allowed
254
+ URL.
255
+ ConnectApiError: If the authorization request fails.
256
+ """
257
+ return await _start_authorization(
258
+ _service(),
259
+ connector,
260
+ subject=subject,
261
+ scopes=scopes,
262
+ installation_id=installation_id,
263
+ return_url=return_url,
264
+ webhook=webhook,
265
+ device_code=device_code,
266
+ expires_in=expires_in,
267
+ options=options,
268
+ )
269
+
270
+
271
+ async def get_connector_metadata(
272
+ connector: str,
273
+ *,
274
+ options: ConnectOptions | None = None,
275
+ ) -> ConnectorMetadata:
276
+ """Read a connector's identity and configuration.
277
+
278
+ Read this instead of hardcoding assumptions about which scopes or subject
279
+ types a connector supports. Provider secrets are redacted by the server.
280
+ Fields outside the documented set are preserved in `extra`.
281
+
282
+ Args:
283
+ connector: Connector id or UID.
284
+ options: Per-call overrides.
285
+
286
+ Returns:
287
+ The connector's metadata.
288
+
289
+ Raises:
290
+ ConnectApiError: If the connector cannot be read.
291
+ """
292
+ return await _get_connector_metadata(_service(), connector, options=options)
293
+
294
+
295
+ async def verify_connect_webhook(
296
+ headers: Mapping[str, str] | Any,
297
+ *,
298
+ project_id: str | None = None,
299
+ environment: str | None = None,
300
+ owner_id: str | None = None,
301
+ audience: str | Sequence[str] | None = None,
302
+ ) -> ConnectWebhookClaims:
303
+ """Verify an inbound Connect trigger request.
304
+
305
+ A connector with triggers enabled receives provider webhooks and forwards
306
+ them to your project with a Vercel OIDC token as the `Authorization` bearer.
307
+ So instead of implementing a different signature scheme per provider, verify
308
+ one thing.
309
+
310
+ Trust boundary: this accepts *any* valid Vercel OIDC token for this project
311
+ and environment. It is not pinned to a specific connector or deployment.
312
+
313
+ Verification pins the issuer to Vercel's OIDC service, accepting both
314
+ `https://oidc.vercel.com` and the team-scoped `https://oidc.vercel.com/<team>`,
315
+ allows only RS256, and resolves the signing key by `kid` from Vercel's JWKS.
316
+ It **fails closed**: if the expected project or environment cannot be
317
+ determined from the arguments or the environment, every request is rejected.
318
+
319
+ Args:
320
+ headers: Inbound request headers, or any request object exposing a
321
+ `headers` mapping (httpx, Starlette, FastAPI, Django). Only
322
+ `Authorization` is read.
323
+ project_id: Expected project. Defaults to `VERCEL_PROJECT_ID`.
324
+ environment: Expected environment. Defaults to `VERCEL_TARGET_ENV`, then
325
+ `VERCEL_ENV`.
326
+ owner_id: Expected team owner. Checked only when supplied.
327
+ audience: Expected audience.
328
+
329
+ Returns:
330
+ The verified claims.
331
+
332
+ Raises:
333
+ ConnectWebhookVerificationError: If the header is missing or malformed,
334
+ the signature does not verify, a claim does not match, or the
335
+ expected project and environment cannot be resolved.
336
+ """
337
+ return await _verify_connect_webhook(
338
+ _service(),
339
+ headers,
340
+ project_id=project_id,
341
+ environment=environment,
342
+ owner_id=owner_id,
343
+ audience=audience,
344
+ )
345
+
346
+
347
+ def create_connect_webhook_verifier(
348
+ *,
349
+ project_id: str | None = None,
350
+ environment: str | None = None,
351
+ owner_id: str | None = None,
352
+ audience: str | Sequence[str] | None = None,
353
+ ) -> Callable[[Mapping[str, str]], Awaitable[ConnectWebhookClaims]]:
354
+ """Build a reusable webhook verifier bound to a set of expectations.
355
+
356
+ Args:
357
+ project_id: Expected project. Defaults to `VERCEL_PROJECT_ID`.
358
+ environment: Expected environment. Defaults to `VERCEL_TARGET_ENV`, then
359
+ `VERCEL_ENV`.
360
+ owner_id: Expected team owner.
361
+ audience: Expected audience.
362
+
363
+ Returns:
364
+ A callable taking request headers and returning verified claims.
365
+ """
366
+
367
+ async def verify(headers: Mapping[str, str]) -> ConnectWebhookClaims:
368
+ return await verify_connect_webhook(
369
+ headers,
370
+ project_id=project_id,
371
+ environment=environment,
372
+ owner_id=owner_id,
373
+ audience=audience,
374
+ )
375
+
376
+ return verify
377
+
378
+
379
+ def delete_token_cache_entry(
380
+ connector: str,
381
+ *,
382
+ subject: ConnectTokenSubject,
383
+ installation_id: str | None = None,
384
+ ) -> None:
385
+ """Drop cached credentials for one connector, subject, and installation.
386
+
387
+ Eviction is by identity, not by reconstructing the original request
388
+ parameters. If a provider returns 401, evict and retry once: that is cheaper
389
+ than forcing a refresh on every call.
390
+
391
+ Args:
392
+ connector: Connector id or UID.
393
+ subject: The subject whose cached credentials should be dropped.
394
+ installation_id: Restrict eviction to one installation.
395
+ """
396
+ _service().delete_token_cache_entry(
397
+ connector,
398
+ subject=subject,
399
+ installation_id=installation_id,
400
+ )
401
+
402
+
403
+ def clear_token_cache() -> None:
404
+ """Drop every cached credential for the active session."""
405
+ _service().clear_token_cache()
406
+
407
+
408
+ __all__ = [
409
+ "ConnectApiError",
410
+ "ConnectAppTokenSubject",
411
+ "ConnectAuthorizationDetail",
412
+ "ConnectAuthorizationResponse",
413
+ "ConnectCredentialsError",
414
+ "ConnectCustomAuthorizationDetail",
415
+ "ConnectError",
416
+ "ConnectGitHubAppInstallationAuthorizationDetail",
417
+ "ConnectJwtBearerTokenSubject",
418
+ "ConnectOptions",
419
+ "ConnectResponseError",
420
+ "ConnectService",
421
+ "ConnectServiceOptions",
422
+ "ConnectTokenExchangeSubject",
423
+ "ConnectTokenResponse",
424
+ "ConnectTokenSubject",
425
+ "ConnectUserTokenSubject",
426
+ "ConnectValidationError",
427
+ "ConnectWebhookClaims",
428
+ "ConnectWebhookVerificationError",
429
+ "ConnectorInstallationRequiredError",
430
+ "AuthorizationDeniedError",
431
+ "AuthorizationExpiredError",
432
+ "AuthorizationPendingError",
433
+ "ConnectNotFoundError",
434
+ "InvalidGrantError",
435
+ "ConnectorMetadata",
436
+ "ConnectorRef",
437
+ "DurationInput",
438
+ "StringContainer",
439
+ "NoValidTokenError",
440
+ "UserAuthorizationRequiredError",
441
+ "VercelTokenInput",
442
+ "__version__",
443
+ "clear_token_cache",
444
+ "create_connect_webhook_verifier",
445
+ "delete_token_cache_entry",
446
+ "get_connect_service",
447
+ "get_connector_metadata",
448
+ "get_token",
449
+ "get_token_response",
450
+ "revoke_token",
451
+ "start_authorization",
452
+ "sync",
453
+ "verify_connect_webhook",
454
+ ]
@@ -0,0 +1 @@
1
+ """Internal Connect implementation."""