Untitled

Anonymous
plain_text
09/02/2026 11:12 AM
13.2 KB
1
Indexable
import os
import sqlite3
import secrets
import hashlib
import hmac

from datetime import datetime, timezone, timedelta
from flask import (
    Flask, request, jsonify, session,
    redirect, render_template_string
)

app = Flask(__name__)

app.secret_key = os.environ.get(
    "APP_SECRET",
    secrets.token_hex(32)
)

DB = "device_info.sqlite3"


def get_db():
    conn = sqlite3.connect(DB)
    conn.row_factory = sqlite3.Row
    return conn


def init_db():
    conn = get_db()

    conn.execute("""
        CREATE TABLE IF NOT EXISTS sessions (
            token TEXT PRIMARY KEY,
            created_at TEXT NOT NULL,
            expires_at TEXT NOT NULL,
            consented INTEGER DEFAULT 0
        )
    """)

    conn.execute("""
        CREATE TABLE IF NOT EXISTS devices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            token TEXT NOT NULL,
            received_at TEXT NOT NULL,
            platform TEXT,
            os_version TEXT,
            model TEXT,
            manufacturer TEXT,
            screen TEXT,
            browser TEXT
        )
    """)

    conn.commit()
    conn.close()


def hash_password(password, salt=None):
    salt = salt or secrets.token_bytes(16)

    digest = hashlib.pbkdf2_hmac(
        "sha256",
        password.encode(),
        salt,
        310000
    )

    return salt.hex() + "$" + digest.hex()


def verify_password(password, stored):
    try:
        salt_hex, digest_hex = stored.split("$", 1)

        salt = bytes.fromhex(salt_hex)

        digest = hashlib.pbkdf2_hmac(
            "sha256",
            password.encode(),
            salt,
            310000
        )

        return hmac.compare_digest(
            digest.hex(),
            digest_hex
        )

    except Exception:
        return False


@app.post("/api/admin/login")
def admin_login():

    data = request.get_json() or {}

    password = data.get("password", "")

    stored_hash = os.environ.get(
        "ADMIN_PASSWORD_HASH",
        ""
    )

    if not stored_hash:
        return jsonify({
            "error": "Admin password is not configured"
        }), 500

    if not verify_password(password, stored_hash):
        return jsonify({
            "error": "Invalid password"
        }), 401

    session["admin"] = True

    return jsonify({
        "success": True
    })


@app.post("/api/admin/logout")
def logout():

    session.clear()

    return jsonify({
        "success": True
    })


@app.post("/api/sessions")
def create_session():

    if not session.get("admin"):
        return jsonify({
            "error": "Unauthorized"
        }), 401

    token = secrets.token_urlsafe(32)

    now = datetime.now(timezone.utc)

    expires = now + timedelta(minutes=30)

    conn = get_db()

    conn.execute(
        """
        INSERT INTO sessions
        (token, created_at, expires_at, consented)
        VALUES (?, ?, ?, 0)
        """,
        (
            token,
            now.isoformat(),
            expires.isoformat()
        )
    )

    conn.commit()
    conn.close()

    return jsonify({
        "token": token,
        "consent_url": f"/consent/{token}"
    })


@app.get("/consent/<token>")
def consent_page(token):

    conn = get_db()

    row = conn.execute(
        "SELECT * FROM sessions WHERE token=?",
        (token,)
    ).fetchone()

    conn.close()

    if not row:
        return "Invalid link", 404

    expires = datetime.fromisoformat(
        row["expires_at"]
    )

    if expires < datetime.now(timezone.utc):
        return "This link has expired", 404

    return render_template_string("""
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport"
          content="width=device-width, initial-scale=1">

    <title>Device Information Consent</title>
</head>

<body>

<h2>Device Information Sharing</h2>

<p>
No information will be sent without your permission.
</p>

<p>
If you agree, only the following basic information
will be shared:
</p>

<ul>
    <li>Platform</li>
    <li>Operating System</li>
    <li>Device Model</li>
    <li>Manufacturer</li>
    <li>Screen Size</li>
    <li>Browser Information</li>
</ul>

<button onclick="shareInfo()">
    I Agree & Share
</button>

<p id="status"></p>

<script>

async function shareInfo() {

    const ua = navigator.userAgent;

    const data = {

        platform:
            navigator.platform || "unknown",

        os_version:
            (ua.match(/Android\\s([\\d.]+)/i) || [])[1]
            || "unknown",

        model:
            (ua.match(/;\\s*([^;)]+)\\s+Build\\//i) || [])[1]
            || "unknown",

        manufacturer:
            "browser-reported",

        screen:
            screen.width + "x" + screen.height,

        browser:
            ua
    };

    const response = await fetch(
        "/api/consent/{{ token }}",
        {
            method: "POST",

            headers: {
                "Content-Type":
                    "application/json"
            },

            body:
                JSON.stringify(data)
        }
    );

    if (response.ok) {

        document.getElementById("status")
            .textContent =
            "Information shared successfully.";

    } else {

        document.getElementById("status")
            .textContent =
            "Unable to share information.";

    }
}

</script>

</body>
</html>
""", token=token)


@app.post("/api/consent/<token>")
def receive_device_info(token):

    data = request.get_json() or {}

    allowed_fields = [
        "platform",
        "os_version",
        "model",
        "manufacturer",
        "screen",
        "browser"
    ]

    if not all(field in data for field in allowed_fields):

        return jsonify({
            "error": "Incomplete information"
        }), 400

    conn = get_db()

    session_row = conn.execute(
        "SELECT * FROM sessions WHERE token=?",
        (token,)
    ).fetchone()

    if not session_row:

        conn.close()

        return jsonify({
            "error": "Invalid session"
        }), 404

    expires = datetime.fromisoformat(
        session_row["expires_at"]
    )

    if expires < datetime.now(timezone.utc):

        conn.close()

        return jsonify({
            "error": "Session expired"
        }), 404

    now = datetime.now(timezone.utc).isoformat()

    conn.execute(
        """
        UPDATE sessions
        SET consented=1
        WHERE token=?
        """,
        (token,)
    )

    conn.execute(
        """
        INSERT INTO devices
        (
            token,
            received_at,
            platform,
            os_version,
            model,
            manufacturer,
            screen,
            browser
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            token,
            now,
            str(data["platform"])[:200],
            str(data["os_version"])[:200],
            str(data["model"])[:200],
            str(data["manufacturer"])[:200],
            str(data["screen"])[:200],
            str(data["browser"])[:500]
        )
    )

    conn.commit()
    conn.close()

    return jsonify({
        "success": True
    })


@app.get("/admin")
def dashboard():

    if not session.get("admin"):
        return redirect("/admin/login")

    conn = get_db()

    devices = conn.execute(
        """
        SELECT *
        FROM devices
        ORDER BY id DESC
        """
    ).fetchall()

    conn.close()

    return render_template_string("""
<!DOCTYPE html>

<html>

<head>

<meta name="viewport"
      content="width=device-width, initial-scale=1">

<title>Admin Dashboard</title>

</head>

<body>

<h2>Admin Dashboard</h2>

<button onclick="createLink()">
    Create Consent Link
</button>

<button onclick="logout()">
    Logout
</button>

<p id="link"></p>

<table border="1" cellpadding="8">

<tr>
    <th>Time</th>
    <th>Platform</th>
    <th>OS</th>
    <th>Model</th>
    <th>Manufacturer</th>
    <th>Screen</th>
    <th>Browser</th>
</tr>

{% for d in devices %}

<tr>

<td>{{ d.received_at }}</td>
<td>{{ d.platform }}</td>
<td>{{ d.os_version }}</td>
<td>{{ d.model }}</td>
<td>{{ d.manufacturer }}</td>
<td>{{ d.screen }}</td>
<td>{{ d.browser }}</td>

</tr>

{% endfor %}

</table>

<script>

async function createLink() {

    const response =
        await fetch(
            "/api/sessions",
            {
                method: "POST"
            }
        );

    const data =
        await response.json();

    const url =
        location.origin +
        data.consent_url;

    document.getElementById("link")
        .innerHTML =
        "<a href='" + url + "'>" +
        url + "</a>";

}

async function logout() {

    await fetch(
        "/api/admin/logout",
        {
            method: "POST"
        }
    );

    location =
        "/admin/login";
}

</script>

</body>

</html>
""", devices=devices)


@app.get("/admin/login")
def login_page():

    return render_template_string("""
<!DOCTYPE html>

<html>

<head>

<meta name="viewport"
      content="width=device-width, initial-scale=1">

<title>Admin Login</title>

</head>

<body>

<h2>Admin Login</h2>

<input
    id="password"
    type="password"
    placeholder="Admin Password"
>

<button onclick="login()">
    Login
</button>

<p id="message"></p>

<script>

async function login() {

    const password =
        document.getElementById(
            "password"
        ).value;

    const response =
        await fetch(
            "/api/admin/login",
            {
                method: "POST",

                headers: {
                    "Content-Type":
                        "application/json"
                },

                body:
                    JSON.stringify({
                        password: password
                    })
            }
        );

    if (response.ok) {

        location =
            "/admin";

    } else {

        document.getElementById(
            "message"
        ).textContent =
            "Invalid password";

    }
}

</script>

</body>

</html>
""")


if __name__ == "__main__":

    init_db()

    app.run(
        host="127.0.0.1",
        port=5000,
        debug=False
    )
Editor is loading...
Leave a Comment