Update app.py
This commit is contained in:
parent
af19566309
commit
5e080893e2
215
app.py
215
app.py
@ -1,5 +1,6 @@
|
|||||||
# app.py
|
# app.py
|
||||||
import base64
|
import base64
|
||||||
|
import hashlib
|
||||||
import html
|
import html
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
@ -22,13 +23,19 @@ from werkzeug.utils import secure_filename
|
|||||||
from features import maybe_cleanup_orphaned_attachments
|
from features import maybe_cleanup_orphaned_attachments
|
||||||
from routes import register_routes
|
from routes import register_routes
|
||||||
|
|
||||||
|
|
||||||
APP_ROOT = Path(__file__).resolve().parent
|
APP_ROOT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
DB_PATH = os.environ.get("CHAT_DB_PATH", str(APP_ROOT / "chat.db"))
|
DB_PATH = os.environ.get("CHAT_DB_PATH", str(APP_ROOT / "chat.db"))
|
||||||
UPLOAD_DIR = Path(os.environ.get("CHAT_UPLOAD_DIR", str(APP_ROOT / "uploads")))
|
UPLOAD_DIR = Path(os.environ.get("CHAT_UPLOAD_DIR", str(APP_ROOT / "uploads")))
|
||||||
SECRET_KEY_PATH = os.environ.get("CHAT_SECRET_KEY_PATH", str(Path(DB_PATH).resolve().parent / "flask_secret.key"))
|
|
||||||
CLIENT_HASH_KEY_PATH = os.environ.get("CHAT_CLIENT_HASH_KEY_PATH", str(Path(DB_PATH).resolve().parent / "client_hash.key"))
|
SECRET_KEY_PATH = os.environ.get(
|
||||||
|
"CHAT_SECRET_KEY_PATH",
|
||||||
|
str(Path(DB_PATH).resolve().parent / "flask_secret.key"),
|
||||||
|
)
|
||||||
|
CLIENT_HASH_KEY_PATH = os.environ.get(
|
||||||
|
"CHAT_CLIENT_HASH_KEY_PATH",
|
||||||
|
str(Path(DB_PATH).resolve().parent / "client_hash.key"),
|
||||||
|
)
|
||||||
|
|
||||||
MAX_REQUEST_BYTES = 6 * 1024 * 1024
|
MAX_REQUEST_BYTES = 6 * 1024 * 1024
|
||||||
MAX_MESSAGE_LENGTH = 1000
|
MAX_MESSAGE_LENGTH = 1000
|
||||||
@ -36,34 +43,33 @@ MAX_USERNAME_LENGTH = 30
|
|||||||
MIN_USERNAME_LENGTH = 3
|
MIN_USERNAME_LENGTH = 3
|
||||||
MIN_PASSWORD_LENGTH = 7
|
MIN_PASSWORD_LENGTH = 7
|
||||||
MAX_PASSWORD_LENGTH = 256
|
MAX_PASSWORD_LENGTH = 256
|
||||||
|
|
||||||
MAX_HISTORY_MESSAGES = 100
|
MAX_HISTORY_MESSAGES = 100
|
||||||
MAX_INCREMENTAL_MESSAGES = 100
|
MAX_INCREMENTAL_MESSAGES = 100
|
||||||
MAX_FEED_MESSAGES = 50
|
MAX_FEED_MESSAGES = 50
|
||||||
MAX_THREAD_REPLIES = 100
|
MAX_THREAD_REPLIES = 100
|
||||||
|
|
||||||
MAX_ATTACHMENT_BYTES = 100 * 1024 * 1024
|
MAX_ATTACHMENT_BYTES = 100 * 1024 * 1024
|
||||||
MAX_ATTACHMENT_NAME_LENGTH = 150
|
MAX_ATTACHMENT_NAME_LENGTH = 150
|
||||||
ATTACHMENT_ORPHAN_TTL_HOURS = 24
|
ATTACHMENT_ORPHAN_TTL_HOURS = 24
|
||||||
ATTACHMENT_CLEANUP_INTERVAL_SECONDS = 60 * 60
|
ATTACHMENT_CLEANUP_INTERVAL_SECONDS = 60 * 60
|
||||||
|
|
||||||
INVITE_TTL_HOURS = 72
|
INVITE_TTL_HOURS = 72
|
||||||
TEAM_INVITE_TTL_HOURS = 72
|
TEAM_INVITE_TTL_HOURS = 72
|
||||||
TYPING_TTL_SECONDS = 7
|
TYPING_TTL_SECONDS = 7
|
||||||
PRESENCE_TTL_SECONDS = 40
|
PRESENCE_TTL_SECONDS = 40
|
||||||
|
|
||||||
MAX_PROFILE_BIO_LENGTH = 5000
|
MAX_PROFILE_BIO_LENGTH = 5000
|
||||||
MAX_DISPLAY_NAME_LENGTH = 80
|
MAX_DISPLAY_NAME_LENGTH = 80
|
||||||
MAX_LOCATION_LENGTH = 120
|
MAX_LOCATION_LENGTH = 120
|
||||||
MAX_WEBSITE_LENGTH = 300
|
MAX_WEBSITE_LENGTH = 300
|
||||||
MAX_CUSTOM_CSS_LENGTH = 20000
|
MAX_CUSTOM_CSS_LENGTH = 20000
|
||||||
MAX_OWNED_TEAMS_PER_USER = 3
|
MAX_OWNED_TEAMS_PER_USER = 3
|
||||||
|
|
||||||
MAX_MESSAGE_EDIT_WINDOW_SECONDS = 15 * 60
|
MAX_MESSAGE_EDIT_WINDOW_SECONDS = 15 * 60
|
||||||
MAX_SEARCH_QUERY_LENGTH = 120
|
MAX_SEARCH_QUERY_LENGTH = 120
|
||||||
MAX_SEARCH_RESULTS = 100
|
MAX_SEARCH_RESULTS = 100
|
||||||
READ_RECEIPT_BATCH_LIMIT = 200
|
READ_RECEIPT_BATCH_LIMIT = 200
|
||||||
|
|
||||||
|
CALLS_BASE = os.environ.get("CALLS_BASE", "https://calls.example.com").rstrip("/")
|
||||||
|
CALL_HELPER_SHARED_SECRET = os.environ.get("CALL_HELPER_SHARED_SECRET", "").strip()
|
||||||
|
CALL_TOKEN_TTL_SECONDS = int(os.environ.get("CALL_TOKEN_TTL_SECONDS", "60"))
|
||||||
|
|
||||||
ALLOWED_ATTACHMENT_EXTENSIONS = {
|
ALLOWED_ATTACHMENT_EXTENSIONS = {
|
||||||
"txt",
|
"txt",
|
||||||
"pdf",
|
"pdf",
|
||||||
@ -189,7 +195,12 @@ app.secret_key = SECRET_KEY
|
|||||||
|
|
||||||
trusted_proxy_hops = int(os.environ.get("TRUSTED_PROXY_HOPS", "0"))
|
trusted_proxy_hops = int(os.environ.get("TRUSTED_PROXY_HOPS", "0"))
|
||||||
if trusted_proxy_hops > 0:
|
if trusted_proxy_hops > 0:
|
||||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=trusted_proxy_hops, x_proto=trusted_proxy_hops, x_host=trusted_proxy_hops)
|
app.wsgi_app = ProxyFix(
|
||||||
|
app.wsgi_app,
|
||||||
|
x_for=trusted_proxy_hops,
|
||||||
|
x_proto=trusted_proxy_hops,
|
||||||
|
x_host=trusted_proxy_hops,
|
||||||
|
)
|
||||||
|
|
||||||
app.config.update(
|
app.config.update(
|
||||||
MAX_CONTENT_LENGTH=MAX_REQUEST_BYTES,
|
MAX_CONTENT_LENGTH=MAX_REQUEST_BYTES,
|
||||||
@ -204,7 +215,8 @@ app.config.update(
|
|||||||
def get_asset_version() -> str:
|
def get_asset_version() -> str:
|
||||||
try:
|
try:
|
||||||
js_dir = APP_ROOT / "static" / "js"
|
js_dir = APP_ROOT / "static" / "js"
|
||||||
values = [int((APP_ROOT / "static" / "style.css").stat().st_mtime), *[int(path.stat().st_mtime) for path in js_dir.glob("*.js")]]
|
values = [int((APP_ROOT / "static" / "style.css").stat().st_mtime)]
|
||||||
|
values.extend(int(path.stat().st_mtime) for path in js_dir.glob("*.js"))
|
||||||
return str(max(values))
|
return str(max(values))
|
||||||
except OSError:
|
except OSError:
|
||||||
return "1"
|
return "1"
|
||||||
@ -217,7 +229,6 @@ def get_db() -> sqlite3.Connection:
|
|||||||
|
|
||||||
conn = sqlite3.connect(DB_PATH, timeout=8, isolation_level=None, check_same_thread=False)
|
conn = sqlite3.connect(DB_PATH, timeout=8, isolation_level=None, check_same_thread=False)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
|
||||||
conn.execute("PRAGMA foreign_keys = ON")
|
conn.execute("PRAGMA foreign_keys = ON")
|
||||||
conn.execute("PRAGMA journal_mode = WAL")
|
conn.execute("PRAGMA journal_mode = WAL")
|
||||||
conn.execute("PRAGMA busy_timeout = 8000")
|
conn.execute("PRAGMA busy_timeout = 8000")
|
||||||
@ -240,7 +251,7 @@ def db_write(query: str, params: tuple = ()) -> sqlite3.Cursor:
|
|||||||
raise
|
raise
|
||||||
if attempt == 4:
|
if attempt == 4:
|
||||||
raise
|
raise
|
||||||
time.sleep(0.05 * (2**attempt))
|
time.sleep(0.05 * (2 ** attempt))
|
||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
|
|
||||||
@ -657,20 +668,44 @@ def client_ip() -> str:
|
|||||||
return (request.remote_addr or "unknown")[:128]
|
return (request.remote_addr or "unknown")[:128]
|
||||||
|
|
||||||
|
|
||||||
def hash_identifier(value: str) -> str:
|
|
||||||
return hmac.new(CLIENT_HASH_KEY.encode("utf-8"), value.encode("utf-8"), ctx_hash()).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def ctx_hash():
|
def ctx_hash():
|
||||||
import hashlib
|
|
||||||
return hashlib.sha256
|
return hashlib.sha256
|
||||||
|
|
||||||
|
|
||||||
|
def hash_identifier(value: str) -> str:
|
||||||
|
return hmac.new(
|
||||||
|
CLIENT_HASH_KEY.encode("utf-8"),
|
||||||
|
value.encode("utf-8"),
|
||||||
|
ctx_hash(),
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def b64url_encode_bytes(data: bytes) -> str:
|
||||||
|
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
|
||||||
|
|
||||||
|
|
||||||
|
def sign_call_helper_token(payload: dict) -> str:
|
||||||
|
payload_json = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
|
||||||
|
payload_bytes = payload_json.encode("utf-8")
|
||||||
|
signature = hmac.new(
|
||||||
|
CALL_HELPER_SHARED_SECRET.encode("utf-8"),
|
||||||
|
payload_bytes,
|
||||||
|
hashlib.sha256,
|
||||||
|
).digest()
|
||||||
|
return f"{b64url_encode_bytes(payload_bytes)}.{b64url_encode_bytes(signature)}"
|
||||||
|
|
||||||
|
|
||||||
def current_client_hash() -> str:
|
def current_client_hash() -> str:
|
||||||
return hash_identifier(client_ip())
|
return hash_identifier(client_ip())
|
||||||
|
|
||||||
|
|
||||||
def audit(action: str, actor_user_id: Optional[int] = None, target_type: Optional[str] = None, target_id: Optional[str] = None, details: Optional[str] = None) -> None:
|
def audit(
|
||||||
|
action: str,
|
||||||
|
actor_user_id: Optional[int] = None,
|
||||||
|
target_type: Optional[str] = None,
|
||||||
|
target_id: Optional[str] = None,
|
||||||
|
details: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
db_write(
|
db_write(
|
||||||
"""
|
"""
|
||||||
INSERT INTO audit_log (
|
INSERT INTO audit_log (
|
||||||
@ -683,7 +718,15 @@ def audit(action: str, actor_user_id: Optional[int] = None, target_type: Optiona
|
|||||||
created_at
|
created_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(actor_user_id, action, target_type, target_id, details, current_client_hash(), now_iso()),
|
(
|
||||||
|
actor_user_id,
|
||||||
|
action,
|
||||||
|
target_type,
|
||||||
|
target_id,
|
||||||
|
details,
|
||||||
|
current_client_hash(),
|
||||||
|
now_iso(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -759,7 +802,16 @@ def rate_limited(scope: str):
|
|||||||
retry_after = check_rate_limit(scope)
|
retry_after = check_rate_limit(scope)
|
||||||
if retry_after is None:
|
if retry_after is None:
|
||||||
return None
|
return None
|
||||||
return jsonify({"error": "Too many requests. Please wait and try again.", "code": "rate_limited", "retry_after": retry_after}), 429
|
return (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"error": "Too many requests. Please wait and try again.",
|
||||||
|
"code": "rate_limited",
|
||||||
|
"retry_after": retry_after,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
429,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def safe_json() -> dict:
|
def safe_json() -> dict:
|
||||||
@ -858,7 +910,6 @@ def login_user(row: sqlite3.Row | dict) -> dict:
|
|||||||
session.permanent = True
|
session.permanent = True
|
||||||
session["user_id"] = row["id"]
|
session["user_id"] = row["id"]
|
||||||
session["csrf_token"] = secrets.token_urlsafe(32)
|
session["csrf_token"] = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": row["id"],
|
"id": row["id"],
|
||||||
"username": row["username"],
|
"username": row["username"],
|
||||||
@ -879,10 +930,17 @@ def require_writable_user():
|
|||||||
user, error = require_user()
|
user, error = require_user()
|
||||||
if error:
|
if error:
|
||||||
return None, error
|
return None, error
|
||||||
|
|
||||||
if user["status"] == "restricted":
|
if user["status"] == "restricted":
|
||||||
return None, (jsonify({"error": "Your account is temporarily restricted.", "code": "restricted", "restricted_until": user["restricted_until"]}), 403)
|
return None, (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"error": "Your account is temporarily restricted.",
|
||||||
|
"code": "restricted",
|
||||||
|
"restricted_until": user["restricted_until"],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
403,
|
||||||
|
)
|
||||||
return user, None
|
return user, None
|
||||||
|
|
||||||
|
|
||||||
@ -905,7 +963,10 @@ def require_admin():
|
|||||||
|
|
||||||
|
|
||||||
def get_asset_template_values():
|
def get_asset_template_values():
|
||||||
return {"asset_version": get_asset_version(), "csp_nonce": getattr(g, "csp_nonce", "")}
|
return {
|
||||||
|
"asset_version": get_asset_version(),
|
||||||
|
"csp_nonce": getattr(g, "csp_nonce", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
@ -942,7 +1003,6 @@ def add_security_headers(response):
|
|||||||
"script-src 'self'; "
|
"script-src 'self'; "
|
||||||
"connect-src 'self';"
|
"connect-src 'self';"
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.path.startswith("/static/"):
|
if request.path.startswith("/static/"):
|
||||||
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
||||||
else:
|
else:
|
||||||
@ -1397,6 +1457,42 @@ def init_db() -> None:
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS group_call_sessions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
scope_kind TEXT NOT NULL,
|
||||||
|
scope_id INTEGER NOT NULL,
|
||||||
|
scope_slug TEXT NOT NULL,
|
||||||
|
helper_call_id TEXT NOT NULL UNIQUE,
|
||||||
|
created_by_user_id INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active',
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
ended_at TEXT,
|
||||||
|
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS group_call_invites (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
group_call_session_id INTEGER NOT NULL,
|
||||||
|
inviter_user_id INTEGER NOT NULL,
|
||||||
|
invitee_user_id INTEGER NOT NULL,
|
||||||
|
helper_token TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
expires_at TEXT NOT NULL,
|
||||||
|
responded_at TEXT,
|
||||||
|
FOREIGN KEY (group_call_session_id) REFERENCES group_call_sessions(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (inviter_user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (invitee_user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
if not column_exists(db, "messages", "room_id"):
|
if not column_exists(db, "messages", "room_id"):
|
||||||
db.execute("ALTER TABLE messages ADD COLUMN room_id INTEGER")
|
db.execute("ALTER TABLE messages ADD COLUMN room_id INTEGER")
|
||||||
if not column_exists(db, "messages", "attachment_id"):
|
if not column_exists(db, "messages", "attachment_id"):
|
||||||
@ -1437,6 +1533,8 @@ def init_db() -> None:
|
|||||||
db.execute("CREATE INDEX IF NOT EXISTS idx_direct_conversation_members_user ON direct_conversation_members(user_id, direct_conversation_id)")
|
db.execute("CREATE INDEX IF NOT EXISTS idx_direct_conversation_members_user ON direct_conversation_members(user_id, direct_conversation_id)")
|
||||||
db.execute("CREATE INDEX IF NOT EXISTS idx_call_signals_lookup ON call_signals(call_session_id, id)")
|
db.execute("CREATE INDEX IF NOT EXISTS idx_call_signals_lookup ON call_signals(call_session_id, id)")
|
||||||
db.execute("CREATE INDEX IF NOT EXISTS idx_user_settings_name_color ON user_settings(user_id, name_color)")
|
db.execute("CREATE INDEX IF NOT EXISTS idx_user_settings_name_color ON user_settings(user_id, name_color)")
|
||||||
|
db.execute("CREATE INDEX IF NOT EXISTS idx_group_call_sessions_scope ON group_call_sessions(scope_kind, scope_id, status)")
|
||||||
|
db.execute("CREATE INDEX IF NOT EXISTS idx_group_call_invites_invitee_status ON group_call_invites(invitee_user_id, status, expires_at)")
|
||||||
|
|
||||||
general = db.execute("SELECT id FROM rooms WHERE slug = 'general'").fetchone()
|
general = db.execute("SELECT id FROM rooms WHERE slug = 'general'").fetchone()
|
||||||
if not general:
|
if not general:
|
||||||
@ -1457,7 +1555,10 @@ def init_db() -> None:
|
|||||||
else:
|
else:
|
||||||
general_id = general["id"]
|
general_id = general["id"]
|
||||||
|
|
||||||
db.execute("UPDATE messages SET room_id = ? WHERE room_id IS NULL AND direct_conversation_id IS NULL", (general_id,))
|
db.execute(
|
||||||
|
"UPDATE messages SET room_id = ? WHERE room_id IS NULL AND direct_conversation_id IS NULL",
|
||||||
|
(general_id,),
|
||||||
|
)
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
@ -1517,10 +1618,12 @@ def init_db() -> None:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
for room in legacy_rooms:
|
for room in legacy_rooms:
|
||||||
existing_channel = db.execute("SELECT id FROM channels WHERE legacy_room_id = ?", (room["id"],)).fetchone()
|
existing_channel = db.execute(
|
||||||
|
"SELECT id FROM channels WHERE legacy_room_id = ?",
|
||||||
|
(room["id"],),
|
||||||
|
).fetchone()
|
||||||
if existing_channel:
|
if existing_channel:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO channels (
|
INSERT INTO channels (
|
||||||
@ -1534,7 +1637,15 @@ def init_db() -> None:
|
|||||||
is_active
|
is_active
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, 1)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, 1)
|
||||||
""",
|
""",
|
||||||
(general_team_id, room["id"], room["slug"], room["name"], room["description"], room["created_by_user_id"], room["created_at"]),
|
(
|
||||||
|
general_team_id,
|
||||||
|
room["id"],
|
||||||
|
room["slug"],
|
||||||
|
room["name"],
|
||||||
|
room["description"],
|
||||||
|
room["created_by_user_id"],
|
||||||
|
room["created_at"],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
@ -1620,7 +1731,10 @@ def init_db() -> None:
|
|||||||
(now_iso(),),
|
(now_iso(),),
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_message_attachment_counts = db.execute("SELECT COUNT(*) AS count FROM message_attachments").fetchone()["count"]
|
existing_message_attachment_counts = db.execute(
|
||||||
|
"SELECT COUNT(*) AS count FROM message_attachments"
|
||||||
|
).fetchone()["count"]
|
||||||
|
|
||||||
if existing_message_attachment_counts == 0:
|
if existing_message_attachment_counts == 0:
|
||||||
rows = db.execute(
|
rows = db.execute(
|
||||||
"""
|
"""
|
||||||
@ -1630,7 +1744,6 @@ def init_db() -> None:
|
|||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
@ -1658,7 +1771,10 @@ def bootstrap_admin() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
with get_db() as db:
|
with get_db() as db:
|
||||||
existing = db.execute("SELECT id FROM users WHERE username = ?", (username,)).fetchone()
|
existing = db.execute(
|
||||||
|
"SELECT id FROM users WHERE username = ?",
|
||||||
|
(username,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
db.execute(
|
db.execute(
|
||||||
@ -1736,7 +1852,13 @@ def api_csrf():
|
|||||||
@app.route("/api/me")
|
@app.route("/api/me")
|
||||||
def api_me():
|
def api_me():
|
||||||
user = current_user()
|
user = current_user()
|
||||||
return jsonify({"authenticated": bool(user), "user": user, "csrf_token": csrf_token()})
|
return jsonify(
|
||||||
|
{
|
||||||
|
"authenticated": bool(user),
|
||||||
|
"user": user,
|
||||||
|
"csrf_token": csrf_token(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/register", methods=["POST"])
|
@app.route("/api/register", methods=["POST"])
|
||||||
@ -1822,7 +1944,13 @@ def register():
|
|||||||
authenticated = login_user(user)
|
authenticated = login_user(user)
|
||||||
audit("account_registered", user["id"], "user", str(user["id"]))
|
audit("account_registered", user["id"], "user", str(user["id"]))
|
||||||
|
|
||||||
return jsonify({"status": "registered", "user": authenticated, "csrf_token": csrf_token()}), 201
|
return jsonify(
|
||||||
|
{
|
||||||
|
"status": "registered",
|
||||||
|
"user": authenticated,
|
||||||
|
"csrf_token": csrf_token(),
|
||||||
|
}
|
||||||
|
), 201
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/login", methods=["POST"])
|
@app.route("/api/login", methods=["POST"])
|
||||||
@ -1868,14 +1996,19 @@ def login():
|
|||||||
authenticated = login_user(row)
|
authenticated = login_user(row)
|
||||||
audit("account_logged_in", row["id"], "user", str(row["id"]))
|
audit("account_logged_in", row["id"], "user", str(row["id"]))
|
||||||
|
|
||||||
return jsonify({"status": "logged_in", "user": authenticated, "csrf_token": csrf_token()})
|
return jsonify(
|
||||||
|
{
|
||||||
|
"status": "logged_in",
|
||||||
|
"user": authenticated,
|
||||||
|
"csrf_token": csrf_token(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/logout", methods=["POST"])
|
@app.route("/api/logout", methods=["POST"])
|
||||||
def logout():
|
def logout():
|
||||||
if not csrf_valid():
|
if not csrf_valid():
|
||||||
return jsonify({"error": "Security token expired.", "code": "csrf"}), 403
|
return jsonify({"error": "Security token expired.", "code": "csrf"}), 403
|
||||||
|
|
||||||
session.clear()
|
session.clear()
|
||||||
return jsonify({"status": "logged_out", "csrf_token": csrf_token()})
|
return jsonify({"status": "logged_out", "csrf_token": csrf_token()})
|
||||||
|
|
||||||
@ -1914,6 +2047,10 @@ register_routes(
|
|||||||
"normalize_username": normalize_username,
|
"normalize_username": normalize_username,
|
||||||
"audit": audit,
|
"audit": audit,
|
||||||
"hash_identifier": hash_identifier,
|
"hash_identifier": hash_identifier,
|
||||||
|
"sign_call_helper_token": sign_call_helper_token,
|
||||||
|
"CALLS_BASE": CALLS_BASE,
|
||||||
|
"CALL_TOKEN_TTL_SECONDS": CALL_TOKEN_TTL_SECONDS,
|
||||||
|
"CALL_HELPER_SHARED_SECRET": CALL_HELPER_SHARED_SECRET,
|
||||||
"room_by_id": room_by_id,
|
"room_by_id": room_by_id,
|
||||||
"get_room_by_slug": get_room_by_slug,
|
"get_room_by_slug": get_room_by_slug,
|
||||||
"can_access_room": can_access_room,
|
"can_access_room": can_access_room,
|
||||||
@ -1936,6 +2073,7 @@ register_routes(
|
|||||||
"team_payload": team_payload,
|
"team_payload": team_payload,
|
||||||
"channel_payload": channel_payload,
|
"channel_payload": channel_payload,
|
||||||
"user_payload": user_payload,
|
"user_payload": user_payload,
|
||||||
|
"get_dm_other_user": lambda dm_id, user_id: __import__("features").get_dm_other_user(get_db, dm_id, user_id),
|
||||||
"MESSAGE_SELECT": MESSAGE_SELECT,
|
"MESSAGE_SELECT": MESSAGE_SELECT,
|
||||||
"team_page_select": team_page_select,
|
"team_page_select": team_page_select,
|
||||||
"UPLOAD_DIR": UPLOAD_DIR,
|
"UPLOAD_DIR": UPLOAD_DIR,
|
||||||
@ -1978,7 +2116,6 @@ register_routes(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
init_db()
|
init_db()
|
||||||
bootstrap_admin()
|
bootstrap_admin()
|
||||||
|
|
||||||
@ -1987,4 +2124,4 @@ if __name__ == "__main__":
|
|||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=int(os.environ.get("PORT", 5000)),
|
port=int(os.environ.get("PORT", 5000)),
|
||||||
debug=os.environ.get("FLASK_DEBUG", "0") == "1",
|
debug=os.environ.get("FLASK_DEBUG", "0") == "1",
|
||||||
)
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user