fix: refine auth cookie and route redirects
This commit is contained in:
@@ -566,6 +566,7 @@
|
|||||||
- 未登录访问后台时提示“前往登录”(`/login`);
|
- 未登录访问后台时提示“前往登录”(`/login`);
|
||||||
- 账号菜单提供“用户管理”(`/users`)作为默认后台入口。
|
- 账号菜单提供“用户管理”(`/users`)作为默认后台入口。
|
||||||
- 退出登录口径:统一跳转到登录页 `/login`(不保留在当前后台路由)。
|
- 退出登录口径:统一跳转到登录页 `/login`(不保留在当前后台路由)。
|
||||||
|
- 宿主机 Nginx 的 `location = /fl` 不要再重定向到 `/fl/`;在 `basePath=/fl` 下,这会和 Next 的路径归一逻辑相互打架。当前口径是把 `/fl` 直接导向 `/fl/login`,并在改完 `quiz.conf` 后立即 `nginx -t && nginx -s reload`。
|
||||||
|
|
||||||
## 站点标题口径(2026-04-24)
|
## 站点标题口径(2026-04-24)
|
||||||
|
|
||||||
|
|||||||
+21
-8
@@ -28,24 +28,37 @@ def _client_ip(request: Request) -> str | None:
|
|||||||
return 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(
|
response.set_cookie(
|
||||||
key=settings.refresh_cookie_name,
|
key=settings.refresh_cookie_name,
|
||||||
value=token,
|
value=token,
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=settings.refresh_cookie_secure,
|
secure=_refresh_cookie_secure(request),
|
||||||
samesite=settings.refresh_cookie_samesite,
|
samesite=settings.refresh_cookie_samesite,
|
||||||
max_age=settings.refresh_token_expire_days * 24 * 60 * 60,
|
max_age=settings.refresh_token_expire_days * 24 * 60 * 60,
|
||||||
path="/api/v1/auth",
|
path="/api/v1/auth",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _clear_refresh_cookie(response: Response) -> None:
|
def _clear_refresh_cookie(request: Request, response: Response) -> None:
|
||||||
response.delete_cookie(
|
response.delete_cookie(
|
||||||
key=settings.refresh_cookie_name,
|
key=settings.refresh_cookie_name,
|
||||||
path="/api/v1/auth",
|
path="/api/v1/auth",
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=settings.refresh_cookie_secure,
|
secure=_refresh_cookie_secure(request),
|
||||||
samesite=settings.refresh_cookie_samesite,
|
samesite=settings.refresh_cookie_samesite,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,7 +84,7 @@ def register(
|
|||||||
user_agent=request.headers.get("user-agent"),
|
user_agent=request.headers.get("user-agent"),
|
||||||
ip_address=_client_ip(request),
|
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)
|
return _to_auth_response(result)
|
||||||
|
|
||||||
|
|
||||||
@@ -88,7 +101,7 @@ def login(
|
|||||||
user_agent=request.headers.get("user-agent"),
|
user_agent=request.headers.get("user-agent"),
|
||||||
ip_address=_client_ip(request),
|
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)
|
return _to_auth_response(result)
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +117,7 @@ def refresh(
|
|||||||
user_agent=request.headers.get("user-agent"),
|
user_agent=request.headers.get("user-agent"),
|
||||||
ip_address=_client_ip(request),
|
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)
|
return _to_auth_response(result)
|
||||||
|
|
||||||
|
|
||||||
@@ -119,7 +132,7 @@ def logout(
|
|||||||
request.cookies.get(settings.refresh_cookie_name),
|
request.cookies.get(settings.refresh_cookie_name),
|
||||||
user_id=None,
|
user_id=None,
|
||||||
)
|
)
|
||||||
_clear_refresh_cookie(response)
|
_clear_refresh_cookie(request, response)
|
||||||
return MessageResponse(message="Logged out")
|
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,仍需按实际反馈继续补别名表。
|
- 当前别名映射基于已知历史菜单路径补齐;若库里还存在其他非常规旧 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;
|
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 {
|
function isBypassedPath(pathname: string): boolean {
|
||||||
if (pathname === "/" || pathname === "/login" || pathname === "/login/") {
|
if (pathname === "/" || pathname === "/login" || pathname === "/login/") {
|
||||||
return true;
|
return true;
|
||||||
@@ -75,18 +63,18 @@ export function middleware(request: NextRequest) {
|
|||||||
// Keep backward compatibility for existing /admin links and legacy menu aliases.
|
// Keep backward compatibility for existing /admin links and legacy menu aliases.
|
||||||
if (pathname.startsWith("/admin/")) {
|
if (pathname.startsWith("/admin/")) {
|
||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
url.pathname = withBasePath(canonicalPath);
|
url.pathname = canonicalPath;
|
||||||
return NextResponse.redirect(url);
|
return NextResponse.redirect(url);
|
||||||
}
|
}
|
||||||
if (canonicalPath !== pathname) {
|
if (canonicalPath !== pathname) {
|
||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
url.pathname = withBasePath(canonicalPath);
|
url.pathname = canonicalPath;
|
||||||
return NextResponse.redirect(url);
|
return NextResponse.redirect(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New public URLs without /admin are internally rewritten to existing routes.
|
// New public URLs without /admin are internally rewritten to existing routes.
|
||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
url.pathname = withBasePath(`/admin${canonicalPath}`);
|
url.pathname = `/admin${canonicalPath}`;
|
||||||
return NextResponse.rewrite(url);
|
return NextResponse.rewrite(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user