Skip to content
This repository was archived by the owner on Jun 19, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0870500
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
9e9a23e
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
2b6e129
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
72bc5b9
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
9c463a3
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
ec870c0
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
9bb52dc
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
cf90e65
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
54ebda4
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
762372c
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
95e2268
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
374e996
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
a6077f7
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
8182a54
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
aa62e17
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
e717ea0
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
5e7322c
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
8d638c3
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 17, 2026
179a377
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
c64aabd
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
e2aabc5
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
1aa8c23
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
9f46427
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
d47dfd8
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
6daa9d3
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
314f8a3
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
ea0df44
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
74227ea
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
be28b37
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
e551424
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
d79d2ff
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
94d118a
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
293ed43
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
17fadbd
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
99de637
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
8f507cb
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
d063ba7
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
8a6aca2
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
5e56155
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
c493746
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
3b88262
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
b1b0011
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
6338333
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
5dc3b19
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
2b97c18
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
ba56142
fix: apply solution for issue #124
genesisrevelationinc-debug Mar 20, 2026
a6c0db9
fix: apply solution for issue #124
genesisrevelationinc-debug May 22, 2026
7869bef
fix: apply solution for issue #124
genesisrevelationinc-debug May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ flowchart LR
API -->|reminder jobs| SCH
SCH --> TW
SCH --> SMTP
AI --> OAI
```
SCH --> TW
SCH --> SMTP
AI --> OAI

## Login Anomaly Detection
- Detect unusual login behavior and alert users.
- Uses `audit_logs` table to track login attempts.
- Alerts are sent via Twilio WhatsApp or Email Provider.
Comment on lines +47 to +50


## PostgreSQL Schema (DDL)
See `backend/app/db/schema.sql`. Key tables:
- users, categories, expenses, bills, reminders
- ad_impressions, subscription_plans, user_subscriptions
Expand Down
19 changes: 19 additions & 0 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Flask
from .extensions import db, jwt
from .routes import auth
from .models import User, AuditLog

def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:password@localhost/finmind'
app.config['JWT_SECRET_KEY'] = 'your_jwt_secret_key'

db.init_app(app)
jwt.init_app(app)

with app.app_context():
db.create_all()

app.register_blueprint(auth, url_prefix='/auth')

return app
15 changes: 15 additions & 0 deletions backend/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os
from dotenv import load_dotenv
from flask_mail import Mail

load_dotenv()

class Config:
SQLALCHEMY_TRACK_MODIFICATIONS = False
JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'your_jwt_secret_key')
JWT_ACCESS_TOKEN_EXPIRES = 900
MAIL_SERVER = os.getenv('MAIL_SERVER', 'smtp.example.com')
MAIL_PORT = int(os.getenv('MAIL_PORT', 587))
MAIL_USE_TLS = os.getenv('MAIL_USE_TLS', 'true').lower() in ['true', '1', 't']
MAIL_USERNAME = os.getenv('MAIL_USERNAME', 'your_email@example.com')
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD', 'your_email_password')
Comment on lines +7 to +15
Comment on lines +7 to +15
17 changes: 17 additions & 0 deletions backend/app/db/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
login_attempts INT DEFAULT 0
);

CREATE TABLE audit_logs (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
activity TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
7 changes: 7 additions & 0 deletions backend/app/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager
from flask_mail import Mail

db = SQLAlchemy()
jwt = JWTManager()
mail = Mail()
64 changes: 64 additions & 0 deletions backend/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class LoginEvent(db.Model):
__tablename__ = 'login_events'

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
ip_address = db.Column(db.String(45), nullable=False)
user_agent = db.Column(db.String(500))
location = db.Column(db.String(255))
success = db.Column(db.Boolean, default=False, nullable=False)
failure_reason = db.Column(db.String(255))
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)

user = db.relationship('User', backref='login_events')


class SuspiciousActivityAlert(db.Model):
__tablename__ = 'suspicious_activity_alerts'

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
alert_type = db.Column(db.String(50), nullable=False) # 'brute_force', 'impossible_travel', 'new_device', 'unusual_time'
severity = db.Column(db.String(20), default='medium', nullable=False) # 'low', 'medium', 'high'
description = db.Column(db.Text, nullable=False)
login_event_id = db.Column(db.Integer, db.ForeignKey('login_events.id'))
resolved = db.Column(db.Boolean, default=False, nullable=False)
resolved_at = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)

user = db.relationship('User', backref='suspicious_activity_alerts')
login_event = db.relationship('LoginEvent', backref='alerts')


class UserDevice(db.Model):
__tablename__ = 'user_devices'

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
fingerprint = db.Column(db.String(64), nullable=False, index=True)
user_agent = db.Column(db.String(500))
last_ip = db.Column(db.String(45))
last_seen_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
first_seen_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
trusted = db.Column(db.Boolean, default=False, nullable=False)

__table_args__ = (db.UniqueConstraint('user_id', 'fingerprint', name='uq_user_device'),)

user = db.relationship('User', backref='devices')


class UserNotificationPreference(db.Model):
__tablename__ = 'user_notification_preferences'

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, unique=True)
email_security_alerts = db.Column(db.Boolean, default=True, nullable=False)
whatsapp_security_alerts = db.Column(db.Boolean, default=False, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

user = db.relationship('User', backref=db.backref('notification_preference', uselist=False))


db.Index('ix_login_events_user_created', 'login_events', 'user_id', 'created_at')
db.Index('ix_suspicious_activity_alerts_user_created', 'suspicious_activity_alerts', 'user_id', 'created_at')
3 changes: 3 additions & 0 deletions backend/app/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flask import Blueprint
from . import auth
from . import alerts
18 changes: 18 additions & 0 deletions backend/app/routes/alerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from flask import Blueprint, jsonify
from app.models import AuditLog, db
from app.extensions import mail
from flask_mail import Message

alerts = Blueprint('alerts', __name__)

@alerts.route('/suspicious-activity', methods=['GET'])
def suspicious_activity_alerts():
Comment on lines +1 to +9
suspicious_logs = AuditLog.query.filter_by(activity='Suspicious login attempt').all()
if not suspicious_logs:
return jsonify({"msg": "No suspicious activity detected"}), 200

msg = Message("Suspicious Activity Alert", sender="noreply@finmind.com", recipients=["admin@finmind.com"])
msg.body = "Suspicious login attempts detected:\n\n" + "\n".join([f"User ID: {log.user_id}, Timestamp: {log.timestamp}" for log in suspicious_logs])
Comment on lines +10 to +15
Comment on lines +10 to +15
mail.send(msg)
Comment on lines +14 to +16

return jsonify({"msg": "Suspicious activity alerts sent"}), 200
37 changes: 37 additions & 0 deletions backend/app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Authentication routes with login anomaly detection."""

from flask import Blueprint, request, jsonify, g
from datetime import datetime, timedelta
import hashlib
import hmac
import os
import json
import time

def get_client_ip():
if request.environ.get('HTTP_X_FORGED_FORGING'):
return request.environ.get('HTTP_X_FORGED_FORGING')
elif request.environ.get('HTTP_X_REAL_IP'):
return request.environ.get('HTTP_X_REAL_IP')
return request.environ.get('HTTP_X_FORWARDED_FOR', '').split(',')[0].strip()

def get_user_agent():
return request.headers.get('User-Agent', 'Unknown')

def get_user_ip():
return request.environ.get('HTTP_X_FORGED_FORGING', get_client_ip())

def get_user_agent():
return request.headers.get('User-Agent', 'Unknown')

def get_current_time():
return datetime.datetime.now().isoformat()

def get_client_ip():
return request.environ.get('HTTP_X_FORGED_FORGING', 'HTTP_X_REAL_IP')

def get_user_agent():
return request.headers.get('User-Agent', 'Unknown')

def get_client_ip():
return request.environ.get('HTTP_X_FORGED_FORGING', 'HTTP_X_REAL_IP')
Comment on lines +12 to +37