95 lines
2.7 KiB
Rust
95 lines
2.7 KiB
Rust
|
|
use actix_web::{dev::ServiceRequest, web, Error, HttpMessage};
|
||
|
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||
|
|
use actix_web_httpauth::extractors::AuthenticationError;
|
||
|
|
use actix_web_httpauth::headers::www_authenticate::bearer::Bearer;
|
||
|
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation, Algorithm};
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
||
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||
|
|
use chrono::{DateTime, Utc};
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize)]
|
||
|
|
pub struct Claims {
|
||
|
|
pub user_id: i32,
|
||
|
|
pub username: String,
|
||
|
|
pub exp: usize,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize)]
|
||
|
|
pub struct RegisterRequest {
|
||
|
|
pub username: String,
|
||
|
|
pub password: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize)]
|
||
|
|
pub struct LoginRequest {
|
||
|
|
pub username: String,
|
||
|
|
pub password: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize)]
|
||
|
|
pub struct LoginResponse {
|
||
|
|
pub token: String,
|
||
|
|
pub user: UserInfo,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize)]
|
||
|
|
pub struct UserInfo {
|
||
|
|
pub id: i32,
|
||
|
|
pub username: String,
|
||
|
|
pub registrations_remaining: i32,
|
||
|
|
pub domain_invite_codes: i32,
|
||
|
|
pub created_at: DateTime<Utc>,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn generate_jwt(user_id: i32, username: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
|
||
|
|
let expiration = SystemTime::now()
|
||
|
|
.duration_since(UNIX_EPOCH)
|
||
|
|
.unwrap()
|
||
|
|
.as_secs() + 86400 * 7; // 7 days
|
||
|
|
|
||
|
|
let claims = Claims {
|
||
|
|
user_id,
|
||
|
|
username: username.to_string(),
|
||
|
|
exp: expiration as usize,
|
||
|
|
};
|
||
|
|
|
||
|
|
encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref()))
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn validate_jwt(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||
|
|
let mut validation = Validation::new(Algorithm::HS256);
|
||
|
|
validation.validate_exp = true;
|
||
|
|
|
||
|
|
decode::<Claims>(token, &DecodingKey::from_secret(secret.as_ref()), &validation)
|
||
|
|
.map(|token_data| token_data.claims)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
|
||
|
|
hash(password, DEFAULT_COST)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn verify_password(password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
|
||
|
|
verify(password, hash)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn jwt_middleware(
|
||
|
|
req: ServiceRequest,
|
||
|
|
credentials: BearerAuth,
|
||
|
|
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
|
||
|
|
let jwt_secret = req
|
||
|
|
.app_data::<web::Data<String>>()
|
||
|
|
.unwrap()
|
||
|
|
.as_ref();
|
||
|
|
|
||
|
|
match validate_jwt(credentials.token(), jwt_secret) {
|
||
|
|
Ok(claims) => {
|
||
|
|
req.extensions_mut().insert(claims);
|
||
|
|
Ok(req)
|
||
|
|
}
|
||
|
|
Err(_) => {
|
||
|
|
let config = AuthenticationError::new(Bearer::default());
|
||
|
|
Err((Error::from(config), req))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|