feat(auth): интегрировать SSO auth-portal
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m52s
Docker images / frontend-image (push) Successful in 2m23s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 38s
Docker images / publish-release (push) Successful in 8s

JWT на backend, handoff/callback на UI, RBAC mm:*, AUTH_* в compose.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-09-04 20:38:28 +07:00
co-authored by Cursor
parent 0208aa4d7c
commit 0e9349e508
27 changed files with 1368 additions and 129 deletions
+22 -4
View File
@@ -1,4 +1,10 @@
import { configuredBackendUrl } from "@/lib/backend-url"
import {
getToken,
isAuthEnabled,
redirectToPortalLogin,
redirectToPortalLoginInteractive,
} from "@/lib/auth"
export class ApiClientError extends Error {
constructor(
@@ -28,14 +34,26 @@ export async function requestJson<T>(
init?: RequestInit,
): Promise<T> {
const hasBody = init?.body != null
const headers = new Headers(init?.headers)
if (hasBody && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json")
}
const token = typeof window !== "undefined" ? getToken() : null
if (token && !headers.has("Authorization")) {
headers.set("Authorization", `Bearer ${token}`)
}
const res = await fetch(resolveRequestUrl(baseUrl, path), {
...init,
headers: {
...(hasBody ? { "Content-Type": "application/json" } : {}),
...(init?.headers ?? {}),
},
headers,
})
if (res.status === 401 && typeof window !== "undefined" && isAuthEnabled()) {
const ok = redirectToPortalLogin()
if (!ok) redirectToPortalLoginInteractive()
throw new ApiClientError("Unauthorized", 401)
}
if (res.status === 204) return undefined as T
const payload = await res.json().catch(() => undefined)