Working example
This commit is contained in:
parent
83345afe04
commit
f514fd741c
10 changed files with 446 additions and 81 deletions
60
server/src/jwt.rs
Executable file
60
server/src/jwt.rs
Executable file
|
@ -0,0 +1,60 @@
|
|||
// use crate::{
|
||||
// config::{DAYS_VALID, JWT_SECRET},
|
||||
// Claims,
|
||||
// };
|
||||
use jsonwebtoken::{
|
||||
decode, encode, errors::Result as JwtResult, DecodingKey, EncodingKey, Header, Validation,
|
||||
};
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const DAYS_VALID: i64 = 7;
|
||||
const JWT_SECRET: &[u8] = "secret".as_bytes();
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String,
|
||||
pub iss: String,
|
||||
pub aud: String,
|
||||
pub iat: usize,
|
||||
pub exp: usize,
|
||||
}
|
||||
|
||||
// JwtResult is just a predefined error from the jsonwebtoken crate
|
||||
pub fn token_factory(user: &str) -> JwtResult<String> {
|
||||
info!("Issuing JWT token for {}", user);
|
||||
|
||||
let token = encode(
|
||||
&Header::default(),
|
||||
&Claims {
|
||||
sub: user.to_string(),
|
||||
iss: "localhost".to_string(),
|
||||
aud: "localhost".to_string(),
|
||||
iat: chrono::Utc::now().timestamp() as usize,
|
||||
exp: (chrono::Utc::now() + chrono::Duration::days(DAYS_VALID)).timestamp() as usize,
|
||||
},
|
||||
&EncodingKey::from_secret(JWT_SECRET),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub fn validate_token(token: &str) -> JwtResult<Claims> {
|
||||
let token_data = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(JWT_SECRET),
|
||||
&Validation::default(),
|
||||
);
|
||||
|
||||
match token_data {
|
||||
Ok(token_data) => {
|
||||
info!("Token validated for {}", token_data.claims.sub);
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Token validation failed: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,11 +4,12 @@ use actix_web::{web::scope, App, HttpServer};
|
|||
use log::info;
|
||||
// use uuid::Uuid;
|
||||
|
||||
mod jwt;
|
||||
mod routes;
|
||||
mod state;
|
||||
mod types;
|
||||
|
||||
use routes::{get_posts, new_post, register, test};
|
||||
use routes::{get_posts, login, new_post, register, test};
|
||||
use sqlx::ConnectOptions;
|
||||
use state::AppState;
|
||||
|
||||
|
@ -33,6 +34,7 @@ async fn main() -> std::io::Result<()> {
|
|||
.service(new_post)
|
||||
.service(routes::vote)
|
||||
.service(test)
|
||||
.service(login)
|
||||
.service(register)
|
||||
.app_data(Data::new(data.clone())),
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::types::{NewPost, Post};
|
||||
use crate::AppState;
|
||||
|
||||
use actix_web::web::{Data, Path};
|
||||
use actix_web::web::{to, Data, Path};
|
||||
use actix_web::{get, post, web::Json, HttpResponse, Responder, Result};
|
||||
use argon2::password_hash::SaltString;
|
||||
use log::*;
|
||||
|
@ -137,3 +137,55 @@ pub async fn register(data: Json<RegisterData>, state: Data<AppState>) -> Result
|
|||
|
||||
Ok(HttpResponse::Ok().json("User registered"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LoginData {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
use sqlx::Row;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct LoginResponse {
|
||||
username: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
use crate::jwt::token_factory;
|
||||
|
||||
#[post("/login")]
|
||||
pub async fn login(data: Json<LoginData>, state: Data<AppState>) -> Result<impl Responder> {
|
||||
let q = "SELECT password FROM users WHERE username = ?";
|
||||
let query = sqlx::query(q).bind(&data.username);
|
||||
let result = query.fetch_one(&state.pool).await.ok();
|
||||
if let Some(row) = result {
|
||||
let phc_from_db = row.get::<String, _>("password");
|
||||
let pwhash = PasswordHash::new(&phc_from_db).unwrap_or_else(|_| {
|
||||
warn!(
|
||||
"Invalid hash for user {} fetched from database (not a valid PHC string)",
|
||||
data.username
|
||||
);
|
||||
panic!();
|
||||
});
|
||||
|
||||
match Argon2::default().verify_password(data.password.as_bytes(), &pwhash) {
|
||||
Ok(some) => {
|
||||
info!("User {} logged in", data.username);
|
||||
let token = token_factory(&data.username).unwrap();
|
||||
println!("{:?}", token);
|
||||
return Ok(HttpResponse::Ok().json(LoginResponse {
|
||||
username: data.username.clone(),
|
||||
token: token,
|
||||
}));
|
||||
// Sign jwt with user claims
|
||||
}
|
||||
Err(e) => {
|
||||
info!("User \"{}\" failed to log in", data.username);
|
||||
return Ok(HttpResponse::BadRequest().json("Error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json("What happens here???"))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue