TREK 登录与注册完全指南:会话 Cookie、密码策略、邀请链接、限流与 SSO 实战解析
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
TREK 是一个可自托管的旅行行程规划器,其登录/注册体系覆盖从密码认证、双因素认证(MFA)、邀请链接注册、强制改密、按 IP 限流到 OIDC SSO 的完整身份链路。本文以 wiki/Login-and-Registration.md 为骨架,结合前端登录页实现与后端认证服务源码,逐层剖析会话 Cookie 的属性与有效期、密码校验规则、注册的三种触发条件、四类限流阈值以及 Demo 模式与 SSO 的交互细节,帮助你理解并正确配置 TREK 实例的身份入口,也能据此排查“登录后反复 401”“明文 HTTP 下 Cookie 丢失”等自托管常见问题。
登录流程与会话 Cookie
访问/login并提交凭据
导航到/login后,TREK 前端(client/src/pages/LoginPage.tsx)会先调用GET /api/auth/app-config获取实例级配置(是否已有用户、是否开放注册、是否配置了 OIDC、是否启用 Demo 等,见 server/src/services/authService.ts 的getAppConfig),再决定渲染登录表单、注册表单,或直接跳转 SSO。
输入邮箱与密码提交后,请求命中POST /api/auth/login(server/src/nest/auth/auth-public.controller.ts)。服务端通过 bcrypt(成本因子 12,见 server/src/services/authService.ts)比对密码哈希,成功后签发 JWT 会话令牌,并通过Set-Cookie写入trek_session。
trek_sessionCookie 的关键属性
会话 Cookie 的构造集中在 server/src/services/cookie.ts,核心属性如下:
| 属性 | 值 | 含义 |
|---|---|---|
httpOnly | true | 脚本无法读取,防御 XSS 窃取会话 |
sameSite | lax | 允许顶级导航携带 Cookie,同时缓解 CSRF |
secure | 生产环境为true | 仅通过 HTTPS 传输(详见下文) |
maxAge | 默认SESSION_DURATION_MS | 会话有效期,与 JWT 的exp声明保持一致 |
path | / | 全站生效 |
24 小时会话与 Remember me
默认情况下,一次登录产生的会话有效期为24 小时,刷新页面、关闭浏览器重启都不会失效,直到会话过期或显式登出。这个默认值来自 server/src/config.ts 中的DEFAULT_SESSION_DURATION = '24h'。
SESSION_DURATION环境变量:可覆盖默认会话时长,支持ms/s/m/h/d/w/y单位的 ms 风格字符串,例如SESSION_DURATION=7d。配置解析在启动时进行,非法值会告警并回退到24h。- Remember me(记住我):登录表单上的开关对应
SESSION_DURATION_REMEMBER,默认30 天(server/src/config.ts)。实现上rememberMe同时影响两处:JWT 的expiresIn与 Cookie 的maxAge(server/src/services/authService.ts 的generateToken、server/src/services/cookie.ts 的resolveMaxAge),两者永远同步、不会漂移。 - 未勾选 Remember me 时,Cookie 不携带
maxAge,属于浏览器会话级 Cookie,关闭浏览器即清除(server/src/services/cookie.ts)。
注意:
secure标志可用COOKIE_SECURE=false显式关闭(适合纯 HTTP 的开发环境),也可用FORCE_HTTPS=true强制开启。此外,server/src/services/cookie.ts 的实现会额外检查req.secure(Express 在trust proxy开启时由X-Forwarded-Proto推导)——也就是说,即使本地NODE_ENV=development且未设FORCE_HTTPS,只要请求经由 Traefik/Caddy/Cloudflare Tunnel 等 HTTPS 反代进入,Cookie 同样会带上Secure标志,这正是不少自托管用户忽略的细节。
明文 HTTP 下的 Cookie 丢失陷阱
若服务端即将下发带Secure标志的 Cookie,而请求本身并非 HTTPS,浏览器会静默丢弃 Cookie,导致下一个请求直接返回 “Access token required”。为此 TREK 在登录响应中附带insecureCookie: true标志(server/src/nest/auth/auth-public.controller.ts),前端检测到后会在登录页展示黄色提示条,指导你改用 HTTPS 或设置COOKIE_SECURE=false(见 client/src/pages/LoginPage.tsx),而不是让你面对一个莫名其妙的 401。
双因素认证(MFA)登录
如果账号已启用 2FA,密码校验通过后不会立即下发会话 Cookie,而是返回{ mfa_required: true, mfa_token }(server/src/services/authService.ts)。这个mfa_token是有效期仅5 分钟的短期 JWT(purpose: 'mfa_login'),专门用于承载后续的 MFA 验证步骤,与会话令牌相互隔离。
前端收到mfa_required后切换到 TOTP 输入界面(client/src/pages/login/useLogin.ts),支持:
- TOTP 验证码:来自你绑定的 Authenticator 应用;
- 备用恢复码(backup code):8 位字符,输入时自动转大写并去除非字母数字字符(server/src/services/authService.ts 的
normalizeBackupCode)。
验证请求走POST /api/auth/mfa/verify-login(server/src/nest/auth/auth-public.controller.ts),成功后才会真正写入trek_session会话 Cookie,并记录user.login审计事件。MFA 验证单独限流为每 IP 每 15 分钟 5 次。
忘记密码与强制改密
忘记密码自助流程
登录表单下方的“Forgot password?”链接启动自助重置流程,完整细节见 wiki/Password-Reset.md。服务端实现上,POST /api/auth/forgot-password(server/src/nest/auth/auth-public.controller.ts)无论邮箱是否存在都返回统一的{ ok: true }响应,并强制等待至少 350ms(FORGOT_MIN_LATENCY_MS),以消除账号枚举的时间侧信道;POST /api/auth/reset-password则负责校验重置令牌并写入新密码。
强制密码更改(Forced password change)
当管理员将账号标记为“必须修改密码”时(数据库中must_change_password字段为 1),成功登录(或 MFA 步骤完成)后,TREK 会直接展示Set new password表单,且不会签发会话 Cookie,直到新密码保存成功:
- 登录接口返回的用户对象带有
must_change_password: true(server/src/services/authService.ts); - 前端捕获该标志,进入
passwordChangeStep状态(client/src/pages/login/useLogin.ts); - 提交新密码走
POST /api/auth/change-password,成功后才会loadUser并跳转(client/src/pages/login/useLogin.ts)。
服务端在执行改密时同样会走validatePassword密码强度校验,并更新password_version、清除must_change_password(server/src/services/authService.ts)。password_version的存在意味着改密后旧令牌即刻失效——这正是会话安全模型的关键一环。
注册流程
注册表单出现的三种条件
TREK 的注册表单只在以下任一条件成立时展示:
- 开放注册:管理员为实例启用了密码注册(
password_registration设置项)。该开关在 server/src/services/authService.ts 的resolveAuthToggles中解析,可由数据库app_settings表或环境变量控制,且在 Demo 模式下被强制关闭(server/src/services/authService.ts)。 - 有效的邀请链接:访问
/login?invite=TOKEN且令牌有效(详见下文“邀请链接流程”)。 - 首个用户:实例中尚不存在任何账号,注册表单自动展示。
前端逻辑与之一一对应:useLogin启动时解析invite查询参数、拉取app-config,若has_users === false则直接切换到注册模式(client/src/pages/login/useLogin.ts 与 client/src/pages/login/useLogin.ts)。
注册字段与校验
注册表单包含三个字段:username(用户名)、email(邮箱)、password(密码)。服务端registerUser(server/src/services/authService.ts)会依次校验:
- 三字段非空;
- 密码满足强度规则(见下节);
- 邮箱格式合法(
EMAIL_REGEX); - 用户名/邮箱未与已有账号冲突(返回 409,且客座账号
is_guest不会阻塞真实注册)。
密码使用 bcrypt 成本因子 12 哈希后落库,随后签发会话令牌并写入trek_sessionCookie。
密码强度要求
密码必须同时满足以下全部规则,实现在 server/src/services/passwordPolicy.ts 的validatePassword:
- 最短8 个字符;
- 至少包含一个大写字母;
- 至少包含一个小写字母;
- 至少包含一个数字;
- 至少包含一个特殊字符(非字母数字);
- 不得是常见弱密码(内置 30 余条黑名单,如
password、12345678、qwerty123、admin123、letmein12、welcome1、passw0rd、changeme等,server/src/services/passwordPolicy.ts); - 不得由单一重复字符构成(正则
^(.)\1+$拦截,如aaaaaaaa)。
管理员提示:可以关闭开放注册,仅保留邀请链接方式,见 wiki/Admin-Users-and-Invites.md。
邀请链接流程
管理员分享形如/login?invite=TOKEN的邀请链接后,访问它会依次发生(前端见 client/src/pages/login/useLogin.ts):
- 向
GET /api/auth/invite/:token校验令牌(该端点本身也受登录桶限流保护,server/src/nest/auth/auth-public.controller.ts); - 校验通过后,登录页自动切换到注册模式;
- 注册请求携带该 token,使本次注册计入邀请链接的使用次数上限(
invite_tokens.used_count递增,server/src/services/authService.ts)。
若令牌无效、过期或用尽,页面会展示错误提示(login.invalidInviteLink)。另外,若邀请链接绑定到具体行程(trip-bound invite),新注册用户会被自动加入该行程(server/src/services/authService.ts)。
首个用户自动成为管理员
在没有任何账号的全新 TREK 实例上,注册表单会直接打开。服务端通过统计非客座用户数量判断isFirstUser,首个账号的role被强制设为admin(server/src/services/authService.ts),后续注册的账号默认均为普通user角色。
按 IP 限流机制
四类限流阈值
TREK 对认证接口实施内存级的按 IP 限流,同一 IP 在 15 分钟窗口内的允许次数如下:
| 操作 | 阈值(每 IP / 15 分钟) | 对应桶 |
|---|---|---|
| 登录失败/登录尝试(含注册、邀请校验) | 10 次 | login |
| MFA 验证尝试 | 5 次 | mfa |
| 忘记密码请求 | 3 次 | forgot |
| 重置密码提交 | 5 次 | reset |
超过阈值后,后续请求返回HTTP 429,直到窗口重置。限流桶定义见 server/src/nest/auth/auth-public.controller.ts 的WINDOW = 15 * 60 * 1000以及各端点调用处。
底层实现
限流核心是 server/src/nest/auth/rate-limit.service.ts 的RateLimitService:一个内存中的双层 Map(buckets → key(IP) → { count, first }),check方法在count >= max且距首次尝试未超过窗口时拒绝请求(server/src/nest/auth/rate-limit.service.ts)。实现要点:
- 按命名桶隔离:登录、MFA、忘记密码、重置密码互不干扰;
- 滑动窗口语义:窗口内第 N+1 次尝试立即被拒,窗口过期后自动视为全新尝试;
- 纯内存实现:服务重启后计数清零(同一实例的 API 网关已通过插件宿主实现自身的限流,见 server/src/nest/plugins/host/rate-limit.ts)。
此外,登录接口还引入了最小响应延迟 350ms(LOGIN_MIN_LATENCY_MS,server/src/nest/auth/auth-public.controller.ts):即使密码错误也会补足耗时,配合“未知邮箱同样执行 bcrypt 假哈希”的策略(server/src/services/authService.ts),从时间维度进一步防御账号枚举与暴力破解。
Demo 模式
服务器以DEMO_MODE=true启动时(生产环境开启会打印安全警告,见 server/src/index.ts),登录表单下方会出现“Try demo”一键登录按钮。点击后调用POST /api/auth/demo-login(server/src/nest/auth/auth-public.controller.ts),免密直接以演示用户身份登录,前端随后播放一段“飞机起飞”过渡动画并进入仪表盘(client/src/pages/login/useLogin.ts)。
演示凭据demo@trek.app/demo12345会随/api/auth/app-config返回(demo_email/demo_password字段,server/src/services/authService.ts),但一键按钮才是官方推荐的进入方式。需要注意:Demo 模式下注册被强制关闭(password_registration: false),且演示账号在许多业务接口上受到只读/受限处理(server/src/middleware/auth.ts)。
SSO(OpenID Connect)登录
常规 OIDC 登录
管理员配置好 OpenID Connect 后,登录表单下方会出现“Sign in with SSO”按钮,按钮文案取自OIDC_DISPLAY_NAME环境变量(默认回退到SSO,server/src/services/oidcService.ts)。点击后前端跳转/api/auth/oidc/login,服务端将浏览器重定向到身份提供方;回调带回oidc_code后由前端完成 code 交换并恢复会话(client/src/pages/login/useLogin.ts)。
OIDC-only 模式
当实例处于OIDC-only 模式(密码登录被禁用)时:
- 访问
/login会自动重定向到身份提供方,不再展示邮箱/密码表单; - 唯一的例外是你刚刚显式登出:此时自动跳转被抑制,改为展示 SSO 按钮,让你主动选择重新登录(client/src/pages/login/useLogin.ts 与 client/src/pages/LoginPage.tsx)。
OIDC-only 可由环境变量OIDC_ONLY=true或数据库设置oidc_only触发,app-config中的env_override_oidc_only字段会标明其来源(server/src/services/authService.ts)。完整配置与登录流说明见 wiki/OIDC-SSO.md。
常见问题速查
| 现象 | 原因与解法 |
|---|---|
| 明文 HTTP 下登录后下一请求 401 | SecureCookie 被浏览器丢弃。改用 HTTPS,或开发环境设COOKIE_SECURE=false |
| 登录被 429 拒绝 | 同一 IP 15 分钟内登录尝试超过 10 次,等待窗口重置 |
| 会话提前失效 | 检查SESSION_DURATION/SESSION_DURATION_REMEMBER是否被修改,以及管理员是否执行了 JWT 轮换(见 server/src/config.ts 的updateJwtSecret) |
| 注册表单不显示 | 实例关闭了开放注册且无有效邀请链接;若为全新实例,请确认has_users为 false |
相关文档:wiki/Password-Reset.md · wiki/OIDC-SSO.md · wiki/Admin-Users-and-Invites.md · wiki/Two-Factor-Authentication.md
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考