diff --git a/identity-api-rs/src/database/actions.rs b/identity-api-rs/src/database/actions.rs index 491abdb..33edbf5 100644 --- a/identity-api-rs/src/database/actions.rs +++ b/identity-api-rs/src/database/actions.rs @@ -1,7 +1,11 @@ use diesel::{SqliteConnection, r2d2::{ConnectionManager, PooledConnection}, RunQueryDsl, QueryDsl, SelectableHelper, ExpressionMethods, OptionalExtension}; use crate::database::models::User; -pub fn user(user_id: &str, conn: &mut PooledConnection>) -> diesel::result::QueryResult { +use super::models::Heir; + +type Connection<'a> = &'a mut PooledConnection>; + +pub fn user(user_id: &str, conn: Connection) -> diesel::result::QueryResult { use crate::database::schema::users::dsl::users; users .find(user_id) @@ -9,7 +13,7 @@ pub fn user(user_id: &str, conn: &mut PooledConnection>) -> diesel::result::QueryResult> { +pub fn user_by_email(email: &str, conn: Connection) -> diesel::result::QueryResult> { use crate::database::schema::users::dsl as users; users::users .filter(users::email.eq(email)) @@ -17,4 +21,12 @@ pub fn user_by_email(email: &str, conn: &mut PooledConnection diesel::result::QueryResult> { + use crate::database::schema::heirs::dsl as heirs; + heirs::heirs + .filter(heirs::user_id.eq(user_id)) + .select(Heir::as_select()) + .load(conn) } \ No newline at end of file diff --git a/identity-api-rs/src/database/models.rs b/identity-api-rs/src/database/models.rs index aec03e2..edccdf4 100644 --- a/identity-api-rs/src/database/models.rs +++ b/identity-api-rs/src/database/models.rs @@ -62,11 +62,11 @@ pub struct Entry { #[diesel(table_name = schema::heirs)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] pub struct Heir { - id: String, - user_id: String, - created_at: NaiveDateTime, - name: String, - email: Option, + pub id: String, + pub user_id: String, + pub created_at: NaiveDateTime, + pub name: String, + pub email: Option, } #[derive(Queryable, Selectable, Serialize, Deserialize)] diff --git a/identity-api-rs/src/http/routes/auth.rs b/identity-api-rs/src/http/routes/auth.rs index 5f690dd..21b0661 100644 --- a/identity-api-rs/src/http/routes/auth.rs +++ b/identity-api-rs/src/http/routes/auth.rs @@ -1,7 +1,7 @@ use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; -use axum::{extract::State, http::StatusCode, routing::{get, post}, Json, Router}; +use axum::{extract::State, http::StatusCode, routing::{get, post, put, delete}, Json, Router}; use chrono::{Utc, NaiveDateTime}; -use diesel::{RunQueryDsl, ExpressionMethods}; +use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods}; use tracing::{error, info}; use serde::{Serialize, Deserialize}; use uuid::Uuid; @@ -13,6 +13,9 @@ pub fn auth_router() -> Router { .route("/auth/register", post(register)) .route("/auth/login", post(login)) .route("/auth/genkey", get(genkey)) + .route("/auth/heirs", get(list_heirs)) + .route("/auth/heirs", put(insert_heir)) + .route("/auth/heirs", delete(delete_heir)) } #[derive(Debug, Serialize)] @@ -46,7 +49,7 @@ async fn genkey(State(state): State, ExtractJwtUser(user): ExtractJwtU let session_key = Uuid::new_v4().to_string(); let result = diesel::insert_into(session_keys) .values(( - user_id.eq(user.uid), + user_id.eq(&user.uid), key.eq(&session_key), )) .execute(&mut conn); @@ -56,7 +59,7 @@ async fn genkey(State(state): State, ExtractJwtUser(user): ExtractJwtU session_key, })) } else { - error!("failed to insert into session_keys"); + error!("failed to insert into session_keys {}, error: {:?}", user.uid, result.err()); Err(StatusCode::INTERNAL_SERVER_ERROR) } } else { @@ -198,4 +201,110 @@ async fn register(State(state): State, Json(req): Json for HttpHeir { + fn from(value: crate::database::models::Heir) -> Self { + Self { + id: value.id, + // Only e-mail is implemented right now + contact_method: "email".into(), + name: value.name, + value: value.email.unwrap() + } + } +} + +async fn list_heirs(State(state): State, ExtractJwtUser(user): ExtractJwtUser) -> Result>, StatusCode> { + if let Ok(mut conn) = state.pool.get() { + let result = actions::list_heirs(&user.uid, &mut conn); + if let Ok(heirs) = result { + Ok(Json(heirs.into_iter().map(HttpHeir::from).collect())) + } else { + error!("failed to obtain heirs: {}, error: {:?}", user.uid, result.err()); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } else { + error!("failed to obtain pooled connection"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct InsertHeirRequest { + contact_method: String, + name: String, + value: String, +} + +async fn insert_heir(State(state): State, ExtractJwtUser(user): ExtractJwtUser, Json(req): Json) -> Result>, StatusCode> { + use crate::database::schema::heirs::dsl::*; + if let Ok(mut conn) = state.pool.get() { + let heir_id = Uuid::new_v4().to_string(); + let result = diesel::insert_into(heirs) + .values(( + id.eq(heir_id), + created_at.eq(Utc::now().naive_utc()), + user_id.eq(&user.uid), + name.eq(req.name), + // Only e-mail is implemented right now + email.eq(req.value), + )) + .execute(&mut conn); + + if result.is_err() { + error!("failed to insert into heirs: {}, error: {:?}", user.uid, result.err()); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + + let result = actions::list_heirs(&user.uid, &mut conn); + if let Ok(heirs_list) = result { + Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect())) + } else { + error!("failed to obtain heirs: {}, error: {:?}", user.uid, result.err()); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } else { + error!("failed to obtain pooled connection"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } +} + + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct DeleteHeirRequest { + id: String, +} + +async fn delete_heir(State(state): State, ExtractJwtUser(user): ExtractJwtUser, Json(req): Json) -> Result>, StatusCode> { + use crate::database::schema::heirs::dsl::*; + if let Ok(mut conn) = state.pool.get() { + let result = diesel::delete(heirs.filter(id.eq(&req.id))).execute(&mut conn); + if result.is_err() { + error!("failed to delete from heirs: {}, heir_id: {}, error: {:?}", user.uid, req.id, result.err()); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + + let result = actions::list_heirs(&user.uid, &mut conn); + if let Ok(heirs_list) = result { + Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect())) + } else { + error!("failed to obtain heirs: {}, error: {:?}", user.uid, result.err()); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } else { + error!("failed to obtain pooled connection"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } } \ No newline at end of file diff --git a/identity-api/src/routes/auth/heirs.ts b/identity-api/src/routes/auth/heirs.ts index ff526a0..f9bae62 100644 --- a/identity-api/src/routes/auth/heirs.ts +++ b/identity-api/src/routes/auth/heirs.ts @@ -102,7 +102,7 @@ export default function register(app: AppInterface, auth: AuthInterface, databas return; } - await database.removeHeir(request.body); + await database.removeHeir(request.body.id); return (await database.listHeirs(payload.uid)) .map((v) => (v["contactMethod"] = "email")) @@ -111,9 +111,9 @@ export default function register(app: AppInterface, auth: AuthInterface, databas }, schema: { headers: { $ref: "schema://identity/authorization" }, - body: { - type: "string", - }, + body: Type.Object({ + id: Type.String(), + }), }, }); } diff --git a/identity-web/src/lib/api.ts b/identity-web/src/lib/api.ts index c09a018..3d50919 100644 --- a/identity-web/src/lib/api.ts +++ b/identity-web/src/lib/api.ts @@ -145,7 +145,10 @@ export async function removeHeir(credentials: Credentials, heirID: string): Prom return await asJson( sendRequest('/auth/heirs', credentials, { method: 'DELETE', - body: heirID + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ id: heirID }) }) ); }