diff --git a/backend/db.sqlite3 b/backend/db.sqlite3 index 015d80b1c48a431ed430ad9bf3268d455e0d26d5..7187ecd40fc68a49fc449761df86f794d74d3f2f 100644 Binary files a/backend/db.sqlite3 and b/backend/db.sqlite3 differ diff --git a/backend/requirements.txt b/backend/requirements.txt index d15ecff9f18c0db817c7addbcf9f8c7b86cecda4..e5169064dc5578fcb525f4686884ca573d7e6db8 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,4 +10,8 @@ typing_extensions==4.9.0 tzdata==2023.4 requests==2.26.0 django-truncate -shodan \ No newline at end of file +shodan +openai==1.25.0 +requests +netifaces +python-nmap \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 206a51123802d4fd293464efc1c0ec5ab0036f15..c5cf86f300764a212ca72fe4a7821573905701dd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,7 +41,8 @@ "react-redux": "^8.1.1", "react-router-dom": "^6.22.2", "redux": "4.2.1", - "simplebar-react": "^2.4.3" + "simplebar-react": "^2.4.3", + "zxcvbn": "^4.4.2" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", diff --git a/frontend/src/components/PasswordStrength.css b/frontend/src/components/PasswordStrength.css new file mode 100644 index 0000000000000000000000000000000000000000..557968fc9a2f903c4b6404be307839c75a07fe98 --- /dev/null +++ b/frontend/src/components/PasswordStrength.css @@ -0,0 +1,59 @@ +.psc-wrapper { + width: 90%; + text-align: left; + margin: 10px auto 10px 5px; + } + .pwd-checker-bar { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 10px; + } + .pwd-checker-bar::-webkit-progress-bar { + background-color: rgb(246, 241, 241); + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + } + .pwd-label { + font-size: 12px; + } + .pwd-checker-bar::-webkit-progress-value { + border-radius: 4px; + background-size: 30px 18px, 100% 100%, 100% 100%; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + } + .label { + margin-top: 10px; + margin-bottom: 0px; + } + .strength-weak::-webkit-progress-value { + background-color: #e20b07; + } + .strength-fair::-webkit-progress-value { + background-color: #ebbd04; + } + .strength-good::-webkit-progress-value { + background-color: #0b75ed; + } + .strength-strong::-webkit-progress-value { + background-color: #01a917; + } + .weak span { + color: #e20b07; + } + .strength-fair span { + color: #ebbd04; + } + .strength-good span { + color: #0b75ed; + } + .strength-strong span { + color: #01a917; + } + \ No newline at end of file diff --git a/frontend/src/components/PasswordStrength.js b/frontend/src/components/PasswordStrength.js new file mode 100644 index 0000000000000000000000000000000000000000..be546c7d26583690348799fc87e74892142d74f2 --- /dev/null +++ b/frontend/src/components/PasswordStrength.js @@ -0,0 +1,50 @@ +import React from 'react' +import PropTypes from 'prop-types' +import zxcvbn from 'zxcvbn' +import './PasswordStrength.css' + +const StrengthMeter = ({ password, actions }) => { + const result = zxcvbn(password) + + actions(result.score.toString()) + + const timeToCrack = () => { + if (result.score === 0 && password.length === 0) return 'N/A' + if (result.score === 0) return 'immediately' + const time = result.crack_times_display.offline_slow_hashing_1e4_per_second + return `${time} (for slow hashing)` + } + + return ( + <> + <div className="psc-wrapper"> + <progress + className={`pwd-checker-bar strength-${result.score}`} + value={result.score} + max="4" + /> + <br /> + <div className="pwd-label"> + {password && ( + <> + <p className={`label strength-${result.score}`}> + Password strength: + <strong>{['Weak', 'Fair', 'Good', 'Strong', 'Very Strong'][result.score]}</strong> + </p> + <p className={`label strength-${result.score}`}> + Estimated time to crack: <strong>{timeToCrack()}</strong> + </p> + </> + )} + </div> + </div> + </> + ) +} + +StrengthMeter.propTypes = { + password: PropTypes.string.isRequired, + actions: PropTypes.func.isRequired, +} + +export default StrengthMeter diff --git a/frontend/src/views/Breaches.js b/frontend/src/views/Breaches.js new file mode 100644 index 0000000000000000000000000000000000000000..b004d08c70cd2ce303793b2a1a00cca314160b47 --- /dev/null +++ b/frontend/src/views/Breaches.js @@ -0,0 +1,214 @@ +import React, { useState } from 'react' +import { + CButton, + CCard, + CCardBody, + CCardHeader, + CCol, + CForm, + CFormInput, + CInputGroup, + CInputGroupText, + CRow, +} from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilTrash, cilPlus, cilCloudDownload } from '@coreui/icons' +import axios from 'axios' +import PasswordStrengthChecker from 'src/components/PasswordStrength' + +const Breaches = () => { + const [password, setPassword] = useState('') + const [file, setFile] = useState(null) + const [accounts, setAccounts] = useState([{ password: '' }]) + const [result, setResult] = useState(null) + + const handlePasswordChange = (e) => setPassword(e.target.value) + const handleFileChange = (e) => setFile(e.target.files[0]) + const handleAccountChange = (index, e) => { + const newAccounts = [...accounts] + newAccounts[index].password = e.target.value + setAccounts(newAccounts) + } + const handleAddAccount = () => setAccounts([...accounts, { password: '' }]) + const handleRemoveAccount = (index) => { + const newAccounts = [...accounts] + newAccounts.splice(index, 1) + setAccounts(newAccounts) + } + const handleDownloadTemplate = () => { + const templateData = 'Password\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 = 'password_template.csv' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + + const handleAddToDatabase = async (e) => { + e.preventDefault() + + const token = localStorage.getItem('token') + console.log('Token retrieved from local storage:', token) + const formData = new FormData() + + if (password) formData.append('password', password) + if (file) formData.append('file', file) + + accounts.forEach((account, index) => { + if (account.password) formData.append(`passwords[${index}]`, account.password) + }) + // Log the form data before sending + console.log('Form Data:', formData) + const headers = { + Authorization: `Bearer ${token}`, + 'Content-Type': 'multipart/form-data', + } + + try { + await axios.post('http://localhost:8000/api/breaches/upload/passwords/', formData, { + headers, + }) + alert('Data submitted successfully') + + setPassword('') + setFile(null) + setAccounts([{ password: '' }]) + } catch (error) { + console.error('Error uploading data:', error) + alert('Error submitting data') + } + } + + const handleSubmitCheck = async (e) => { + e.preventDefault() + if (password) { + try { + const apiUrl = `http://localhost:8000/haveibeenpwned/passwords/${password}/` + const response = await fetch(apiUrl) + const data = await response.json() + console.log('Result from API:', data) // Log the result + if (data.error) { + // No breach detected + setResult([]) + setCount(0) + } else { + // Breach detected + let _ct = data.count + setCount(_ct) + setResult(data.response) + + console.log(breachCount) + } + } catch (error) { + console.error('Error fetching data:', error) + } + } + } + + const [isStrong, initRobustPassword] = useState(null) + const initPwdInput = async (childData) => { + initRobustPassword(childData) + } + const [breachCount, setCount] = useState(null) + return ( + <CRow> + <CCol lg={12}> + <CCard className={'mb-4'}> + <CCardHeader>Quick Check</CCardHeader> + <CCardBody> + <CForm> + <CInputGroup className="mb-3"> + <CFormInput + placeholder="Enter Your Password" + type="password" + value={password} + onChange={handlePasswordChange} + /> + </CInputGroup> + <PasswordStrengthChecker password={password} actions={initPwdInput} /> + <CButton color="success" onClick={handleSubmitCheck}> + Check + </CButton> + </CForm> + {/* Conditional rendering for results */} + {breachCount !== null && ( + <h2 style={{ margin: '12px 0' }}> + The Password has been breached {breachCount.toLocaleString()} times + </h2> + )} + {result && result.length > 0 ? ( + <div> + <table className="table table-striped table-responsive table-bordered mt-3"> + <thead> + <tr> + <th style={{ padding: '10px' }}>Breach Name</th> + <th style={{ padding: '10px' }}>Breach Date</th> + {/* Add more table headers if needed */} + </tr> + </thead> + <tbody> + {result.map((breach) => ( + <tr key={breach.Name}> + <td style={{ padding: '10px' }}>{breach.Name}</td> + <td style={{ padding: '10px' }}>{breach.BreachDate}</td> + {/* Add more table cells if needed */} + </tr> + ))} + </tbody> + </table> + </div> + ) : result && result.length === 0 ? ( + <h4 className={'mt-3'}>No breach detected for the provided password</h4> + ) : null} + </CCardBody> + </CCard> + + <CCard> + <CCardHeader>Monitor Passwords</CCardHeader> + <CCardBody> + {accounts.map((account, index) => ( + <CInputGroup key={index} className="mb-3"> + <CFormInput + type="password" + placeholder="Add/check Password" + name="password" + value={account.password} + onChange={(e) => handleAccountChange(index, e)} + /> + + <CButton + type="button" + color="danger" + variant="outline" + onClick={() => handleRemoveAccount(index)} + > + <CIcon icon={cilTrash} /> + </CButton> + </CInputGroup> + ))} + <CButton color="primary" className="mb-3" onClick={handleAddAccount}> + <CIcon icon={cilPlus} /> Add Another Account + </CButton> + + <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="info" onClick={handleAddToDatabase}> + Add to Database + </CButton> + </div> + </CCardBody> + </CCard> + </CCol> + </CRow> + ) +} + +export default Breaches diff --git a/frontend/src/views/passwords/Breaches.js b/frontend/src/views/passwords/Breaches.js index 9ffd041f61dc54b7103299ab810b031b9a8a1e3a..b004d08c70cd2ce303793b2a1a00cca314160b47 100644 --- a/frontend/src/views/passwords/Breaches.js +++ b/frontend/src/views/passwords/Breaches.js @@ -14,13 +14,13 @@ import { import CIcon from '@coreui/icons-react' import { cilTrash, cilPlus, cilCloudDownload } from '@coreui/icons' import axios from 'axios' +import PasswordStrengthChecker from 'src/components/PasswordStrength' const Breaches = () => { const [password, setPassword] = useState('') const [file, setFile] = useState(null) const [accounts, setAccounts] = useState([{ password: '' }]) const [result, setResult] = useState(null) - const [breachCount, setCount] = useState(0) const handlePasswordChange = (e) => setPassword(e.target.value) const handleFileChange = (e) => setFile(e.target.files[0]) @@ -107,6 +107,11 @@ const Breaches = () => { } } + const [isStrong, initRobustPassword] = useState(null) + const initPwdInput = async (childData) => { + initRobustPassword(childData) + } + const [breachCount, setCount] = useState(null) return ( <CRow> <CCol lg={12}> @@ -122,14 +127,17 @@ const Breaches = () => { onChange={handlePasswordChange} /> </CInputGroup> + <PasswordStrengthChecker password={password} actions={initPwdInput} /> <CButton color="success" onClick={handleSubmitCheck}> Check </CButton> </CForm> {/* Conditional rendering for results */} - <h2 style={{ margin: '12px 0' }}> - The Password has been breached {breachCount.toLocaleString()} times - </h2> + {breachCount !== null && ( + <h2 style={{ margin: '12px 0' }}> + The Password has been breached {breachCount.toLocaleString()} times + </h2> + )} {result && result.length > 0 ? ( <div> <table className="table table-striped table-responsive table-bordered mt-3"> @@ -164,11 +172,12 @@ const Breaches = () => { <CInputGroup key={index} className="mb-3"> <CFormInput type="password" - placeholder="Add Password" + placeholder="Add/check Password" name="password" value={account.password} onChange={(e) => handleAccountChange(index, e)} /> + <CButton type="button" color="danger" diff --git a/frontend/src/views/passwords/BreachesPasswords.js b/frontend/src/views/passwords/BreachesPasswords.js index a74ee3c063f117d937c1e728769ddc2faac6e60a..9b6882575d504d68bc32833be05b2b536d34b38c 100644 --- a/frontend/src/views/passwords/BreachesPasswords.js +++ b/frontend/src/views/passwords/BreachesPasswords.js @@ -1,10 +1,9 @@ // 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() }, []) @@ -12,7 +11,6 @@ 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}`, @@ -21,7 +19,6 @@ const BreachesPasswords = () => { if (response.status === 401) { console.error('Unauthorized access to the API') - navigate('/login') return } @@ -55,8 +52,7 @@ const BreachesPasswords = () => { ) } const countUniquePasswords = (results) => { - const uniquePasswords = new Set(results.map((result) => result.password)) - return uniquePasswords.size + return results.filter((result) => result.count === 0).length } export { countUniquePasswords } // Correct export statement