urun-cli 0.4.0__tar.gz → 0.4.1__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.
- {urun_cli-0.4.0 → urun_cli-0.4.1}/PKG-INFO +1 -1
- {urun_cli-0.4.0 → urun_cli-0.4.1}/pyproject.toml +1 -1
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/cli.py +79 -20
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/discovery.py +14 -1
- {urun_cli-0.4.0 → urun_cli-0.4.1}/.gitignore +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/CHANGELOG.md +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/LICENSE +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/README.md +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/SECURITY.md +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/__init__.py +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/api.py +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/config.py +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/deps.py +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/errors.py +0 -0
- {urun_cli-0.4.0 → urun_cli-0.4.1}/src/urun/manifest.py +0 -0
|
@@ -126,9 +126,21 @@ def add_auth_parser(sub: argparse._SubParsersAction) -> None:
|
|
|
126
126
|
description=(
|
|
127
127
|
"Register (or replace) the trusted key source the control plane "
|
|
128
128
|
"verifies this org's session JWTs against. Provide EXACTLY ONE of "
|
|
129
|
-
"--
|
|
130
|
-
"
|
|
131
|
-
"
|
|
129
|
+
"--workos-client-id (templates the WorkOS AuthKit JWKS for you), "
|
|
130
|
+
"--jwks-url (a remote JWK Set endpoint), or --jwks-json (an inline "
|
|
131
|
+
"JWK Set from a file or '-' for stdin). Optionally pin the expected "
|
|
132
|
+
"token issuer/audience. This registers trust only; it does not mint a JWT."
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
set_parser.add_argument(
|
|
136
|
+
"--workos-client-id",
|
|
137
|
+
metavar="CLIENT_ID",
|
|
138
|
+
help=(
|
|
139
|
+
"WorkOS Client ID (client_...). Templates the WorkOS AuthKit JWKS "
|
|
140
|
+
"endpoint and access-token issuer for you, so you do not have to "
|
|
141
|
+
"supply raw JWKS URLs: --jwks-url becomes "
|
|
142
|
+
"https://api.workos.com/sso/jwks/<client_id> and --issuer defaults to "
|
|
143
|
+
"https://api.workos.com. Override either with --issuer/--audience."
|
|
132
144
|
),
|
|
133
145
|
)
|
|
134
146
|
set_parser.add_argument(
|
|
@@ -142,7 +154,11 @@ def add_auth_parser(sub: argparse._SubParsersAction) -> None:
|
|
|
142
154
|
)
|
|
143
155
|
set_parser.add_argument(
|
|
144
156
|
"--issuer",
|
|
145
|
-
help=
|
|
157
|
+
help=(
|
|
158
|
+
"Optional expected JWT `iss` claim; tokens whose iss differs are "
|
|
159
|
+
"rejected. With --workos-client-id this defaults to "
|
|
160
|
+
"https://api.workos.com (pass to override, e.g. a custom AuthKit domain)."
|
|
161
|
+
),
|
|
146
162
|
)
|
|
147
163
|
set_parser.add_argument(
|
|
148
164
|
"--audience",
|
|
@@ -229,38 +245,81 @@ def auth(args: argparse.Namespace) -> int:
|
|
|
229
245
|
getattr(args, "auth_command", None) != "jwks"
|
|
230
246
|
or getattr(args, "jwks_command", None) != "set"
|
|
231
247
|
):
|
|
232
|
-
raise UrunError(
|
|
248
|
+
raise UrunError(
|
|
249
|
+
"usage: urun auth jwks set "
|
|
250
|
+
"--workos-client-id <client_...> | --jwks-url <url> | --jwks-json <file|->"
|
|
251
|
+
)
|
|
233
252
|
return auth_jwks_set(args)
|
|
234
253
|
|
|
235
254
|
|
|
255
|
+
# WorkOS publishes one JWK Set per OAuth client at this unauthenticated endpoint;
|
|
256
|
+
# it hosts the public key used to verify AuthKit / User Management access tokens.
|
|
257
|
+
WORKOS_JWKS_URL_TEMPLATE = "https://api.workos.com/sso/jwks/{client_id}"
|
|
258
|
+
# A WorkOS AuthKit (User Management) access token's `iss` claim is the static
|
|
259
|
+
# WorkOS API host (NOT a per-client URL). It becomes your custom AuthKit domain
|
|
260
|
+
# only when one is configured, which is why --issuer can override this default.
|
|
261
|
+
WORKOS_ACCESS_TOKEN_ISSUER = "https://api.workos.com"
|
|
262
|
+
# A WorkOS client id looks like `client_<base32-ish>`.
|
|
263
|
+
WORKOS_CLIENT_ID_RE = re.compile(r"^client_[0-9A-Za-z]+$")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def workos_jwks_template(client_id: str) -> dict[str, str]:
|
|
267
|
+
"""Template the WorkOS AuthKit trust anchor from just the WorkOS Client ID.
|
|
268
|
+
|
|
269
|
+
Returns the JWKS endpoint URL and the expected access-token issuer for the
|
|
270
|
+
given WorkOS client so callers never have to hand-build raw WorkOS URLs.
|
|
271
|
+
|
|
272
|
+
Note (deliberately omitted): WorkOS AuthKit *access tokens* do NOT carry an
|
|
273
|
+
`aud` claim by default, so no audience is templated — pin one with
|
|
274
|
+
--audience only if your AuthKit config actually sets it (pinning a wrong
|
|
275
|
+
audience would silently 401 every real token).
|
|
276
|
+
"""
|
|
277
|
+
if not WORKOS_CLIENT_ID_RE.fullmatch(client_id):
|
|
278
|
+
raise UrunError(
|
|
279
|
+
"invalid WorkOS client id; expected client_<alphanumeric> (e.g. client_01ABC...)"
|
|
280
|
+
)
|
|
281
|
+
return {
|
|
282
|
+
"jwks_url": WORKOS_JWKS_URL_TEMPLATE.format(client_id=client_id),
|
|
283
|
+
"expected_issuer": WORKOS_ACCESS_TOKEN_ISSUER,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
236
287
|
def auth_jwks_set(args: argparse.Namespace) -> int:
|
|
237
288
|
jwks_url = string_value(args.jwks_url)
|
|
238
289
|
jwks_json_arg = string_value(args.jwks_json)
|
|
239
|
-
|
|
240
|
-
|
|
290
|
+
workos_client_id = string_value(getattr(args, "workos_client_id", None))
|
|
291
|
+
issuer = string_value(args.issuer)
|
|
292
|
+
audience = string_value(args.audience)
|
|
293
|
+
|
|
294
|
+
sources = [s for s in (workos_client_id, jwks_url, jwks_json_arg) if s]
|
|
295
|
+
if len(sources) != 1:
|
|
296
|
+
raise UrunError("provide exactly one of --workos-client-id, --jwks-url, or --jwks-json")
|
|
241
297
|
|
|
242
298
|
payload: dict[str, Any] = {}
|
|
243
|
-
if
|
|
299
|
+
if workos_client_id:
|
|
300
|
+
# Template the WorkOS well-known structure from the client id so the
|
|
301
|
+
# caller never supplies raw URLs. --issuer/--audience still override.
|
|
302
|
+
template = workos_jwks_template(workos_client_id)
|
|
303
|
+
payload["jwks_url"] = template["jwks_url"]
|
|
304
|
+
issuer = issuer or template.get("expected_issuer")
|
|
305
|
+
elif jwks_url:
|
|
244
306
|
payload["jwks_url"] = jwks_url
|
|
245
|
-
issuer = string_value(args.issuer)
|
|
246
|
-
audience = string_value(args.audience)
|
|
247
|
-
if issuer:
|
|
248
|
-
payload["expected_issuer"] = issuer
|
|
249
|
-
if audience:
|
|
250
|
-
payload["expected_audience"] = audience
|
|
251
307
|
else:
|
|
252
308
|
payload["jwks_json"] = read_jwks_json(jwks_json_arg)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
payload["expected_audience"] = audience
|
|
309
|
+
|
|
310
|
+
if issuer:
|
|
311
|
+
payload["expected_issuer"] = issuer
|
|
312
|
+
if audience:
|
|
313
|
+
payload["expected_audience"] = audience
|
|
259
314
|
|
|
260
315
|
api_url, api_key = resolve_api_credentials(args)
|
|
261
316
|
result = ApiClient(api_url, api_key).register_trusted_jwks(payload)
|
|
262
317
|
org_id = string_value(result.get("org_id")) or "(unknown)"
|
|
263
318
|
print(f"Registered trusted JWKS for org {org_id}.")
|
|
319
|
+
if workos_client_id:
|
|
320
|
+
print(f" WorkOS client: {workos_client_id}")
|
|
321
|
+
print(f" JWKS URL: {payload['jwks_url']}")
|
|
322
|
+
print(f" Expected iss: {payload.get('expected_issuer', '(unset)')}")
|
|
264
323
|
print("This registers a trust relationship only; no JWT was minted or fetched.")
|
|
265
324
|
return 0
|
|
266
325
|
|
|
@@ -133,8 +133,21 @@ def resolve_relative_import(node: ast.ImportFrom, source: Path, project_root: Pa
|
|
|
133
133
|
def module_path_candidates(base: Path, parts: list[str]) -> set[Path]:
|
|
134
134
|
if not parts:
|
|
135
135
|
return set()
|
|
136
|
-
path = base.joinpath(*parts)
|
|
137
136
|
candidates: set[Path] = set()
|
|
137
|
+
# Every INTERMEDIATE package __init__.py along the dotted path must ship
|
|
138
|
+
# too: importing pkg.sub.mod executes pkg/__init__.py and pkg/sub/__init__.py.
|
|
139
|
+
# Resolving only the leaf dropped those when nothing imported the package
|
|
140
|
+
# name bare — in the pod the package degrades to an implicit NAMESPACE
|
|
141
|
+
# package, so the import still "succeeds" while every module-level
|
|
142
|
+
# definition in the dropped __init__ is silently gone (caught in-pod by the
|
|
143
|
+
# spmd-canary bundle-completeness gate, urun-infra run 27303509334).
|
|
144
|
+
prefix = base
|
|
145
|
+
for part in parts[:-1]:
|
|
146
|
+
prefix = prefix / part
|
|
147
|
+
intermediate_init = prefix / "__init__.py"
|
|
148
|
+
if intermediate_init.is_file():
|
|
149
|
+
candidates.add(intermediate_init.resolve())
|
|
150
|
+
path = base.joinpath(*parts)
|
|
138
151
|
module_file = path.with_suffix(".py")
|
|
139
152
|
package_init = path / "__init__.py"
|
|
140
153
|
if module_file.is_file():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|