Skip to content
Merged
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
21 changes: 21 additions & 0 deletions SFS/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
<<<<<<< HEAD
__pycache__/
node_modules/
.env
firebase_key.json
*.pyc
.DS_Store

# Environment & local files
.env.local
/backend/sfs_backend/.env
/backups/
*.sqlite3
db.sqlite3
staticfiles/
venv/
.venv/
.vscode/
*.log
=======
# Logs
logs
*.log
Expand All @@ -22,3 +42,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
>>>>>>> bcc90d3f06f7aa01dccbcb36cbf03bbf0ad74362
78 changes: 77 additions & 1 deletion SFS/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,78 @@
# SFS: Secure File System

# Secure File System (SFS)

This repository contains a Django + DRF backend for a Secure File System and a minimal frontend using Bootstrap and Axios.

## Features

- User registration + JWT authentication (djangorestframework-simplejwt)
- AES-256 encrypted files, RSA-2048 per-user key pairs, HMAC-SHA256 integrity
- Upload / download / delete files
- Local, Firebase and S3 storage options (local implemented)
- Activity logs

## Quick setup (development)

1. Create virtual environment and activate

```powershell
python -m venv venv
.\venv\Scripts\Activate.ps1
```

2. Install backend dependencies

```powershell
pip install -r backend/requirements.txt
```

3. Setup env

```powershell
copy backend/sfs_backend/.env.example backend/sfs_backend/.env
# edit .env if needed
```

4. Apply migrations and create superuser

```powershell
cd backend/sfs_backend
python manage.py migrate
python manage.py createsuperuser
```

5. Run server

```powershell
python manage.py runserver
```

Access:

- API root: http://127.0.0.1:8000/api/
- Frontend: http://127.0.0.1:8000/login.html
- Admin: http://127.0.0.1:8000/admin/

## Production notes

- Set `DJANGO_DEBUG=False` and configure `POSTGRES_*` env vars for PostgreSQL
- Use a secure `DJANGO_SECRET_KEY` and handle FILE master key
- Configure proper CORS origins
- Serve static files with WhiteNoise or from a CDN

## Project layout

(see project description in your IDE)

## Tests

Run tests with

```powershell
cd backend/sfs_backend
python manage.py test
```


### Information Security Project – 3rd Semester
**Under the supervision of Sir Khalid Mehmood**
Expand Down Expand Up @@ -60,5 +134,7 @@ The system utilizes the browser’s native **WebCrypto API** to perform **client

---


© 2025 **Coding Moves – Engineering Branch**
Developed for academic purposes under the **Information Security curriculum**.

11 changes: 11 additions & 0 deletions SFS/backend/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os

class Config:
SECRET_KEY = "YOUR_SECRET_KEY"
JWT_SECRET = "JWT_SECRET_KEY"

MONGO_URI = "mongodb://localhost:27017/"
DB_NAME = "SecureFileStore"

# Firebase or AWS keys
CLOUD_BUCKET = "your-bucket"
19 changes: 19 additions & 0 deletions SFS/backend/app/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Flask
from flask_cors import CORS
from .config import Config
from .routes import routes_bp
from pymongo import MongoClient

def create_app():
app = Flask(__name__)
CORS(app)

app.config.from_object(Config)

# Connect MongoDB
app.db = MongoClient(Config.MONGO_URI)[Config.DB_NAME]

# Register routes
app.register_blueprint(routes_bp)

return app
5 changes: 5 additions & 0 deletions SFS/backend/app/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def get_user_collection(app):
return app.db["users"]

def get_file_collection(app):
return app.db["files"]
46 changes: 46 additions & 0 deletions SFS/backend/app/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from flask import Blueprint, request, jsonify
from .models import get_user_collection, get_file_collection
from .utils.auth import register_user, login_user, token_required
from .utils.storage import upload_to_cloud, download_from_cloud
from .utils.encryption import encrypt_file, decrypt_file

routes_bp = Blueprint("routes", __name__)

@routes_bp.route("/register", methods=["POST"])
def register():
data = request.json
return register_user(data)

@routes_bp.route("/login", methods=["POST"])
def login():
data = request.json
return login_user(data)

@routes_bp.route("/upload", methods=["POST"])
@token_required
def upload(user):
file = request.files["file"]
encrypted_file_path, encrypted_key = encrypt_file(file)

cloud_url = upload_to_cloud(encrypted_file_path)

files = get_file_collection(routes_bp.app)
files.insert_one({
"owner": user["_id"],
"filename": file.filename,
"cloud_url": cloud_url,
"encrypted_key": encrypted_key
})

return jsonify({"msg": "File uploaded securely"}), 200

@routes_bp.route("/download/<file_id>", methods=["GET"])
@token_required
def download(user, file_id):
files = get_file_collection(routes_bp.app)
file_data = files.find_one({"_id": file_id})

cloud_file = download_from_cloud(file_data["cloud_url"])
decrypted = decrypt_file(cloud_file, file_data["encrypted_key"])

return decrypted
57 changes: 57 additions & 0 deletions SFS/backend/app/utils/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import jwt
import bcrypt
from flask import current_app, jsonify, request
from functools import wraps
from ..models import get_user_collection

def register_user(data):
users = get_user_collection(current_app)

hashed_pw = bcrypt.hashpw(data["password"].encode(), bcrypt.gensalt())

new_user = {
"username": data["username"],
"email": data["email"],
"password": hashed_pw,
}

users.insert_one(new_user)
return jsonify({"msg": "User registered"}), 201


def login_user(data):
users = get_user_collection(current_app)
user = users.find_one({"email": data["email"]})

if not user:
return jsonify({"error": "User not found"}), 404

if not bcrypt.checkpw(data["password"].encode(), user["password"]):
return jsonify({"error": "Wrong password"}), 401

token = jwt.encode(
{"email": user["email"]},
current_app.config["JWT_SECRET"],
algorithm="HS256"
)
return jsonify({"token": token})


def token_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
token = request.headers.get("Authorization")

if not token:
return jsonify({"error": "Token missing"}), 401

try:
data = jwt.decode(token, current_app.config["JWT_SECRET"], algorithms=["HS256"])
users = get_user_collection(current_app)
user = users.find_one({"email": data["email"]})
except:
return jsonify({"error": "Token invalid"}), 401

return f(user, *args, **kwargs)

return wrapper
22 changes: 22 additions & 0 deletions SFS/backend/app/utils/encrytiopn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
import base64
import os

def encrypt_file(file):
aes_key = get_random_bytes(32)

cipher_aes = AES.new(aes_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(file.read())

encrypted_file_path = "encrypted_" + file.filename

with open(encrypted_file_path, "wb") as f:
f.write(cipher_aes.nonce + tag + ciphertext)

rsa_key = RSA.generate(2048)
cipher_rsa = PKCS1_OAEP.new(rsa_key.publickey())
encrypted_key = cipher_rsa.encrypt(aes_key)

return encrypted_file_path, base64.b64encode(encrypted_key).decode()
16 changes: 16 additions & 0 deletions SFS/backend/app/utils/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import firebase_admin
from firebase_admin import credentials, storage

cred = credentials.Certificate("firebase-service.json")
firebase_admin.initialize_app(cred, {"storageBucket": "your-bucket"})

def upload_to_cloud(filepath):
bucket = storage.bucket()
blob = bucket.blob(filepath)
blob.upload_from_filename(filepath)
return blob.public_url

def download_from_cloud(url):
# simplified for demonstration
import requests
return requests.get(url).content
22 changes: 22 additions & 0 deletions SFS/backend/backend/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == '__main__':
main()
Empty file.
18 changes: 18 additions & 0 deletions SFS/backend/backend/sfs_backend/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib import admin
from .models import FileMetadata, UserKeyPair, ActivityLog

@admin.register(FileMetadata)
class FileMetadataAdmin(admin.ModelAdmin):
list_display = ('id', 'original_filename', 'owner', 'size', 'storage_backend', 'created_at')
readonly_fields = ('created_at', 'updated_at')


@admin.register(UserKeyPair)
class UserKeyPairAdmin(admin.ModelAdmin):
list_display = ('user',)


@admin.register(ActivityLog)
class ActivityLogAdmin(admin.ModelAdmin):
list_display = ('action', 'user', 'file', 'timestamp')
readonly_fields = ('timestamp',)
5 changes: 5 additions & 0 deletions SFS/backend/backend/sfs_backend/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = 'api'
50 changes: 50 additions & 0 deletions SFS/backend/backend/sfs_backend/api/cloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
from django.conf import settings

STORAGE_BASE = getattr(settings, 'STORAGE_ROOT', settings.BASE_DIR / 'storage')


def ensure_user_dir(user_id: int):
path = os.path.join(STORAGE_BASE, str(user_id))
os.makedirs(path, exist_ok=True)
return path


def upload_to_local(owner_id: int, stored_filename: str, data: bytes) -> str:
user_dir = ensure_user_dir(owner_id)
path = os.path.join(user_dir, stored_filename)
with open(path, 'wb') as f:
f.write(data)
return path


def download_from_local(owner_id: int, stored_filename: str) -> bytes:
path = os.path.join(STORAGE_BASE, str(owner_id), stored_filename)
with open(path, 'rb') as f:
return f.read()


def delete_from_local(owner_id: int, stored_filename: str) -> None:
path = os.path.join(STORAGE_BASE, str(owner_id), stored_filename)
try:
os.remove(path)
except FileNotFoundError:
pass


# NOTE: firebase and s3 implementations are placeholders that show how to extend

def upload_to_firebase(owner_id: int, stored_filename: str, data: bytes) -> str:
raise NotImplementedError('Firebase upload not implemented in this example')


def download_from_firebase(owner_id: int, stored_filename: str) -> bytes:
raise NotImplementedError('Firebase download not implemented in this example')


def upload_to_s3(owner_id: int, stored_filename: str, data: bytes) -> str:
raise NotImplementedError('S3 upload not implemented in this example')


def download_from_s3(owner_id: int, stored_filename: str) -> bytes:
raise NotImplementedError('S3 download not implemented in this example')
Loading
Loading