feat: 实现动态密码系统,消除硬编码
核心改动: 1. db.rs - 默认密码从环境变量读取或自动生成 - 优先级: MOMENTRY_DEFAULT_PASSWORD > 自动生成 - 格式: demo_<timestamp> 或自定义 - 首次创建时打印密码到控制台 2. main.rs - SMB 服务密码动态化 - 从 SMB_PASSWORD 或 MOMENTRY_DEFAULT_PASSWORD 读取 - 支持自定义 SMB 用户名 3. user_tool.rs - 新增用户管理工具 - list: 列出所有用户 - create: 创建新用户 - set-password: 修改密码 - delete: 删除用户 - init-demo: 初始化 demo 用户 4. install.sh - 安装时自动生成密码 - 生成随机密码: demo_$(openssl rand -hex 4) - 保存到 .env 文件 - 初始化数据库 5. .env.template - 新增密码配置 - MOMENTRY_DEFAULT_PASSWORD - SMB_USER / SMB_PASSWORD 安全改进: - 消除硬编码密码 'demo123' - 每次安装密码唯一 - 密码哈希存储 (Argon2id) - 支持自定义强密码 文档: - docs/DEFAULT_PASSWORD_SYSTEM.md 完整说明
This commit is contained in:
@@ -58,6 +58,22 @@ REDIS_URL=redis://localhost:6379
|
|||||||
# Momentry Core API Key
|
# Momentry Core API Key
|
||||||
MOMENTRY_API_KEY=your_momentry_api_key_here
|
MOMENTRY_API_KEY=your_momentry_api_key_here
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 默认用户密码
|
||||||
|
# ========================================
|
||||||
|
# 在安装时自动生成,或手动设置
|
||||||
|
# 格式: demo_<random>
|
||||||
|
MOMENTRY_DEFAULT_PASSWORD=demo_change_this
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# SMB 共享配置
|
||||||
|
# ========================================
|
||||||
|
# SMB 用户名(默认: demo)
|
||||||
|
SMB_USER=demo
|
||||||
|
|
||||||
|
# SMB 密码(默认使用 MOMENTRY_DEFAULT_PASSWORD)
|
||||||
|
SMB_PASSWORD=${MOMENTRY_DEFAULT_PASSWORD}
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# 工具路径
|
# 工具路径
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|||||||
464
docs/DEFAULT_PASSWORD_SYSTEM.md
Normal file
464
docs/DEFAULT_PASSWORD_SYSTEM.md
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
# 默认用户密码系统
|
||||||
|
|
||||||
|
**更新时间**: 2026-10-12
|
||||||
|
**状态**: ✅ 已实现动态密码系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 目标
|
||||||
|
|
||||||
|
**消除硬编码的默认密码 `demo123`,实现每次安装时自动生成唯一密码**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 实现内容
|
||||||
|
|
||||||
|
### 1. 密码生成策略
|
||||||
|
|
||||||
|
**优先级顺序**:
|
||||||
|
|
||||||
|
1. **环境变量 `MOMENTRY_DEFAULT_PASSWORD`** (最高优先级)
|
||||||
|
- 手动设置:`export MOMENTRY_DEFAULT_PASSWORD=my_secure_password`
|
||||||
|
- 自动生成:`demo_<timestamp>`
|
||||||
|
|
||||||
|
2. **自动生成随机密码** (如果环境变量未设置)
|
||||||
|
- 格式:`demo_<6位随机数>`
|
||||||
|
- 示例:`demo_123456`, `demo_789012`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 修改的文件
|
||||||
|
|
||||||
|
#### `src-tauri/src/db.rs`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```rust
|
||||||
|
// 旧代码 (硬编码)
|
||||||
|
let default_password = "demo123";
|
||||||
|
|
||||||
|
// 新代码 (动态密码)
|
||||||
|
let default_password = std::env::var("MOMENTRY_DEFAULT_PASSWORD")
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
format!("demo_{}", timestamp % 1000000) // demo_123456
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**新增功能**:
|
||||||
|
- 首次创建用户时,打印默认密码到控制台
|
||||||
|
- 提示用户保存密码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `src-tauri/src/main.rs`
|
||||||
|
|
||||||
|
**SMB 服务密码修改**:
|
||||||
|
```rust
|
||||||
|
// 旧代码 (硬编码)
|
||||||
|
format!("... --user demo:demo123 ...")
|
||||||
|
|
||||||
|
// 新代码 (动态密码)
|
||||||
|
let smb_user = std::env::var("SMB_USER").unwrap_or_else(|_| "demo".to_string());
|
||||||
|
let smb_password = std::env::var("SMB_PASSWORD")
|
||||||
|
.or_else(|_| std::env::var("MOMENTRY_DEFAULT_PASSWORD"))
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
// Fallback to a generated password
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
format!("smb_{}", timestamp % 1000000)
|
||||||
|
});
|
||||||
|
format!("... --user {}:{} ...", smb_user, smb_password)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 新增工具
|
||||||
|
|
||||||
|
#### `src-tauri/src/bin/user_tool.rs`
|
||||||
|
|
||||||
|
**用户管理命令行工具**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出所有用户
|
||||||
|
./user_tool list
|
||||||
|
|
||||||
|
# 创建新用户
|
||||||
|
./user_tool create <username> <password>
|
||||||
|
|
||||||
|
# 修改用户密码
|
||||||
|
./user_tool set-password <username> <password>
|
||||||
|
|
||||||
|
# 删除用户
|
||||||
|
./user_tool delete <username>
|
||||||
|
|
||||||
|
# 初始化 demo 用户
|
||||||
|
./user_tool init-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
- 独立可执行文件,不依赖 Tauri 运行时
|
||||||
|
- 支持从环境变量读取默认密码
|
||||||
|
- 支持密码哈希(Argon2id)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 安装脚本更新
|
||||||
|
|
||||||
|
#### `install.sh`
|
||||||
|
|
||||||
|
**新增步骤 5.5**: 初始化默认用户
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生成安全随机密码
|
||||||
|
DEFAULT_PASSWORD="demo_$(openssl rand -hex 4)"
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
export MOMENTRY_DEFAULT_PASSWORD="${DEFAULT_PASSWORD}"
|
||||||
|
|
||||||
|
# 保存到 .env 文件
|
||||||
|
echo "MOMENTRY_DEFAULT_PASSWORD=${DEFAULT_PASSWORD}" >> .env
|
||||||
|
|
||||||
|
# 初始化数据库
|
||||||
|
./src-tauri/target/release/user_tool init-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
**输出示例**:
|
||||||
|
```
|
||||||
|
========================================
|
||||||
|
Demo user initialized:
|
||||||
|
Username: demo
|
||||||
|
Password: demo_a1b2c3d4
|
||||||
|
========================================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 配置文件更新
|
||||||
|
|
||||||
|
#### `.env.template`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ========================================
|
||||||
|
# 默认用户密码
|
||||||
|
# ========================================
|
||||||
|
# 在安装时自动生成,或手动设置
|
||||||
|
# 格式: demo_<random>
|
||||||
|
MOMENTRY_DEFAULT_PASSWORD=demo_change_this
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# SMB 共享配置
|
||||||
|
# ========================================
|
||||||
|
SMB_USER=demo
|
||||||
|
SMB_PASSWORD=${MOMENTRY_DEFAULT_PASSWORD}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 架构对比
|
||||||
|
|
||||||
|
### 旧架构 (硬编码)
|
||||||
|
|
||||||
|
```
|
||||||
|
安装时
|
||||||
|
└─> 硬编码密码: demo123
|
||||||
|
└─> 所有安装使用相同密码
|
||||||
|
└─> 安全风险高 ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 新架构 (动态密码)
|
||||||
|
|
||||||
|
```
|
||||||
|
安装时
|
||||||
|
├─> 检查环境变量 MOMENTRY_DEFAULT_PASSWORD
|
||||||
|
│ ├─> 已设置 → 使用该值
|
||||||
|
│ └─> 未设置 → 生成随机密码
|
||||||
|
│
|
||||||
|
├─> 保存到 .env 文件
|
||||||
|
│
|
||||||
|
└─> 初始化数据库
|
||||||
|
└─> 用户看到生成的密码
|
||||||
|
└─> 每次安装密码唯一 ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 密码存储位置
|
||||||
|
|
||||||
|
### SQLite 数据库
|
||||||
|
|
||||||
|
**路径**: `data/users/demo.sqlite`
|
||||||
|
|
||||||
|
**表结构**:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE app_users (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password_hash TEXT, -- Argon2id 哈希
|
||||||
|
display_name TEXT,
|
||||||
|
role TEXT DEFAULT 'user',
|
||||||
|
created_at TEXT,
|
||||||
|
updated_at TEXT
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**存储方式**:
|
||||||
|
- 密码使用 Argon2id 算法哈希
|
||||||
|
- **不存储明文密码**
|
||||||
|
- 哈希格式:`$argon2id$v=19$m=19456,t=2,p=1$...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### .env 文件
|
||||||
|
|
||||||
|
**路径**: `momentry_studio/.env`
|
||||||
|
|
||||||
|
**内容**:
|
||||||
|
```bash
|
||||||
|
MOMENTRY_DEFAULT_PASSWORD=demo_a1b2c3d4
|
||||||
|
```
|
||||||
|
|
||||||
|
**用途**:
|
||||||
|
- 首次启动时读取
|
||||||
|
- 传递给 SMB 服务
|
||||||
|
- 方便管理员查看
|
||||||
|
|
||||||
|
**安全性**:
|
||||||
|
- ⚠️ 文件权限应为 `600` (仅所有者可读写)
|
||||||
|
- ⚠️ 不要提交到 Git (已在 `.gitignore`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 使用流程
|
||||||
|
|
||||||
|
### 1. 自动安装 (推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行安装脚本
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# 输出示例:
|
||||||
|
# ========================================
|
||||||
|
# Default user password: demo_a1b2c3d4
|
||||||
|
# Please save this password securely!
|
||||||
|
# ========================================
|
||||||
|
```
|
||||||
|
|
||||||
|
**脚本会自动**:
|
||||||
|
- 生成随机密码
|
||||||
|
- 保存到 `.env`
|
||||||
|
- 初始化数据库
|
||||||
|
- 打印密码给用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 手动设置密码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置环境变量
|
||||||
|
export MOMENTRY_DEFAULT_PASSWORD=my_secure_password
|
||||||
|
|
||||||
|
# 运行安装
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# 或手动初始化
|
||||||
|
./src-tauri/target/release/user_tool init-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 查看用户列表
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./src-tauri/target/release/user_tool list
|
||||||
|
|
||||||
|
# 输出:
|
||||||
|
# Users:
|
||||||
|
# ID Username Display Name Role
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# demo demo Demo User admin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 修改密码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./src-tauri/target/release/user_tool set-password demo new_password_123
|
||||||
|
|
||||||
|
# 输出:
|
||||||
|
# ✅ Password updated for user 'demo'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 创建新用户
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./src-tauri/target/release/user_tool create admin secure_pass_456
|
||||||
|
|
||||||
|
# 输出:
|
||||||
|
# ✅ User 'admin' created successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 部署到新环境
|
||||||
|
|
||||||
|
### m4promini 环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 克隆代码
|
||||||
|
cd ~
|
||||||
|
git clone <repo_url> momentry_studio
|
||||||
|
|
||||||
|
# 2. 运行安装
|
||||||
|
cd momentry_studio
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# 3. 记录生成的密码 (例如: demo_a1b2c3d4)
|
||||||
|
|
||||||
|
# 4. 启动服务
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**系统会自动**:
|
||||||
|
- 检测用户主目录:`/Users/nextorageai`
|
||||||
|
- 创建数据目录:`~/momentry/var/sftpgo/data`
|
||||||
|
- 生成唯一密码:`demo_<random>`
|
||||||
|
- 初始化 demo 用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 安全注意事项
|
||||||
|
|
||||||
|
### 1. 密码管理
|
||||||
|
|
||||||
|
✅ **推荐做法**:
|
||||||
|
- 使用强密码 (至少 12 位)
|
||||||
|
- 定期更换密码
|
||||||
|
- 不同环境使用不同密码
|
||||||
|
- 将 `.env` 文件权限设为 `600`
|
||||||
|
|
||||||
|
❌ **不推荐做法**:
|
||||||
|
- 使用硬编码密码
|
||||||
|
- 在代码中存储明文密码
|
||||||
|
- 多个环境共用相同密码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 文件权限
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置 .env 文件权限
|
||||||
|
chmod 600 .env
|
||||||
|
|
||||||
|
# 设置数据库文件权限
|
||||||
|
chmod 600 data/users/demo.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 环境隔离
|
||||||
|
|
||||||
|
每个环境应有独立的密码:
|
||||||
|
|
||||||
|
| 环境 | 用户 | 密码 | 配置文件 |
|
||||||
|
|-----|------|------|---------|
|
||||||
|
| m5max48 | demo | `demo_a1b2c3` | `/Users/accusys/momentry_studio/.env` |
|
||||||
|
| m4promini | demo | `demo_x9y8z7` | `/Users/nextorageai/momentry_studio/.env` |
|
||||||
|
| 生产环境 | demo | `demo_prod_...` | `/opt/momentry/.env` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 故障排除
|
||||||
|
|
||||||
|
### 问题 1: 忘记密码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法 1: 查看 .env 文件
|
||||||
|
cat .env | grep MOMENTRY_DEFAULT_PASSWORD
|
||||||
|
|
||||||
|
# 方法 2: 重置密码
|
||||||
|
./src-tauri/target/release/user_tool set-password demo new_password
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题 2: 数据库初始化失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查数据库目录
|
||||||
|
ls -la data/users/
|
||||||
|
|
||||||
|
# 手动创建目录
|
||||||
|
mkdir -p data/users
|
||||||
|
|
||||||
|
# 重新初始化
|
||||||
|
./src-tauri/target/release/user_tool init-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题 3: SMB 服务无法连接
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 SMB 密码配置
|
||||||
|
cat .env | grep SMB_PASSWORD
|
||||||
|
|
||||||
|
# 确保密码一致
|
||||||
|
export SMB_PASSWORD=${MOMENTRY_DEFAULT_PASSWORD}
|
||||||
|
|
||||||
|
# 重启 SMB 服务
|
||||||
|
./src-tauri/target/release/momentry-studio # 或通过 GUI
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 检查清单
|
||||||
|
|
||||||
|
在部署前确认:
|
||||||
|
|
||||||
|
- [ ] `.env` 文件已创建并包含 `MOMENTRY_DEFAULT_PASSWORD`
|
||||||
|
- [ ] `.env` 文件权限为 `600`
|
||||||
|
- [ ] 数据库文件已初始化
|
||||||
|
- [ ] demo 用户密码已记录
|
||||||
|
- [ ] SMB 密码已配置(如需 SMB 服务)
|
||||||
|
- [ ] 不同环境使用不同密码
|
||||||
|
- [ ] `.env` 文件已添加到 `.gitignore`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
### 改进前
|
||||||
|
|
||||||
|
- ❌ 硬编码密码 `demo123`
|
||||||
|
- ❌ 所有安装使用相同密码
|
||||||
|
- ❌ 无法自定义密码
|
||||||
|
- ❌ 安全风险高
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 改进后
|
||||||
|
|
||||||
|
- ✅ 动态生成唯一密码
|
||||||
|
- ✅ 每次安装密码独立
|
||||||
|
- ✅ 支持自定义密码
|
||||||
|
- ✅ 密码哈希存储
|
||||||
|
- ✅ 用户管理工具
|
||||||
|
- ✅ 安装自动化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档位置**: `docs/DEFAULT_PASSWORD_SYSTEM.md`
|
||||||
|
**Commit**: 待提交
|
||||||
|
**Status**: ✅ 已实现动态密码系统
|
||||||
29
install.sh
29
install.sh
@@ -121,6 +121,35 @@ log_info "✅ 构建完成"
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# 步骤 5.5: 初始化默认用户
|
||||||
|
log_step "步骤 5.5/6: 初始化默认用户..."
|
||||||
|
|
||||||
|
# Generate a secure random password
|
||||||
|
DEFAULT_PASSWORD="demo_$(openssl rand -hex 4)"
|
||||||
|
|
||||||
|
# Set environment variable for password
|
||||||
|
export MOMENTRY_DEFAULT_PASSWORD="${DEFAULT_PASSWORD}"
|
||||||
|
|
||||||
|
log_info "默认用户密码: ${DEFAULT_PASSWORD}"
|
||||||
|
log_warn "请妥善保存此密码!"
|
||||||
|
|
||||||
|
# Save to .env file
|
||||||
|
if [ -f "${SCRIPT_DIR}/.env" ]; then
|
||||||
|
echo "" >> "${SCRIPT_DIR}/.env"
|
||||||
|
echo "# Default user password (generated during installation)" >> "${SCRIPT_DIR}/.env"
|
||||||
|
echo "MOMENTRY_DEFAULT_PASSWORD=${DEFAULT_PASSWORD}" >> "${SCRIPT_DIR}/.env"
|
||||||
|
log_info "✅ 密码已保存到 .env 文件"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize database
|
||||||
|
cd "${SCRIPT_DIR}/src-tauri"
|
||||||
|
"${SCRIPT_DIR}/src-tauri/target/release/user_tool" init-demo 2>/dev/null || {
|
||||||
|
log_warn "用户工具未编译,将在首次启动时初始化"
|
||||||
|
}
|
||||||
|
cd "${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
# 步骤 6: 创建启动脚本
|
# 步骤 6: 创建启动脚本
|
||||||
log_step "步骤 6/6: 创建启动脚本..."
|
log_step "步骤 6/6: 创建启动脚本..."
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ path = "src/main.rs"
|
|||||||
name = "momentry-proxy"
|
name = "momentry-proxy"
|
||||||
path = "src/bin/proxy.rs"
|
path = "src/bin/proxy.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "user_tool"
|
||||||
|
path = "src/bin/user_tool.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
tauri = { version = "2", features = ["protocol-asset"] }
|
tauri = { version = "2", features = ["protocol-asset"] }
|
||||||
|
|||||||
176
src-tauri/src/bin/user_tool.rs
Normal file
176
src-tauri/src/bin/user_tool.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
use rusqlite::Connection;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use argon2::{self, Algorithm, Argon2, Params, PasswordHasher, Version};
|
||||||
|
use argon2::password_hash::{SaltString, rand_core::OsRng};
|
||||||
|
|
||||||
|
fn db_path() -> PathBuf {
|
||||||
|
let base = std::env::var("MOMENTRY_DATA_DIR").unwrap_or_else(|_| "../data/users".to_string());
|
||||||
|
std::fs::create_dir_all(&base).ok();
|
||||||
|
PathBuf::from(base).join("demo.sqlite")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_password(password: &str) -> String {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default());
|
||||||
|
argon2.hash_password(password.as_bytes(), &salt).unwrap().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if args.len() < 2 {
|
||||||
|
print_usage();
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = &args[1];
|
||||||
|
let path = db_path();
|
||||||
|
let conn = Connection::open(&path).expect("Failed to open database");
|
||||||
|
|
||||||
|
match command.as_str() {
|
||||||
|
"list" => {
|
||||||
|
list_users(&conn);
|
||||||
|
}
|
||||||
|
"create" => {
|
||||||
|
if args.len() < 4 {
|
||||||
|
println!("Usage: user_tool create <username> <password>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let username = &args[2];
|
||||||
|
let password = &args[3];
|
||||||
|
create_user(&conn, username, password);
|
||||||
|
}
|
||||||
|
"set-password" => {
|
||||||
|
if args.len() < 4 {
|
||||||
|
println!("Usage: user_tool set-password <username> <password>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let username = &args[2];
|
||||||
|
let password = &args[3];
|
||||||
|
set_password(&conn, username, password);
|
||||||
|
}
|
||||||
|
"delete" => {
|
||||||
|
if args.len() < 3 {
|
||||||
|
println!("Usage: user_tool delete <username>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let username = &args[2];
|
||||||
|
delete_user(&conn, username);
|
||||||
|
}
|
||||||
|
"init-demo" => {
|
||||||
|
init_demo_user(&conn);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_usage() {
|
||||||
|
println!("Momentry Studio User Management Tool");
|
||||||
|
println!();
|
||||||
|
println!("Usage:");
|
||||||
|
println!(" user_tool list - List all users");
|
||||||
|
println!(" user_tool create <user> <pass> - Create a new user");
|
||||||
|
println!(" user_tool set-password <user> <pass> - Change user password");
|
||||||
|
println!(" user_tool delete <user> - Delete a user");
|
||||||
|
println!(" user_tool init-demo - Initialize demo user");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_users(conn: &Connection) {
|
||||||
|
let mut stmt = conn
|
||||||
|
.prepare("SELECT id, username, display_name, role FROM app_users")
|
||||||
|
.expect("Failed to prepare statement");
|
||||||
|
|
||||||
|
let users = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, String>(0)?,
|
||||||
|
row.get::<_, String>(1)?,
|
||||||
|
row.get::<_, Option<String>>(2)?,
|
||||||
|
row.get::<_, String>(3)?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.expect("Failed to query users");
|
||||||
|
|
||||||
|
println!("Users:");
|
||||||
|
println!("{:<10} {:<15} {:<20} {:<10}", "ID", "Username", "Display Name", "Role");
|
||||||
|
println!("{}", "-".repeat(60));
|
||||||
|
|
||||||
|
for user in users {
|
||||||
|
let (id, username, display_name, role) = user.expect("Failed to get user");
|
||||||
|
let display = display_name.unwrap_or_else(|| "-".to_string());
|
||||||
|
println!("{:<10} {:<15} {:<20} {:<10}", id, username, display, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user(conn: &Connection, username: &str, password: &str) {
|
||||||
|
let password_hash = hash_password(password);
|
||||||
|
let id = format!("user_{}", chrono::Utc::now().timestamp());
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO app_users (id, username, password_hash, role) VALUES (?1, ?2, ?3, 'user')",
|
||||||
|
rusqlite::params![id, username, password_hash],
|
||||||
|
)
|
||||||
|
.expect("Failed to create user");
|
||||||
|
|
||||||
|
println!("✅ User '{}' created successfully", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_password(conn: &Connection, username: &str, password: &str) {
|
||||||
|
let password_hash = hash_password(password);
|
||||||
|
|
||||||
|
let rows = conn
|
||||||
|
.execute(
|
||||||
|
"UPDATE app_users SET password_hash = ?1 WHERE username = ?2",
|
||||||
|
rusqlite::params![password_hash, username],
|
||||||
|
)
|
||||||
|
.expect("Failed to update password");
|
||||||
|
|
||||||
|
if rows > 0 {
|
||||||
|
println!("✅ Password updated for user '{}'", username);
|
||||||
|
} else {
|
||||||
|
println!("❌ User '{}' not found", username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_user(conn: &Connection, username: &str) {
|
||||||
|
let rows = conn
|
||||||
|
.execute(
|
||||||
|
"DELETE FROM app_users WHERE username = ?1",
|
||||||
|
rusqlite::params![username],
|
||||||
|
)
|
||||||
|
.expect("Failed to delete user");
|
||||||
|
|
||||||
|
if rows > 0 {
|
||||||
|
println!("✅ User '{}' deleted", username);
|
||||||
|
} else {
|
||||||
|
println!("❌ User '{}' not found", username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_demo_user(conn: &Connection) {
|
||||||
|
let default_password = std::env::var("MOMENTRY_DEFAULT_PASSWORD")
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
format!("demo_{}", timestamp % 1000000)
|
||||||
|
});
|
||||||
|
|
||||||
|
let password_hash = hash_password(&default_password);
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO app_users (id, username, password_hash, display_name, role) VALUES ('demo', 'demo', ?1, 'Demo User', 'admin')",
|
||||||
|
rusqlite::params![password_hash],
|
||||||
|
)
|
||||||
|
.expect("Failed to create demo user");
|
||||||
|
|
||||||
|
println!("========================================");
|
||||||
|
println!("Demo user initialized:");
|
||||||
|
println!(" Username: demo");
|
||||||
|
println!(" Password: {}", default_password);
|
||||||
|
println!("========================================");
|
||||||
|
}
|
||||||
@@ -66,9 +66,19 @@ pub fn init_db() -> Result<(), String> {
|
|||||||
// Add password_hash column if not exists (migration)
|
// Add password_hash column if not exists (migration)
|
||||||
conn.execute("ALTER TABLE app_users ADD COLUMN password_hash TEXT", []).ok();
|
conn.execute("ALTER TABLE app_users ADD COLUMN password_hash TEXT", []).ok();
|
||||||
|
|
||||||
|
// Get default user credentials from environment or use generated values
|
||||||
let default_id = "demo";
|
let default_id = "demo";
|
||||||
let default_user = "demo";
|
let default_user = "demo";
|
||||||
let default_password = "demo123";
|
let default_password = std::env::var("MOMENTRY_DEFAULT_PASSWORD")
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
// Generate a random password if not set
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
format!("demo_{}", timestamp % 1000000) // demo_123456
|
||||||
|
});
|
||||||
|
|
||||||
// Check if demo user exists and has password
|
// Check if demo user exists and has password
|
||||||
let password_hash: Option<String> = conn
|
let password_hash: Option<String> = conn
|
||||||
@@ -80,12 +90,19 @@ pub fn init_db() -> Result<(), String> {
|
|||||||
.unwrap_or(None);
|
.unwrap_or(None);
|
||||||
|
|
||||||
if password_hash.is_none() || password_hash.as_ref().map_or(true, |h| h.is_empty()) {
|
if password_hash.is_none() || password_hash.as_ref().map_or(true, |h| h.is_empty()) {
|
||||||
let password_hash = hash_password(default_password);
|
let hashed = hash_password(&default_password);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO app_users (id, username, password_hash, display_name, role) VALUES (?1, ?2, ?3, ?4, 'admin')",
|
"INSERT OR REPLACE INTO app_users (id, username, password_hash, display_name, role) VALUES (?1, ?2, ?3, ?4, 'admin')",
|
||||||
rusqlite::params![default_id, default_user, password_hash, "Demo User"],
|
rusqlite::params![default_id, default_user, hashed, "Demo User"],
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("Failed to insert default user: {}", e))?;
|
.map_err(|e| format!("Failed to insert default user: {}", e))?;
|
||||||
|
|
||||||
|
// Print default credentials for user
|
||||||
|
eprintln!("========================================");
|
||||||
|
eprintln!("Default user created:");
|
||||||
|
eprintln!(" Username: {}", default_user);
|
||||||
|
eprintln!(" Password: {}", default_password);
|
||||||
|
eprintln!("========================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut lock = DB_CONN.lock().map_err(|e| format!("DB lock error: {}", e))?;
|
let mut lock = DB_CONN.lock().map_err(|e| format!("DB lock error: {}", e))?;
|
||||||
|
|||||||
@@ -1427,7 +1427,20 @@ async fn start_service(name: String) -> Result<String, String> {
|
|||||||
"SMB" => {
|
"SMB" => {
|
||||||
let markbase_root = get_markbase_root();
|
let markbase_root = get_markbase_root();
|
||||||
let storage_root = get_storage_root();
|
let storage_root = get_storage_root();
|
||||||
format!("cd {} && nohup cargo run --bin markbase-core --features smb-server -- smb-start --port 4445 --share-name markbase --root {} --user demo:demo123 > /tmp/markbase_smb.log 2>&1 &", markbase_root, storage_root)
|
// Get SMB credentials from environment or use default
|
||||||
|
let smb_user = std::env::var("SMB_USER").unwrap_or_else(|_| "demo".to_string());
|
||||||
|
let smb_password = std::env::var("SMB_PASSWORD")
|
||||||
|
.or_else(|_| std::env::var("MOMENTRY_DEFAULT_PASSWORD"))
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
// Fallback to a generated password
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
format!("smb_{}", timestamp % 1000000)
|
||||||
|
});
|
||||||
|
format!("cd {} && nohup cargo run --bin markbase-core --features smb-server -- smb-start --port 4445 --share-name markbase --root {} --user {}:{} > /tmp/markbase_smb.log 2>&1 &", markbase_root, storage_root, smb_user, smb_password)
|
||||||
}
|
}
|
||||||
_ => return Err(format!("Unknown service: {}", name)),
|
_ => return Err(format!("Unknown service: {}", name)),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user