diff --git a/backend/backend.py b/backend/backend.py index a5a11d2..5de232b 100644 --- a/backend/backend.py +++ b/backend/backend.py @@ -16,6 +16,9 @@ import email from email.header import decode_header import base64 import csv +from datetime import datetime, timedelta +from googleapiclient.discovery import build + app = Flask(__name__) CORS(app) @@ -24,37 +27,38 @@ app.secret_key = 'your_secret_key' os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' CLIENT_SECRETS_FILE = "client_secret.json" -SCOPES = ['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.modify'] +SCOPES = ['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.modify',"https://www.googleapis.com/auth/userinfo.email",'https://www.googleapis.com/auth/userinfo.profile', + 'openid'] API_SERVICE_NAME = 'gmail' API_VERSION = 'v1' - SAFE_EMAILS_FILE = 'safe_emails.json' SAFE_TRASH_FILE = 'safe_trash.csv' -# Load safe emails from file - def load_safe_emails(): if os.path.exists(SAFE_EMAILS_FILE): - with open(SAFE_EMAILS_FILE, 'r') as file: - return json.load(file) + if os.path.getsize(SAFE_EMAILS_FILE) > 0: + with open(SAFE_EMAILS_FILE, 'r') as file: + try: + return json.load(file) + except json.JSONDecodeError: + return [] return [] -# Save safe emails to file - def save_safe_emails(safe_emails): with open(SAFE_EMAILS_FILE, 'w') as file: json.dump(safe_emails, file) - def save_safe_trash(safe_trash): file_exists = os.path.isfile(SAFE_TRASH_FILE) with open(SAFE_TRASH_FILE, 'a', newline='', encoding='utf-8') as file: - fieldnames = ['from', 'subject', 'body'] + fieldnames = ['from', 'subject', 'body', 'date',"username"] writer = csv.DictWriter(file, fieldnames=fieldnames) if not file_exists: writer.writeheader() + if 'date' not in safe_trash: + safe_trash['date'] = datetime.now().isoformat() writer.writerow(safe_trash) safe_emails = load_safe_emails() @@ -77,7 +81,6 @@ def load_credentials(): return None @app.route('/authorize') - def authorize(): client_secrets = load_client_secrets() flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( @@ -104,6 +107,7 @@ def oauth2callback(): authorization_response = request.url app.logger.info(f"Authorization response: {authorization_response}") + try: flow.fetch_token(authorization_response=authorization_response) credentials = flow.credentials @@ -124,12 +128,13 @@ def oauth2callback(): app.logger.error(f"Exception: {str(e)}") app.logger.error("Traceback: " + traceback.format_exc()) return jsonify({'error': 'Exception', 'message': str(e)}), 500 -@app.route('/validate-outlook-login', methods=['POST']) +@app.route('/validate-outlook-login', methods=['POST']) def validate_outlook_login(): data = request.json username = data['email'] password = data['password'] + try: mail = imaplib.IMAP4_SSL("outlook.office365.com") mail.login(username, password) @@ -141,11 +146,11 @@ def validate_outlook_login(): return jsonify({"success": False, "error": "Encoding error"}), 400 @app.route('/fetch-emails', methods=['POST']) - def fetch_emails(): data = request.json username = data['username'] password = data['password'] + try: mail = imaplib.IMAP4_SSL("outlook.office365.com") mail.login(username, password) @@ -154,11 +159,14 @@ def fetch_emails(): return jsonify({"error": "Login failed. Check your email and password."}), 401 except UnicodeEncodeError: return jsonify({"error": "Encoding error. Ensure your email and password contain only valid characters."}), 400 + status, messages = mail.search(None, "ALL") if status != "OK": return jsonify({"error": "Failed to retrieve emails."}), 500 + status, messages = mail.search(None, "ALL") email_ids = messages[0].split() + emails = [] for email_id in email_ids: @@ -172,6 +180,7 @@ def fetch_emails(): from_ = msg.get("From") name, email_address = email.utils.parseaddr(from_) body = "" + result = False if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == "text/plain" and part.get("Content-Disposition") is None: @@ -180,7 +189,9 @@ def fetch_emails(): body = msg.get_payload(decode=True).decode(msg.get_content_charset() or "utf-8") email_vectorized = vectorizer.transform([body]) prediction = model.predict(email_vectorized) - result = True if prediction == 1 or contains_suspicious_links(body) or contains_phishing_indicators(subject, email_address,body) else False + if not any(email['id'] == email_id.decode() for email in safe_emails): + result = True if prediction == 1 or contains_suspicious_links(body) or contains_phishing_indicators(subject, email_address,body) else False + emails.append({"id": email_id.decode(), "from": email_address, "subject": subject, @@ -191,7 +202,6 @@ def fetch_emails(): return jsonify(emails) @app.route('/check_auth_status', methods=['GET']) - def check_auth_status(): if 'credentials' in session: credentials = google.oauth2.credentials.Credentials(**session['credentials']) @@ -200,24 +210,20 @@ def check_auth_status(): return jsonify({'logged_in': False}) @app.route('/check_mail') - def check_mail(): if 'credentials' not in session: return redirect('authorize') - + credentials = google.oauth2.credentials.Credentials( **session['credentials']) - + gmail = googleapiclient.discovery.build( 'gmail', 'v1', credentials=credentials) - + results = gmail.users().messages().list(userId='me', labelIds=['INBOX']).execute() messages = results.get('messages', []) - - - - emails = [] + emails = [] for message in messages: msg = gmail.users().messages().get(userId='me', id=message['id']).execute() snippet = msg.get('snippet', '') @@ -233,8 +239,9 @@ def check_mail(): sender = header['value'] email_vectorized = vectorizer.transform([snippet]) prediction = model.predict(email_vectorized) - if message['id'] not in safe_emails: - result = True if prediction == 1 or contains_suspicious_links(snippet) or contains_phishing_indicators(subject, sender,snippet) else False + if not any(email['id'] == message['id'] for email in safe_emails): + result = True if prediction == 1 or contains_suspicious_links(snippet) or contains_phishing_indicators(subject, sender, snippet) else False + emails.append({ 'subject': subject, 'from': sender, @@ -243,22 +250,31 @@ def check_mail(): 'suspicious': result }) return jsonify(emails) -@app.route('/logout', methods=['POST']) +@app.route('/logout', methods=['POST']) def logout(): session.clear() app.logger.info("Session cleared. User logged out.") return jsonify({'message': 'Logged out'}), 200 + @app.route('/mark_safe/', methods=['POST']) - def mark_safe(email_id): + data = request.json + email = data['email'] + username = data['username'] + if email == "gmail": + credentials = google.oauth2.credentials.Credentials( + **session['credentials']) + service = build('people', 'v1', credentials=credentials) + profile = service.people().get(resourceName='people/me', personFields='emailAddresses').execute() + username = profile.get('emailAddresses', [])[0].get('value') global safe_emails - safe_emails.append(email_id) + email_data = {'id': email_id, 'date': datetime.now().isoformat(),"username":username} + safe_emails.append(email_data) save_safe_emails(safe_emails) - app.logger.info(f'Email {email_id} marked as safe') - return jsonify({"message": f"Email {email_id} marked as safe"}), 200 -@app.route('/move_trash/', methods=['POST']) + return jsonify({'status': 'success'}) +@app.route('/move_trash/', methods=['POST']) def move_trash(email_id): if 'credentials' not in session: return jsonify({'message': 'Not logged in'}), 401 @@ -266,7 +282,11 @@ def move_trash(email_id): **session['credentials']) gmail = googleapiclient.discovery.build( 'gmail', 'v1', credentials=credentials) - + + service = build('people', 'v1', credentials=credentials) + profile = service.people().get(resourceName='people/me', personFields='emailAddresses').execute() + username = profile.get('emailAddresses', [])[0].get('value') + try: message = gmail.users().messages().get(userId='me', id=email_id).execute() payload = message.get('payload', {}) @@ -290,7 +310,8 @@ def move_trash(email_id): safe_trash = { "from": sender, "subject": subject, - "body": body + "body": body, + "username": username } save_safe_trash(safe_trash) @@ -306,13 +327,12 @@ def move_trash(email_id): return jsonify({"error": str(e)}), 500 @app.route('/delete-email', methods=['POST']) - def delete_email(): data = request.json email_id = data['email_id'] username = data['username'] password = data['password'] - + try: mail = imaplib.IMAP4_SSL("outlook.office365.com") mail.login(username, password) @@ -327,7 +347,6 @@ def delete_email(): if isinstance(subject, bytes): subject = subject.decode(encoding if encoding else "utf-8") from_ = msg.get("From") - name, email_address = email.utils.parseaddr(from_) body = "" if msg.is_multipart(): for part in msg.walk(): @@ -335,23 +354,63 @@ def delete_email(): body += part.get_payload(decode=True).decode(part.get_content_charset() or "utf-8") else: body = msg.get_payload(decode=True).decode(msg.get_content_charset() or "utf-8") + safe_trash = { "from": from_, "subject": subject, - "body": body + "body": body, + "username": username } save_safe_trash(safe_trash) + mail.store(email_id, '+FLAGS', '\\Deleted') mail.expunge() return jsonify({"message": f"Email {email_id} deleted"}) except imaplib.IMAP4.error: return jsonify({"error": "Failed to delete email"}), 500 +@app.route('/get_email_stats', methods=['POST']) +def get_email_stats(): + data = request.json + email = data['emailService'] + username = data['username'] + if email == "gmail": + credentials = google.oauth2.credentials.Credentials( + **session['credentials']) + service = build('people', 'v1', credentials=credentials) + profile = service.people().get(resourceName='people/me', personFields='emailAddresses').execute() + username = profile.get('emailAddresses', [])[0].get('value') + start_date = datetime.fromisoformat(data['start_date']) + end_date = datetime.fromisoformat(data['end_date']) + timedelta(days=1) + + safe_emails_count = 0 + trashed_emails_count = 0 + + safe_emails = load_safe_emails() + for email in safe_emails: + if email['username'] == username and 'date' in email: + email_date = datetime.fromisoformat(email['date']) + if start_date <= email_date < end_date: + safe_emails_count += 1 + + if os.path.exists(SAFE_TRASH_FILE): + with open(SAFE_TRASH_FILE, 'r', encoding='utf-8') as file: + reader = csv.DictReader(file) + for row in reader: + if row['username'] == username: + email_date = datetime.fromisoformat(row['date']) + if start_date <= email_date < end_date: + trashed_emails_count += 1 + return jsonify({ + 'safe_emails_count': safe_emails_count, + 'trashed_emails_count': trashed_emails_count + }) + + def contains_suspicious_links(snippet): url_pattern = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') urls = url_pattern.findall(snippet) suspicious_domains = ['phishingsite.com', 'malicious.com'] - for url in urls: for domain in suspicious_domains: if domain in url: diff --git a/extension/background.js b/extension/background.js index b97a216..e7d258b 100644 --- a/extension/background.js +++ b/extension/background.js @@ -42,12 +42,26 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { ); } } else if (message.type === "mark-safe") { - fetch(`http://localhost:5000/mark_safe/${message.emailId}`, { - method: "POST", - }) - .then((response) => response.json()) - .then((data) => console.log(data)) - .catch((error) => console.error("Error:", error)); + chrome.storage.local.get( + ["email", "password", "emailService"], + function (result) { + const emailService = result.emailService; + const emailId = message.emailId; + fetch(`http://localhost:5000/mark_safe/${emailId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username: result.email, + email: emailService, + }), + }) + .then((response) => response.json()) + .then((data) => console.log(data)) + .catch((error) => console.error("Error:", error)); + } + ); } else if (message.type === "move-trash") { chrome.storage.local.get( ["email", "password", "emailService"], @@ -72,6 +86,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { } else { fetch(`http://localhost:5000/move_trash/${emailId}`, { method: "POST", + headers: { + "Content-Type": "application/json", + }, }) .then((response) => response.json()) .then((data) => console.log(data)) diff --git a/extension/manifest.json b/extension/manifest.json index 4c07a65..3726281 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -19,15 +19,15 @@ "action": { "default_popup": "popup.html", "default_icon": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" } }, "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" }, "oauth2": { "client_id": "", diff --git a/extension/notification.html b/extension/notification.html index 15c0adc..1890f64 100644 --- a/extension/notification.html +++ b/extension/notification.html @@ -15,6 +15,13 @@

+
+ + + + + +