Compare commits

..

2 commits

Author SHA1 Message Date
Sofía Aritz c6e3fa3eee
Implement heir endpoints 2024-10-15 22:58:47 +02:00
Sofía Aritz fae3d0ebaa
Reduce JWT exp to 30 days 2024-10-15 22:25:31 +02:00
6 changed files with 141 additions and 17 deletions

View file

@ -24,5 +24,5 @@ pub fn expiration_time() -> u64 {
SystemTime::now() SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.expect("time went backwards") .expect("time went backwards")
.as_secs() + 180 * 24 * 3600 .as_secs() + 30 * 24 * 3600
} }

View file

@ -1,7 +1,11 @@
use diesel::{SqliteConnection, r2d2::{ConnectionManager, PooledConnection}, RunQueryDsl, QueryDsl, SelectableHelper, ExpressionMethods, OptionalExtension}; use diesel::{SqliteConnection, r2d2::{ConnectionManager, PooledConnection}, RunQueryDsl, QueryDsl, SelectableHelper, ExpressionMethods, OptionalExtension};
use crate::database::models::User; use crate::database::models::User;
pub fn user(user_id: &str, conn: &mut PooledConnection<ConnectionManager<SqliteConnection>>) -> diesel::result::QueryResult<User> { use super::models::Heir;
type Connection<'a> = &'a mut PooledConnection<ConnectionManager<SqliteConnection>>;
pub fn user(user_id: &str, conn: Connection) -> diesel::result::QueryResult<User> {
use crate::database::schema::users::dsl::users; use crate::database::schema::users::dsl::users;
users users
.find(user_id) .find(user_id)
@ -9,7 +13,7 @@ pub fn user(user_id: &str, conn: &mut PooledConnection<ConnectionManager<SqliteC
.first(conn) .first(conn)
} }
pub fn user_by_email(email: &str, conn: &mut PooledConnection<ConnectionManager<SqliteConnection>>) -> diesel::result::QueryResult<Option<User>> { pub fn user_by_email(email: &str, conn: Connection) -> diesel::result::QueryResult<Option<User>> {
use crate::database::schema::users::dsl as users; use crate::database::schema::users::dsl as users;
users::users users::users
.filter(users::email.eq(email)) .filter(users::email.eq(email))
@ -17,4 +21,12 @@ pub fn user_by_email(email: &str, conn: &mut PooledConnection<ConnectionManager<
.select(User::as_select()) .select(User::as_select())
.first(conn) .first(conn)
.optional() .optional()
}
pub fn list_heirs(user_id: &str, conn: Connection) -> diesel::result::QueryResult<Vec<Heir>> {
use crate::database::schema::heirs::dsl as heirs;
heirs::heirs
.filter(heirs::user_id.eq(user_id))
.select(Heir::as_select())
.load(conn)
} }

View file

@ -62,11 +62,11 @@ pub struct Entry {
#[diesel(table_name = schema::heirs)] #[diesel(table_name = schema::heirs)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))] #[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Heir { pub struct Heir {
id: String, pub id: String,
user_id: String, pub user_id: String,
created_at: NaiveDateTime, pub created_at: NaiveDateTime,
name: String, pub name: String,
email: Option<String>, pub email: Option<String>,
} }
#[derive(Queryable, Selectable, Serialize, Deserialize)] #[derive(Queryable, Selectable, Serialize, Deserialize)]

View file

@ -1,7 +1,7 @@
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; 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 chrono::{Utc, NaiveDateTime};
use diesel::{RunQueryDsl, ExpressionMethods}; use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods};
use tracing::{error, info}; use tracing::{error, info};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use uuid::Uuid; use uuid::Uuid;
@ -13,6 +13,9 @@ pub fn auth_router() -> Router<AppState> {
.route("/auth/register", post(register)) .route("/auth/register", post(register))
.route("/auth/login", post(login)) .route("/auth/login", post(login))
.route("/auth/genkey", get(genkey)) .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)] #[derive(Debug, Serialize)]
@ -46,7 +49,7 @@ async fn genkey(State(state): State<AppState>, ExtractJwtUser(user): ExtractJwtU
let session_key = Uuid::new_v4().to_string(); let session_key = Uuid::new_v4().to_string();
let result = diesel::insert_into(session_keys) let result = diesel::insert_into(session_keys)
.values(( .values((
user_id.eq(user.uid), user_id.eq(&user.uid),
key.eq(&session_key), key.eq(&session_key),
)) ))
.execute(&mut conn); .execute(&mut conn);
@ -56,7 +59,7 @@ async fn genkey(State(state): State<AppState>, ExtractJwtUser(user): ExtractJwtU
session_key, session_key,
})) }))
} else { } 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) Err(StatusCode::INTERNAL_SERVER_ERROR)
} }
} else { } else {
@ -198,4 +201,110 @@ async fn register(State(state): State<AppState>, Json(req): Json<RegisterRequest
error!("failed to obtain pooled connection"); error!("failed to obtain pooled connection");
Err(StatusCode::INTERNAL_SERVER_ERROR) Err(StatusCode::INTERNAL_SERVER_ERROR)
} }
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct HttpHeir {
id: String,
contact_method: String,
name: String,
value: String,
}
impl From<crate::database::models::Heir> 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<AppState>, ExtractJwtUser(user): ExtractJwtUser) -> Result<Json<Vec<HttpHeir>>, 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<AppState>, ExtractJwtUser(user): ExtractJwtUser, Json(req): Json<InsertHeirRequest>) -> Result<Json<Vec<HttpHeir>>, 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<AppState>, ExtractJwtUser(user): ExtractJwtUser, Json(req): Json<DeleteHeirRequest>) -> Result<Json<Vec<HttpHeir>>, 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)
}
} }

View file

@ -102,7 +102,7 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
return; return;
} }
await database.removeHeir(request.body); await database.removeHeir(request.body.id);
return (await database.listHeirs(payload.uid)) return (await database.listHeirs(payload.uid))
.map((v) => (v["contactMethod"] = "email")) .map((v) => (v["contactMethod"] = "email"))
@ -111,9 +111,9 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
}, },
schema: { schema: {
headers: { $ref: "schema://identity/authorization" }, headers: { $ref: "schema://identity/authorization" },
body: { body: Type.Object({
type: "string", id: Type.String(),
}, }),
}, },
}); });
} }

View file

@ -145,7 +145,10 @@ export async function removeHeir(credentials: Credentials, heirID: string): Prom
return await asJson( return await asJson(
sendRequest('/auth/heirs', credentials, { sendRequest('/auth/heirs', credentials, {
method: 'DELETE', method: 'DELETE',
body: heirID headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: heirID })
}) })
); );
} }