Files
iot-dboard/README.md
T
2026-08-06 11:11:11 +08:00

273 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dashboard - 多人副屏系统
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
---
## 项目简介
多用户副屏系统,支持日历、天气、闹钟、番茄钟、倒计时等功能。每个用户拥有完全隔离的数据与独立的显示端/控制端:
- **显示端** (`/`) — 大屏/触屏设备展示信息
- **控制端** (`/control`) — 完整配置面板
- **Root 管理员** (`id=0`) — 首个自动创建的账户,拥有全部功能与用户管理权限
---
## 功能
- **多用户**:Root 管理员可注册/编辑/删除用户;所有用户数据完全隔离,普通用户无管理权限
- **两种注册方式**:管理员代注册(正式账户,可代配 TOTP/Passkey);用户自助申请(管理员确认后生成临时账户)
- **临时账户**:服务器持有临时 TOTP 密钥,管理员仅可见实时验证码;用户登录后被强制引导配置自己的登录方式,配置完成即正式生效
- **日历**:支持订阅 iCalendar (.ics) 链接及本地文件上传,自动同步日程
- **天气**:基于和风天气 (QWeather) API,含逐小时/分钟级预报
- **闹钟**:多组闹钟设定,自动弹出提醒,支持自定义铃声
- **番茄钟**:定时提醒,自动继续,断网重连自动恢复
- **倒计时**:可自定义时长,到时提醒
- **自定义展示页**:支持多组自定义 HTML 模板,一键切换
---
## 🔐 安全特性
支持**双因素认证 (MFA)**,适合公网部署:
| 方式 | 说明 |
|------|------|
| **Passkey (WebAuthn)** | 指纹、面容、安全密钥等设备内置生物/硬件认证 |
| **TOTP** | Google Authenticator / Microsoft Authenticator 等标准应用 |
| **一次性恢复码** | 8 个恢复码,每个仅可使用一次 |
| **临时密码** | 首次启动为 Root 自动生成 6 位随机密码,显示在控制台 |
**登录流程**:
```
首次启动(无数据库)→ 自动创建 Root 管理员 (id=0),控制台打印临时密码
↓
访问 /control → 输入 6 位数字(自动识别 TOTP / 临时账户 TOTP / Root 临时密码)
↓
Root 临时密码 → 强制跳转 /mfa-setup 配置 MFA
正式用户 TOTP/Passkey → 直接进入控制面板
临时账户 TOTP → 强制跳转 /mfa-setup 配置登录方式,配置完成后临时 TOTP 立即失效
```
**用户管理(仅 Root)**:
- 管理员代注册:填写信息 → 正式账户,可随后代配置 TOTP(生成密钥+二维码)或 Passkey(注册仪式在当前浏览器完成)
- 用户自助申请:用户在 `/register-request` 填写信息 → 管理员确认 → 生成临时账户(服务器持有临时 TOTP 密钥,管理员仅看到实时验证码)
- 注册/编辑备注/删除用户均需**二次验证管理员身份**(Passkey 优先,无 Passkey 时用 TOTP)
- 删除用户会级联删除其全部数据并使其会话立即失效
- 用户 ID 对用户透明(仅在备注默认为 ID 时可见一次),普通用户只能看到自己的数据
**安全防护**:
- 登录频率限制(渐进式封禁:5次→5分钟、10次→15分钟、15次→24小时)
- CSRF 防护(Origin/Referer 校验)
- 敏感操作二次验证(用户管理、MFA 配置需重新确认身份)
- 会话 Cookie HttpOnly + SameSite=Lax + Secure(HTTPS 下)
- 会话版本按用户存储:删除用户 / "踢掉所有显示端" 立即失效
- 内容安全策略(CSP)限制脚本/样式来源
- 铃声上传仅允许音频格式+MIME 校验
- 第三方日历数据 HTML 转义防止 XSS
---
## 快速开始
### 开发环境
```bash
# 1. 安装依赖
pip install -r requirements.txt
# 2. 直接运行(调试模式)
python start.py
# 3. 访问
# 显示端: http://localhost:5000
# 控制端: http://localhost:5000/control
```
### 生产部署 (推荐 Nginx + Certbot)
```
客户端 (HTTPS)
│
▼ 443
Nginx (SSL termination + Certbot)
│ proxy_pass http://127.0.0.1:5000
│
▼ 5000 (HTTP)
Flask (127.0.0.1:5000)
│
▼
data.db (SQLite)
```
#### 1. Nginx 配置
```nginx
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 可选:限制上传大小
client_max_body_size 5M;
}
# HTTP → HTTPS 强制跳转
server {
listen 80;
server_name your-domain.com;
return 301 https://$host$request_uri;
}
```
#### 2. 启动 Flask
```bash
# 生产模式启动
FLASK_HTTPS=yes python start.py
# 可选:固定 SECRET_KEY(保持跨重启会话有效)
export FLASK_SECRET_KEY="$(openssl rand -hex 32)"
```
#### 3. Certbot 申请证书
```bash
sudo certbot --nginx -d your-domain.com
```
---
---
## Docker 部署
### 快速启动
```bash
# 构建并启动(后台运行,首次会自动从 Gitee clone 代码)
docker compose up -d
# 查看启动日志(获取临时密码)
docker compose logs app
# 停止
docker compose down
```
访问 `http://你的IP:5000`,日志中会打印临时密码。
### 更新代码
```bash
# 方法一:进入容器内 git pull(推荐)
docker compose exec app git pull
docker compose restart app
# 方法二:重建镜像(较慢)
docker compose down
docker compose build --no-cache
docker compose up -d
```
### 环境变量配置
编辑 `docker-compose.yml` 的 `environment` 段:
```yaml
environment:
- FLASK_BIND=0.0.0.0
- FLASK_SECRET_KEY=your-secret-here # 可选:固定密钥
- FLASK_HTTPS=yes # 如果前面有 HTTPS 反代
```
### 持久化数据
- **数据库**:`./data/` 目录挂载到 `/app/data/`,数据库文件 `data.db` 自动在此创建
- **项目代码**:命名卷 `app_code` 挂载到 `/app`,首次自动初始化为镜像内克隆的代码,之后 `git pull` 的修改永久保留数据库文件 `data.db` 会自动在该目录下创建,重启/升级后数据保留。
```bash
# 数据文件位置
ls -la data/
# data.db ← 数据库
```
### 搭配 Nginx 反代(生产)
使用宿主机上的 Nginx(或额外 Nginx 容器)做 SSL 终结。配置与前文 Nginx 部分相同,只需将 `proxy_pass` 指向 Docker 宿主机 IP:5000:
```nginx
location / {
proxy_pass http://宿主机IP:5000;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
```
> ⚠️ 注意:容器内 Flask 绑定 `0.0.0.0:5000`,宿主机可直接访问 `localhost:5000`。如需限制仅 Nginx 访问,在 `docker-compose.yml` 中将 `ports` 改为 `127.0.0.1:5000:5000`。
---
## 环境变量
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `FLASK_HTTPS` | 空 | 设为 `yes`/`true` 启用 Secure Cookie(Nginx 反代时启用) |
| `FLASK_SECRET_KEY` | 自动随机生成 | 固定密钥使会话跨重启保持有效 |
| `FLASK_BIND` | `127.0.0.1` | 监听地址,Docker 部署需设为 `0.0.0.0` |
| `FLASK_DB_PATH` | `./data.db` | 数据库文件路径,Docker 挂载数据卷时需同步修改 |
| `FLASK_DEBUG` | 空 | 设为 `true` 开启 Flask 调试模式(**生产环境禁用**) |
---
## 文件结构
```
├── run.py # 入口(端口 5001,仅开发)
├── start.py # 入口(端口 5000,推荐生产使用)
├── requirements.txt
├── backend/
│ ├── app.py # Flask 应用工厂 + 安全配置
│ ├── auth.py # MFA 认证(TOTP/Passkey/恢复码/临时密码)
│ ├── webauthn_mgr.py # WebAuthn (Passkey) 注册与认证
│ ├── jwt_util.py # Ed25519 JWT 签名(和风天气)
│ ├── models.py # SQLite 数据库模型与迁移
│ ├── routes.py # 全部 API 路由 + CSRF 防护
│ └── *_mgr.py # 各功能模块管理器
└── frontend/
├── templates/ # HTML 模板
└── static/
├── css/ # 样式
├── js/ # JavaScript(纯原生,无框架)
└── ringtones/ # 自定义铃声
```
---
### 数据库迁移(升级无需重写数据)
启动时会自动检测并迁移数据库:旧版单用户数据库的数据自动归属 Root (id=0);
新增的字段一律带 DEFAULT 并在迁移时自动填充;`camera_config` 等已删除的表自动丢弃。
旧版 4 位 PIN 不会被迁移。运行测试:`python3 tests/test_multi_user.py`。
## 注意
- **Passkey (WebAuthn) 需要 HTTPS**(localhost 除外)
- 旧版 4 位 PIN 已弃用,数据库迁移时旧 PIN 不会被迁移
### License
MIT