diff --git a/backend/app.py b/backend/app.py deleted file mode 100644 index 6d61282..0000000 --- a/backend/app.py +++ /dev/null @@ -1,95 +0,0 @@ -from flask import Flask, request, jsonify -from flask_cors import CORS -import imaplib -import email -from email.header import decode_header -import joblib - -app = Flask(__name__) -CORS(app) - -model = joblib.load('spam_classifier_model.pkl') -vectorizer = joblib.load('vectorizer.pkl') - - -@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) - mail.select("inbox") - except imaplib.IMAP4.error: - return jsonify({"error": "Login failed. Check your email and password."}), 401 - - status, messages = mail.search(None, "ALL") - email_ids = messages[0].split() - - emails = [] - - for email_id in email_ids: - res, msg = mail.fetch(email_id, "(RFC822)") - for response_part in msg: - if isinstance(response_part, tuple): - msg = email.message_from_bytes(response_part[1]) - subject, encoding = decode_header(msg["Subject"])[0] - 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(): - if part.get_content_type() == "text/plain" and part.get("Content-Disposition") is None: - 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") - - emails.append({"id": email_id.decode(), "from": from_, "name": name, "email_address": email_address, - "subject": subject, "body": body}) - - return jsonify(emails) - - -@app.route('/classify-email', methods=['POST']) -def classify_email(): - data = request.json - email_body = data['body'] - email_vectorized = vectorizer.transform([email_body]) - prediction = model.predict(email_vectorized) - result = "Suspicious" if prediction == 1 else "Not suspicious" - return jsonify({"result": result}) - - -@app.route('/mark-safe', methods=['POST']) -def mark_safe(): - data = request.json - email_id = data['email_id'] - # Logic to mark email as safe - return jsonify({"message": f"Email {email_id} marked as safe"}) - - -@app.route('/delete-email', methods=['POST']) -def delete_email(): - data = request.json - email_id = data['email_id'] - - # Connect to the mail server and delete the email - username = data['username'] - password = data['password'] - try: - mail = imaplib.IMAP4_SSL("outlook.office365.com") - mail.login(username, password) - mail.select("inbox") - 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 - - -if __name__ == '__main__': - app.run(debug=True) diff --git a/backend/backend.py b/backend/backend.py new file mode 100644 index 0000000..31b4fe8 --- /dev/null +++ b/backend/backend.py @@ -0,0 +1,184 @@ +from flask import Flask, request, jsonify, session, redirect, url_for +from flask_cors import CORS +import google.oauth2.credentials +import google_auth_oauthlib.flow +import googleapiclient.discovery +import os +import json +import traceback +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.naive_bayes import MultinomialNB +import pandas as pd +import re +import joblib +import imaplib +import email +from email.header import decode_header + +app = Flask(__name__) +CORS(app) +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'] +API_SERVICE_NAME = 'gmail' +API_VERSION = 'v1' + +model = joblib.load('spam_classifier_model.pkl') +vectorizer = joblib.load('vectorizer.pkl') +def load_client_secrets(): + with open(CLIENT_SECRETS_FILE) as f: + return json.load(f) + +def save_credentials(credentials): + session['credentials'] = credentials_to_dict(credentials) + app.logger.info("Credentials saved in session.") + +def load_credentials(): + if 'credentials' in session: + return google.oauth2.credentials.Credentials( + **session['credentials']) + return None + +@app.route('/authorize') +def authorize(): + client_secrets = load_client_secrets() + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES) + flow.redirect_uri = url_for('oauth2callback', _external=True) + authorization_url, state = flow.authorization_url( + access_type='offline', + include_granted_scopes='true') + session['state'] = state + app.logger.info(f"Authorization URL: {authorization_url}, State: {state}") + return jsonify({'url': authorization_url, 'state': state}) + +@app.route('/oauth2callback') +def oauth2callback(): + state = session.pop('state', None) + if not state: + app.logger.error('CSRF Warning: State mismatch') + return jsonify({'error': 'CSRF Warning: State mismatch'}), 400 + + client_secrets = load_client_secrets() + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) + flow.redirect_uri = url_for('oauth2callback', _external=True) + + authorization_response = request.url + app.logger.info(f"Authorization response: {authorization_response}") + + try: + flow.fetch_token(authorization_response=authorization_response) + credentials = flow.credentials + save_credentials(credentials) + app.logger.info(f"Token received and saved: {credentials.token}") + return """ + + """ + except KeyError as e: + app.logger.error(f"KeyError: {str(e)}") + app.logger.error("Client secrets: " + json.dumps(client_secrets, indent=2)) + app.logger.error("Authorization response: " + authorization_response) + return jsonify({'error': 'KeyError', 'message': str(e)}), 500 + except Exception as e: + app.logger.error(f"Exception: {str(e)}") + app.logger.error("Traceback: " + traceback.format_exc()) + return jsonify({'error': 'Exception', 'message': str(e)}), 500 + +@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) + mail.select("inbox") + except imaplib.IMAP4.error: + return jsonify({"error": "Login failed. Check your email and password."}), 401 + + status, messages = mail.search(None, "ALL") + email_ids = messages[0].split() + + emails = [] + + for email_id in email_ids: + res, msg = mail.fetch(email_id, "(RFC822)") + for response_part in msg: + if isinstance(response_part, tuple): + msg = email.message_from_bytes(response_part[1]) + subject, encoding = decode_header(msg["Subject"])[0] + 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(): + if part.get_content_type() == "text/plain" and part.get("Content-Disposition") is None: + 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") + + emails.append({"id": email_id.decode(), "from": from_, "name": name, "email_address": email_address, + "subject": subject, "body": body}) + + return jsonify(emails) + + +@app.route('/classify-email', methods=['POST']) +def classify_email(): + data = request.json + email_body = data['body'] + email_vectorized = vectorizer.transform([email_body]) + prediction = model.predict(email_vectorized) + result = "Suspicious" if prediction == 1 else "Not suspicious" + return jsonify({"result": result}) + + +@app.route('/mark-safe', methods=['POST']) +def mark_safe(): + data = request.json + email_id = data['email_id'] + # Logic to mark email as safe + return jsonify({"message": f"Email {email_id} marked as safe"}) + + +@app.route('/delete-email', methods=['POST']) +def delete_email(): + data = request.json + email_id = data['email_id'] + + # Connect to the mail server and delete the email + username = data['username'] + password = data['password'] + try: + mail = imaplib.IMAP4_SSL("outlook.office365.com") + mail.login(username, password) + mail.select("inbox") + 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 + +def credentials_to_dict(credentials): + return { + 'token': credentials.token, + 'refresh_token': credentials.refresh_token, + 'token_uri': credentials.token_uri, + 'client_id': credentials.client_id, + 'client_secret': credentials.client_secret, + 'scopes': credentials.scopes + } + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/extension/manifest.json b/extension/manifest.json index d99d204..4c07a65 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -2,16 +2,38 @@ "manifest_version": 3, "name": "PhishGuardian", "version": "1.0", - "description": "Classify emails as spam or not spam.", - "permissions": ["storage", "activeTab", "scripting", "windows"], + "permissions": [ + "storage", + "activeTab", + "scripting", + "notifications", + "identity" + ], + "host_permissions": [ + "http://localhost:5000/*", + "https://www.googleapis.com/" + ], "background": { "service_worker": "background.js" }, "action": { + "default_popup": "popup.html", "default_icon": { - "16": "images/icon16.png", - "48": "images/icon48.png", - "128": "images/icon128.png" + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" } + }, + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "oauth2": { + "client_id": "", + "scopes": ["https://www.googleapis.com/auth/gmail.readonly"] + }, + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self'" } }