diff --git a/NetworkScanner/NetworkScanner.py b/NetworkScanner/NetworkScanner.py new file mode 100644 index 0000000000000000000000000000000000000000..ff268d4eb1f52597dc82daa61821808f2c4feee9 --- /dev/null +++ b/NetworkScanner/NetworkScanner.py @@ -0,0 +1,183 @@ +import requests +import netifaces +import ipaddress +import threading +import itertools +import time +import sys +import nmap + + +def list_network_interfaces(): + """ + List all network interfaces and their associated IP addresses. + :return: Dictionary of interfaces with their IP addresses and subnet masks. + """ + interfaces = {} + for interface in netifaces.interfaces(): + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET in addrs: + ipv4_info = addrs[netifaces.AF_INET][0] + ip = ipv4_info.get('addr') + netmask = ipv4_info.get('netmask') + if ip and netmask: + network = ipaddress.ip_network(f"{ip}/{netmask}", strict=False) + interfaces[interface] = f"{network}" + return interfaces + +def get_user_selected_interface(interfaces): + """ + Prompt user to select an interface from a list. + :param interfaces: Dictionary of interfaces and their IP ranges. + :return: CIDR notation of the selected network range. + """ + print("Available network interfaces and their IP ranges:") + for idx, (interface, ip_range) in enumerate(interfaces.items(), start=1): + print(f"{idx}. Interface: {interface} - IP Range: {ip_range}") + + while True: + choice = input("Enter the number of the interface you want to use: ") + if choice.isdigit() and 1 <= int(choice) <= len(interfaces): + selected_interface = list(interfaces.keys())[int(choice) - 1] + return interfaces[selected_interface] + else: + print("Invalid selection, please try again.") + + +done = False + +def spinner(): + spinner_chars = itertools.cycle(['-', '/', '|', '\\']) + while not done: + sys.stdout.write(next(spinner_chars) + '\r') # write the next character and return to start of line + sys.stdout.flush() # flush stdout buffer + time.sleep(0.1) + sys.stdout.write('Done scanning!\n') + +def perform_scan(target, options="-sV"): + global done + thread = threading.Thread(target=spinner) + thread.start() + + scanner = nmap.PortScanner() + try: + scanner.scan(hosts=target, arguments=options) + print(f"Scan on {target} completed.") + except Exception as e: + print(f"Scanning failed: {e}") + finally: + done = True + thread.join() # Wait for the spinner to finish + + hosts_list = list(scanner.all_hosts()) + scan_results = [] + for host in hosts_list: + if scanner[host].state() == "up": + host_data = { + "ip_address": host, + "hostname": scanner[host].hostname() or "None", + "mac_address": scanner[host]['addresses'].get('mac') or "None", + "services": [] + } + for proto in scanner[host].all_protocols(): + for port in scanner[host][proto].keys(): + service = scanner[host][proto][port] + service_data = { + "port": port, + "name": service.get('name') or "None", + "state": service.get('state') or "None", + "product": service.get('product') or "None", + "version": service.get('version') or "None", + "extra_info": service.get('extrainfo') or "None" + } + host_data["services"].append(service_data) + scan_results.append(host_data) + return scan_results + + + +def send_data_to_backend(api_url, login_url, data, username, password): + """ + Send scan data to the Django backend API. + :param api_url: URL of the Django API endpoint. + :param data: Data to be sent as JSON. + :param username: Username for authentication. + :param password: Password for authentication. + """ + token = authenticate(login_url, username, password) + if not token: + print("Failed to authenticate.") + return + + headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'} + response = requests.post(api_url, json={"devices": data}, headers=headers) + + if response.status_code == 200: + print("Data sent successfully.") + elif response.status_code == 401: + print("Authentication failed or token expired. Re-authenticating...") + token = authenticate(username, password) + if not token: + print("Failed to re-authenticate.") + return + headers['Authorization'] = f'Bearer {token}' + response = requests.post(api_url, json={"devices": data}, headers=headers) + if response.status_code == 200: + print("Data sent successfully after re-authentication.") + else: + print(f"Failed to send data after re-authentication: {response.status_code} - {response.text}") + else: + print(f"Failed to send data: {response.status_code} - {response.text}") + + + +def authenticate(login_url, username, password): + """ + Authenticate with the Django backend and retrieve a token. + :param login_url: URL of the Django API login endpoint. + :param username: Username for login. + :param password: Password for login. + :return: Token or None if authentication fails. + """ + try: + response = requests.post(login_url, json={"username": username, "password": password}) + if response.status_code == 200: + return response.json().get("access") # Match the key to your JavaScript handling + else: + print(f"Authentication failed: {response.status_code} - {response.text}") + except requests.exceptions.RequestException as e: + print(f"Failed to connect to {login_url}: {e}") + return None + +def main(): + base_url = "http://localhost:8000/api/auth/" + login_url = f"{base_url}login/" + api_url = "http://localhost:8000/api/networkscanner/results/" + + print("Please log in to the system.") + username = input("Username: ") + password = input("Password: ") + + token = authenticate(login_url, username, password) + if token: + interfaces = list_network_interfaces() + if interfaces: + selected_network_range = get_user_selected_interface(interfaces) + print(f"Selected network range: {selected_network_range}") + + print("Starting the network scan...") + scan_results = perform_scan(selected_network_range, "-sV") + if scan_results: + print("Scan complete, sending data to backend...") + send_data_to_backend(api_url, login_url, scan_results, username, password) # Pass username and password + else: + print("No devices found during scan.") + else: + print("No valid network interfaces found. Please check your network connection.") + else: + print("Exiting due to failed authentication.") + + +if __name__ == "__main__": + main() + diff --git a/backend/auth_app/views.py b/backend/auth_app/views.py index 164510a57ad78ba3296ccf71aa818667918ee08c..018ab74bd00ed6c7eb33dded3d2eff5deb792763 100644 --- a/backend/auth_app/views.py +++ b/backend/auth_app/views.py @@ -6,11 +6,14 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.permissions import AllowAny + # This view extends TokenObtainPairView to handle token creation on user login. class MyTokenObtainPairView(TokenObtainPairView): permission_classes = (permissions.AllowAny,) # Allow any user to access this view # You can customize this view further if needed, e.g., adding extra data to the token response. + + class RegisterView(APIView): permission_classes = (AllowAny,) @@ -26,4 +29,4 @@ class RegisterView(APIView): return Response({'error': 'Username is already taken.'}, status=status.HTTP_400_BAD_REQUEST) user = User.objects.create_user(username, email, password) - return Response({'msg': 'User created successfully.'}, status=status.HTTP_201_CREATED) \ No newline at end of file + return Response({'msg': 'User created successfully.'}, status=status.HTTP_201_CREATED) diff --git a/backend/db.sqlite3 b/backend/db.sqlite3 index 2594849a915f05ef5472c2475ef5937c13cae8d9..015d80b1c48a431ed430ad9bf3268d455e0d26d5 100644 Binary files a/backend/db.sqlite3 and b/backend/db.sqlite3 differ diff --git a/backend/network_scanner/__init__.py b/backend/network_scanner/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/backend/network_scanner/admin.py b/backend/network_scanner/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..beebb662b0f29e57be9cc0ca5b138af45c9fc590 --- /dev/null +++ b/backend/network_scanner/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from .models import ScanResult, Service, Recommendation + +# Register your models here. +admin.site.register(ScanResult) +admin.site.register(Service) +admin.site.register(Recommendation) diff --git a/backend/network_scanner/apps.py b/backend/network_scanner/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..5a83774c2237691d3bac1fcbedaa5cc3049f3be0 --- /dev/null +++ b/backend/network_scanner/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NetworkScannerConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "network_scanner" diff --git a/backend/network_scanner/models.py b/backend/network_scanner/models.py new file mode 100644 index 0000000000000000000000000000000000000000..345282abf802c202add64981307306850cc63af3 --- /dev/null +++ b/backend/network_scanner/models.py @@ -0,0 +1,33 @@ +# backend/networkscanner/models.py + +from django.db import models + + +class ScanResult(models.Model): + ip_address = models.CharField(max_length=100) + hostname = models.CharField(max_length=255) + mac_address = models.CharField(max_length=100) + + def __str__(self): + return f"{self.ip_address} - {self.hostname}" + + +class Service(models.Model): + scan_result = models.ForeignKey(ScanResult, related_name='services', on_delete=models.CASCADE) + port = models.IntegerField() + name = models.CharField(max_length=100) + state = models.CharField(max_length=50) + product = models.CharField(max_length=100, blank=True) + version = models.CharField(max_length=50, blank=True) + extra_info = models.CharField(max_length=200, blank=True) + + def __str__(self): + return f"Port {self.port} - {self.name}" + + +class Recommendation(models.Model): + scan_result = models.ForeignKey(ScanResult, related_name='recommendations', on_delete=models.CASCADE) + recommendation_text = models.TextField() + + def __str__(self): + return f"Recommendation for {self.scan_result.ip_address}" diff --git a/backend/network_scanner/serializers.py b/backend/network_scanner/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..de9c96f7df79e415b13e4566cf895e859d2c7985 --- /dev/null +++ b/backend/network_scanner/serializers.py @@ -0,0 +1,35 @@ +# backend/networkscanner/serializers.py + +from rest_framework import serializers +from .models import ScanResult, Service, Recommendation + + +class ServiceSerializer(serializers.ModelSerializer): + class Meta: + model = Service + fields = ['port', 'name', 'state', 'product', 'version', 'extra_info'] + + +class ScanResultSerializer(serializers.ModelSerializer): + services = ServiceSerializer(many=True) + + class Meta: + model = ScanResult + fields = ['ip_address', 'hostname', 'mac_address', 'services'] + + def create(self, validated_data): + print("Creating ScanResult with data:", validated_data) + services_data = validated_data.pop('services', []) + scan_result = ScanResult.objects.create(**validated_data) + print("Created ScanResult, now creating services...") + for service_data in services_data: + print("Creating service with data:", service_data) + Service.objects.create(scan_result=scan_result, **service_data) + print("All services created.") + return scan_result + + +class RecommendationSerializer(serializers.ModelSerializer): + class Meta: + model = Recommendation + fields = '__all__' \ No newline at end of file diff --git a/backend/network_scanner/tests.py b/backend/network_scanner/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/backend/network_scanner/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/network_scanner/urls.py b/backend/network_scanner/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..7cbac3e1b148dcd45a820f5c5178c9bed93f7442 --- /dev/null +++ b/backend/network_scanner/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from .views import recommendations_view, ResultsView #fetch_recommendations + +urlpatterns = [ + path('results/', ResultsView.as_view(), name='results'), + path('recommendations/', recommendations_view, name='recommendations_view'), + #path('fetch-recommendations/', fetch_recommendations, name='fetch_recommendations'), + # Add other URL patterns as needed +] diff --git a/backend/network_scanner/utils.py b/backend/network_scanner/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..90aee23a5774fa525f2944514c01e9608b23d9f7 --- /dev/null +++ b/backend/network_scanner/utils.py @@ -0,0 +1,38 @@ +import openai + + + +def prepare_scan_data_for_openai(scan_result): + """ + Format the scan result data into a structured summary that the AI can understand. + """ + services_info = [] + for service in scan_result.services.all(): + service_info = f"Service: {service.name}, Port: {service.port}, Product: {service.product}, Version: {service.version}" + services_info.append(service_info) + + scan_data = f"Analyze the following network scan and provide network recommendations for IP {scan_result.ip_address}: " + ", ".join(services_info) + return scan_data + + +def get_recommendations_from_openai(scan_result): + """ + Uses OpenAI's chat completions to generate security recommendations based on scan results. + """ + openai.api_key = 'sk-proj-mWLHYmnxOBQUZoTRzVbHT3BlbkFJYZn5zyGI6ZCvTzHUwsAh' # Ensure this is securely managed + + prompt = prepare_scan_data_for_openai(scan_result) + + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", # Specify the appropriate chat model + messages=[ + {"role": "system", "content": "You are an AI trained to provide network security advice."}, + {"role": "user", "content": prompt} + ] + ) + return response['choices'][0]['message']['content'].strip() + except Exception as e: + print(f"Failed to generate recommendations: {str(e)}") + return "Failed to generate recommendations." + diff --git a/backend/network_scanner/views.py b/backend/network_scanner/views.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d24779617be78c1b080bd40b68f7c18b708862 --- /dev/null +++ b/backend/network_scanner/views.py @@ -0,0 +1,53 @@ +from rest_framework.views import APIView +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status +from .models import ScanResult, Recommendation +from .serializers import ScanResultSerializer +from .utils import get_recommendations_from_openai + + +class ResultsView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + serializer = ScanResultSerializer(data=request.data.get('devices', []), many=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def recommendations_view(request): + all_scans = ScanResult.objects.all() + if not all_scans: + return Response({'error': 'No scan results available'}, status=status.HTTP_404_NOT_FOUND) + + all_recommendations = [] + for scan in all_scans: + recommendations = Recommendation.objects.filter(scan_result=scan) + if recommendations.exists(): + all_recommendations.extend([{ + 'id': rec.id, + 'ip_address': rec.scan_result.ip_address, + 'recommendation_text': rec.recommendation_text + } for rec in recommendations]) + else: + # Generate recommendations if none exist + recommendation_text = get_recommendations_from_openai(scan) + if recommendation_text and not recommendation_text.startswith("Failed"): + new_rec = Recommendation.objects.create(scan_result=scan, recommendation_text=recommendation_text) + all_recommendations.append({ + 'id': new_rec.id, + 'ip_address': scan.ip_address, + 'recommendation_text': recommendation_text + }) + + if all_recommendations: + return Response(all_recommendations) + else: + return Response({'error': 'Failed to generate recommendations for any scans'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/backend/watchstone_backend/settings.py b/backend/watchstone_backend/settings.py index 4e8508250ad5ee6be4773cd08707ed7824d9575d..05cc913ddedef184eb7b4ed6a23dbdc8a7a8677f 100644 --- a/backend/watchstone_backend/settings.py +++ b/backend/watchstone_backend/settings.py @@ -35,6 +35,7 @@ INSTALLED_APPS = [ "rest_framework_simplejwt", "breach_check", "shodan_scan", + "network_scanner" ] diff --git a/backend/watchstone_backend/urls.py b/backend/watchstone_backend/urls.py index eb7d3fbc58be810a1f077eb2d4ad433fba594a20..168d007780dffa43b2c5263057f0b9b8ceb31f8a 100644 --- a/backend/watchstone_backend/urls.py +++ b/backend/watchstone_backend/urls.py @@ -12,5 +12,6 @@ urlpatterns = [ path('haveibeenpwned/<str:identifier>/', have_i_been_pwned, name='have_i_been_pwned'), path('haveibeenpwned/passwords/<str:identifier>/', breached_password_info, name='breached_password_info'), path('', include('shodan_scan.urls')), + path('api/networkscanner/', include('network_scanner.urls')), # Add other URL patterns as needed ] diff --git a/frontend/src/_nav.js b/frontend/src/_nav.js index f059e987a0e9ecf419a6d6885d3c673784449a9e..0041f891d245653404d2548c0cf14ed07eee93ce 100644 --- a/frontend/src/_nav.js +++ b/frontend/src/_nav.js @@ -69,6 +69,12 @@ const _nav = [ to: '/scan', icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, }, + { + component: CNavItem, + name: 'Network Recommendations', + to: '/recommendations', + icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, + }, { component: CNavTitle, name: 'Theme', diff --git a/frontend/src/components/AppFooter.js b/frontend/src/components/AppFooter.js index c853f5b87e51153e32dcb5d8e7445d5ea51422fd..9c4c680abb4ddfc91a485d84aebe6e0f55a000de 100644 --- a/frontend/src/components/AppFooter.js +++ b/frontend/src/components/AppFooter.js @@ -5,16 +5,7 @@ const AppFooter = () => { return ( <CFooter> <div> - <a href="https://coreui.io" target="_blank" rel="noopener noreferrer"> - CoreUI - </a> - <span className="ms-1">© 2023 creativeLabs.</span> - </div> - <div className="ms-auto"> - <span className="me-1">Powered by</span> - <a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer"> - CoreUI React Admin & Dashboard Template - </a> + <span className="ms-1">© 2024 Data Guardian.</span> </div> </CFooter> ) diff --git a/frontend/src/index.js b/frontend/src/index.js index d19a3bcd3e9ed9f42132ba6de1e49b616ada1ab7..9e822ebb1693ec5f326fcd29ed2e0567eca87fbb 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -6,6 +6,20 @@ import App from './App' import reportWebVitals from './reportWebVitals' import { Provider } from 'react-redux' import store from './store' +import axios from 'axios' + +// Setting up Axios Interceptors +axios.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + console.error('Unauthorized access detected.') + // Redirect to the login page + //window.location.href = '/login' FIX THIS LATER, not the answer to protected routes. + } + return Promise.reject(error) + }, +) createRoot(document.getElementById('root')).render( <Provider store={store}> diff --git a/frontend/src/routes.js b/frontend/src/routes.js index ab66997bb48d1093ad90b85ea8bffc4b50c97afe..583ee2a34741be7064f9c69f2c7c86280023f8ec 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -56,6 +56,9 @@ const PasswordBreaches = React.lazy(() => import('./views/passwords/Breaches')) const TrackedPasswords = React.lazy(() => import('./views/passwords/BreachesPasswords')) const Profile = React.lazy(() => import('./views/pages/profile/Profile')) const shodan_scan = React.lazy(() => import('./views/shodan/shodanscan')) +const RecommendationsPage = React.lazy(() => + import('./views/NetworkScanner/RecommendationComponent'), +) const routes = [ { path: '/', exact: true, name: 'Home' }, @@ -108,6 +111,7 @@ const routes = [ { path: '/password-breaches/view', name: 'View Breached Passwords', element: TrackedPasswords }, { path: '/profile', name: 'Profile', element: Profile }, { path: '/scan', name: 'Shodan Scan', element: shodan_scan }, + { path: '/recommendations', name: 'Recommendations', element: RecommendationsPage }, ] export default routes diff --git a/frontend/src/views/NetworkScanner/RecommendationComponent.js b/frontend/src/views/NetworkScanner/RecommendationComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..4a11241920efd9dc8f9eaf0276d54e5ecf4f5eab --- /dev/null +++ b/frontend/src/views/NetworkScanner/RecommendationComponent.js @@ -0,0 +1,65 @@ +import React, { useState, useEffect } from 'react' +import axios from 'axios' + +const RecommendationsPage = () => { + const [recommendations, setRecommendations] = useState([]) + + useEffect(() => { + const fetchRecommendations = async () => { + const token = localStorage.getItem('token') + console.log('Token:', token) + if (!token) { + console.error('No token available.') + return + } + + try { + const response = await axios.get( + 'http://localhost:8000/api/networkscanner/recommendations/', + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ) + setRecommendations(response.data) + } catch (error) { + console.error('Error fetching recommendations:', error) + // Handle different errors appropriately here without redirecting + if (error.response) { + console.error('Error status:', error.response.status) // Log specific error status + } + } + } + + fetchRecommendations() + }, []) + + return ( + <div> + <h2>Network Security Recommendations</h2> + <table className="table table-striped table-responsive mt-3"> + <thead> + <tr> + <th>IP Address</th> + <th>Port</th> + <th>Service Name</th> + <th>Recommendation</th> + </tr> + </thead> + <tbody> + {recommendations.map((rec) => ( + <tr key={rec.id}> + <td>{rec.ip_address}</td> + <td>{rec.port}</td> + <td>{rec.service_name}</td> + <td>{rec.recommendation_text}</td> + </tr> + ))} + </tbody> + </table> + </div> + ) +} + +export default RecommendationsPage diff --git a/frontend/src/views/emails/BreachesEmails.js b/frontend/src/views/emails/BreachesEmails.js index 738a7f8778bf4d8de4c54c2ec50674d04f6faaab..11eff4d26009931c206db2d22b8606774b0d8281 100644 --- a/frontend/src/views/emails/BreachesEmails.js +++ b/frontend/src/views/emails/BreachesEmails.js @@ -1,9 +1,10 @@ // BreachesPasswords.js import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' const BreachesEmails = () => { const [breachResults, setBreachResults] = useState([]) - + const navigate = useNavigate() useEffect(() => { fetchBreachResultsData() }, []) @@ -19,6 +20,7 @@ const BreachesEmails = () => { if (response.status === 401) { console.error('Unauthorized access to the API') + navigate('/login') return } diff --git a/frontend/src/views/pages/login/Login.js b/frontend/src/views/pages/login/Login.js index ad2d38464af9839a85e9baf0c2e9e71d75a470d9..8d08d612263c87ee241beea5430ea639acd79f6c 100644 --- a/frontend/src/views/pages/login/Login.js +++ b/frontend/src/views/pages/login/Login.js @@ -1,6 +1,5 @@ import React, { useState } from 'react' -import { Link } from 'react-router-dom' -import { useNavigate } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import axios from 'axios' import { CButton, @@ -21,6 +20,7 @@ import { logo } from '../../../assets/brand/logo' const Login = () => { const [loginData, setLoginData] = useState({ username: '', password: '' }) + const [error, setError] = useState('') // State to hold login error const navigate = useNavigate() const handleChange = (e) => { @@ -29,15 +29,18 @@ const Login = () => { const handleSubmit = async (e) => { e.preventDefault() + setError('') // Clear previous errors try { const response = await axios.post('http://localhost:8000/api/auth/login/', loginData) localStorage.setItem('token', response.data.access) // Save token console.log('Login successful:', response.data) - // Handle successful login (redirect to dashboard, etc.) navigate('/dashboard') } catch (error) { - console.error('Login error:', error.response) - // Handle login errors (show error message, etc.) + if (error.response && error.response.status === 401) { + setError('Invalid username or password.') + } else { + setError('Something went wrong. Please try again.') + } } } @@ -53,6 +56,7 @@ const Login = () => { <CIcon className="sidebar-brand-full" icon={logo} height={35} /> <h1>Welcome back!</h1> <p className="text-medium-emphasis">Sign In to your account</p> + {error && <p className="text-danger">{error}</p>} {/* Display login error */} <CInputGroup className="mb-3"> <CInputGroupText> <CIcon icon={cilUser} /> diff --git a/frontend/src/views/pages/register/Register.js b/frontend/src/views/pages/register/Register.js index 26f07f6af432f0fb8931fd080e7396f4580689fa..8a6fe6105987bd11814d0f0e880f56cc9e750348 100644 --- a/frontend/src/views/pages/register/Register.js +++ b/frontend/src/views/pages/register/Register.js @@ -23,6 +23,7 @@ const Register = () => { password: '', confirmPassword: '', }) + const [error, setError] = useState('') const navigate = useNavigate() const handleChange = (e) => { @@ -31,8 +32,15 @@ const Register = () => { const handleSubmit = async (e) => { e.preventDefault() + setError('') // Clear previous errors + if (!isPasswordComplex(userData.password)) { + setError( + 'Password must be at least 8 characters long, include uppercase and lowercase letters, a number, and a special character.', + ) + return + } if (userData.password !== userData.confirmPassword) { - alert('Passwords do not match') + setError('Passwords do not match.') return } try { @@ -42,14 +50,18 @@ const Register = () => { password: userData.password, }) console.log(response.data) - // Handle success - redirect or clear form navigate('/login') } catch (error) { console.error('Registration error:', error.response) - // Handle errors - show error message + setError('Failed to register. Please try again.') } } + function isPasswordComplex(password) { + const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/ + return regex.test(password) + } + return ( <div className="bg-light min-vh-100 d-flex flex-row align-items-center"> <CContainer> @@ -60,6 +72,7 @@ const Register = () => { <CForm onSubmit={handleSubmit}> <h1>Register</h1> <p className="text-medium-emphasis">Create your account</p> + {error && <p className="text-danger">{error}</p>} {/* Display error message */} <CInputGroup className="mb-3"> <CInputGroupText> <CIcon icon={cilUser} /> diff --git a/frontend/src/views/passwords/BreachesPasswords.js b/frontend/src/views/passwords/BreachesPasswords.js index ce07cf20a5fa8934ed7a4f23e942df61d2bb9292..a74ee3c063f117d937c1e728769ddc2faac6e60a 100644 --- a/frontend/src/views/passwords/BreachesPasswords.js +++ b/frontend/src/views/passwords/BreachesPasswords.js @@ -1,9 +1,10 @@ // BreachesPasswords.js import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' const BreachesPasswords = () => { const [breachResults, setBreachResults] = useState([]) - + const navigate = useNavigate() useEffect(() => { fetchBreachResultsData() }, []) @@ -11,6 +12,7 @@ const BreachesPasswords = () => { const fetchBreachResultsData = async () => { try { const token = localStorage.getItem('token') + const response = await fetch('http://localhost:8000/api/breaches/passwords/details/', { headers: { Authorization: `Bearer ${token}`, @@ -19,6 +21,7 @@ const BreachesPasswords = () => { if (response.status === 401) { console.error('Unauthorized access to the API') + navigate('/login') return } diff --git a/frontend/src/views/widgets/WidgetsDropdown.js b/frontend/src/views/widgets/WidgetsDropdown.js index 66e7a5cc026ecb5c19a73a5a941a54c82d621d56..f79b8c66f6523f8696704ccfd74673dabb05e205 100644 --- a/frontend/src/views/widgets/WidgetsDropdown.js +++ b/frontend/src/views/widgets/WidgetsDropdown.js @@ -13,10 +13,10 @@ import CIcon from '@coreui/icons-react' import { cilArrowBottom, cilArrowTop, cilOptions } from '@coreui/icons' import { countUniqueEmails } from '../emails/BreachesEmails' import React, { useState, useEffect } from 'react' - +import { useNavigate } from 'react-router-dom' const WidgetsDropdown = () => { const [breachResults, setBreachResults] = useState([]) - + const navigate = useNavigate() useEffect(() => { // Fetch or set your breachResults data // Example: Fetching data using an async function @@ -31,6 +31,7 @@ const WidgetsDropdown = () => { if (response.status === 401) { console.error('Unauthorized access to the API') + navigate('/login') return }