Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ wrangler secret put TWO_FACTOR_ENC_KEY

- JWT_SECRET:访问令牌签名密钥
- JWT_REFRESH_SECRET:刷新令牌签名密钥
- ALLOWED_EMAILS:首个账号注册白名单(仅在“数据库还没有任何用户”时启用),多个邮箱用英文逗号分隔
- ALLOWED_EMAILS:注册白名单,多个邮箱用英文逗号分隔;可使用 `*` 表示允许所有邮箱注册
- TWO_FACTOR_ENC_KEY:可选,Base64 的 32 字节密钥;用于加密存储 TOTP 秘钥(不设置则以 `plain:` 形式存储)

### 4. 部署
Expand Down
5 changes: 5 additions & 0 deletions sql/migrations/20260129_add_kdf_fields.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Add KDF memory and parallelism columns to users table
-- These are required for Argon2id support

ALTER TABLE users ADD COLUMN kdf_memory INTEGER;
ALTER TABLE users ADD COLUMN kdf_parallelism INTEGER;
6 changes: 6 additions & 0 deletions sql/schema_full.sql
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ CREATE TABLE IF NOT EXISTS devices (
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS login_rate_limits (
key TEXT PRIMARY KEY NOT NULL,
count INTEGER NOT NULL,
reset_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_ciphers_user_id ON ciphers(user_id);
CREATE INDEX IF NOT EXISTS idx_ciphers_folder_id ON ciphers(folder_id);
CREATE INDEX IF NOT EXISTS idx_folders_user_id ON folders(user_id);
Expand Down
8 changes: 6 additions & 2 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use axum::{
extract::FromRequestParts,
http::{header, request::Parts},
};
use jsonwebtoken::{decode, DecodingKey, Validation};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use worker::Env;
Expand Down Expand Up @@ -45,7 +45,11 @@ impl FromRequestParts<Arc<Env>> for Claims

// Decode and validate the token
let decoding_key = DecodingKey::from_secret(secret.to_string().as_ref());
let token_data = decode::<Claims>(&token, &decoding_key, &Validation::default())
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_nbf = true;
validation.leeway = 60;

let token_data = decode::<Claims>(&token, &decoding_key, &validation)
.map_err(|_| AppError::Unauthorized("Invalid token".to_string()))?;

Ok(token_data.claims)
Expand Down
34 changes: 33 additions & 1 deletion src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,37 @@ use std::sync::Arc;
use worker::{D1Database, Env};

pub fn get_db(env: &Arc<Env>) -> Result<D1Database, AppError> {
env.d1("vault1").map_err(AppError::Worker)
// 尝试获取名为 "warden-mima" 的数据库绑定 (根据 wrangler.jsonc)
// 如果失败,尝试旧的 "vault1" 以保持兼容性,或者报错
env.d1("warden-mima")
.or_else(|_| env.d1("vault1"))
.map_err(|e| {
log::error!("❌ Failed to get database binding: {}", e);
AppError::Worker(e)
})
}

/// 将 worker::Error 转换为更有意义的 AppError
pub fn handle_db_error(error: worker::Error) -> AppError {
let error_str = error.to_string();

// 记录原始错误以便调试
log::error!("🗄️ Raw Database Error: {}", error_str);

if error_str.contains("UNIQUE constraint failed") {
if error_str.contains("email") {
return AppError::DatabaseConstraint("Email already registered".to_string());
}
return AppError::DatabaseConstraint(format!("Record already exists: {}", error_str));
}

if error_str.contains("NOT NULL constraint failed") {
return AppError::BadRequest(format!("Missing required field: {}", error_str));
}

if error_str.contains("FOREIGN KEY constraint failed") {
return AppError::BadRequest("Invalid reference".to_string());
}

AppError::Database(error_str)
}
71 changes: 47 additions & 24 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ pub enum AppError {
#[error("Worker error: {0}")]
Worker(#[from] worker::Error),

#[error("Database query failed")]
Database,
#[error("Database query failed: {0}")]
Database(String),

#[error("Database constraint violation: {0}")]
DatabaseConstraint(String),

#[error("Not found: {0}")]
NotFound(String),
Expand All @@ -21,39 +24,59 @@ pub enum AppError {
#[error("Unauthorized: {0}")]
Unauthorized(String),

#[error("Too many requests: {0}")]
TooManyRequests(String),

#[error("Cryptography error: {0}")]
Crypto(String),

#[error(transparent)]
JsonWebToken(#[from] jsonwebtoken::errors::Error),

#[error("Internal server error")]
Internal,
#[error("Internal server error: {0}")]
Internal(String),
}

impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::Worker(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Worker error: {}", e),
),
AppError::Database => (
StatusCode::INTERNAL_SERVER_ERROR,
"Database error".to_string(),
),
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
AppError::Crypto(msg) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Crypto error: {}", msg),
),
let (status, error_message) = match &self {
AppError::Worker(e) => {
log::error!("🔴 Worker error: {}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Worker error: {}", e),
)
}
AppError::Database(msg) => {
log::error!("💾 Database error: {}", msg);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Database error".to_string(),
)
}
AppError::DatabaseConstraint(msg) => {
log::warn!("⚠️ Database constraint violation: {}", msg);
(StatusCode::BAD_REQUEST, msg.clone())
}
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
AppError::TooManyRequests(msg) => (StatusCode::TOO_MANY_REQUESTS, msg.clone()),
AppError::Crypto(msg) => {
log::error!("🔐 Crypto error: {}", msg);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Crypto error: {}", msg),
)
}
AppError::JsonWebToken(_) => (StatusCode::UNAUTHORIZED, "Invalid token".to_string()),
AppError::Internal => (
StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error".to_string(),
),
AppError::Internal(msg) => {
log::error!("🔥 Internal error: {}", msg);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error".to_string(),
)
}
};

let body = Json(json!({ "error": error_message }));
Expand Down
Loading