diff --git a/README.md b/README.md index 9c38e9c..4fcad6e 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,127 @@ BUCKET > 📝 详细配置说明请参考 `登录限制功能配置说明.md` 文件 -### 6. 重新部署项目 +### 6. 配置 D1 数据库账户体系(可选,推荐) + +[#6-配置-d1-数据库账户体系可选推荐](#6-配置-d1-数据库账户体系可选推荐) + +默认情况下,账户仍然使用第 3 步中基于环境变量的 `username:password=dir1/,dir2/` 配置方式(无需任何改动即可继续使用)。 + +增强版额外支持将账户存储在 **Cloudflare D1** 数据库中,优点是: + +- ✅ 用户可以**自助修改密码**,无需管理员改环境变量、重新部署 +- ✅ 密码使用 PBKDF2-SHA256 加盐哈希存储,不落明文 +- ✅ 登录时优先查询 D1,查不到再自动回退到旧版环境变量账户,**文件读写权限层面两种方式可以混用,完全向后兼容** + +⚠️ **账户管理权限(管理员)和文件权限是两回事**:环境变量账户不管配置了什么权限(哪怕是 `"*"`),都**不具备**调用 `/api/auth/register`、`/api/auth/unban`、`/api/auth/ban-status` 这些管理接口的能力——这些接口只认 D1 数据库里 `is_admin = 1` 的账户。第一个管理员账户需要直接用 `wrangler d1` 命令写入数据库,见下面 6.4。 + +#### 6.1 创建 D1 数据库 + +1. 进入 Cloudflare 控制台 → **Workers & Pages** → **D1 SQL Database** +2. 点击 **Create Database**,输入一个名称(例如 `flaredrive-users`) + +#### 6.2 绑定 D1 到项目 + +1. 进入 Pages 项目设置 → **Settings** → **Functions** +2. 在 **D1 database bindings** 部分点击 **Add binding** +3. **Variable name**:`DB`(必须叫这个名字,代码里写死了这个绑定名) +4. **D1 database**:选择刚创建的数据库 + +#### 6.3 初始化表结构 + +在本地使用 Wrangler 执行仓库自带的迁移文件: + +```bash +# 首次在本地测试可以不加 --remote,先在本地模拟数据库里建表 +wrangler d1 execute flaredrive-users --file=./migrations/0001_create_users.sql + +# 确认无误后,对线上(生产)数据库执行: +wrangler d1 execute flaredrive-users --file=./migrations/0001_create_users.sql --remote +``` + +`flaredrive-users` 替换成你实际的数据库名称。 + +#### 6.4 创建第一个管理员账户 + +D1 里还没有任何账户时无法直接调用 `/api/auth/register`(它本身要求调用者已经是管理员)。**第一个管理员账户必须直接用 `wrangler d1` 命令写进数据库**,不再依赖环境变量账户来"引导"——因为数据库里的密码是加盐哈希存的,没法手写一条 SQL 就把明文密码塞进去,仓库自带了一个小脚本帮你算出正确的哈希: + +```bash +node scripts/create-admin-sql.js admin "一个强密码" > admin.sql +wrangler d1 execute flaredrive-users --file=./admin.sql --remote +rm admin.sql # 里面包含密码哈希,用完记得删掉 +``` + +- 第二个参数是密码,第三个参数(可选)是权限,默认 `"*"`(全部目录) +- 加 `--readonly` 参数可以创建只读管理员(能管理账户,但不能写文件) +- `flaredrive-users` 换成你实际的数据库名称 + +登录这个账户之后,就可以用它调用 `/api/auth/register` 创建更多账户了: + +```bash +curl -X POST https://你的域名/api/auth/register \ + -H "Authorization: Basic $(echo -n 'admin:一个强密码' | base64)" \ + -H "Content-Type: application/json" \ + -d '{ + "username": "alice", + "password": "至少6位的密码", + "permissions": ["personal/", "work/"], + "isReadOnly": false, + "isAdmin": false + }' +``` + +- `permissions` 只代表文件目录权限,传 `["*"]` 表示能读写所有目录,**不代表管理员** +- `isAdmin` 为 `true` 时才会创建出真正的管理员账户(能调用 register/unban/ban-status),只有调用者自己已经是管理员时才生效,默认为 `false` +- `isReadOnly` 为 `true` 时创建只读账户 + +**用户名重复时**,接口会返回 `409 Conflict` 和如下结构,方便前端直接判断并提示: + +```json +{ + "success": false, + "code": "USERNAME_TAKEN", + "message": "用户名 \"alice\" 已存在,请换一个用户名" +} +``` + +重复判定包含两种情况:D1 数据库里已有同名账户,或环境变量里已配置同名账户(因为登录时优先匹配 D1 账户,同名会导致原有的环境变量账户失效)。 + +创建成功后,该用户就可以像环境变量账户一样正常登录、上传、下载了,同时还能使用第 6.5 节的接口自助改密码。 + +#### 6.5 用户自助修改密码 + +已登录用户可以调用 `/api/auth/change-password` 接口修改自己的密码(仅对 D1 账户生效,环境变量账户请管理员直接改环境变量): + +```bash +curl -X POST https://你的域名/api/auth/change-password \ + -H "Authorization: Basic $(echo -n 'alice:旧密码' | base64)" \ + -H "Content-Type: application/json" \ + -d '{ "newPassword": "至少6位的新密码" }' +``` + +前端登录后,点击右上角用户菜单里的「修改密码」即可打开对话框完成上述操作,无需手动调用接口。 + +> ⚠️ D1 与环境变量账户可以同名同时存在,此时登录会优先匹配 D1 中的账户。建议不要让两者重名,避免混淆。 + +### 🔒 安全加固说明 + +本次改动顺带修复了几个安全问题,升级时请留意: + +1. **未鉴权的文件写入(严重)**:`/api/write/items/[[path]].ts` 的分片上传接口(`POST ?uploads` 创建、`POST ?uploadId` 完成)此前完全没有做权限校验,配合已有鉴权的 `PUT` 分片上传步骤,任何人不登录即可写入任意目录。现已补上校验,与 `PUT`/`DELETE` 保持一致。 +2. **`/raw/` 文件预览的存储型 XSS**:上传一个 `Content-Type` 为 `text/html`(或 `.svg` 等)的文件后,通过 `/raw/文件路径` 直接打开会在本域名下执行其中的脚本,从而窃取保存在 `localStorage` 里的登录凭据。现已给 `/raw/` 响应统一加上 `X-Content-Type-Options: nosniff` 和 `Content-Security-Policy: sandbox`(彻底禁止脚本执行、表单提交等,不影响图片/视频/音频的正常预览),并对 `text/html`、`image/svg+xml` 等类型额外强制 `Content-Disposition: attachment`。 +3. **游客缩略图写入范围过大**:只要配置了 `GUEST` 环境变量(不论允许哪些目录),未登录用户此前都能写入共享的 `_$flaredrive$/thumbnails/` 目录。现在缩略图路径和普通文件一样,按 `GUEST` 配置的具体目录做匹配,需要显式在 `GUEST` 里加上该路径(或 `*`)才允许游客写入。 +4. **Basic Auth 凭据被跨站请求利用的风险**:写操作接口此前在 401 响应里带有 `WWW-Authenticate` 头,会导致浏览器弹出原生登录框并缓存凭据,而缓存后的 Basic 凭据会被浏览器自动附加到**任意来源**发起的同域请求上,构成 CSRF 隐患。现已移除该响应头,并为 `PUT`/`POST`/`DELETE` 增加了 `Origin` 同源校验(仅在请求带有 `Origin` 且与当前域名不一致时才拒绝,不影响 curl 等不带 `Origin` 头的合法调用)。 +5. **权限校验路径与实际写入路径解码方式不一致**、以及仓库里附带的 `test-permission.html` / `test-login-limit.html` 调试页面此前会被一起部署到线上、且没有任何鉴权:已将权限校验路径改为与实际写入路径一致的解码方式,并移除了这两个调试页面(如果你本地还需要用它们做权限测试,可以从 Git 历史中找回,但不建议再部署到线上)。 +6. **D1 账户越权成为管理员(严重)**:`isAdmin` 之前的判断逻辑是 `is_admin 列 || permissions 包含 "*"`。由于 `is_admin` 列此前从来没有任何代码路径会把它设成 1(`/api/auth/register` 一直硬编码 `isAdmin: false`),这个 `||` 分支实际上是 D1 账户唯一能拿到管理员权限的途径——只要给某个账户配置了 `"*"` 目录权限(哪怕它同时被标成 `isReadOnly: true`,本意只是"能看所有目录但不能写"),它就会被当成真正的管理员,能调用 `/api/auth/register`、`/api/auth/unban`、`/api/auth/ban-status` 这些本该只有管理员才能用的接口,创建新账户、封禁/解封用户。现在 `isAdmin` 只看 `is_admin` 这一个独立字段,不再和目录权限挂钩,也**不再允许环境变量账户具备任何账户管理能力**(哪怕配置了 `"*"`)——管理员权限现在完全只属于 D1 里 `is_admin = 1` 的账户,第一个管理员账户需要直接用 `wrangler d1` 命令写入数据库(见「配置 D1 数据库账户体系」6.4 节和仓库自带的 `scripts/create-admin-sql.js`),之后才能用它通过 `/api/auth/register` 的 `isAdmin: true` 字段创建更多管理员。 + + ⚠️ **如果你已经部署过 D1 账户体系,请务必检查**:升级后,任何此前依赖"给账户配置 `*` 权限"来获得管理员能力的 D1 账户,都会**立刻失去**调用上述管理接口的权限,而且**不能再用环境变量账户去重新授予**(环境变量账户现在完全没有管理员能力了)。如果你确实需要某个账户保留管理员能力,直接执行: + ```bash + wrangler d1 execute --command="UPDATE users SET is_admin = 1 WHERE username = '你的账户名'" --remote + ``` + +如果你是从旧版本升级,建议同时检查一下自己的 `GUEST` 环境变量配置是否符合预期(第 3 条的行为变化可能影响到依赖旧行为的部署),并按第 6 条检查是否有账户需要重新授予管理员权限。 + +### 8. 重新部署项目 完成所有设置后,回到 Pages 控制台,点击「Deployments」页面右上角的「Trigger Redeploy」以重新部署服务。 @@ -469,3 +589,8 @@ GUEST=public/ - 确认已先复制文件到剪贴板 - 检查用户权限是否允许写操作 - 移动端查看底部是否显示粘贴工具栏 + +--- + ++ **当前版本修复了只读用户显示所有目录的bug,只显示权限目录** ++ **文件名高度不够,已修复** diff --git a/assets/App.vue b/assets/App.vue index 850263f..cc67633 100644 --- a/assets/App.vue +++ b/assets/App.vue @@ -14,12 +14,12 @@