feat: Add axum-extra dependency and implement typed headers
feat: Define unique index on users table for email and fix minor syntax error refactor: Update permissions_guard to use claims from JWT and improve user retrieval refactor: Modify user-related service methods to accept user details directly fix: Update token generation functions to include user ID and permissions test: Update Google OAuth flow tests to reflect changes in token generation
This commit is contained in:
Generated
+48
@@ -461,6 +461,29 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"headers",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-test"
|
name = "axum-test"
|
||||||
version = "17.3.0"
|
version = "17.3.0"
|
||||||
@@ -1656,6 +1679,30 @@ dependencies = [
|
|||||||
"foldhash",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bytes",
|
||||||
|
"headers-core",
|
||||||
|
"http 1.3.1",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||||
|
dependencies = [
|
||||||
|
"http 1.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heapless"
|
name = "heapless"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -2204,6 +2251,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
"axum-test",
|
"axum-test",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ validator = { version = "0.20.0", features = ["derive"] }
|
|||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
axum-test = "17.2.0"
|
axum-test = "17.2.0"
|
||||||
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
fancy-regex = "0.15.0"
|
fancy-regex = "0.15.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
db.use_ns(env.surrealdb_namespace.clone())
|
db.use_ns(env.surrealdb_namespace.clone())
|
||||||
.use_db(env.surrealdb_dbname.clone())
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
db.query("DEFINE INDEX user_email_index ON TABLE users COLUMNS email UNIQUE;")
|
||||||
|
.await?;
|
||||||
|
println!("✅ Index 'user_email_index' defined on table 'users' for column 'email'.");
|
||||||
|
|
||||||
let roles_permissions = vec![
|
let roles_permissions = vec![
|
||||||
(
|
(
|
||||||
@@ -75,7 +78,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
PermissionsEnum::ReadListGachaItems,
|
PermissionsEnum::ReadListGachaItems,
|
||||||
PermissionsEnum::ReadDetailGachaItems,
|
PermissionsEnum::ReadDetailGachaItems,
|
||||||
PermissionsEnum::ReadListUsers,
|
PermissionsEnum::ReadListUsers,
|
||||||
PermissionsEnum::ReadDetailUsers,,
|
PermissionsEnum::ReadDetailUsers,
|
||||||
PermissionsEnum::CreateGachaClaims,
|
PermissionsEnum::CreateGachaClaims,
|
||||||
PermissionsEnum::ReadDetailGachaClaims,
|
PermissionsEnum::ReadDetailGachaClaims,
|
||||||
PermissionsEnum::ReadDetailGachaRolls,
|
PermissionsEnum::ReadDetailGachaRolls,
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ pub async fn get_mentor_list(
|
|||||||
Query(meta): Query<MetaRequestDto>,
|
Query(meta): Query<MetaRequestDto>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::ReadListMentors],
|
vec![PermissionsEnum::ReadListMentors],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => MentorsService::get_mentor_list(&app_state, meta).await,
|
Ok((_user, app_state)) => MentorsService::get_mentor_list(&app_state, meta).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,13 +90,13 @@ pub async fn get_mentor_by_id(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::ReadDetailMentors],
|
vec![PermissionsEnum::ReadDetailMentors],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => MentorsService::get_mentor_by_id(&app_state, &id).await,
|
Ok((_user, app_state)) => MentorsService::get_mentor_by_id(&app_state, &id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,13 +126,13 @@ pub async fn put_update_mentor(
|
|||||||
Json(dto): Json<MentorUpdateRequestDto>,
|
Json(dto): Json<MentorUpdateRequestDto>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::UpdateMentors],
|
vec![PermissionsEnum::UpdateMentors],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => MentorsService::update_mentor(&app_state, &id, dto).await,
|
Ok((_user, app_state)) => MentorsService::update_mentor(&app_state, &id, dto).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,13 +159,13 @@ pub async fn delete_mentor(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::DeleteMentors],
|
vec![PermissionsEnum::DeleteMentors],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => MentorsService::delete_mentor(&app_state, &id).await,
|
Ok((_user, app_state)) => MentorsService::delete_mentor(&app_state, &id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,13 +195,13 @@ pub async fn put_verify_mentor(
|
|||||||
Json(dto): Json<MentorVerifyRequestDto>,
|
Json(dto): Json<MentorVerifyRequestDto>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::VerifyMentors],
|
vec![PermissionsEnum::VerifyMentors],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => MentorsService::verify_mentor(&app_state, &id, dto).await,
|
Ok((_user, app_state)) => MentorsService::verify_mentor(&app_state, &id, dto).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,17 +221,17 @@ pub async fn put_verify_mentor(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn get_mentor_me(
|
pub async fn get_mentor_me(
|
||||||
Extension(app_state): Extension<AppState>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
Extension(app_state): Extension<AppState>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers.clone(),
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::ReadOwnMentorProfile],
|
vec![PermissionsEnum::ReadOwnMentorProfile],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok((_user, app_state)) => {
|
||||||
let email = match extract_email(&headers) {
|
let email = match extract_email(&headers) {
|
||||||
Some(email) => email,
|
Some(email) => email,
|
||||||
None => {
|
None => {
|
||||||
@@ -268,18 +268,18 @@ pub async fn get_mentor_me(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn put_update_mentor_me(
|
pub async fn put_update_mentor_me(
|
||||||
Extension(app_state): Extension<AppState>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
Extension(app_state): Extension<AppState>,
|
||||||
Json(dto): Json<MentorUpdateRequestDto>,
|
Json(dto): Json<MentorUpdateRequestDto>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers.clone(),
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::UpdateOwnMentorProfile],
|
vec![PermissionsEnum::UpdateOwnMentorProfile],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok((_user, app_state)) => {
|
||||||
let email = match extract_email(&headers) {
|
let email = match extract_email(&headers) {
|
||||||
Some(email) => email,
|
Some(email) => email,
|
||||||
None => {
|
None => {
|
||||||
@@ -325,17 +325,17 @@ pub async fn put_update_mentor_no_id() -> Response {
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn get_mentor_status(
|
pub async fn get_mentor_status(
|
||||||
Extension(app_state): Extension<AppState>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
Extension(app_state): Extension<AppState>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers.clone(),
|
||||||
app_state.clone(),
|
Extension(app_state),
|
||||||
vec![PermissionsEnum::ReadOwnMentorStatus],
|
vec![PermissionsEnum::ReadOwnMentorStatus],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok((_user, app_state)) => {
|
||||||
let email = match extract_email(&headers) {
|
let email = match extract_email(&headers) {
|
||||||
Some(email) => email,
|
Some(email) => email,
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ pub async fn get_detail_gacha_claim(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailGachaClaims],
|
vec![PermissionsEnum::ReadDetailGachaClaims],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaClaimService::get_gacha_claim_by_id(&state, id).await,
|
Ok((_user, state)) => GachaClaimService::get_gacha_claim_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,13 +54,13 @@ pub async fn post_create_gacha_claim(
|
|||||||
Json(payload): Json<GachaClaimRequestDto>,
|
Json(payload): Json<GachaClaimRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreateGachaClaims],
|
vec![PermissionsEnum::CreateGachaClaims],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaClaimService::create_gacha_claim(&state, payload).await,
|
Ok((_user, state)) => GachaClaimService::create_gacha_claim(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ pub async fn get_gacha_item_list(
|
|||||||
Query(meta): Query<MetaRequestDto>,
|
Query(meta): Query<MetaRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadListGachaItems],
|
vec![PermissionsEnum::ReadListGachaItems],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaItemService::get_gacha_item_list(&state, meta).await,
|
Ok((_user, state)) => GachaItemService::get_gacha_item_list(&state, meta).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,13 @@ pub async fn get_gacha_item_by_id(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailGachaItems],
|
vec![PermissionsEnum::ReadDetailGachaItems],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaItemService::get_gacha_item_by_id(&state, id).await,
|
Ok((_user, state)) => GachaItemService::get_gacha_item_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,13 +94,13 @@ pub async fn post_create_gacha_item(
|
|||||||
Json(payload): Json<GachaItemRequestDto>,
|
Json(payload): Json<GachaItemRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreateGachaItems],
|
vec![PermissionsEnum::CreateGachaItems],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaItemService::create_gacha_item(&state, payload).await,
|
Ok((_user, state)) => GachaItemService::create_gacha_item(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,13 +124,13 @@ pub async fn put_update_gacha_item(
|
|||||||
Json(payload): Json<GachaItemUpdateRequestDto>,
|
Json(payload): Json<GachaItemUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::UpdateGachaItems],
|
vec![PermissionsEnum::UpdateGachaItems],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaItemService::update_gacha_item(&state, payload, id).await,
|
Ok((_user, state)) => GachaItemService::update_gacha_item(&state, payload, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,13 +152,13 @@ pub async fn delete_gacha_item(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::DeleteGachaItems],
|
vec![PermissionsEnum::DeleteGachaItems],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaItemService::delete_gacha_item(&state, id).await,
|
Ok((_user, state)) => GachaItemService::delete_gacha_item(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ pub async fn get_detail_gacha_roll(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailGachaRolls],
|
vec![PermissionsEnum::ReadDetailGachaRolls],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaRollService::get_gacha_roll_by_id(&state, id).await,
|
Ok((_user, state)) => GachaRollService::get_gacha_roll_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,13 +54,13 @@ pub async fn post_create_gacha_roll(
|
|||||||
Json(payload): Json<GachaRollRequestDto>,
|
Json(payload): Json<GachaRollRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreateGachaRolls],
|
vec![PermissionsEnum::CreateGachaRolls],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaRollService::create_gacha_roll(&state, payload).await,
|
Ok((_user, state)) => GachaRollService::create_gacha_roll(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,13 +81,13 @@ pub async fn post_execute_gacha_roll(
|
|||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers.clone(),
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ExecuteGachaRolls],
|
vec![PermissionsEnum::ExecuteGachaRolls],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaRollService::execute_roll_once(headers, &state).await,
|
Ok((_user, state)) => GachaRollService::execute_roll_once(headers, &state).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,13 +110,13 @@ pub async fn delete_gacha_roll(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::DeleteGachaRolls],
|
vec![PermissionsEnum::DeleteGachaRolls],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => GachaRollService::soft_delete_gacha_roll(&state, id).await,
|
Ok((_user, state)) => GachaRollService::soft_delete_gacha_roll(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ log.workspace = true
|
|||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
axum-extra.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dotenvy.workspace = true
|
dotenvy.workspace = true
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
decode_refresh_token, encode_access_token, encode_refresh_token,
|
decode_refresh_token, encode_access_token, encode_refresh_token,
|
||||||
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
|
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
|
||||||
hash_password, make_thing, send_email, success_response, validate_request,
|
hash_password, make_thing, send_email, success_response, validate_request,
|
||||||
verify_password,
|
verify_password, surrealdb_init_ws, surrealdb_init_mem,
|
||||||
};
|
};
|
||||||
use axum::{http::StatusCode, response::Response};
|
use axum::{http::StatusCode, response::Response};
|
||||||
use surrealdb::Uuid;
|
use surrealdb::Uuid;
|
||||||
@@ -87,7 +87,8 @@ impl AuthServiceTrait for AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let access_token = match encode_access_token(payload.email.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -101,7 +102,8 @@ impl AuthServiceTrait for AuthService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let refresh_token = match encode_refresh_token(payload.email.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -182,7 +184,8 @@ impl AuthServiceTrait for AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let access_token = match encode_access_token(payload.email.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -196,7 +199,8 @@ impl AuthServiceTrait for AuthService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let refresh_token = match encode_refresh_token(payload.email.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -374,32 +378,54 @@ impl AuthServiceTrait for AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mutation_refresh_token(
|
async fn mutation_refresh_token(payload: AuthRefreshTokenRequestDto) -> Response {
|
||||||
payload: AuthRefreshTokenRequestDto,
|
|
||||||
) -> Response {
|
|
||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let email = match decode_refresh_token(&payload.refresh_token) {
|
|
||||||
Ok(token) => token.claims.sub,
|
let surrealdb_ws = match surrealdb_init_ws().await {
|
||||||
|
Ok(db) => db,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to initialize websocket database: {}", e);
|
||||||
|
return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Database initialization error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let surrealdb_mem = match surrealdb_init_mem().await {
|
||||||
|
Ok(db) => db,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to initialize memory database: {}", e);
|
||||||
|
return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Database initialization error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let state = AppState { surrealdb_ws, surrealdb_mem };
|
||||||
|
let user_repo = UsersRepository::new(&state);
|
||||||
|
let user = match decode_refresh_token(&payload.refresh_token) {
|
||||||
|
Ok(token_data) => {
|
||||||
|
match user_repo.query_user_by_email(token_data.claims.sub.clone()).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(_) => return common_response(StatusCode::UNAUTHORIZED, "User not found"),
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return common_response(StatusCode::UNAUTHORIZED, "Invalid refresh token");
|
return common_response(StatusCode::UNAUTHORIZED, "Invalid refresh token");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let access_token = match encode_access_token(email.clone()) {
|
|
||||||
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let access_token = match encode_access_token(user.email.clone(), user.id.id.to_raw(), permissions.clone()) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!("Failed to generate access token for {}: {}", email, _e);
|
error!("Failed to generate access token for {}: {}", user.email, _e);
|
||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate access token",
|
"Failed to generate access token",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let refresh_token = match encode_refresh_token(email.clone()) {
|
let refresh_token = match encode_refresh_token(user.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!("Failed to generate refresh token for {}: {}", email, _e);
|
error!("Failed to generate refresh token for {}: {}", user.email, _e);
|
||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate refresh token",
|
"Failed to generate refresh token",
|
||||||
@@ -440,7 +466,8 @@ impl AuthServiceTrait for AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let token = match encode_reset_password_token(user.email.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let token = match encode_reset_password_token(user.email.clone(), user.id.id.to_raw(), permissions) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -525,32 +552,31 @@ impl AuthServiceTrait for AuthService {
|
|||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
|
let user_repo = UsersRepository::new(state);
|
||||||
let email = match extract_email_token(payload.token.clone()) {
|
let email = match extract_email_token(payload.token.clone()) {
|
||||||
Some(token) => token,
|
Some(email) => email,
|
||||||
None => {
|
None => {
|
||||||
return common_response(StatusCode::BAD_REQUEST, "Invalid or missing token");
|
return common_response(StatusCode::BAD_REQUEST, "Invalid or missing token");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let user = match user_repo.query_user_by_email(email).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(_) => return common_response(StatusCode::BAD_REQUEST, "User not found"),
|
||||||
|
};
|
||||||
let password = match hash_password(&payload.password) {
|
let password = match hash_password(&payload.password) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!("Failed to hash new password for {}: {}", email, _e);
|
error!("Failed to hash new password for {}: {}", user.email, _e);
|
||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to hash password",
|
"Failed to hash password",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let user = match repo.query_user_by_email(email.clone()).await {
|
|
||||||
Ok(user) if !user.is_deleted => user,
|
|
||||||
_ => {
|
|
||||||
return common_response(StatusCode::NOT_FOUND, "User not found");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let patch = UsersSchema {
|
let patch = UsersSchema {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
password,
|
password,
|
||||||
..Default::default()
|
..UsersSchema::from(user.clone())
|
||||||
};
|
};
|
||||||
match repo.query_update_user(patch).await {
|
match repo.query_update_user(patch).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
|
|||||||
@@ -310,13 +310,15 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let access_token = encode_access_token(user.email.clone())
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let access_token = encode_access_token(user.email.clone(), user.id.clone(), permissions)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Failed to generate access token for {}: {:?}", user.email, e);
|
error!("Failed to generate access token for {}: {:?}", user.email, e);
|
||||||
Error::Auth("Failed to generate access token".to_string())
|
Error::Auth("Failed to generate access token".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let refresh_token = encode_refresh_token(user.email.clone())
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
||||||
|
let refresh_token = encode_refresh_token(user.email.clone(), user.id.clone(), permissions)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Failed to generate refresh token for {}: {:?}", user.email, e);
|
error!("Failed to generate refresh token for {}: {:?}", user.email, e);
|
||||||
Error::Auth("Failed to generate refresh token".to_string())
|
Error::Auth("Failed to generate refresh token".to_string())
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ pub async fn get_permission_list(
|
|||||||
Query(meta): Query<MetaRequestDto>,
|
Query(meta): Query<MetaRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadListPermissions],
|
vec![PermissionsEnum::ReadListPermissions],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => PermissionsService::get_permission_list(&state, meta).await,
|
Ok((_user, state)) => PermissionsService::get_permission_list(&state, meta).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ pub async fn get_permission_by_id(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailPermissions],
|
vec![PermissionsEnum::ReadDetailPermissions],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => PermissionsService::get_permission_by_id(&state, id).await,
|
Ok((_user, state)) => PermissionsService::get_permission_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,13 +99,13 @@ pub async fn post_create_permission(
|
|||||||
Json(payload): Json<PermissionsRequestDto>,
|
Json(payload): Json<PermissionsRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreatePermissions],
|
vec![PermissionsEnum::CreatePermissions],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => PermissionsService::create_role(&state, payload).await,
|
Ok((_user, state)) => PermissionsService::create_role(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,13 +129,13 @@ pub async fn put_update_permission(
|
|||||||
Json(payload): Json<PermissionsUpdateRequestDto>,
|
Json(payload): Json<PermissionsUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::UpdatePermissions],
|
vec![PermissionsEnum::UpdatePermissions],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => PermissionsService::update_permission(&state, payload, id).await,
|
Ok((_user, state)) => PermissionsService::update_permission(&state, payload, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,13 +157,13 @@ pub async fn delete_permission(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::DeletePermissions],
|
vec![PermissionsEnum::DeletePermissions],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => PermissionsService::delete_permission(&state, id).await,
|
Ok((_user, state)) => PermissionsService::delete_permission(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,41 @@
|
|||||||
use super::PermissionsEnum;
|
use super::PermissionsEnum;
|
||||||
use crate::{AppState, AuthRepository, common_response, extract_email, extract_email_async, UsersDetailQueryDto};
|
use crate::{AppState, common_response, decode_access_token, UsersDetailQueryDto, UsersRepository};
|
||||||
use axum::{
|
use axum::{
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
response::Response,
|
response::Response, Extension,
|
||||||
};
|
};
|
||||||
|
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
|
||||||
|
use imphnen_utils::make_thing;
|
||||||
|
|
||||||
pub async fn permissions_guard(
|
pub async fn permissions_guard(
|
||||||
headers: &HeaderMap,
|
headers: HeaderMap,
|
||||||
state: AppState,
|
Extension(state): Extension<AppState>,
|
||||||
required_permissions: Vec<PermissionsEnum>,
|
required_permissions: Vec<PermissionsEnum>,
|
||||||
) -> Result<UsersDetailQueryDto, Response> {
|
) -> Result<(UsersDetailQueryDto, AppState), Response> {
|
||||||
let auth_repo = AuthRepository::new(&state);
|
let auth_header = headers
|
||||||
|
.typed_get::<Authorization<Bearer>>()
|
||||||
// Try synchronous email extraction first (for internal JWT tokens)
|
.ok_or_else(|| {
|
||||||
let email = match extract_email(headers) {
|
common_response(
|
||||||
Some(email) => email,
|
StatusCode::UNAUTHORIZED,
|
||||||
None => {
|
"Invalid or missing authorization token",
|
||||||
// If sync extraction fails, try async (for Google tokens)
|
)
|
||||||
match extract_email_async(headers).await {
|
})?;
|
||||||
Some(email) => email,
|
|
||||||
None => {
|
let token = auth_header.token();
|
||||||
return Err(common_response(
|
|
||||||
StatusCode::UNAUTHORIZED,
|
let claims = decode_access_token(token)
|
||||||
"Invalid or missing authorization token",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let raw_user = auth_repo
|
|
||||||
.query_get_stored_user(email.clone())
|
|
||||||
.await
|
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
common_response(
|
common_response(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"User session expired or not found",
|
"Invalid or expired token",
|
||||||
)
|
)
|
||||||
})?;
|
})?
|
||||||
let role_permissions: Vec<String> =
|
.claims;
|
||||||
raw_user.role.permissions.iter().map(|perm| perm.name.clone()).collect();
|
|
||||||
|
|
||||||
|
// Use permissions from JWT for the check
|
||||||
for required in &required_permissions {
|
for required in &required_permissions {
|
||||||
let required_str = required.to_string();
|
let required_str = required.to_string();
|
||||||
if !role_permissions.contains(&required_str) {
|
if !claims.permissions.contains(&required_str) {
|
||||||
eprintln!(" MISSING REQUIRED PERMISSION: {required_str}");
|
eprintln!(" MISSING REQUIRED PERMISSION: {required_str}");
|
||||||
return Err(common_response(
|
return Err(common_response(
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
@@ -51,5 +43,18 @@ pub async fn permissions_guard(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(raw_user)
|
|
||||||
|
// Fetch full user details from the database using user_id from JWT
|
||||||
|
let user_repo = UsersRepository::new(&state);
|
||||||
|
let user_id_thing = make_thing("app_users", &claims.user_id);
|
||||||
|
let raw_user = user_repo.query_user_by_id(&user_id_thing)
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
common_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR, // Changed to internal server error as user ID should be valid from JWT
|
||||||
|
"Failed to retrieve user details",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((raw_user, state))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ pub async fn get_role_list(
|
|||||||
Query(meta): Query<MetaRequestDto>,
|
Query(meta): Query<MetaRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadListRoles],
|
vec![PermissionsEnum::ReadListRoles],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => RolesService::get_role_list(&state, meta).await,
|
Ok((_user, state)) => RolesService::get_role_list(&state, meta).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,13 +68,13 @@ pub async fn get_role_by_id(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailRoles],
|
vec![PermissionsEnum::ReadDetailRoles],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => RolesService::get_role_by_id(&state, id).await,
|
Ok((_user, state)) => RolesService::get_role_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,13 +97,13 @@ pub async fn post_create_role(
|
|||||||
Json(payload): Json<RolesRequestCreateDto>,
|
Json(payload): Json<RolesRequestCreateDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreateRoles],
|
vec![PermissionsEnum::CreateRoles],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => RolesService::create_role(&state, payload).await,
|
Ok((_user, state)) => RolesService::create_role(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,13 +127,13 @@ pub async fn put_update_role(
|
|||||||
Json(payload): Json<RolesRequestUpdateDto>,
|
Json(payload): Json<RolesRequestUpdateDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::UpdateRoles],
|
vec![PermissionsEnum::UpdateRoles],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => RolesService::update_role(&state, id, payload).await,
|
Ok((_user, state)) => RolesService::update_role(&state, id, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,13 +155,13 @@ pub async fn delete_role(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::DeleteRoles],
|
vec![PermissionsEnum::DeleteRoles],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => RolesService::delete_role(&state, id).await,
|
Ok((_user, state)) => RolesService::delete_role(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,30 +49,30 @@ pub async fn get_user_list(
|
|||||||
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
|
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadListUsers],
|
vec![PermissionsEnum::ReadListUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::get_user_list(&state, meta).await,
|
Ok((_user, state)) => UsersService::get_user_list(&state, meta).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
security(
|
security(
|
||||||
("Bearer" = [])
|
("Bearer" = [])
|
||||||
),
|
),
|
||||||
path = "/v1/users/detail/{id}",
|
path = "/v1/users/detail/{id}",
|
||||||
params(
|
params(
|
||||||
("id" = String, Path, description = "User ID")
|
("id" = String, Path, description = "User ID")
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get user by ID", body = ResponseSuccessDto<UsersDetailItemDto>)
|
(status = 200, description = "Get user by ID", body = ResponseSuccessDto<UsersDetailItemDto>)
|
||||||
),
|
),
|
||||||
tag = "Users"
|
tag = "Users"
|
||||||
)]
|
)]
|
||||||
pub async fn get_user_by_id(
|
pub async fn get_user_by_id(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -80,34 +80,34 @@ pub async fn get_user_by_id(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ReadDetailUsers],
|
vec![PermissionsEnum::ReadDetailUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::get_user_by_id(&state, id).await,
|
Ok((_user, state)) => UsersService::get_user_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
security(
|
security(
|
||||||
("Bearer" = [])
|
("Bearer" = [])
|
||||||
),
|
),
|
||||||
path = "/v1/users/me",
|
path = "/v1/users/me",
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get user by ID", body = ResponseSuccessDto<UsersDetailItemDto>)
|
(status = 200, description = "Get user by ID", body = ResponseSuccessDto<UsersDetailItemDto>)
|
||||||
),
|
),
|
||||||
tag = "Users"
|
tag = "Users"
|
||||||
)]
|
)]
|
||||||
pub async fn get_user_me(
|
pub async fn get_user_me(
|
||||||
Extension(state): Extension<AppState>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(&headers, state.clone(), vec![]).await {
|
match permissions_guard(headers, Extension(state), vec![]).await {
|
||||||
Ok(_) => UsersService::get_user_me(headers, &state).await,
|
Ok((user, state)) => UsersService::get_user_me(user, &state).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,13 +130,13 @@ pub async fn post_create_user(
|
|||||||
Json(payload): Json<UsersCreateRequestDto>,
|
Json(payload): Json<UsersCreateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::CreateUsers],
|
vec![PermissionsEnum::CreateUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::create_user(&state, payload).await,
|
Ok((_user, state)) => UsersService::create_user(&state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,13 +163,13 @@ pub async fn put_update_user(
|
|||||||
Json(payload): Json<UsersUpdateRequestDto>,
|
Json(payload): Json<UsersUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::UpdateUsers],
|
vec![PermissionsEnum::UpdateUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::update_user(&state, id, payload).await,
|
Ok((_user, state)) => UsersService::update_user(&state, id, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,8 +191,8 @@ pub async fn put_update_user_me(
|
|||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
Json(payload): Json<UsersUpdateRequestDto>,
|
Json(payload): Json<UsersUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(&headers, state.clone(), vec![]).await {
|
match permissions_guard(headers.clone(), Extension(state), vec![]).await {
|
||||||
Ok(_) => UsersService::update_user_me(headers, &state, payload).await,
|
Ok((_user, state)) => UsersService::update_user_me(headers, &state, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,13 +219,13 @@ pub async fn patch_user_active_status(
|
|||||||
Json(payload): Json<UsersActiveInactiveRequestDto>,
|
Json(payload): Json<UsersActiveInactiveRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::ActivateUsers],
|
vec![PermissionsEnum::ActivateUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::set_user_active_status(&state, id, payload).await,
|
Ok((_user, state)) => UsersService::set_user_active_status(&state, id, payload).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,13 +247,13 @@ pub async fn delete_user(
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![PermissionsEnum::DeleteUsers],
|
vec![PermissionsEnum::DeleteUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => UsersService::delete_user(&state, id).await,
|
Ok((_user, state)) => UsersService::delete_user(&state, id).await,
|
||||||
Err(response) => response,
|
Err(response) => response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,15 +284,15 @@ pub async fn upload_file(
|
|||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Check authentication first
|
// Check authentication first
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
headers,
|
||||||
state.clone(),
|
Extension(state),
|
||||||
vec![], // No specific permission needed, just authentication
|
vec![], // No specific permission needed, just authentication
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(user) => {
|
Ok((user, state)) => {
|
||||||
// Extract user ID from user data
|
// Extract user ID from user data
|
||||||
let user_id = user.id.to_string();
|
let user_id = user.id.id.to_string(); // Use user.id.id to get the actual ID
|
||||||
|
|
||||||
// Process upload - don't use match here since it returns Response directly
|
// Process upload - don't use match here since it returns Response directly
|
||||||
UsersService::upload_file(&state, user_id, multipart).await
|
UsersService::upload_file(&state, user_id, multipart).await
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ResponseSuccessDto, common_response, extract_email, extract_email_async, success_list_response,
|
ResponseSuccessDto, common_response, extract_email, extract_email_async, success_list_response,
|
||||||
success_response, validate_request,
|
success_response, validate_request, UsersDetailQueryDto,
|
||||||
};
|
};
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
use axum::{http::StatusCode, response::Response, extract::Multipart};
|
use axum::{http::StatusCode, response::Response, extract::Multipart};
|
||||||
@@ -24,7 +24,7 @@ use serde_json::json;
|
|||||||
pub trait UsersServiceTrait: Send + Sync + 'static {
|
pub trait UsersServiceTrait: Send + Sync + 'static {
|
||||||
async fn get_user_list(state: &AppState, meta: MetaRequestDto) -> Response;
|
async fn get_user_list(state: &AppState, meta: MetaRequestDto) -> Response;
|
||||||
async fn get_user_by_id(state: &AppState, id: String) -> Response;
|
async fn get_user_by_id(state: &AppState, id: String) -> Response;
|
||||||
async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response;
|
async fn get_user_me(user: UsersDetailQueryDto, state: &AppState) -> Response;
|
||||||
async fn create_user(state: &AppState, new_user: UsersCreateRequestDto) -> Response;
|
async fn create_user(state: &AppState, new_user: UsersCreateRequestDto) -> Response;
|
||||||
async fn update_user(state: &AppState, id: String, user: UsersUpdateRequestDto) -> Response;
|
async fn update_user(state: &AppState, id: String, user: UsersUpdateRequestDto) -> Response;
|
||||||
async fn update_user_me(headers: HeaderMap, state: &AppState, user: UsersUpdateRequestDto) -> Response;
|
async fn update_user_me(headers: HeaderMap, state: &AppState, user: UsersUpdateRequestDto) -> Response;
|
||||||
@@ -76,28 +76,11 @@ impl UsersServiceTrait for UsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response {
|
async fn get_user_me(user: UsersDetailQueryDto, _state: &AppState) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
// User data is already provided by the permissions_guard
|
||||||
|
success_response(ResponseSuccessDto {
|
||||||
// Try synchronous email extraction first (for internal JWT tokens)
|
data: UserDto::from(&user),
|
||||||
let email = match extract_email(&headers) {
|
})
|
||||||
Some(email) => email,
|
|
||||||
None => {
|
|
||||||
// If sync extraction fails, try async (for Google tokens)
|
|
||||||
match extract_email_async(&headers).await {
|
|
||||||
Some(email) => email,
|
|
||||||
None => return common_response(StatusCode::UNAUTHORIZED, "Invalid token"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match repo.query_user_by_email(email).await {
|
|
||||||
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
|
|
||||||
data: UserDto::from(&user), // Corrected to use UserDto::from by reference
|
|
||||||
}),
|
|
||||||
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
|
|
||||||
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(
|
async fn create_user(
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ pub struct Claims {
|
|||||||
pub exp: usize,
|
pub exp: usize,
|
||||||
pub iat: usize,
|
pub iat: usize,
|
||||||
pub sub: String,
|
pub sub: String,
|
||||||
|
pub user_id: String,
|
||||||
|
pub permissions: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_access_token(sub: String) -> Result<String, StatusCode> {
|
pub fn encode_access_token(sub: String, user_id: String, permissions: Vec<String>) -> Result<String, StatusCode> {
|
||||||
let env = &ENV;
|
let env = &ENV;
|
||||||
let secret: String = env.access_token_secret.clone();
|
let secret: String = env.access_token_secret.clone();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let expire: TimeDelta = Duration::minutes(15);
|
let expire: TimeDelta = Duration::minutes(15);
|
||||||
let exp: usize = (now + expire).timestamp() as usize;
|
let exp: usize = (now + expire).timestamp() as usize;
|
||||||
let iat: usize = now.timestamp() as usize;
|
let iat: usize = now.timestamp() as usize;
|
||||||
let claim = Claims { iat, exp, sub };
|
let claim = Claims { iat, exp, sub, user_id, permissions };
|
||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
&claim,
|
&claim,
|
||||||
@@ -29,14 +31,14 @@ pub fn encode_access_token(sub: String) -> Result<String, StatusCode> {
|
|||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_reset_password_token(sub: String) -> Result<String, StatusCode> {
|
pub fn encode_reset_password_token(sub: String, user_id: String, permissions: Vec<String>) -> Result<String, StatusCode> {
|
||||||
let env = &ENV;
|
let env = &ENV;
|
||||||
let secret: String = env.access_token_secret.clone();
|
let secret: String = env.access_token_secret.clone();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let expire: TimeDelta = Duration::minutes(5);
|
let expire: TimeDelta = Duration::minutes(5);
|
||||||
let exp: usize = (now + expire).timestamp() as usize;
|
let exp: usize = (now + expire).timestamp() as usize;
|
||||||
let iat: usize = now.timestamp() as usize;
|
let iat: usize = now.timestamp() as usize;
|
||||||
let claim = Claims { iat, exp, sub };
|
let claim = Claims { iat, exp, sub, user_id, permissions };
|
||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
&claim,
|
&claim,
|
||||||
@@ -59,14 +61,14 @@ pub fn decode_access_token(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_refresh_token(sub: String) -> Result<String, StatusCode> {
|
pub fn encode_refresh_token(sub: String, user_id: String, permissions: Vec<String>) -> Result<String, StatusCode> {
|
||||||
let env = &ENV;
|
let env = &ENV;
|
||||||
let secret: String = env.refresh_token_secret.clone();
|
let secret: String = env.refresh_token_secret.clone();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let expire: TimeDelta = Duration::days(1);
|
let expire: TimeDelta = Duration::days(1);
|
||||||
let exp: usize = (now + expire).timestamp() as usize;
|
let exp: usize = (now + expire).timestamp() as usize;
|
||||||
let iat: usize = now.timestamp() as usize;
|
let iat: usize = now.timestamp() as usize;
|
||||||
let claim = Claims { iat, exp, sub };
|
let claim = Claims { iat, exp, sub, user_id, permissions };
|
||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
&claim,
|
&claim,
|
||||||
@@ -89,6 +91,6 @@ pub fn decode_refresh_token(
|
|||||||
result // Explicitly return result
|
result // Explicitly return result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_jwt(user_id: &str) -> Result<String, StatusCode> {
|
pub fn generate_jwt(user_id: &str, permissions: Vec<String>) -> Result<String, StatusCode> {
|
||||||
encode_access_token(user_id.to_string())
|
encode_access_token(user_id.to_string(), user_id.to_string(), permissions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ mod tests {
|
|||||||
let mut mock_users_service = UsersServiceMock::new(); // Changed from MockUsersServiceMock to UsersServiceMock
|
let mut mock_users_service = UsersServiceMock::new(); // Changed from MockUsersServiceMock to UsersServiceMock
|
||||||
|
|
||||||
let user_email = "new.user@example.com".to_string();
|
let user_email = "new.user@example.com".to_string();
|
||||||
let expected_access_token = generate_jwt("test_user_id").unwrap();
|
let expected_access_token = generate_jwt(&user_email, "test_user_id", vec![]).unwrap();
|
||||||
let expected_refresh_token = generate_jwt("test_user_id").unwrap();
|
let expected_refresh_token = generate_jwt(&user_email, "test_user_id", vec![]).unwrap();
|
||||||
|
|
||||||
let expected_response_dto = AuthLoginResponsetDto {
|
let expected_response_dto = AuthLoginResponsetDto {
|
||||||
token: TokenDto {
|
token: TokenDto {
|
||||||
@@ -195,8 +195,8 @@ mod tests {
|
|||||||
let mut mock_users_service = UsersServiceMock::new(); // Changed from MockUsersServiceMock to UsersServiceMock
|
let mut mock_users_service = UsersServiceMock::new(); // Changed from MockUsersServiceMock to UsersServiceMock
|
||||||
|
|
||||||
let user_email = "existing.user@example.com".to_string();
|
let user_email = "existing.user@example.com".to_string();
|
||||||
let expected_access_token = generate_jwt("existing_user_id").unwrap();
|
let expected_access_token = generate_jwt(&user_email, "existing_user_id", vec![]).unwrap();
|
||||||
let expected_refresh_token = generate_jwt("existing_user_id").unwrap();
|
let expected_refresh_token = generate_jwt(&user_email, "existing_user_id", vec![]).unwrap();
|
||||||
|
|
||||||
let existing_user_dto = UsersDetailItemDto { // Changed from UserDto to UsersDetailItemDto
|
let existing_user_dto = UsersDetailItemDto { // Changed from UserDto to UsersDetailItemDto
|
||||||
id: "existing_user_id".to_string(),
|
id: "existing_user_id".to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user