setup local dev env

This commit is contained in:
2026-03-10 17:13:23 +01:00
parent b27db93a90
commit f4d3fade9b
61 changed files with 6695 additions and 228 deletions

12
backend/common/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "common"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = { workspace = true }
chrono = { workspace = true }
jsonwebtoken = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }

View File

@@ -0,0 +1,40 @@
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Serialize)]
pub struct ApiErrorBody {
pub error: String,
}
#[derive(Debug, Error)]
pub enum AppError {
#[error("unauthorized: {0}")]
Unauthorized(String),
#[error("bad request: {0}")]
BadRequest(String),
#[error("internal error: {0}")]
Internal(String),
}
pub type AppResult<T> = Result<T, AppError>;
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status = match self {
AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
AppError::BadRequest(_) => StatusCode::BAD_REQUEST,
AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let body = ApiErrorBody {
error: self.to_string(),
};
(status, Json(body)).into_response()
}
}

49
backend/common/src/jwt.rs Normal file
View File

@@ -0,0 +1,49 @@
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use crate::{AppError, AppResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iat: usize,
pub exp: usize,
pub global_roles: Vec<String>,
}
pub fn issue_access_token(
secret: &str,
user_id: &str,
global_roles: Vec<String>,
) -> AppResult<String> {
let now = Utc::now();
let exp = now + Duration::minutes(15);
let claims = Claims {
sub: user_id.to_owned(),
iat: now.timestamp() as usize,
exp: exp.timestamp() as usize,
global_roles,
};
encode(
&Header::new(Algorithm::HS256),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.map_err(|err| AppError::Internal(format!("failed to encode JWT: {err}")))
}
pub fn decode_jwt(secret: &str, token: &str) -> AppResult<Claims> {
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;
decode::<Claims>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&validation,
)
.map(|data| data.claims)
.map_err(|err| AppError::Unauthorized(format!("invalid token: {err}")))
}

View File

@@ -0,0 +1,5 @@
pub mod error;
pub mod jwt;
pub use error::{ApiErrorBody, AppError, AppResult};
pub use jwt::{decode_jwt, issue_access_token, Claims};