From 8480596df12a973ec145e22909fd07067c3ac3fa Mon Sep 17 00:00:00 2001 From: James2Tulloch <146088090+James2Tulloch@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:46:08 +0000 Subject: [PATCH] JWT First implementation --- django_project/myapp/views.py | 29 +++++++++++------------ rust_crud_api/Cargo.toml | 2 +- rust_crud_api/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/django_project/myapp/views.py b/django_project/myapp/views.py index 63c9eba..9c2717f 100644 --- a/django_project/myapp/views.py +++ b/django_project/myapp/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render, redirect from django.http import JsonResponse from django.conf import settings +import time import os import rust_crud_api # This is the module we built @@ -61,38 +62,38 @@ def register_view(request): def login_view(request): """ - Handles user login by checking for the user's email in the database using the Rust extension. - If a matching user is found, their info is stored in the session. + Handles user login by checking if a user with the provided email exists. + If found, stores user details in the session and redirects to the account page. """ - db_url = settings.DATABASE_URL # This should be defined in your settings.py + db_url = settings.DATABASE_URL # Ensure this is defined in your settings.py context = {} - + if request.method == 'POST': - email = request.POST.get('email') + email = request.POST.get('email', '').strip() if not email: context['error'] = "Email is required." return render(request, 'myapp/login.html', context) - + try: - # Retrieve all users from the database via the Rust extension + # Retrieve all users from the database via the Rust extension. users = rust_crud_api.get_all_users(db_url) - # Find a user with the matching email + # Find the user with a matching email (case-insensitive). user = next((u for u in users if u.email.lower() == email.lower()), None) - if user is None: + + if not user: context['error'] = "User not found. Please register first." return render(request, 'myapp/login.html', context) - - # Simulate a login by storing user information in the session. + + # Simulate login by storing user information in the session. request.session['user_id'] = user.id request.session['user_email'] = user.email request.session['user_name'] = user.name - - # Redirect to the account page or another dashboard. + return redirect('account') except Exception as e: context['error'] = f"An error occurred: {str(e)}" return render(request, 'myapp/login.html', context) - + return render(request, 'myapp/login.html', context) diff --git a/rust_crud_api/Cargo.toml b/rust_crud_api/Cargo.toml index d3cb558..1714ef2 100644 --- a/rust_crud_api/Cargo.toml +++ b/rust_crud_api/Cargo.toml @@ -12,4 +12,4 @@ pyo3 = { version = "0.18", features = ["extension-module"] } postgres = "0.19" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" - +jsonwebtoken = "8.2.0" diff --git a/rust_crud_api/src/lib.rs b/rust_crud_api/src/lib.rs index ef201c3..0d446d7 100644 --- a/rust_crud_api/src/lib.rs +++ b/rust_crud_api/src/lib.rs @@ -3,7 +3,10 @@ use pyo3::prelude::*; use pyo3::exceptions::PyRuntimeError; use postgres::{Client, NoTls}; +use pyo3::types::PyDict; use serde::{Serialize, Deserialize}; /// Import derive macros from serde_derive +use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey}; + /// Define our User model, which will be exposed to Python. #[pyclass] @@ -26,6 +29,44 @@ struct Group { name: String, } +//JWT Claims for Token Payload + +#[derive(Debug,Serialize,Deserialize)] +struct Claims { + sub: String, // Subject + exp: usize, // Expiration time +} + +/// Generate a JWT token for a given user. +/// Returns the token as a String. +#[pyfunction] +fn generate_jwt(user_email: String, secret: String, exp: usize) -> PyResult<String> { + let claims = Claims { sub: user_email, exp }; + encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref())) + .map_err(|e| PyRuntimeError::new_err(e.to_string())) +} + +/// Verify a JWT token and return its claims as a Python dictionary. +#[pyfunction] +fn verify_jwt(token: String, secret: String) -> PyResult<PyObject> { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let token_data = decode::<Claims>( + &token, + &DecodingKey::from_secret(secret.as_ref()), + &Validation::default() + ).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + + // Extract the claims. + let claims = token_data.claims; + // Create a Python dictionary and insert the claim fields. + let dict = PyDict::new(py); + dict.set_item("sub", claims.sub)?; + dict.set_item("exp", claims.exp)?; + Ok(dict.to_object(py)) +} + /// A helper function to convert postgres::Error into a Python RuntimeError. fn pg_err(e: postgres::Error) -> PyErr { PyRuntimeError::new_err(e.to_string()) @@ -227,6 +268,8 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(get_all_groups, m)?)?; m.add_function(wrap_pyfunction!(add_user_to_group, m)?)?; m.add_function(wrap_pyfunction!(get_group_members, m)?)?; + m.add_function(wrap_pyfunction!(generate_jwt, m)?)?; + m.add_function(wrap_pyfunction!(verify_jwt, m)?)?; Ok(()) } -- GitLab