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 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:
+█████╗ ██████╗ ██████╗███████╗███████╗███████╗ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝ ███████║██║ ██║ █████╗ ███████╗███████╗ @@ -175,8 +190,10 @@ ██████╔╝███████╗██║ ╚████║██║███████╗██████╔╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚══════╝╚═════╝-@@ -201,26 +219,32 @@- root@intercepted:~# sudo access --grant-permission
++ root@intercepted:~# sudo access --grant-permission
-
[sudo] password for user: ********
Error: User is not in the sudoers file.
This incident will be reported. @@ -186,7 +203,8 @@ "In a world of locked doors, the man with the key is king.
And you, my friend, just threw away the key."-iNTERCEPT // See the Invisible v{{ version }}
-Signal Intelligence & Counter Surveillance Platform PAGER
+iNTERCEPT // See the Invisible v{{ version + }}
+Signal Intelligence & Counter Surveillance Platform PAGER
@@ -347,12 +371,18 @@ ▼-📟Pager -📡433MHz - ✈️Aircraft -📍APRS -🛰️Satellite -📻Listening Post +📟Pager +📡433MHz + ✈️Aircraft +📍APRS +🛰️Satellite +📻Listening Post @@ -362,8 +392,10 @@ ▼-📶WiFi -🔵Bluetooth +📶WiFi +🔵Bluetooth @@ -373,11 +405,13 @@ ▼@@ -392,8 +426,12 @@ 🌙 ☀️ --🔍TSCM +🔍TSCM 🔧 +🔧 ? ++ ⏻ +
| No signals detected | +No + signals detected |
No known devices registered. Devices you mark as "known" will be excluded from threat scoring.
' : - `No known devices registered. Devices you mark as "known" will be excluded from threat scoring.
' : + `No cases created. Cases help you organize sweeps and findings for specific locations or clients.
' : - `No cases created. Cases help you organize sweeps and findings for specific locations or clients.
' : + `No sweeps linked to this case yet.
' : - `No sweeps linked to this case yet.
' : + `No threats flagged in this case.
' : - `No threats flagged in this case.
' : + `${escapeHtml(step.details || step.description || '')}
${step.safety_note ? `Check which tools are installed for each mode. ● = Installed, ● = Missing
+Check which tools are installed for each mode. ● = Installed, ● = Missing
acarsdec which must be built from source.
- See github.com/TLeconte/acarsdec or run ./setup.sh for automated installation.
+ Note: ACARS decoding requires acarsdec which must be built from
+ source.
+ See github.com/TLeconte/acarsdec or run
+ ./setup.sh for automated installation.
- Data from celestrak.org. + Data from celestrak.org. Note: Some categories (Starlink, OneWeb) contain many satellites.