diff --git a/.github/workflows/shuttle-run.yml b/.github/workflows/shuttle-run.yml index d38a7c5..ecbdb93 100644 --- a/.github/workflows/shuttle-run.yml +++ b/.github/workflows/shuttle-run.yml @@ -10,8 +10,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Run shuttle project locally. + - name: Run shuttle project locally. uses: ivinjabraham/shuttle-run@v1.1 - with: + with: secrets: | ROOT_SECRET = '${{ secrets.ROOT_SECRET }}' diff --git a/Cargo.lock b/Cargo.lock index bf74805..3051460 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1919,7 +1919,10 @@ dependencies = [ "async-graphql-axum", "axum 0.7.5", "chrono", + "hex", + "hmac", "serde", + "sha2", "shuttle-axum", "shuttle-runtime", "shuttle-shared-db", diff --git a/Cargo.toml b/Cargo.toml index d663a29..ca5dcaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ shuttle-runtime = "0.46.0" shuttle-shared-db = { version = "0.46.0", features = ["postgres", "sqlx"] } sqlx = { version = "0.7.1", features = ["chrono"] } tokio = "1.28.2" +hmac = "0.12.1" +sha2 = "0.10.8" +hex = "0.4.3" \ No newline at end of file diff --git a/migrations/20240813131821_rename_present_to_isPresent.sql b/migrations/20240813131821_rename_present_to_isPresent.sql new file mode 100644 index 0000000..6e2ff32 --- /dev/null +++ b/migrations/20240813131821_rename_present_to_isPresent.sql @@ -0,0 +1 @@ +ALTER TABLE Attendance RENAME COLUMN is_prresent TO is_present; diff --git a/migrations/20240813132052_rename_present_to_is_present.sql b/migrations/20240813132052_rename_present_to_is_present.sql deleted file mode 100644 index 8b13789..0000000 --- a/migrations/20240813132052_rename_present_to_is_present.sql +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/graphql/mutations.rs b/src/graphql/mutations.rs index db8d3d5..ea3f032 100644 --- a/src/graphql/mutations.rs +++ b/src/graphql/mutations.rs @@ -1,8 +1,14 @@ use async_graphql::{Context, Object}; +use ::chrono::Local; use chrono::{NaiveDate, NaiveTime}; use sqlx::PgPool; use sqlx::types::chrono; use std::sync::Arc; +use hmac::{Hmac,Mac}; +use sha2::Sha256; + + +type HmacSha256 = Hmac; use crate::db::{member::Member, attendance::Attendance}; @@ -22,9 +28,12 @@ impl MutationRoot { sex: String, year: i32, macaddress: String, + ) -> Result { let pool = ctx.data::>().expect("Pool not found in context"); + + let member = sqlx::query_as::<_, Member>( "INSERT INTO Member (rollno, name, hostel, email, sex, year, macaddress) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *" ) @@ -44,22 +53,29 @@ impl MutationRoot { //Mutation for adding attendance to the Attendance table async fn add_attendance( + &self, + ctx: &Context<'_>, id: i32, date: NaiveDate, timein: NaiveTime, timeout: NaiveTime, + is_present: bool, + ) -> Result { let pool = ctx.data::>().expect("Pool not found in context"); + let attendance = sqlx::query_as::<_, Attendance>( - "INSERT INTO Attendance (id, date, timein, timeout) VALUES ($1, $2, $3, $4) RETURNING *" + "INSERT INTO Attendance (id, date, timein, timeout, is_present) VALUES ($1, $2, $3, $4, $5) RETURNING *" ) + .bind(id) .bind(date) .bind(timein) .bind(timeout) + .bind(is_present) .fetch_one(pool.as_ref()) .await?; @@ -72,17 +88,53 @@ impl MutationRoot { id: i32, date: NaiveDate, is_present: bool, + hmac_signature: String, ) -> Result { + let pool = ctx.data::>().expect("Pool not found in context"); + let secret_key = ctx.data::().expect("HMAC secret not found in context"); + + let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes()).expect("HMAC can take key of any size"); + + let message = format!("{}{}{}", id, date, is_present); + mac.update(message.as_bytes()); + + let expected_signature = mac.finalize().into_bytes(); + + + // Convert the received HMAC signature from the client to bytes for comparison + let received_signature = hex::decode(hmac_signature) + .map_err(|_| sqlx::Error::Protocol("Invalid HMAC signature".into()))?; + + + if expected_signature.as_slice() != received_signature.as_slice() { + + return Err(sqlx::Error::Protocol("HMAC verification failed".into())); + } + + + + + let current_time = Local::now().time(); + let attendance = sqlx::query_as::<_, Attendance>( - "UPDATE Attendance SET is_present = $1 WHERE id = $2 AND date = $3 RETURNING *" + " + UPDATE Attendance + SET + timein = CASE WHEN timein = '00:00:00' THEN $1 ELSE timein END, + timeout = $1, + is_present = $2 + WHERE id = $3 AND date = $4 + RETURNING * + " ) + .bind(current_time) .bind(is_present) .bind(id) - .bind(date) + .bind(date) .fetch_one(pool.as_ref()) - .await?; + .await?; Ok(attendance) } diff --git a/src/main.rs b/src/main.rs index c9fa020..2040367 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,16 @@ use std::{env, sync::Arc}; use tokio::task; -use tokio::time::{sleep, sleep_until, Instant}; +use tokio::time::{ sleep_until, Instant}; use std::time::Duration; use async_graphql_axum::GraphQL; use axum::{routing::get, Router}; use chrono::{ Local, NaiveTime}; -use db::attendance::Attendance; + use db::member::Member; use sqlx::PgPool; use async_graphql::{ Schema, EmptySubscription}; - +use shuttle_runtime::SecretStore; use crate::graphql::mutations::MutationRoot; use crate::graphql::query::QueryRoot; use crate::routes::graphiql; @@ -22,21 +22,24 @@ mod routes; #[derive(Clone)] struct MyState { pool: Arc, + secret_key: String, } //Main method #[shuttle_runtime::main] -async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::ShuttleAxum { +async fn main(#[shuttle_shared_db::Postgres] pool: PgPool,#[shuttle_runtime::Secrets] secrets: SecretStore,) -> shuttle_axum::ShuttleAxum { env::set_var("PGOPTIONS", "-c ignore_version=true"); sqlx::migrate!().run(&pool).await.expect("Failed to run migrations"); let pool = Arc::new(pool); + let secret_key = secrets.get("ROOT_SECRET").expect("ROOT_SECRET not found"); let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription) .data(pool.clone()) + .data(secret_key.clone()) // .finish(); - let state = MyState { pool: pool.clone() }; + let state = MyState { pool: pool.clone() , secret_key: secret_key.clone()}; let router = Router::new() .route("/", get(graphiql).post_service(GraphQL::new(schema.clone())))