diff --git a/backend/breaches/__init__.py b/backend/breaches/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/backend/breaches/admin.py b/backend/breaches/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..b85e5101203b0033afd9aa5227e3aab96af28aa0 --- /dev/null +++ b/backend/breaches/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import MonitoredEmail + +@admin.register(MonitoredEmail) +class MonitoredEmailAdmin(admin.ModelAdmin): + list_display = ('email', 'added_on') diff --git a/backend/breaches/apps.py b/backend/breaches/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..d6eb14128c137355e03d7e80c7095aa8b2b1dd57 --- /dev/null +++ b/backend/breaches/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BreachesConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "breaches" diff --git a/backend/breaches/migrations/0001_initial.py b/backend/breaches/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..dd19cac13400773f3292bcc33f18b8a78f9ab634 --- /dev/null +++ b/backend/breaches/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.9 on 2024-02-16 23:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Account", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("email", models.EmailField(max_length=254, unique=True)), + ], + ), + ] diff --git a/backend/breaches/migrations/__init__.py b/backend/breaches/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/backend/breaches/models.py b/backend/breaches/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e9c6d574be1a293003191c63c5a6bdcede784f5a --- /dev/null +++ b/backend/breaches/models.py @@ -0,0 +1,8 @@ +from django.db import models + +class MonitoredEmail(models.Model): + email = models.EmailField(unique=True) + added_on = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.email diff --git a/backend/breaches/tests.py b/backend/breaches/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/backend/breaches/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/breaches/urls.py b/backend/breaches/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..462f72fdfb0f78cdb2cd042443cd08ef52b63576 --- /dev/null +++ b/backend/breaches/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('upload/', views.upload_breaches, name='upload_breaches'), +] diff --git a/backend/breaches/views.py b/backend/breaches/views.py new file mode 100644 index 0000000000000000000000000000000000000000..14bdf432498de4725e521aa7dbb9b2777cf0eddd --- /dev/null +++ b/backend/breaches/views.py @@ -0,0 +1,80 @@ +from django.http import JsonResponse +from .models import MonitoredEmail +from django.views.decorators.http import require_http_methods +import csv +from io import StringIO +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from django.http import JsonResponse +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import csrf_exempt +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +import csv +from io import StringIO +from .models import MonitoredEmail +from rest_framework.parsers import JSONParser + +@require_http_methods(["POST"]) +@permission_classes([IsAuthenticated]) +@csrf_exempt +def upload_breaches(request): + # Initialize a list to hold emails + emails = [] + + # Extracting emails sent as 'email' + email = request.POST.get('email') + if email: + emails.append(email) + + # Extracting emails sent as 'emails[index]' + for key in request.POST.keys(): + if key.startswith('emails['): + emails.append(request.POST[key]) + + # Handle file if it's uploaded + file = request.FILES.get('file') + if file: + file_data = file.read().decode('utf-8') + csv_data = csv.reader(StringIO(file_data)) + next(csv_data, None) # Skip the header row + for row in csv_data: + if row: # Ensure row is not empty + emails.append(row[0]) + + # Process each email + for email in emails: + MonitoredEmail.objects.get_or_create(email=email) + + if emails: + return JsonResponse({'message': f'{len(emails)} emails added for monitoring.'}, status=201) + else: + return JsonResponse({'error': 'No emails provided.'}, status=400) + + +@require_http_methods(["POST"]) +def check_email_breaches(request): + email = request.POST.get('email') + file = request.FILES.get('file') + results = {} + + if email: + # Check the single email against breaches + results[email] = check_breach(email) + + if file: + # Check each email in the CSV against breaches + file_data = file.read().decode('utf-8') + csv_data = csv.reader(StringIO(file_data)) + for row in csv_data: + email = row[0] + results[email] = check_breach(email) + + return JsonResponse({'results': results}, status=200) + + +def check_breach(email): + # Placeholder for breach checking logic + # This could involve querying your own database or calling an external API + # For demonstration, it returns a dummy response + return "Not breached" # or "Breached" based on actual check diff --git a/backend/db.sqlite3 b/backend/db.sqlite3 index 41edd444f67de71615187c74d2c0db5a4f1d0dac..39a81d346f3fd73efa2982e4f5fbf9b9663658d6 100644 Binary files a/backend/db.sqlite3 and b/backend/db.sqlite3 differ diff --git a/backend/watchstone_backend/settings.py b/backend/watchstone_backend/settings.py index c48b24f2381b0c4f73ac4bf30f49f2d3b5cc9364..ba29334f2511299cdd1a12bd1de1bf29ec5031b2 100644 --- a/backend/watchstone_backend/settings.py +++ b/backend/watchstone_backend/settings.py @@ -41,6 +41,7 @@ INSTALLED_APPS = [ "rest_framework", "rest_framework_simplejwt", "corsheaders", + "breaches", ] MIDDLEWARE = [ diff --git a/backend/watchstone_backend/urls.py b/backend/watchstone_backend/urls.py index 1cc09dbf9da8a55e9cfd885ab7b7d62ae875a005..d98ae5086d229e7bb7cdcad5f0c43980e8792378 100644 --- a/backend/watchstone_backend/urls.py +++ b/backend/watchstone_backend/urls.py @@ -1,26 +1,9 @@ -""" -URL configuration for watchstone_backend project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -# watchstone_backend/urls.py - from django.contrib import admin from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), - path('api/', include('core.urls')), # This line includes your app URLs + path('api/', include('core.urls')), # Core app URLs path('api/auth/', include('auth_app.urls')), + path('api/breaches/', include('breaches.urls')), # Include breaches app URLs ] diff --git a/frontend/src/views/modules/Breaches.js b/frontend/src/views/modules/Breaches.js index 46daf8c99513b07c1b97469f7905f979ac735bde..68af94267fd5cd96c7eea3deda3e1f4c46c7c151 100644 --- a/frontend/src/views/modules/Breaches.js +++ b/frontend/src/views/modules/Breaches.js @@ -12,42 +12,94 @@ import { CRow, } from '@coreui/react' import CIcon from '@coreui/icons-react' -import { cilTrash, cilPlus } from '@coreui/icons' +import { cilTrash, cilPlus, cilCloudDownload } from '@coreui/icons' +import axios from 'axios' const Breaches = () => { const [email, setEmail] = useState('') const [file, setFile] = useState(null) const [accounts, setAccounts] = useState([{ email: '' }]) + // Function to handle email change const handleEmailChange = (e) => setEmail(e.target.value) - const handleFileChange = (e) => { - setFile(e.target.files[0]) - } + // Function to handle file change + const handleFileChange = (e) => setFile(e.target.files[0]) - const handleAccountChange = (index, event) => { + // Function to handle account email changes + const handleAccountChange = (index, e) => { const newAccounts = [...accounts] - newAccounts[index][event.target.name] = event.target.value + newAccounts[index].email = e.target.value setAccounts(newAccounts) } + // Function to add a new account input field const handleAddAccount = () => { setAccounts([...accounts, { email: '' }]) } + // Function to remove an account input field const handleRemoveAccount = (index) => { const newAccounts = [...accounts] newAccounts.splice(index, 1) setAccounts(newAccounts) } - const handleSubmit = (e) => { + // Function to handle the download of a CSV template + const handleDownloadTemplate = () => { + const templateData = 'Email\nexample@example.com' + const blob = new Blob([templateData], { type: 'text/csv;charset=utf-8;' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = 'email_template.csv' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + + // Function to handle form submission for adding to database + const handleAddToDatabase = async (e) => { + e.preventDefault() + const formData = new FormData() + + // Add single email to formData if provided + if (email) formData.append('email', email) + + // Add file to formData if provided + if (file) formData.append('file', file) + + // Add manually added accounts to formData + accounts.forEach((account, index) => { + if (account.email) formData.append(`emails[${index}]`, account.email) + }) + + // Retrieve the token from localStorage + const token = localStorage.getItem('token') + + try { + await axios.post('http://localhost:8000/api/breaches/upload/', formData, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'multipart/form-data', + }, + }) + alert('Data submitted successfully') + + // Reset form fields + setEmail('') + setFile(null) + setAccounts([{ email: '' }]) + } catch (error) { + console.error('Error uploading data:', error) + alert('Error submitting data') + } + } + + // Function for quick check (stubbed, not implemented) + const handleSubmitCheck = async (e) => { e.preventDefault() - // Integration with backend to handle the form data goes here - console.log('Email:', email, 'File:', file, 'Accounts:', accounts) - setEmail('') - setFile(null) - setAccounts([{ email: '' }]) + // Implement the quick check functionality here + alert('Quick check functionality not implemented yet') } return ( @@ -56,7 +108,9 @@ const Breaches = () => { <CCard> <CCardHeader>Check for Breaches</CCardHeader> <CCardBody> - <CForm onSubmit={handleSubmit}> + <CForm> + {/* Quick Check Section */} + <CCardHeader>Quick Check</CCardHeader> <CInputGroup className="mb-3"> <CInputGroupText>@</CInputGroupText> <CFormInput @@ -64,15 +118,11 @@ const Breaches = () => { type="email" value={email} onChange={handleEmailChange} - required /> </CInputGroup> - <CInputGroup className="mb-3"> - <CFormInput type="file" id="fileInput" onChange={handleFileChange} accept=".csv" /> - </CInputGroup> - - <CCardHeader>Add Accounts Manually</CCardHeader> + {/* Add Monitored Emails Section */} + <CCardHeader>Add Monitored Emails</CCardHeader> {accounts.map((account, index) => ( <CInputGroup key={index} className="mb-3"> <CFormInput @@ -80,8 +130,7 @@ const Breaches = () => { placeholder="Account Email" name="email" value={account.email} - onChange={(event) => handleAccountChange(index, event)} - required + onChange={(e) => handleAccountChange(index, e)} /> <CButton type="button" @@ -96,9 +145,21 @@ const Breaches = () => { <CButton color="primary" className="mb-3" onClick={handleAddAccount}> <CIcon icon={cilPlus} /> Add Another Account </CButton> - <div className="d-grid"> - <CButton type="submit" color="success"> - Submit + + {/* File Upload Section */} + <CInputGroup className="mb-3"> + <CFormInput type="file" id="fileInput" onChange={handleFileChange} accept=".csv" /> + </CInputGroup> + <CButton color="secondary" className="mb-3" onClick={handleDownloadTemplate}> + <CIcon icon={cilCloudDownload} /> Download CSV Template + </CButton> + + <div className="d-grid gap-2"> + <CButton color="success" onClick={handleSubmitCheck}> + Check + </CButton> + <CButton color="info" onClick={handleAddToDatabase}> + Add to Database </CButton> </div> </CForm>