fix: refine auth cookie and route redirects
This commit is contained in:
@@ -566,6 +566,7 @@
|
||||
- 未登录访问后台时提示“前往登录”(`/login`);
|
||||
- 账号菜单提供“用户管理”(`/users`)作为默认后台入口。
|
||||
- 退出登录口径:统一跳转到登录页 `/login`(不保留在当前后台路由)。
|
||||
- 宿主机 Nginx 的 `location = /fl` 不要再重定向到 `/fl/`;在 `basePath=/fl` 下,这会和 Next 的路径归一逻辑相互打架。当前口径是把 `/fl` 直接导向 `/fl/login`,并在改完 `quiz.conf` 后立即 `nginx -t && nginx -s reload`。
|
||||
|
||||
## 站点标题口径(2026-04-24)
|
||||
|
||||
|
||||
+21
-8
@@ -28,24 +28,37 @@ def _client_ip(request: Request) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _set_refresh_cookie(response: Response, token: str) -> None:
|
||||
def _is_secure_request(request: Request) -> bool:
|
||||
forwarded_proto = request.headers.get("x-forwarded-proto")
|
||||
if forwarded_proto:
|
||||
return forwarded_proto.split(",")[0].strip().lower() == "https"
|
||||
return request.url.scheme == "https"
|
||||
|
||||
|
||||
def _refresh_cookie_secure(request: Request) -> bool:
|
||||
if not settings.refresh_cookie_secure:
|
||||
return False
|
||||
return _is_secure_request(request)
|
||||
|
||||
|
||||
def _set_refresh_cookie(request: Request, response: Response, token: str) -> None:
|
||||
response.set_cookie(
|
||||
key=settings.refresh_cookie_name,
|
||||
value=token,
|
||||
httponly=True,
|
||||
secure=settings.refresh_cookie_secure,
|
||||
secure=_refresh_cookie_secure(request),
|
||||
samesite=settings.refresh_cookie_samesite,
|
||||
max_age=settings.refresh_token_expire_days * 24 * 60 * 60,
|
||||
path="/api/v1/auth",
|
||||
)
|
||||
|
||||
|
||||
def _clear_refresh_cookie(response: Response) -> None:
|
||||
def _clear_refresh_cookie(request: Request, response: Response) -> None:
|
||||
response.delete_cookie(
|
||||
key=settings.refresh_cookie_name,
|
||||
path="/api/v1/auth",
|
||||
httponly=True,
|
||||
secure=settings.refresh_cookie_secure,
|
||||
secure=_refresh_cookie_secure(request),
|
||||
samesite=settings.refresh_cookie_samesite,
|
||||
)
|
||||
|
||||
@@ -71,7 +84,7 @@ def register(
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
ip_address=_client_ip(request),
|
||||
)
|
||||
_set_refresh_cookie(response, result.refresh_token)
|
||||
_set_refresh_cookie(request, response, result.refresh_token)
|
||||
return _to_auth_response(result)
|
||||
|
||||
|
||||
@@ -88,7 +101,7 @@ def login(
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
ip_address=_client_ip(request),
|
||||
)
|
||||
_set_refresh_cookie(response, result.refresh_token)
|
||||
_set_refresh_cookie(request, response, result.refresh_token)
|
||||
return _to_auth_response(result)
|
||||
|
||||
|
||||
@@ -104,7 +117,7 @@ def refresh(
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
ip_address=_client_ip(request),
|
||||
)
|
||||
_set_refresh_cookie(response, result.refresh_token)
|
||||
_set_refresh_cookie(request, response, result.refresh_token)
|
||||
return _to_auth_response(result)
|
||||
|
||||
|
||||
@@ -119,7 +132,7 @@ def logout(
|
||||
request.cookies.get(settings.refresh_cookie_name),
|
||||
user_id=None,
|
||||
)
|
||||
_clear_refresh_cookie(response)
|
||||
_clear_refresh_cookie(request, response)
|
||||
return MessageResponse(message="Logged out")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
## Work Log - graphify 构建 api/app 后端知识图谱(2026-05-12)
|
||||
|
||||
- 背景:
|
||||
- 对 `./api/app` 执行 `$graphify`,目标是生成后端结构图谱,辅助后续代码导航与跨模块关系追踪。
|
||||
- 仓库根目录检测结果超过 graphify 建议阈值,因此改为对子目录 `api/app` 单独建图。
|
||||
|
||||
- 语料与执行结果:
|
||||
- `api/app` 检测为纯代码语料:`110` 个代码文件,约 `60,168` 词。
|
||||
- 按 graphify 规则跳过 semantic extraction,仅执行 AST 抽取、聚类、报告与 HTML 可视化。
|
||||
- 产物位于仓库根目录 `graphify-out/`:
|
||||
- `graph.html`
|
||||
- `GRAPH_REPORT.md`
|
||||
- `graph.json`
|
||||
- `cost.json`
|
||||
- `manifest.json`
|
||||
|
||||
- 关键指标:
|
||||
- 图规模:`1174` nodes / `3445` edges / `35` communities。
|
||||
- Token benchmark:
|
||||
- 语料约 `80,224` naive tokens
|
||||
- 平均查询成本约 `12,893` tokens
|
||||
- 约 `6.2x` token reduction per query
|
||||
|
||||
- 关注点:
|
||||
- 主要桥接节点包括 `utcnow()`、`publish_topic()`、`Base`、`UserPublic`、`normalize_virtual_path()`。
|
||||
- 低内聚社区主要集中在 `Elevation Workflow`、`Lightning Analysis`,适合后续做依赖与职责拆分审视。
|
||||
|
||||
- 风险与影响:
|
||||
- 本次未改业务代码,仅新增图谱输出与当天记忆文件。
|
||||
- `graphify-out/` 与 `api/app/graphify-out/cache/ast/` 为新生成工件,提交前应按需要决定是否纳入版本管理。
|
||||
@@ -128,3 +128,24 @@
|
||||
|
||||
- 风险与关注点:
|
||||
- 当前别名映射基于已知历史菜单路径补齐;若库里还存在其他非常规旧 path,仍需按实际反馈继续补别名表。
|
||||
|
||||
## Work Log - 生产入口 `/fl/login` 404 排查与 host Nginx 修复(2026-05-16)
|
||||
|
||||
- 背景:
|
||||
- 线上 `https://www.quizck.cn/fl/login` 一度返回 404,但仓库内 `web` 容器与本地代理均已包含 `/login` 页面。
|
||||
- 进一步比对发现,问题出在宿主机 Nginx 的 `/fl` 精确匹配:`location = /fl { return 301 /fl/; }` 会把入口导向 `/fl/`,而 Next 在 `/fl/` 上又会归一回 `/fl`,形成不必要的重定向回环/错路由链。
|
||||
|
||||
- 本次修复:
|
||||
- `/etc/nginx/conf.d/quiz.conf`
|
||||
- 将 `location = /fl` 的重定向目标改为 `/fl/login`。
|
||||
- 保留 `location ^~ /fl/` 继续反代到 `127.0.0.1:3000`。
|
||||
- 执行 `nginx -t && nginx -s reload` 让配置立即生效。
|
||||
|
||||
- 验证:
|
||||
- `curl -I https://www.quizck.cn/fl/login` 返回 `200 OK`。
|
||||
- `curl -IL https://www.quizck.cn/fl` 返回 `301 -> /fl/login -> 200`。
|
||||
- `curl -IL https://www.quizck.cn/fl/` 返回 `308 -> /fl -> /fl/login -> 200`。
|
||||
|
||||
- 风险与关注点:
|
||||
- `/fl/` 仍会多一次跳转,但不再出现 404。
|
||||
- 后续若统一外部入口,优先直接使用 `/fl/login`,避免再依赖 `/fl` 的归一逻辑。
|
||||
|
||||
+3
-15
@@ -39,18 +39,6 @@ function stripBasePath(pathname: string): string {
|
||||
return pathname;
|
||||
}
|
||||
|
||||
function withBasePath(pathname: string): string {
|
||||
if (!APP_BASE_PATH) {
|
||||
return pathname;
|
||||
}
|
||||
|
||||
if (pathname === "/") {
|
||||
return APP_BASE_PATH;
|
||||
}
|
||||
|
||||
return `${APP_BASE_PATH}${pathname}`;
|
||||
}
|
||||
|
||||
function isBypassedPath(pathname: string): boolean {
|
||||
if (pathname === "/" || pathname === "/login" || pathname === "/login/") {
|
||||
return true;
|
||||
@@ -75,18 +63,18 @@ export function middleware(request: NextRequest) {
|
||||
// Keep backward compatibility for existing /admin links and legacy menu aliases.
|
||||
if (pathname.startsWith("/admin/")) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = withBasePath(canonicalPath);
|
||||
url.pathname = canonicalPath;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
if (canonicalPath !== pathname) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = withBasePath(canonicalPath);
|
||||
url.pathname = canonicalPath;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// New public URLs without /admin are internally rewritten to existing routes.
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = withBasePath(`/admin${canonicalPath}`);
|
||||
url.pathname = `/admin${canonicalPath}`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user