backend/cookie/jwt.rs
1use axum::{Json, http::StatusCode};
2use jsonwebtoken::{DecodingKey, EncodingKey, Header, decode, encode};
3use serde::{Deserialize, Serialize};
4
5use crate::models::Claims;
6
7/// Error response for JWT token operations.
8///
9/// Returned when token encoding or decoding fails via `encode_token` or `decode_token`.
10/// Used in error responses for invalid or expired [`Claims`] tokens.
11///
12/// # Fields
13/// - `status`: HTTP status text (e.g., "error")
14/// - `message`: Human-readable error description
15#[derive(Debug, Deserialize, Serialize)]
16pub struct Error {
17 pub status: &'static str,
18 pub message: String,
19}
20
21/// Encodes user information into a JSON Web Token (JWT).
22///
23/// This function creates a new JWT with the provided user ID as the subject,
24/// sets the issued-at and expiration times (60 minutes from now), and signs it
25/// using the given encoding key. The resulting token is a serialized [`Claims`].
26///
27/// # Arguments
28/// - `header`: The JWT header, specifying the algorithm (e.g., HS256).
29/// - `id`: The user ID (`String`) to be embedded as the subject (`sub`) claim.
30/// - `key`: The `EncodingKey` used to sign the JWT.
31///
32/// # Returns
33/// A `String` representing the encoded JWT containing [`Claims`].
34///
35/// # Panics
36/// Panics if the token encoding fails for any reason (e.g., invalid key).
37pub fn encode_token(header: &Header, id: String, key: &EncodingKey) -> String {
38 let now = chrono::Utc::now();
39 let expires = (now + chrono::Duration::minutes(60)).timestamp();
40 let claims: Claims = Claims {
41 sub: id,
42 issued: now.timestamp() as usize,
43 expires: expires as usize,
44 };
45 let token = encode(header, &claims, key);
46 return token.expect("token return failed");
47}
48
49/// Decodes and validates a JSON Web Token (JWT).
50///
51/// This function attempts to decode a JWT string, validate its signature and claims
52/// using the provided decoding key. It specifically ignores expiration (`validate_exp`)
53/// and "not before" (`validate_nbf`) claims during validation. Returns the extracted [`Claims`]
54/// on success.
55///
56/// # Arguments
57/// - `token`: The JWT string to decode.
58/// - `key`: The `DecodingKey` used to verify the JWT's signature.
59///
60/// # Returns
61/// - `Ok(Claims)`: If the token is successfully decoded and verified, returns the extracted [`Claims`].
62/// - `Err((StatusCode, Json<Error>))`: If the token is invalid, expired, or cannot be decoded,
63/// returns an `UNAUTHORIZED` status code along with a JSON error message.
64pub fn decode_token(token: String, key: &DecodingKey) -> Result<Claims, (StatusCode, Json<Error>)> {
65 let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
66 validation.validate_exp = false;
67 validation.validate_nbf = false;
68 validation.leeway = 0;
69
70 let claims = decode::<Claims>(&token, key, &validation)
71 .map_err(|err| {
72 let message = format!("Invalid Token: {}", err);
73 let error = Error {
74 status: "error",
75 message,
76 };
77 (StatusCode::UNAUTHORIZED, Json(error))
78 })?
79 .claims;
80 return Ok(claims);
81}