Skip to content

Register + Login compatiblity #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
SECRET_KEY=django-insecure-*$l30g@bo6!s*5y4i(z@@8aq(cc*k07cp0h4vk^jp$-mufw1rt

DB_NAME=healthchaindb
DB_USER=postgres
DB_PASSWORD=testing123
DB_HOST=localhost
DB_PORT=5432

NODE_ENV=development
PORT=5000
MONGODB_URI=mongodb://localhost:27017/healthchain
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
FRONTEND_URL=http://localhost:5173
Empty file added backend/api/__init__.py
Empty file.
Binary file added backend/api/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file added backend/api/__pycache__/admin.cpython-312.pyc
Binary file not shown.
Binary file added backend/api/__pycache__/apps.cpython-312.pyc
Binary file not shown.
Binary file added backend/api/__pycache__/models.cpython-312.pyc
Binary file not shown.
Binary file not shown.
Binary file added backend/api/__pycache__/urls.cpython-312.pyc
Binary file not shown.
Binary file added backend/api/__pycache__/views.cpython-312.pyc
Binary file not shown.
3 changes: 3 additions & 0 deletions backend/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
1 change: 1 addition & 0 deletions backend/api/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file not shown.
1 change: 1 addition & 0 deletions backend/api/management/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

161 changes: 161 additions & 0 deletions backend/api/management/commands/encrypt_existing_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import Q
from api.models import User, EmergencyAccessLog, EmergencyPIN
from api.utils.crypto import encryption

class Command(BaseCommand):
help = 'Encrypt existing unencrypted data in the database'

def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Only show what would be encrypted without making changes',
)

def handle(self, *args, **options):
dry_run = options['dry_run']

if dry_run:
self.stdout.write(self.style.WARNING('DRY RUN MODE: No data will be modified'))

# Start transaction - we'll roll back in dry run mode
with transaction.atomic():
self.encrypt_user_data(dry_run)
self.encrypt_emergency_logs(dry_run)
self.encrypt_emergency_pins(dry_run)

if dry_run:
# Roll back all changes in dry run mode
transaction.set_rollback(True)
self.stdout.write(self.style.WARNING('DRY RUN COMPLETED - All changes rolled back'))
else:
self.stdout.write(self.style.SUCCESS('Successfully encrypted all sensitive data'))

def encrypt_user_data(self, dry_run):
"""Encrypt sensitive fields in User model"""
count = 0
users = User.objects.all()
total_users = users.count()

self.stdout.write(f"Processing {total_users} user records...")

for user in users:
# Skip already encrypted data (if we can identify it)
# For demonstration purposes - you may need different logic

# Phone number
if user.phone_number and not self._is_likely_encrypted(user.phone_number):
if not dry_run:
# Temporarily bypass the auto-encryption to manually set
user.phone_number = encryption.encrypt(user.phone_number)
count += 1

# License number
if user.license_number and not self._is_likely_encrypted(user.license_number):
if not dry_run:
user.license_number = encryption.encrypt(user.license_number)

# Hospital name
if user.hospital_name and not self._is_likely_encrypted(user.hospital_name):
if not dry_run:
user.hospital_name = encryption.encrypt(user.hospital_name)

# Location
if user.location and not self._is_likely_encrypted(user.location):
if not dry_run:
user.location = encryption.encrypt(user.location)

# Emergency contacts
if user.emergency_contacts and not self._is_likely_encrypted(str(user.emergency_contacts)):
if not dry_run:
user.emergency_contacts = encryption.encrypt(user.emergency_contacts)

# Critical health info
if user.critical_health_info and not self._is_likely_encrypted(str(user.critical_health_info)):
if not dry_run:
user.critical_health_info = encryption.encrypt(user.critical_health_info)

if not dry_run:
user.save()

self.stdout.write(f"Processed {count} user records with sensitive data")

def encrypt_emergency_logs(self, dry_run):
"""Encrypt sensitive fields in EmergencyAccessLog model"""
count = 0
logs = EmergencyAccessLog.objects.all()
total_logs = logs.count()

self.stdout.write(f"Processing {total_logs} emergency access logs...")

for log in logs:
# IP address
if log.ip_address and not self._is_likely_encrypted(str(log.ip_address)):
if not dry_run:
log.ip_address = encryption.encrypt(log.ip_address)
count += 1

# User agent
if log.user_agent and not self._is_likely_encrypted(log.user_agent):
if not dry_run:
log.user_agent = encryption.encrypt(log.user_agent)

# Details
if log.details and not self._is_likely_encrypted(str(log.details)):
if not dry_run:
log.details = encryption.encrypt(log.details)

if not dry_run:
log.save()

self.stdout.write(f"Processed {count} emergency access logs with sensitive data")

def encrypt_emergency_pins(self, dry_run):
"""Encrypt sensitive fields in EmergencyPIN model"""
count = 0
pins = EmergencyPIN.objects.all()
total_pins = pins.count()

self.stdout.write(f"Processing {total_pins} emergency PINs...")

for pin in pins:
# PIN
if pin.pin and not self._is_likely_encrypted(pin.pin):
if not dry_run:
pin.pin = encryption.encrypt(pin.pin)
count += 1

# Revocation reason
if pin.revoked_reason and not self._is_likely_encrypted(pin.revoked_reason):
if not dry_run:
pin.revoked_reason = encryption.encrypt(pin.revoked_reason)

if not dry_run:
pin.save()

self.stdout.write(f"Processed {count} emergency PINs with sensitive data")

def _is_likely_encrypted(self, value):
"""
Try to detect if a value is already encrypted.
This is a heuristic - encrypted data is base64-encoded and fairly long.
"""
if not isinstance(value, str):
return False

# Encrypted data should be fairly long base64 string
import base64
import re

# Simple heuristic: check if it's a long base64 string
if len(value) > 100 and re.match(r'^[A-Za-z0-9_-]+={0,2}$', value):
# Try to decode and see if it might be base64
try:
base64.urlsafe_b64decode(value.encode('ascii'))
return True
except:
pass

return False
98 changes: 98 additions & 0 deletions backend/api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Generated by Django 4.2.10 on 2025-04-08 14:56

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('role', models.CharField(choices=[('patient', 'Patient'), ('doctor', 'Doctor')], default='patient', max_length=10)),
('phone_number', models.CharField(blank=True, max_length=15)),
('date_of_birth', models.DateField(blank=True, null=True)),
('gender', models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')], max_length=10)),
('license_number', models.CharField(blank=True, max_length=50)),
('specialization', models.CharField(blank=True, max_length=100)),
('hospital_name', models.CharField(blank=True, max_length=200)),
('location', models.CharField(blank=True, max_length=200)),
('emergency_contacts', models.JSONField(default=list)),
('critical_health_info', models.JSONField(default=dict)),
('emergency_access_enabled', models.BooleanField(default=True)),
('emergency_access_expires_at', models.DateTimeField(blank=True, null=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'User',
'verbose_name_plural': 'Users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='EmergencyPIN',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('pin', models.CharField(max_length=6)),
('pin_hash', models.CharField(max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField(default=django.utils.timezone.now)),
('is_used', models.BooleanField(default=False)),
('used_at', models.DateTimeField(blank=True, null=True)),
('access_duration', models.IntegerField(default=60)),
('delivery_method', models.CharField(choices=[('SMS', 'SMS'), ('EMAIL', 'Email'), ('BOTH', 'Both')], default='BOTH', max_length=10)),
('delivery_status', models.CharField(choices=[('PENDING', 'Pending'), ('SENT', 'Sent'), ('FAILED', 'Failed')], default='PENDING', max_length=20)),
('access_token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('failed_attempts', models.IntegerField(default=0)),
('last_attempt', models.DateTimeField(blank=True, null=True)),
('is_revoked', models.BooleanField(default=False)),
('revoked_at', models.DateTimeField(blank=True, null=True)),
('revoked_reason', models.TextField(blank=True, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emergency_pins', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='EmergencyAccessLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(auto_now_add=True)),
('action', models.CharField(choices=[('GENERATED', 'PIN Generated'), ('VERIFIED', 'PIN Verified'), ('EXPIRED', 'PIN Expired'), ('REVOKED', 'Access Revoked'), ('FAILED', 'Failed Attempt')], max_length=20)),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('user_agent', models.TextField(blank=True, null=True)),
('details', models.JSONField(default=dict)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emergency_access_logs', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-timestamp'],
},
),
]
18 changes: 18 additions & 0 deletions backend/api/migrations/0002_add_role_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2025-04-08 16:43

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='user',
name='role',
field=models.CharField(choices=[('patient', 'Patient'), ('doctor', 'Doctor')], default='patient', max_length=10),
),
]
68 changes: 68 additions & 0 deletions backend/api/migrations/0003_add_missing_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Generated by Django 4.2.10 on 2025-04-08 22:15

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0002_add_role_field'),
]

operations = [
migrations.AddField(
model_name='user',
name='phone_number',
field=models.CharField(blank=True, max_length=15),
),
migrations.AddField(
model_name='user',
name='date_of_birth',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='user',
name='gender',
field=models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')], max_length=10),
),
migrations.AddField(
model_name='user',
name='license_number',
field=models.CharField(blank=True, max_length=50),
),
migrations.AddField(
model_name='user',
name='specialization',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='user',
name='hospital_name',
field=models.CharField(blank=True, max_length=200),
),
migrations.AddField(
model_name='user',
name='location',
field=models.CharField(blank=True, max_length=200),
),
migrations.AddField(
model_name='user',
name='emergency_contacts',
field=models.JSONField(default=list),
),
migrations.AddField(
model_name='user',
name='critical_health_info',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='user',
name='emergency_access_enabled',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='user',
name='emergency_access_expires_at',
field=models.DateTimeField(blank=True, null=True),
),
]
Loading