setup local dev env
This commit is contained in:
12
backend/common/Cargo.toml
Normal file
12
backend/common/Cargo.toml
Normal 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 }
|
||||
40
backend/common/src/error.rs
Normal file
40
backend/common/src/error.rs
Normal 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
49
backend/common/src/jwt.rs
Normal 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}")))
|
||||
}
|
||||
5
backend/common/src/lib.rs
Normal file
5
backend/common/src/lib.rs
Normal 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};
|
||||
Reference in New Issue
Block a user