Compare commits
No commits in common. "727f9b1f0586b788f2fcb83478405acc3b9c9949" and "c1e0ccb2e8dc102747940f9cceaed41b81470ae4" have entirely different histories.
727f9b1f05
...
c1e0ccb2e8
6 changed files with 51 additions and 106 deletions
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
import { Lock } from "lucide-react";
|
||||
|
||||
import { logout } from "@/lib/logout";
|
||||
|
||||
interface NoAccessScreenProps {
|
||||
/** "user" — юзер вне ролей; "path" — путь вне scope */
|
||||
variant: "user" | "path";
|
||||
|
|
@ -102,26 +100,6 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
|||
{path}
|
||||
</p>
|
||||
) : null}
|
||||
{/* Always-available logout — иначе locked/no-access юзер заперт:
|
||||
UserMenu (где обычно «Выйти») на этом экране не рендерится. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
style={{
|
||||
marginTop: 20,
|
||||
background: "var(--accent)",
|
||||
color: "#FFFFFF",
|
||||
border: "none",
|
||||
borderRadius: 8,
|
||||
padding: "10px 16px",
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
cursor: "pointer",
|
||||
fontFamily: "inherit",
|
||||
}}
|
||||
>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ import { useEffect, useRef, useState } from "react";
|
|||
|
||||
import { LogOut } from "lucide-react";
|
||||
|
||||
import { logout } from "@/lib/logout";
|
||||
|
||||
import { useMe, type Role } from "@/lib/useMe";
|
||||
|
||||
/**
|
||||
|
|
@ -39,6 +37,29 @@ const ROLE_LABELS: Record<Role, string> = {
|
|||
analyst: "Аналитик",
|
||||
};
|
||||
|
||||
/**
|
||||
* Logout — invalidate basic_auth cache + reload.
|
||||
*
|
||||
* Trick: fetch на защищённый endpoint с явно неверным Basic-токеном.
|
||||
* Caddy ответит 401 (because creds wrong) → браузер выкинет старые cached
|
||||
* creds для этого origin. Затем hard reload — Caddy снова запросит basic_auth
|
||||
* prompt, потому что cached creds invalidated.
|
||||
*/
|
||||
async function logout(): Promise<void> {
|
||||
try {
|
||||
await fetch("/api/v1/me", {
|
||||
headers: {
|
||||
Authorization: "Basic " + btoa("logout:logout"),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch {
|
||||
// Сетевая ошибка — всё равно делаем hard reload.
|
||||
}
|
||||
// Hard reload форсит новый basic_auth handshake.
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
export function UserMenu() {
|
||||
const { data, isLoading, error } = useMe();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Logout — invalidate basic_auth cache + reload.
|
||||
*
|
||||
* Trick: fetch на защищённый endpoint с явно неверным Basic-токеном.
|
||||
* Caddy ответит 401 (because creds wrong) → браузер выкинет старые cached
|
||||
* creds для этого origin. Затем hard reload — Caddy снова запросит basic_auth
|
||||
* prompt, потому что cached creds invalidated.
|
||||
*
|
||||
* Это работает в Safari/Chrome/Firefox; legacy `ClearAuthenticationCache`
|
||||
* не нужен.
|
||||
*/
|
||||
export async function logout(): Promise<void> {
|
||||
try {
|
||||
await fetch("/api/v1/me", {
|
||||
headers: {
|
||||
Authorization: "Basic " + btoa("logout:logout"),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch {
|
||||
// Сетевая ошибка — всё равно делаем hard reload.
|
||||
}
|
||||
// Hard reload форсит новый basic_auth handshake.
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
|
@ -9,8 +9,6 @@
|
|||
* Token-based styling per `.claude/rules/ui-tokens.md` (см. globals.css).
|
||||
*/
|
||||
|
||||
import { logout } from "@/lib/logout";
|
||||
|
||||
interface NoAccessScreenProps {
|
||||
variant: "user" | "path" | "session" | "trial";
|
||||
path?: string;
|
||||
|
|
@ -118,26 +116,6 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
|||
{path}
|
||||
</p>
|
||||
) : null}
|
||||
{/* Always-available logout — иначе locked/no-access юзер заперт:
|
||||
UserMenu (где обычно «Выйти») на этом экране не рендерится. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
style={{
|
||||
marginTop: 20,
|
||||
background: "var(--accent)",
|
||||
color: "#FFFFFF",
|
||||
border: "none",
|
||||
borderRadius: 8,
|
||||
padding: "10px 16px",
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
cursor: "pointer",
|
||||
fontFamily: "inherit",
|
||||
}}
|
||||
>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,10 +21,37 @@
|
|||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { logout } from "@/lib/logout";
|
||||
import { API_BASE_URL } from "@/lib/api";
|
||||
|
||||
import { useMe } from "@/lib/useMe";
|
||||
|
||||
/**
|
||||
* Logout — invalidate basic_auth cache + reload.
|
||||
*
|
||||
* При basePath=/trade-in мы должны бить именно по защищённому пути
|
||||
* (`/trade-in/api/v1/me`), иначе Caddy не вытолкнет cached creds для текущего
|
||||
* scope. После 401 — hard reload на корень сайта (`/`), там basic_auth
|
||||
* prompt появится заново.
|
||||
*/
|
||||
async function logout(): Promise<void> {
|
||||
// Используем `API_BASE_URL` (тот же который `apiFetchWithStatus` для /me) —
|
||||
// single source of truth. В проде `API_BASE_URL` = `/trade-in` (через
|
||||
// `NEXT_PUBLIC_API_BASE_URL` или `NEXT_PUBLIC_BASE_PATH` fallback); в dev "".
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/api/v1/me`, {
|
||||
headers: {
|
||||
Authorization: "Basic " + btoa("logout:logout"),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch {
|
||||
// Сетевая ошибка — всё равно делаем hard reload.
|
||||
}
|
||||
// Hard reload форсит новый basic_auth handshake. Уходим в корень сайта,
|
||||
// чтобы prompt не залип в /trade-in scope.
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
/** Inline LogOut icon (lucide-react `LogOut` SVG path, stroke 1.5). */
|
||||
function LogOutIcon() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import { API_BASE_URL } from "@/lib/api";
|
||||
|
||||
/**
|
||||
* Logout — invalidate basic_auth cache + reload.
|
||||
*
|
||||
* Logout flow — basic_auth quirk:
|
||||
* 1. Отправляем fetch с заведомо неправильными Basic creds → 401 от Caddy
|
||||
* → браузер забывает cached creds для этого origin.
|
||||
* 2. Hard reload (`window.location.href = "/"`) — basic_auth prompt
|
||||
* покажется заново.
|
||||
*
|
||||
* При basePath=/trade-in мы должны бить именно по защищённому пути
|
||||
* (`/trade-in/api/v1/me`), иначе Caddy не вытолкнет cached creds для текущего
|
||||
* scope. После 401 — hard reload на корень сайта (`/`), там basic_auth
|
||||
* prompt появится заново.
|
||||
*/
|
||||
export async function logout(): Promise<void> {
|
||||
// Используем `API_BASE_URL` (тот же который `apiFetchWithStatus` для /me) —
|
||||
// single source of truth. В проде `API_BASE_URL` = `/trade-in` (через
|
||||
// `NEXT_PUBLIC_API_BASE_URL` или `NEXT_PUBLIC_BASE_PATH` fallback); в dev "".
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/api/v1/me`, {
|
||||
headers: {
|
||||
Authorization: "Basic " + btoa("logout:logout"),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch {
|
||||
// Сетевая ошибка — всё равно делаем hard reload.
|
||||
}
|
||||
// Hard reload форсит новый basic_auth handshake. Уходим в корень сайта,
|
||||
// чтобы prompt не залип в /trade-in scope.
|
||||
window.location.href = "/";
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue