Implement frontend authentication system with login/registration

- Add comprehensive authentication module with mock service
- Create login and registration components with form validation
- Implement protected routing with yew-router
- Add responsive UI styling with gradient design
- Enable JWT token persistence via localStorage
- Support demo credentials (demo/password) and flexible auth for development

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-28 15:56:18 -04:00
parent ad176dd423
commit 181e0c58c1
4 changed files with 515 additions and 0 deletions

123
src/auth.rs Normal file
View File

@@ -0,0 +1,123 @@
// Frontend-only authentication module (simplified for WASM compatibility)
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: String,
pub username: String,
pub email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub id: String,
pub username: String,
pub email: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RegisterRequest {
pub username: String,
pub email: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthResponse {
pub token: String,
pub user: UserInfo,
}
// Simplified frontend-only auth service
pub struct AuthService;
impl AuthService {
pub fn new() -> Self {
Self
}
// Mock authentication methods for development
// In production, these would make HTTP requests to a backend API
pub async fn register(&self, request: RegisterRequest) -> Result<AuthResponse, String> {
// Simulate API delay
gloo_timers::future::TimeoutFuture::new(500).await;
// Basic validation
if request.username.trim().is_empty() || request.email.trim().is_empty() || request.password.is_empty() {
return Err("All fields are required".to_string());
}
if request.password.len() < 6 {
return Err("Password must be at least 6 characters".to_string());
}
// Mock successful registration
Ok(AuthResponse {
token: format!("mock-jwt-token-{}", request.username),
user: UserInfo {
id: "user-123".to_string(),
username: request.username,
email: request.email,
},
})
}
pub async fn login(&self, request: LoginRequest) -> Result<AuthResponse, String> {
// Simulate API delay
gloo_timers::future::TimeoutFuture::new(500).await;
// Basic validation
if request.username.trim().is_empty() || request.password.is_empty() {
return Err("Username and password are required".to_string());
}
// Mock authentication - accept demo/password or any user/password combo
if request.username == "demo" && request.password == "password" {
Ok(AuthResponse {
token: "mock-jwt-token-demo".to_string(),
user: UserInfo {
id: "demo-user-123".to_string(),
username: request.username,
email: "demo@example.com".to_string(),
},
})
} else if !request.password.is_empty() {
// Accept any non-empty password for development
let username = request.username.clone();
Ok(AuthResponse {
token: format!("mock-jwt-token-{}", username),
user: UserInfo {
id: format!("user-{}", username),
username: request.username,
email: format!("{}@example.com", username),
},
})
} else {
Err("Invalid credentials".to_string())
}
}
pub async fn verify_token(&self, token: &str) -> Result<UserInfo, String> {
// Simulate API delay
gloo_timers::future::TimeoutFuture::new(100).await;
// Mock token verification
if token.starts_with("mock-jwt-token-") {
let username = token.strip_prefix("mock-jwt-token-").unwrap_or("unknown");
Ok(UserInfo {
id: format!("user-{}", username),
username: username.to_string(),
email: format!("{}@example.com", username),
})
} else {
Err("Invalid token".to_string())
}
}
}