Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

209 lines
6.9 KiB
Python

# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import os
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path
from typing import Mapping
from mirage.server.auth import storage as _storage
from mirage.server.daemon_config import read_daemon_table
from mirage.server.paths import mirage_home
ENV_AUTH_MODE = "MIRAGE_AUTH_MODE"
ENV_AUTH_TOKEN = "MIRAGE_AUTH_TOKEN"
ENV_JWT_PUBKEY = "MIRAGE_JWT_PUBKEY"
ENV_JWT_PUBKEY_FILE = "MIRAGE_JWT_PUBKEY_FILE"
ENV_JWT_ALG = "MIRAGE_JWT_ALG"
ENV_JWT_ISSUER = "MIRAGE_JWT_ISSUER"
ENV_JWT_AUDIENCE = "MIRAGE_JWT_AUDIENCE"
ENV_JWT_AUTHORIZED_PARTIES = "MIRAGE_JWT_AUTHORIZED_PARTIES"
ENV_JWT_CLOCK_SKEW = "MIRAGE_JWT_CLOCK_SKEW_SECONDS"
DEFAULT_CLOCK_SKEW_SECONDS = 5
class AuthMode(StrEnum):
LOCAL = "local"
TOKEN = "token"
JWT = "jwt"
@dataclass(frozen=True)
class JWTConfig:
key: str
algorithm: str
issuer: str | None = None
audience: str | None = None
authorized_parties: tuple[str, ...] = field(default_factory=tuple)
clock_skew_seconds: int = DEFAULT_CLOCK_SKEW_SECONDS
@dataclass(frozen=True)
class AuthConfig:
mode: AuthMode
local_token: str | None = None
bearer_token: str | None = None
jwt: JWTConfig | None = None
def resolve_local_token(
env: Mapping[str, str] | None = None,
token_file: Path | None = None,
) -> str | None:
"""Resolve the local-mode bearer token via env > file > None.
Args:
env (Mapping[str, str] | None): environment to read
``MIRAGE_AUTH_TOKEN`` from. Defaults to ``os.environ``.
token_file (Path | None): location of the token file.
Defaults to ``default_token_file()``.
Returns:
str | None: resolved token, or ``None`` if no source provides one.
"""
e = env if env is not None else os.environ
val = e.get(ENV_AUTH_TOKEN, "").strip()
if val:
return val
path = (token_file
if token_file is not None else _storage.default_token_file())
return _storage.read_token_file(path)
def _read_jwt_key(env: Mapping[str, str]) -> str:
inline = env.get(ENV_JWT_PUBKEY, "").strip()
if inline:
return inline
path = env.get(ENV_JWT_PUBKEY_FILE, "").strip()
if path:
return Path(path).read_text()
raise RuntimeError(
f"mode=jwt requires {ENV_JWT_PUBKEY} or {ENV_JWT_PUBKEY_FILE}")
def _parse_csv(value: str) -> tuple[str, ...]:
return tuple(p.strip() for p in value.split(",") if p.strip())
_CONFIG_ENV_KEYS = {
"auth_mode": ENV_AUTH_MODE,
"jwt_alg": ENV_JWT_ALG,
"jwt_issuer": ENV_JWT_ISSUER,
"jwt_audience": ENV_JWT_AUDIENCE,
"jwt_pubkey_file": ENV_JWT_PUBKEY_FILE,
"jwt_clock_skew": ENV_JWT_CLOCK_SKEW,
"jwt_authorized_parties": ENV_JWT_AUTHORIZED_PARTIES,
}
def _merge_config_table(env: Mapping[str, str],
table: Mapping[str, object]) -> dict[str, str]:
"""Fold config.toml auth keys under their env names, env winning.
Only non-secret keys have config counterparts: the raw
``MIRAGE_AUTH_TOKEN`` and inline ``MIRAGE_JWT_PUBKEY`` stay
env-only (use the token file / ``jwt_pubkey_file`` instead).
Args:
env (Mapping[str, str]): the process environment view.
table (Mapping[str, object]): the ``[daemon]`` config table.
Returns:
dict[str, str]: env copy with config fallbacks applied.
"""
merged = dict(env)
for cfg_key, env_name in _CONFIG_ENV_KEYS.items():
if merged.get(env_name, "").strip():
continue
value = table.get(cfg_key)
if value is not None and str(value).strip():
merged[env_name] = str(value)
return merged
def resolve_auth_config(
env: Mapping[str, str] | None = None,
token_file: Path | None = None,
table: Mapping[str, object] | None = None,
) -> AuthConfig:
"""Resolve daemon auth configuration from environment and config.
Per key the environment variable wins over the ``[daemon]`` table
in ``config.toml``, which wins over the default.
Args:
env (Mapping[str, str] | None): environment to read from.
Defaults to ``os.environ``.
token_file (Path | None): override the local-mode token file
location. Defaults to ``default_token_file()``.
table (Mapping[str, object] | None): the ``[daemon]`` config
table. Defaults to reading ``$MIRAGE_HOME/config.toml``
when ``env`` is also defaulted; an explicit ``env`` with no
``table`` stays hermetic and reads no file.
Returns:
AuthConfig: resolved configuration.
Raises:
RuntimeError: if required settings are missing for the chosen mode.
"""
if table is None:
table = read_daemon_table(mirage_home()) if env is None else {}
e = _merge_config_table(env if env is not None else os.environ, table)
raw_mode = (e.get(ENV_AUTH_MODE, "")
or AuthMode.LOCAL.value).strip().lower()
try:
mode = AuthMode(raw_mode)
except ValueError as exc:
valid = ", ".join(m.value for m in AuthMode)
raise RuntimeError(
f"{ENV_AUTH_MODE} must be one of ({valid}), got {raw_mode!r}"
) from exc
if mode == AuthMode.LOCAL:
return AuthConfig(
mode=mode,
local_token=resolve_local_token(env=e, token_file=token_file),
)
if mode == AuthMode.TOKEN:
token = e.get(ENV_AUTH_TOKEN, "").strip()
if not token:
raise RuntimeError(
f"mode=token requires {ENV_AUTH_TOKEN} to be set")
return AuthConfig(mode=mode, bearer_token=token)
key = _read_jwt_key(e)
alg = e.get(ENV_JWT_ALG, "").strip()
if not alg:
raise RuntimeError(f"mode=jwt requires {ENV_JWT_ALG} (e.g. RS256)")
issuer = (e.get(ENV_JWT_ISSUER) or "").strip() or None
audience = (e.get(ENV_JWT_AUDIENCE) or "").strip() or None
azp = _parse_csv(e.get(ENV_JWT_AUTHORIZED_PARTIES, ""))
skew_raw = (e.get(ENV_JWT_CLOCK_SKEW) or "").strip()
skew = int(skew_raw) if skew_raw else DEFAULT_CLOCK_SKEW_SECONDS
return AuthConfig(
mode=mode,
jwt=JWTConfig(
key=key,
algorithm=alg,
issuer=issuer,
audience=audience,
authorized_parties=azp,
clock_skew_seconds=skew,
),
)