auth: своя Bearer-авторизация agentik (сервер + клиент + standalone)
Добавлена собственная авторизация по токену. Это ОТДЕЛЬНАЯ подсистема: библиотека A2A (pw.binom.a2a) имеет свой независимый token, общих типов и общей логики не вводится. Поведение по умолчанию не меняется: token = null -> авторизация выключена, сервер открыт (обратная совместимость), CLI/TUI не затронуты. Сервер (:server): - новый route-scoped плагин BearerTokenPlugin (BearerTokenConfig); - agentikAgent(agent, path, token) ставит плагин на всё поддерево /agentik, когда token != null; иначе плагин не устанавливается; - при несовпадении заголовка Authorization: Bearer <token> -> 401 Unauthorized; - /health всегда открыт (liveness для балансировщика). Клиент (:client): - defaultAgentikHttpClient(token) навешивает Authorization: Bearer <token> через DefaultRequest на весь HttpClient -> накрывает все 10 вызовов и оба SSE; - AgentikAgent(id, baseUrl, token, httpClient) — token необязательный, 9 существующих мест создания агента не тронуты. Standalone: - AgentSection.authToken (env AGENTIK_TOKEN) -> /agentik; - AgentSection.a2aToken (env AGENTIK_A2A_TOKEN) -> /a2a; - два независимых значения, связи между ними нет. Тесты: BearerTokenTest (5), BearerHeaderTest (3) — 401 без токена и с чужим, 200 с верным, /health открыт, null -> открыто. Мутационная проверка пройдена.
This commit is contained in:
@@ -51,6 +51,8 @@ import pw.binom.agentik.llm.tools.SkillMiner
|
||||
*
|
||||
* Вся конфигурация — [AppConfig.fromEnv] (см. [AppConfig]). Источники:
|
||||
* - AGENTIK_PORT / AGENTIK_DB_PATH
|
||||
* - AGENTIK_TOKEN — Bearer-токен для HTTP-фасада /agentik (пусто — авторизация выключена)
|
||||
* - AGENTIK_A2A_TOKEN — Bearer-токен для A2A-фасада /a2a (пусто — авторизация выключена)
|
||||
* - LLM: AGENTIK_LLM_BACKEND, OPENAI_* либо AGENTIK_GOOGLE_*
|
||||
* - MCP: AGENTIK_MCP_CONFIG=<path>.json (формат Claude Desktop)
|
||||
* - Skills: AGENTIK_SKILLS_DIR=<path> (папка с SKILL.md / *.yaml)
|
||||
@@ -353,8 +355,13 @@ private fun runServer() {
|
||||
val server = embeddedServer(CIO, port = config.agent.port) {
|
||||
routing {
|
||||
get("/health") { call.respondText("ok") }
|
||||
agentikAgent(agent, path = "/agentik")
|
||||
a2aAgent(agentName = "agentik", handler = A2aBridge(agent), path = "/a2a")
|
||||
agentikAgent(agent, path = "/agentik", token = config.agent.authToken)
|
||||
a2aAgent(
|
||||
agentName = "agentik",
|
||||
handler = A2aBridge(agent),
|
||||
path = "/a2a",
|
||||
token = config.agent.a2aToken,
|
||||
)
|
||||
if (config.debug.endpoints) {
|
||||
debugRoutes(
|
||||
agent = agent,
|
||||
|
||||
@@ -50,6 +50,18 @@ data class AppConfig(
|
||||
* `null` — файл не читается, секция не добавляется.
|
||||
*/
|
||||
val soulPath: String? = null,
|
||||
/**
|
||||
* Токен для HTTP-фасада agentik (`/agentik`). `null` — авторизация выключена,
|
||||
* сервер открыт (поведение по умолчанию, обратная совместимость).
|
||||
* Источник: env `AGENTIK_TOKEN`.
|
||||
*/
|
||||
val authToken: String? = null,
|
||||
/**
|
||||
* Токен для A2A-фасада (`/a2a`). НЕ связан с [authToken] — это разные
|
||||
* интерфейсы и разные токены (см. библиотеку pw.binom.a2a).
|
||||
* `null` — авторизация выключена. Источник: env `AGENTIK_A2A_TOKEN`.
|
||||
*/
|
||||
val a2aToken: String? = null,
|
||||
)
|
||||
|
||||
/** Долговременная память. */
|
||||
@@ -163,6 +175,8 @@ data class AppConfig(
|
||||
dbPath = env("AGENTIK_DB_PATH")?.takeIf { it.isNotBlank() } ?: DEFAULT_DB_PATH,
|
||||
skillsDir = env("AGENTIK_SKILLS_DIR")?.takeIf { it.isNotBlank() },
|
||||
soulPath = env("AGENTIK_SOUL")?.takeIf { it.isNotBlank() },
|
||||
authToken = env("AGENTIK_TOKEN")?.takeIf { it.isNotBlank() },
|
||||
a2aToken = env("AGENTIK_A2A_TOKEN")?.takeIf { it.isNotBlank() },
|
||||
),
|
||||
llm = llm,
|
||||
mcp = McpConfig.fromEnv(env),
|
||||
|
||||
Reference in New Issue
Block a user