Skip to content

Commit

Permalink
Merge pull request #6 from amfoss/fix-adding-and-marking-attendance
Browse files Browse the repository at this point in the history
Fix adding and marking attendance, and adds device verification using a secret with sha2 and hmac
  • Loading branch information
ivinjabraham authored Sep 26, 2024
2 parents 9a41467 + e281aed commit c90df74
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/shuttle-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Run shuttle project locally.
- name: Run shuttle project locally.
uses: ivinjabraham/[email protected]
with:
with:
secrets: |
ROOT_SECRET = '${{ secrets.ROOT_SECRET }}'
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions migrations/20240813131821_rename_present_to_isPresent.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE Attendance RENAME COLUMN is_prresent TO is_present;
1 change: 0 additions & 1 deletion migrations/20240813132052_rename_present_to_is_present.sql

This file was deleted.

60 changes: 56 additions & 4 deletions src/graphql/mutations.rs
Original file line number Diff line number Diff line change
@@ -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<Sha256>;

use crate::db::{member::Member, attendance::Attendance};

Expand All @@ -22,9 +28,12 @@ impl MutationRoot {
sex: String,
year: i32,
macaddress: String,

) -> Result<Member, sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().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 *"
)
Expand All @@ -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<Attendance, sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().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?;

Expand All @@ -72,17 +88,53 @@ impl MutationRoot {
id: i32,
date: NaiveDate,
is_present: bool,
hmac_signature: String,
) -> Result<Attendance,sqlx::Error> {

let pool = ctx.data::<Arc<PgPool>>().expect("Pool not found in context");

let secret_key = ctx.data::<String>().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)
}
Expand Down
13 changes: 8 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,21 +22,24 @@ mod routes;
#[derive(Clone)]
struct MyState {
pool: Arc<PgPool>,
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())))
Expand Down

0 comments on commit c90df74

Please sign in to comment.