Cleanup
This commit is contained in:
parent
2cb2004840
commit
5ad6a1986d
6 changed files with 21 additions and 30 deletions
18
src/auth.rs
18
src/auth.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{FromRequestParts, State},
|
extract::FromRequestParts,
|
||||||
http::{header, request::Parts, StatusCode},
|
http::{header, request::Parts},
|
||||||
};
|
};
|
||||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -9,10 +9,6 @@ use worker::Env;
|
||||||
|
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
|
|
||||||
// Secret key for signing JWTs. In a real application, this should be a strong,
|
|
||||||
// securely stored secret from the Worker's environment variables.
|
|
||||||
const JWT_SECRET: &str = "a-very-secure-secret-key-that-should-be-in-env";
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Claims {
|
pub struct Claims {
|
||||||
pub sub: String, // User ID
|
pub sub: String, // User ID
|
||||||
|
|
@ -26,13 +22,11 @@ pub struct Claims {
|
||||||
pub amr: Vec<String>,
|
pub amr: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> FromRequestParts<S> for Claims
|
impl FromRequestParts<Arc<Env>> for Claims
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
{
|
||||||
type Rejection = AppError;
|
type Rejection = AppError;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, state: &Arc<Env>) -> Result<Self, Self::Rejection> {
|
||||||
// Extract the token from the authorization header
|
// Extract the token from the authorization header
|
||||||
let token = parts
|
let token = parts
|
||||||
.headers
|
.headers
|
||||||
|
|
@ -47,8 +41,10 @@ where
|
||||||
})
|
})
|
||||||
.ok_or_else(|| AppError::Unauthorized("Missing or invalid token".to_string()))?;
|
.ok_or_else(|| AppError::Unauthorized("Missing or invalid token".to_string()))?;
|
||||||
|
|
||||||
|
let secret = state.secret("JWT_SECRET")?;
|
||||||
|
|
||||||
// Decode and validate the token
|
// Decode and validate the token
|
||||||
let decoding_key = DecodingKey::from_secret(JWT_SECRET.as_ref());
|
let decoding_key = DecodingKey::from_secret(secret.to_string().as_ref());
|
||||||
let token_data = decode::<Claims>(&token, &decoding_key, &Validation::default())
|
let token_data = decode::<Claims>(&token, &decoding_key, &Validation::default())
|
||||||
.map_err(|_| AppError::Unauthorized("Invalid token".to_string()))?;
|
.map_err(|_| AppError::Unauthorized("Invalid token".to_string()))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ pub async fn prelogin(
|
||||||
.ok_or_else(|| AppError::BadRequest("Missing email".to_string()))?;
|
.ok_or_else(|| AppError::BadRequest("Missing email".to_string()))?;
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
|
|
||||||
log::info!("Getting kdf for user {email:?}");
|
|
||||||
let stmt = db.prepare("SELECT kdf_iterations FROM users WHERE email = ?1");
|
let stmt = db.prepare("SELECT kdf_iterations FROM users WHERE email = ?1");
|
||||||
let query = stmt.bind(&[email.into()])?;
|
let query = stmt.bind(&[email.into()])?;
|
||||||
let kdf_iterations: Option<i32> = query
|
let kdf_iterations: Option<i32> = query
|
||||||
|
|
@ -29,7 +28,6 @@ pub async fn prelogin(
|
||||||
.await
|
.await
|
||||||
.map_err(|_| AppError::Database)?;
|
.map_err(|_| AppError::Database)?;
|
||||||
|
|
||||||
log::info!("Returning {kdf_iterations:?}");
|
|
||||||
Ok(Json(PreloginResponse {
|
Ok(Json(PreloginResponse {
|
||||||
kdf: 0, // PBKDF2
|
kdf: 0, // PBKDF2
|
||||||
kdf_iterations: kdf_iterations.unwrap_or(600_000),
|
kdf_iterations: kdf_iterations.unwrap_or(600_000),
|
||||||
|
|
@ -41,9 +39,20 @@ pub async fn register(
|
||||||
State(env): State<Arc<Env>>,
|
State(env): State<Arc<Env>>,
|
||||||
Json(payload): Json<RegisterRequest>,
|
Json(payload): Json<RegisterRequest>,
|
||||||
) -> Result<Json<Value>, AppError> {
|
) -> Result<Json<Value>, AppError> {
|
||||||
log::info!("Get db");
|
let allowed_emails = env
|
||||||
|
.secret("ALLOWED_EMAILS")
|
||||||
|
.map_err(|_| AppError::Internal)?;
|
||||||
|
let allowed_emails = allowed_emails
|
||||||
|
.as_ref()
|
||||||
|
.as_string()
|
||||||
|
.ok_or_else(|| AppError::Internal)?;
|
||||||
|
if allowed_emails
|
||||||
|
.split(",")
|
||||||
|
.all(|email| email.trim() != payload.email)
|
||||||
|
{
|
||||||
|
return Err(AppError::Unauthorized("Not allowed to signup".to_string()));
|
||||||
|
}
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
log::info!("db got");
|
|
||||||
let now = Utc::now().to_rfc3339();
|
let now = Utc::now().to_rfc3339();
|
||||||
let user = User {
|
let user = User {
|
||||||
id: Uuid::new_v4().to_string(),
|
id: Uuid::new_v4().to_string(),
|
||||||
|
|
@ -62,7 +71,6 @@ pub async fn register(
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("User {user:?}");
|
|
||||||
let query = query!(
|
let query = query!(
|
||||||
&db,
|
&db,
|
||||||
"INSERT INTO users (id, name, email, master_password_hash, key, private_key, public_key, kdf_iterations, security_stamp, created_at, updated_at)
|
"INSERT INTO users (id, name, email, master_password_hash, key, private_key, public_key, kdf_iterations, security_stamp, created_at, updated_at)
|
||||||
|
|
@ -79,13 +87,11 @@ pub async fn register(
|
||||||
user.created_at,
|
user.created_at,
|
||||||
user.updated_at
|
user.updated_at
|
||||||
).map_err(|error|{
|
).map_err(|error|{
|
||||||
log::error!("failed {error:?}");
|
|
||||||
AppError::Database
|
AppError::Database
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
.map_err(|error|{
|
.map_err(|error|{
|
||||||
log::error!("failed {error:?}");
|
|
||||||
AppError::Database
|
AppError::Database
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ pub async fn token(
|
||||||
State(env): State<Arc<Env>>,
|
State(env): State<Arc<Env>>,
|
||||||
Form(payload): Form<TokenRequest>,
|
Form(payload): Form<TokenRequest>,
|
||||||
) -> Result<Json<TokenResponse>, AppError> {
|
) -> Result<Json<TokenResponse>, AppError> {
|
||||||
log::info!("Token payload {payload:?}");
|
|
||||||
if payload.grant_type != "password" {
|
if payload.grant_type != "password" {
|
||||||
return Err(AppError::BadRequest("Unsupported grant_type".to_string()));
|
return Err(AppError::BadRequest("Unsupported grant_type".to_string()));
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +63,6 @@ pub async fn token(
|
||||||
|
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
|
|
||||||
log::info!("Lookup user");
|
|
||||||
let user: Value = db
|
let user: Value = db
|
||||||
.prepare("SELECT * FROM users WHERE email = ?1")
|
.prepare("SELECT * FROM users WHERE email = ?1")
|
||||||
.bind(&[payload.username.to_lowercase().into()])?
|
.bind(&[payload.username.to_lowercase().into()])?
|
||||||
|
|
@ -72,17 +70,14 @@ pub async fn token(
|
||||||
.await
|
.await
|
||||||
.map_err(|_| AppError::Unauthorized("Invalid credentials".to_string()))?
|
.map_err(|_| AppError::Unauthorized("Invalid credentials".to_string()))?
|
||||||
.ok_or_else(|| AppError::Unauthorized("Invalid credentials".to_string()))?;
|
.ok_or_else(|| AppError::Unauthorized("Invalid credentials".to_string()))?;
|
||||||
log::info!("User is {user:?}");
|
|
||||||
let user: User = serde_json::from_value(user).map_err(|_| AppError::Internal)?;
|
let user: User = serde_json::from_value(user).map_err(|_| AppError::Internal)?;
|
||||||
// Securely compare the provided hash with the stored hash
|
// Securely compare the provided hash with the stored hash
|
||||||
if !constant_time_eq(
|
if !constant_time_eq(
|
||||||
user.master_password_hash.as_bytes(),
|
user.master_password_hash.as_bytes(),
|
||||||
password_hash.as_bytes(),
|
password_hash.as_bytes(),
|
||||||
) {
|
) {
|
||||||
log::info!("Password is incorrect");
|
|
||||||
return Err(AppError::Unauthorized("Invalid credentials".to_string()));
|
return Err(AppError::Unauthorized("Invalid credentials".to_string()));
|
||||||
}
|
}
|
||||||
log::info!("Password matched");
|
|
||||||
|
|
||||||
// Create JWT claims
|
// Create JWT claims
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ pub async fn get_sync_data(
|
||||||
let user_id = claims.sub;
|
let user_id = claims.sub;
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
|
|
||||||
log::info!("Fetch user");
|
|
||||||
// Fetch profile
|
// Fetch profile
|
||||||
let user: User = db
|
let user: User = db
|
||||||
.prepare("SELECT * FROM users WHERE id = ?1")
|
.prepare("SELECT * FROM users WHERE id = ?1")
|
||||||
|
|
@ -32,7 +31,6 @@ pub async fn get_sync_data(
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| AppError::NotFound("User not found".to_string()))?;
|
.ok_or_else(|| AppError::NotFound("User not found".to_string()))?;
|
||||||
|
|
||||||
log::info!("Fetch folders");
|
|
||||||
// Fetch folders
|
// Fetch folders
|
||||||
let folders: Vec<Folder> = db
|
let folders: Vec<Folder> = db
|
||||||
.prepare("SELECT * FROM folders WHERE user_id = ?1")
|
.prepare("SELECT * FROM folders WHERE user_id = ?1")
|
||||||
|
|
@ -41,7 +39,6 @@ pub async fn get_sync_data(
|
||||||
.await?
|
.await?
|
||||||
.results()?;
|
.results()?;
|
||||||
|
|
||||||
log::info!("Fetch ciphers");
|
|
||||||
// Fetch ciphers
|
// Fetch ciphers
|
||||||
let ciphers: Vec<Value> = db
|
let ciphers: Vec<Value> = db
|
||||||
.prepare("SELECT * FROM ciphers WHERE user_id = ?1")
|
.prepare("SELECT * FROM ciphers WHERE user_id = ?1")
|
||||||
|
|
@ -64,7 +61,6 @@ pub async fn get_sync_data(
|
||||||
.map(|cipher| cipher.into())
|
.map(|cipher| cipher.into())
|
||||||
.collect::<Vec<Cipher>>();
|
.collect::<Vec<Cipher>>();
|
||||||
|
|
||||||
log::info!("Fetch time");
|
|
||||||
let time = chrono::DateTime::parse_from_rfc3339(&user.created_at)
|
let time = chrono::DateTime::parse_from_rfc3339(&user.created_at)
|
||||||
.map_err(|_| AppError::Internal)?
|
.map_err(|_| AppError::Internal)?
|
||||||
.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
|
.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use axum::Router;
|
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
use worker::*;
|
use worker::*;
|
||||||
|
|
@ -19,7 +18,7 @@ pub async fn main(
|
||||||
) -> Result<axum::http::Response<axum::body::Body>> {
|
) -> Result<axum::http::Response<axum::body::Body>> {
|
||||||
// Set up logging
|
// Set up logging
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
console_log::init_with_level(log::Level::Debug);
|
let _ = console_log::init_with_level(log::Level::Debug);
|
||||||
|
|
||||||
// Allow all origins for CORS, which is typical for a public API like Bitwarden's.
|
// Allow all origins for CORS, which is typical for a public API like Bitwarden's.
|
||||||
let cors = CorsLayer::new()
|
let cors = CorsLayer::new()
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,6 @@ impl Serialize for Cipher {
|
||||||
response_map.insert("creationDate".to_string(), json!(self.created_at));
|
response_map.insert("creationDate".to_string(), json!(self.created_at));
|
||||||
response_map.insert("deletedDate".to_string(), json!(self.deleted_at));
|
response_map.insert("deletedDate".to_string(), json!(self.deleted_at));
|
||||||
|
|
||||||
log::debug!("Response data is {:?}", self.data);
|
|
||||||
if let Some(data_obj) = self.data.as_object() {
|
if let Some(data_obj) = self.data.as_object() {
|
||||||
let data_clone = data_obj.clone();
|
let data_clone = data_obj.clone();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue