implement client users

This commit is contained in:
2025-09-10 20:53:13 +03:00
parent 45e14c9536
commit b8c163455c
3 changed files with 90 additions and 1 deletions

73
app.py
View File

@@ -7,6 +7,7 @@ from urllib.parse import urlparse
from flask import (
Flask, render_template, request, redirect, url_for, flash, session, jsonify, abort
)
from werkzeug.security import generate_password_hash, check_password_hash
# ----------------------------------------------------------------------------
# Config
@@ -121,6 +122,39 @@ def create_app():
rows = conn.execute(query).fetchall()
return render_template("apps_list.html", apps=rows)
@app.route("/admin/users/new", methods=["GET", "POST"])
@login_required
def user_create():
"""Admin page to create application users (username + password)."""
if request.method == "POST":
username = (request.form.get("username", "").strip()).lower()
password = request.form.get("password", "")
# Basic validations
if not username:
flash("Username is required", "danger")
return render_template("create_user.html")
if not password or len(password) < 6:
flash("Password must be at least 6 characters", "danger")
return render_template("create_user.html")
pwd_hash = generate_password_hash(password)
try:
with get_db() as conn:
conn.execute(
"INSERT INTO users (username, password_hash, created_at) VALUES (?,?,?)",
(username, pwd_hash, datetime.utcnow().isoformat(timespec='seconds')),
)
conn.commit()
flash("User created", "success")
return redirect(url_for("apps_list"))
except sqlite3.IntegrityError:
flash("Username already exists", "danger")
return render_template("create_user.html")
# GET
return render_template("create_user.html")
@app.route("/apps/new", methods=["GET", "POST"])
@login_required
def app_create():
@@ -221,6 +255,36 @@ def create_app():
# ---------------------------
# API Endpoint (public or protected — here public for simplicity)
# ---------------------------
@app.route("/api/login", methods=["POST"])
def api_login():
"""Simple login endpoint for the Android app.
Expects JSON: {"username": "...", "password": "..."}
Returns 200 on success with optional token field, 401 on failure.
"""
try:
payload = request.get_json(silent=True) or {}
username = (payload.get("username") or "").strip().lower()
password = payload.get("password") or ""
except Exception:
return jsonify({"error": "Invalid JSON"}), 400
if not username or not password:
return jsonify({"error": "username and password are required"}), 400
with sqlite3.connect(DB_PATH) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute(
"SELECT id, username, password_hash FROM users WHERE username=?",
(username,),
).fetchone()
if not row or not check_password_hash(row["password_hash"], password):
return jsonify({"error": "Invalid credentials"}), 401
# For now we do not issue a JWT; Android accepts null/absent token.
# If you later add JWT, return it here as {"token": "..."}
return jsonify({"token": None}), 200
@app.route("/api/apps", methods=["GET"])
def api_apps():
with get_db() as conn:
@@ -256,6 +320,13 @@ CREATE TABLE IF NOT EXISTS apps (
priority INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL
);
"""
@@ -263,7 +334,7 @@ def init_db():
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(DB_PATH) as conn:
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute(SCHEMA_SQL)
conn.executescript(SCHEMA_SQL)
conn.commit()

View File

@@ -12,6 +12,7 @@
<nav>
{% if session.get('user') %}
<a href="{{ url_for('apps_list') }}">Home</a>
<a href="{{ url_for('user_create') }}">Create User</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</nav>

View File

@@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}Create User LaunchPad Admin{% endblock %}
{% block content %}
<h1>Create User</h1>
<form method="post" class="form">
<label>Username
<input type="text" name="username" required autofocus />
</label>
<label>Password
<input type="password" name="password" minlength="6" required />
</label>
<div class="form-actions">
<a class="btn" href="{{ url_for('apps_list') }}">Cancel</a>
<button class="btn primary" type="submit">Create</button>
</div>
</form>
{% endblock %}