feat: refresh token
This commit is contained in:
@@ -30,6 +30,7 @@ impl From<DomainError> for ApiError {
|
||||
match e {
|
||||
DomainError::NotFound(msg) => Self::NotFound(msg),
|
||||
DomainError::Forbidden(msg) => Self::Forbidden(msg),
|
||||
DomainError::Unauthorized(_) => Self::Unauthorized,
|
||||
DomainError::Conflict(msg) => Self::Conflict(msg),
|
||||
DomainError::Validation(msg) => Self::Validation(msg),
|
||||
DomainError::Repository(msg) => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use api_types::auth::{AuthResponse, LoginRequest, RegisterRequest, UserResponse};
|
||||
use api_types::auth::{
|
||||
AuthResponse, LoginRequest, LogoutRequest, RefreshRequest, RegisterRequest, UserResponse,
|
||||
};
|
||||
use utoipa::OpenApi;
|
||||
|
||||
#[derive(OpenApi)]
|
||||
@@ -6,8 +8,17 @@ use utoipa::OpenApi;
|
||||
paths(
|
||||
crate::routes::auth::login_handler,
|
||||
crate::routes::auth::register_handler,
|
||||
crate::routes::auth::refresh_handler,
|
||||
crate::routes::auth::logout_handler,
|
||||
crate::routes::auth::me_handler,
|
||||
),
|
||||
components(schemas(LoginRequest, RegisterRequest, AuthResponse, UserResponse))
|
||||
components(schemas(
|
||||
LoginRequest,
|
||||
RegisterRequest,
|
||||
RefreshRequest,
|
||||
LogoutRequest,
|
||||
AuthResponse,
|
||||
UserResponse
|
||||
))
|
||||
)]
|
||||
pub struct AuthDoc;
|
||||
|
||||
@@ -5,11 +5,14 @@ use axum::{
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
use api_types::auth::{AuthResponse, LoginRequest, RegisterRequest, UserResponse};
|
||||
use api_types::auth::{
|
||||
AuthResponse, LoginRequest, LogoutRequest, RefreshRequest, RegisterRequest, UserResponse,
|
||||
};
|
||||
use application::auth::{
|
||||
commands::{LoginCommand, RegisterCommand},
|
||||
login, register,
|
||||
login, logout, refresh, register,
|
||||
};
|
||||
use domain::auth::RefreshSession;
|
||||
|
||||
use crate::{
|
||||
error::{ApiError, ApiResult},
|
||||
@@ -22,6 +25,8 @@ pub fn router() -> Router<PresentationState> {
|
||||
Router::new()
|
||||
.route("/login", post(login_handler))
|
||||
.route("/register", post(register_handler))
|
||||
.route("/refresh", post(refresh_handler))
|
||||
.route("/logout", post(logout_handler))
|
||||
.route("/me", get(me_handler))
|
||||
}
|
||||
|
||||
@@ -52,9 +57,20 @@ pub async fn login_handler(
|
||||
.create_token(&user)
|
||||
.map_err(|e| ApiError::internal(format!("jwt error: {e}")))?;
|
||||
|
||||
let session = RefreshSession::new(user.id, state.ctx.config.refresh_token_ttl_seconds);
|
||||
let refresh_token = session.token().to_string();
|
||||
state
|
||||
.ctx
|
||||
.repos
|
||||
.refresh_session
|
||||
.create(&session)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(Json(AuthResponse {
|
||||
user: user_response(user),
|
||||
access_token: token,
|
||||
refresh_token,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -90,15 +106,72 @@ pub async fn register_handler(
|
||||
.create_token(&user)
|
||||
.map_err(|e| ApiError::internal(format!("jwt error: {e}")))?;
|
||||
|
||||
let session = RefreshSession::new(user.id, state.ctx.config.refresh_token_ttl_seconds);
|
||||
let refresh_token = session.token().to_string();
|
||||
state
|
||||
.ctx
|
||||
.repos
|
||||
.refresh_session
|
||||
.create(&session)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(AuthResponse {
|
||||
user: user_response(user),
|
||||
access_token: token,
|
||||
refresh_token,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/auth/refresh",
|
||||
request_body = RefreshRequest,
|
||||
responses(
|
||||
(status = 200, body = AuthResponse),
|
||||
(status = 401, body = api_types::errors::ErrorResponse, description = "Invalid or expired refresh token"),
|
||||
)
|
||||
)]
|
||||
pub async fn refresh_handler(
|
||||
State(state): State<PresentationState>,
|
||||
Json(payload): Json<RefreshRequest>,
|
||||
) -> ApiResult<Json<AuthResponse>> {
|
||||
let result = refresh::execute(&state.ctx, &payload.refresh_token)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let token = state
|
||||
.jwt_validator
|
||||
.create_token(&result.user)
|
||||
.map_err(|e| ApiError::internal(format!("jwt error: {e}")))?;
|
||||
|
||||
Ok(Json(AuthResponse {
|
||||
user: user_response(result.user),
|
||||
access_token: token,
|
||||
refresh_token: result.refresh_token,
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/auth/logout",
|
||||
request_body = LogoutRequest,
|
||||
responses(
|
||||
(status = 204, description = "Logged out"),
|
||||
)
|
||||
)]
|
||||
pub async fn logout_handler(
|
||||
State(state): State<PresentationState>,
|
||||
Json(payload): Json<LogoutRequest>,
|
||||
) -> ApiResult<StatusCode> {
|
||||
logout::execute(&state.ctx, &payload.refresh_token)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/auth/me",
|
||||
responses(
|
||||
|
||||
Reference in New Issue
Block a user