扫码登录:cdrop 自签会话原语 + 薄账户层 + 受限访客 + 2FA step-up
身份仍源自 OAuth provider(user_id = OIDC sub),cdrop 在其上自维护一薄层: 自签会话令牌 + accounts 表,并新增扫码快速登录。实施蓝本见 AUTH.md。 后端 · 自签会话原语 - web_sessions 加 kind/scope/granted_by 列(bootstrap 幂等迁移);既有 oidc 会话行为不变,新增 self(滑动续期)/ guest(受限·不续)两类 - cdrop 自签 HS256 会话 access token,密钥派生自 SESSION_SECRET,与落盘 AES、 shortcut HS256 三密钥域隔离;jwtauth.verifySelfToken 无状态校验、靠 typ 区分 - requireFullSession 守卫:guest 会话不得改账号 / 再批准设备 / 签长效 token - /auth/refresh 按 kind 分流:self/guest 纯自签、不触 IdP 后端 · 扫码登录(internal/httpapi/qr.go) - qr/start・status・request・approve・deny 五端点 + login_requests 表 + reaper - 三密钥分离:QR 仅含批准信息,会话只投递给持私有 poll_secret 的原设备 (偷拍 QR 者无 poll_secret 领不到会话、未登录批不了准) - 会话在新设备侧领取(cookie 不经手机)、单次消费、短 TTL 后端 · 薄账户层与 2FA step-up - accounts 表(键 sub,不含任何凭证):exchange/refresh upsert match_key / 显示名 / 头像 / roles,供显示与未来管理员开启迁移标记时跨源关联 - step-up(默认关):开启后 qr/approve 要求新鲜 prompt=login 授权码,后端就地 换 id_token、JWKS 验签 + auth_time 窗口 + sub 匹配,provider 2FA 于此往返强制 前端(web/) - 显码页 /link/new + 批准页 /link + net/qr.ts,对齐 Theme B、复用 AuthShell 与聚珍排版管线、零新全局样式 - step-up 再认证流:批准前跑 prompt=login PKCE,回调分叉(不消费一次性 code、 独立 state key)后带 step_up_code/verifier 调 approve - 三语 i18n qr.*;新增 qrcode 依赖 修复 · clipboard sweeper(早已提交的损坏) - ClearExpiredClipboards 因 clipboard.sql 全角注释触发 sqlc 1.31 多字节偏移 bug,生成 SQL 被截断为「UPDATE clipboard_state SET content =」,sweeper 运行 期报「incomplete input」、过期剪贴板内容从未清除(短 TTL 暴露保护失效) - 注释改纯 ASCII 并加 bug 警告,重生成得完整 SQL;prod 已验证 sweeper 由 ERROR 转为正常清理(cleared count=1) 构建 - .dockerignore:排除本地 node_modules 等,避免宿主原生二进制污染镜像内 vite 构建 - Dockerfile.base:GOPROXY 改为可经 --build-arg 覆盖(默认仍官方代理,受限网络 构建时传区域镜像即可,仓库不固化区域值) 文档 - 新增 AUTH.md(账户与登录实施蓝本);README 特性;.env.example / compose.snippet 增配置项(QR / 自签会话 TTL / step-up / match_claim) 测试 - 自签 token 密钥域隔离、扫码端到端(领取 / 单次 / poll_secret 校验 / deny / step-up 门)、extractIdentity、web_sessions 升级迁移
This commit is contained in:
@@ -93,6 +93,50 @@ export const enUS: Partial<TranslationDict> = {
|
||||
"oauth.signingIn": "Signing in…",
|
||||
"oauth.missingParams": "Missing 'code' or 'state' query parameter",
|
||||
|
||||
// ---- QR sign-in · entry --------------------------------------------
|
||||
"qr.entry.fromLogin": "Sign in with a QR code",
|
||||
|
||||
// ---- QR sign-in · show page (new device) ---------------------------
|
||||
"qr.show.title": "Sign in with a QR code",
|
||||
"qr.show.subtitle": "Name this device, then scan the code from a signed-in phone to let it into your account.",
|
||||
"qr.show.nameHint": "Shown to your other devices. ASCII characters only.",
|
||||
"qr.show.generate": "Generate code",
|
||||
"qr.show.scanTitle": "Scan to approve",
|
||||
"qr.show.howto1": "Open the camera or cdrop on another signed-in device.",
|
||||
"qr.show.howto2": "Scan the code above and approve this device.",
|
||||
"qr.show.waiting": "Waiting for approval…",
|
||||
"qr.show.approved": "Approved — signing you in…",
|
||||
"qr.show.expired": "This code has expired. Generate a new one and scan again.",
|
||||
"qr.show.denied": "This request was declined. If that was you, generate a new code and try again.",
|
||||
"qr.show.regenerate": "Generate a new code",
|
||||
|
||||
// ---- QR sign-in · approve page (phone) -----------------------------
|
||||
"qr.approve.title": "Approve a new device",
|
||||
"qr.approve.subtitle": "Confirm this device may sign in to your account. Check the device and origin first.",
|
||||
"qr.approve.loading": "Reading device details…",
|
||||
"qr.approve.badLink": "This approval link is incomplete. Generate a new code on the device and scan it again.",
|
||||
"qr.approve.signInFirst": "Sign in to your account first, then approve this device.",
|
||||
"qr.approve.trustLabel": "Trust duration",
|
||||
"qr.approve.trust.title": "Trust this device",
|
||||
"qr.approve.trust.hint": "Skip approval for 7 days, renewed on each use.",
|
||||
"qr.approve.once.title": "Just this once",
|
||||
"qr.approve.once.hint": "Signed in for 1 hour, then scan again.",
|
||||
"qr.approve.scopeGuest": "This device signs in as a limited guest: it can send and receive files and messages, but can't change your account or approve other devices.",
|
||||
"qr.approve.approve": "Approve sign-in",
|
||||
"qr.approve.stepUp.notice": "For security, verify your identity before approving — the button below takes you through a fresh sign-in.",
|
||||
"qr.approve.stepUp.button": "Verify and approve",
|
||||
"qr.approve.stepUp.rejected": "Identity verification didn't pass or has expired. Verify again to confirm it's you, then approve.",
|
||||
"qr.approve.deny": "Decline",
|
||||
"qr.approve.doneApprovedTitle": "Approved",
|
||||
"qr.approve.doneApproved": "This device is now linked to your account. You can send and receive files on it.",
|
||||
"qr.approve.doneDeniedTitle": "Declined",
|
||||
"qr.approve.doneDenied": "The request was declined. That device won't sign in to your account.",
|
||||
"qr.approve.expiredTitle": "Code expired",
|
||||
"qr.approve.expired": "This code is no longer valid. Generate a new one on the device and scan again.",
|
||||
"qr.approve.errorTitle": "Something went wrong",
|
||||
"qr.approve.error": "That didn't go through. Try again, or generate a new code on the device.",
|
||||
"qr.approve.backHome": "Back to home",
|
||||
|
||||
// ---- settings -------------------------------------------------------
|
||||
"settings.title": "Settings",
|
||||
"settings.currentDevice.title": "This device",
|
||||
|
||||
@@ -91,6 +91,50 @@ export const zhCN = {
|
||||
"oauth.signingIn": "正在登录…",
|
||||
"oauth.missingParams": "缺少code或state查询参数",
|
||||
|
||||
// ---- 扫码登录 · 入口 -------------------------------------------------
|
||||
"qr.entry.fromLogin": "扫码登录此设备",
|
||||
|
||||
// ---- 扫码登录 · 显码页(新设备)-------------------------------------
|
||||
"qr.show.title": "扫码登录此设备",
|
||||
"qr.show.subtitle": "先给这台设备起个名字,再用已登录的手机扫码批准它接入你的账号。",
|
||||
"qr.show.nameHint": "此名称会展示给你的其他设备,仅可使用 ASCII 字符。",
|
||||
"qr.show.generate": "生成二维码",
|
||||
"qr.show.scanTitle": "用手机扫码批准",
|
||||
"qr.show.howto1": "在另一台已登录的设备上打开相机或 cdrop。",
|
||||
"qr.show.howto2": "扫描上方二维码,按提示批准此设备。",
|
||||
"qr.show.waiting": "等待批准…",
|
||||
"qr.show.approved": "已批准,正在进入…",
|
||||
"qr.show.expired": "二维码已过期。重新生成一张再扫。",
|
||||
"qr.show.denied": "这次接入被拒绝。如确为你本人操作,重新生成二维码再试。",
|
||||
"qr.show.regenerate": "重新生成二维码",
|
||||
|
||||
// ---- 扫码登录 · 批准页(手机)-------------------------------------
|
||||
"qr.approve.title": "批准新设备",
|
||||
"qr.approve.subtitle": "确认让这台设备登入你的账号。请核对设备与来源无误。",
|
||||
"qr.approve.loading": "正在读取设备信息…",
|
||||
"qr.approve.badLink": "这个批准链接不完整。请在新设备上重新生成二维码再扫。",
|
||||
"qr.approve.signInFirst": "先登录你的账号,再批准这台设备接入。",
|
||||
"qr.approve.trustLabel": "信任时长",
|
||||
"qr.approve.trust.title": "信任此设备",
|
||||
"qr.approve.trust.hint": "7 天内免再次批准,每次使用自动续期。",
|
||||
"qr.approve.once.title": "仅此一次",
|
||||
"qr.approve.once.hint": "登录有效 1 小时,到期需重新扫码。",
|
||||
"qr.approve.scopeGuest": "这台设备将以受限访客身份登入:可收发文件与消息,但不能更改账号、不能批准其他设备。",
|
||||
"qr.approve.approve": "批准登入",
|
||||
"qr.approve.stepUp.notice": "为安全起见,批准前需先验证你的身份——点下方按钮会引导你重新登录一次。",
|
||||
"qr.approve.stepUp.button": "验证并批准",
|
||||
"qr.approve.stepUp.rejected": "身份验证未通过或已过期。请再验证一次,确认是你本人后即可批准。",
|
||||
"qr.approve.deny": "拒绝",
|
||||
"qr.approve.doneApprovedTitle": "已批准",
|
||||
"qr.approve.doneApproved": "这台设备已接入你的账号,现在即可在它上面收发文件。",
|
||||
"qr.approve.doneDeniedTitle": "已拒绝",
|
||||
"qr.approve.doneDenied": "这次接入已被拒绝,那台设备不会登入你的账号。",
|
||||
"qr.approve.expiredTitle": "二维码已过期",
|
||||
"qr.approve.expired": "这张二维码已失效。请在新设备上重新生成再扫。",
|
||||
"qr.approve.errorTitle": "出错了",
|
||||
"qr.approve.error": "操作没能完成。请重试,或在新设备上重新生成二维码。",
|
||||
"qr.approve.backHome": "返回主页",
|
||||
|
||||
// ---- 设置页 ----------------------------------------------------------
|
||||
"settings.title": "设置",
|
||||
"settings.currentDevice.title": "当前设备",
|
||||
|
||||
@@ -95,6 +95,50 @@ export const zhTW: Partial<TranslationDict> = {
|
||||
"oauth.signingIn": "正在登入…",
|
||||
"oauth.missingParams": "缺少code或state查詢參數",
|
||||
|
||||
// ---- 掃碼登入 · 入口 -------------------------------------------------
|
||||
"qr.entry.fromLogin": "掃碼登入此裝置",
|
||||
|
||||
// ---- 掃碼登入 · 顯碼頁(新裝置)-------------------------------------
|
||||
"qr.show.title": "掃碼登入此裝置",
|
||||
"qr.show.subtitle": "先為這部裝置命名,再用已登入的手機掃碼批准它接入你的帳號。",
|
||||
"qr.show.nameHint": "此名稱會顯示給你的其他裝置,僅可使用 ASCII 字元。",
|
||||
"qr.show.generate": "產生 QR 碼",
|
||||
"qr.show.scanTitle": "用手機掃碼批准",
|
||||
"qr.show.howto1": "在另一部已登入的裝置上開啟相機或 cdrop。",
|
||||
"qr.show.howto2": "掃描上方 QR 碼,依提示批准此裝置。",
|
||||
"qr.show.waiting": "等待批准…",
|
||||
"qr.show.approved": "已批准,正在進入…",
|
||||
"qr.show.expired": "QR 碼已逾時。重新產生一張再掃。",
|
||||
"qr.show.denied": "這次接入被拒絕。如確為你本人操作,重新產生 QR 碼再試。",
|
||||
"qr.show.regenerate": "重新產生 QR 碼",
|
||||
|
||||
// ---- 掃碼登入 · 批准頁(手機)-------------------------------------
|
||||
"qr.approve.title": "批准新裝置",
|
||||
"qr.approve.subtitle": "確認讓這部裝置登入你的帳號。請核對裝置與來源無誤。",
|
||||
"qr.approve.loading": "正在讀取裝置資訊…",
|
||||
"qr.approve.badLink": "這個批准連結不完整。請在新裝置上重新產生 QR 碼再掃。",
|
||||
"qr.approve.signInFirst": "先登入你的帳號,再批准這部裝置接入。",
|
||||
"qr.approve.trustLabel": "信任時長",
|
||||
"qr.approve.trust.title": "信任此裝置",
|
||||
"qr.approve.trust.hint": "7 天內免再次批准,每次使用自動續期。",
|
||||
"qr.approve.once.title": "僅此一次",
|
||||
"qr.approve.once.hint": "登入有效 1 小時,逾時需重新掃碼。",
|
||||
"qr.approve.scopeGuest": "這部裝置將以受限訪客身分登入:可收發檔案與訊息,但不能變更帳號、不能批准其他裝置。",
|
||||
"qr.approve.approve": "批准登入",
|
||||
"qr.approve.stepUp.notice": "為安全起見,批准前需先驗證你的身分——點下方按鈕會引導你重新登入一次。",
|
||||
"qr.approve.stepUp.button": "驗證並批准",
|
||||
"qr.approve.stepUp.rejected": "身分驗證未通過或已逾時。請再驗證一次,確認是你本人後即可批准。",
|
||||
"qr.approve.deny": "拒絕",
|
||||
"qr.approve.doneApprovedTitle": "已批准",
|
||||
"qr.approve.doneApproved": "這部裝置已接入你的帳號,現在即可在它上面收發檔案。",
|
||||
"qr.approve.doneDeniedTitle": "已拒絕",
|
||||
"qr.approve.doneDenied": "這次接入已被拒絕,那部裝置不會登入你的帳號。",
|
||||
"qr.approve.expiredTitle": "QR 碼已逾時",
|
||||
"qr.approve.expired": "這張 QR 碼已失效。請在新裝置上重新產生再掃。",
|
||||
"qr.approve.errorTitle": "出錯了",
|
||||
"qr.approve.error": "操作沒能完成。請重試,或在新裝置上重新產生 QR 碼。",
|
||||
"qr.approve.backHome": "返回首頁",
|
||||
|
||||
// ---- 設定頁 ----------------------------------------------------------
|
||||
"settings.title": "設定",
|
||||
"settings.currentDevice.title": "目前裝置",
|
||||
|
||||
Reference in New Issue
Block a user