diff --git a/.dockerignore b/.dockerignore index ec27b35..6b0c09f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,6 +15,7 @@ venv/ .eggs/ *.egg-info/ *.egg +.uv # IDE .idea/ diff --git a/.gitignore b/.gitignore index 6308bb9..d4e73ce 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ build/ uv.lock *.db *.sqlite3 +intercept.db \ No newline at end of file diff --git a/app.py b/app.py index 677e3fd..58fc070 100644 --- a/app.py +++ b/app.py @@ -9,6 +9,8 @@ from __future__ import annotations import sys import site +from utils.database import get_db + # Ensure user site-packages is available (may be disabled when running as root/sudo) if not site.ENABLE_USER_SITE: user_site = site.getusersitepackages() @@ -23,8 +25,8 @@ import subprocess from typing import Any -from flask import Flask, render_template, jsonify, send_file, Response, request - +from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session +from werkzeug.security import check_password_hash from config import VERSION, CHANGELOG from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES from utils.process import cleanup_stale_processes @@ -36,14 +38,15 @@ from utils.constants import ( MAX_BT_DEVICE_AGE_SECONDS, QUEUE_MAX_SIZE, ) - +import logging # Track application start time for uptime calculation import time as _time _app_start_time = _time.time() - +logger = logging.getLogger('intercept.database') # Create Flask app app = Flask(__name__) +app.secret_key = "signals_intelligence_secret" # Required for flash messages # Disable Werkzeug debugger PIN (not needed for local development tool) os.environ['WERKZEUG_DEBUG_PIN'] = 'off' @@ -156,6 +159,49 @@ cleanup_manager.register(adsb_aircraft) # MAIN ROUTES # ============================================ +@app.before_request +def require_login(): + # Routes that don't require login (to avoid infinite redirect loop) + allowed_routes = ['login', 'static', 'favicon', 'health'] + + # If user is not logged in and the current route is not allowed... + if 'logged_in' not in session and request.endpoint not in allowed_routes: + return redirect(url_for('login')) + +@app.route('/logout') +def logout(): + session.pop('logged_in', None) + return redirect(url_for('login')) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + + # Connect to DB and find user + with get_db() as conn: + cursor = conn.execute( + 'SELECT password_hash, role FROM users WHERE username = ?', + (username,) + ) + user = cursor.fetchone() + + # Verify user exists and password is correct + if user and check_password_hash(user['password_hash'], password): + # Store data in session + session['logged_in'] = True + session['username'] = username + session['role'] = user['role'] + + logger.info(f"User '{username}' logged in successfully.") + return redirect(url_for('index')) + else: + logger.warning(f"Failed login attempt for username: {username}") + flash("ACCESS DENIED: INVALID CREDENTIALS", "error") + + return render_template('login.html', version=VERSION) + @app.route('/') def index() -> str: tools = { diff --git a/config.py b/config.py index bc6e81f..fc5514e 100644 --- a/config.py +++ b/config.py @@ -125,6 +125,9 @@ SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30) SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30) SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45) +# Admin credentials +ADMIN_USERNAME = _get_env('ADMIN_USERNAME', 'admin') +ADMIN_PASSWORD = _get_env('ADMIN_PASSWORD', 'admin') def configure_logging() -> None: """Configure application logging.""" diff --git a/instance/intercept.db b/instance/intercept.db deleted file mode 100644 index 6780b06..0000000 Binary files a/instance/intercept.db and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml index fc24f29..b6faae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "flask>=2.0.0", "skyfield>=1.45", "pyserial>=3.5", + "Werkzeug>=3.1.5", ] [project.urls] diff --git a/static/css/index.css b/static/css/index.css index 82df9b2..61437ab 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -990,6 +990,93 @@ header h1 .tagline { animation: pulse-glow 2s infinite; } +.help-btn { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 20px; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.help-btn:hover { + border-color: var(--accent-cyan); + color: var(--accent-cyan); + background: var(--bg-secondary); +} + +#depsBtn { + right: 5.5em; +} + +#helpBtn { + right: 3.5em; +} + +.theme-toggle { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 8em; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); + font-size: 16px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.theme-toggle:hover { + border-color: var(--accent-cyan); + color: var(--accent-cyan); + background: var(--bg-secondary); +} + +.theme-toggle .icon-sun, +.theme-toggle .icon-moon { + position: absolute; + transition: opacity 0.2s, transform 0.2s; +} + +.theme-toggle .icon-sun { + opacity: 0; + transform: rotate(-90deg); +} + +.theme-toggle .icon-moon { + opacity: 1; + transform: rotate(0deg); +} + +[data-theme="light"] .theme-toggle .icon-sun { + opacity: 1; + transform: rotate(0deg); +} + +[data-theme="light"] .theme-toggle .icon-moon { + opacity: 0; + transform: rotate(90deg); +} + .help-modal { display: none; position: fixed; @@ -5037,4 +5124,4 @@ body::before { .preset-freq-btn:active { transform: scale(0.98); -} +} \ No newline at end of file diff --git a/static/css/login.css b/static/css/login.css new file mode 100644 index 0000000..ca1da91 --- /dev/null +++ b/static/css/login.css @@ -0,0 +1,123 @@ +/* Container Layout */ +.landing-overlay { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: var(--bg-primary); + display: flex; + flex-direction: column; /* Stack logo, title, box vertically */ + align-items: center; + justify-content: center; + overflow: hidden; +} + +.landing-content { + position: relative; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +/* Background Effects */ +.landing-scanline { + position: absolute; + top: 0; left: 0; width: 100%; height: 2px; + background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent); + animation: scanlineMove 5s linear infinite; + opacity: 0.4; + z-index: 1; /* Behind content */ + pointer-events: none; +} + +@keyframes scanlineMove { + 0% { top: 0; } + 100% { top: 100%; } +} + +/* Typography */ +.landing-title { + font-family: 'JetBrains Mono', monospace; + font-size: 2.2rem; + font-weight: 700; + letter-spacing: 0.4em; + color: var(--text-primary); + margin: 20px 0 5px 0; + text-indent: 0.4em; + text-align: center; +} + +.landing-tagline { + font-family: 'JetBrains Mono', monospace; + color: var(--accent-cyan); + font-size: 0.9rem; + letter-spacing: 0.15em; + margin-bottom: 30px; +} + +/* The Login Box */ +.login-box { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + padding: 30px; + border-radius: 4px; + width: 380px; + z-index: 20; + box-shadow: 0 0 40px rgba(0, 0, 0, 0.6), inset 0 0 20px var(--accent-cyan-dim); + box-sizing: border-box; /* Ensures padding doesn't hide inputs */ + display: flex; + flex-direction: column; +} + +/* Hacker Style Error */ +.flash-error { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: var(--accent-red); + background: rgba(239, 68, 68, 0.1); + border-left: 3px solid var(--accent-red); + padding: 10px; + margin-bottom: 20px; + display: flex; + gap: 10px; + text-transform: uppercase; + box-sizing: border-box; +} + +.error-prefix { font-weight: 700; opacity: 0.7; } + +/* Inputs */ +.form-input { + width: 100%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--accent-cyan); + padding: 12px; + margin-bottom: 15px; + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + outline: none; + box-sizing: border-box; /* Crucial for visibility */ +} + +.landing-enter-btn { + width: 100%; + background: transparent; + border: 2px solid var(--accent-cyan); + color: var(--accent-cyan); + padding: 15px; + font-family: 'JetBrains Mono', monospace; + font-weight: 600; + letter-spacing: 3px; + cursor: pointer; + transition: all 0.3s ease; + box-sizing: border-box; +} + +.landing-version { + margin-top: 25px; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: rgba(255, 255, 255, 0.3); + letter-spacing: 2px; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e2b1a14..cc80149 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,12 +1,13 @@ + iNTERCEPT // See the Invisible - + @@ -16,6 +17,7 @@ +
@@ -24,16 +26,22 @@
@@ -130,13 +138,17 @@
⚠️

DISCLAIMER

- iNTERCEPT is a signal intelligence tool designed for educational purposes only. + iNTERCEPT is a signal intelligence tool designed for educational purposes + only.

By using this software, you acknowledge and agree that:

    -
  • This tool is intended for use by cyber security professionals and researchers only
  • -
  • You will only use this software in a controlled environment with proper authorization
  • -
  • Intercepting communications without consent may be illegal in your jurisdiction
  • +
  • This tool is intended for use by cyber security professionals and researchers only +
  • +
  • You will only use this software in a controlled environment with proper + authorization
  • +
  • Intercepting communications without consent may be illegal in your jurisdiction +
  • You are solely responsible for ensuring compliance with all applicable laws and regulations
  • The developers assume no liability for misuse of this software
@@ -145,13 +157,15 @@

- +
-