move users to database instead of config

This commit is contained in:
Blake Blackshear 2024-05-09 06:23:55 -05:00
parent 7cf3abc850
commit fafc0aab2f
4 changed files with 125 additions and 12 deletions

View File

@ -10,12 +10,14 @@ import time
from datetime import datetime
from pathlib import Path
from flask import Blueprint, current_app, make_response, request
from flask import Blueprint, current_app, jsonify, make_response, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from joserfc import jwt
from peewee import DoesNotExist
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
from frigate.models import User
logger = logging.getLogger(__name__)
@ -204,17 +206,13 @@ def login():
content = request.get_json()
user = content["user"]
password = content["password"]
password_hash = next(
(
u.password_hash
for u in current_app.frigate_config.auth.users
if u.user == user
),
None,
)
# if the user wasn't found in the config
if password_hash is None:
make_response({"message": "Login failed"}, 400)
try:
db_user: User = User.get_by_id(user)
except DoesNotExist:
return make_response({"message": "Login failed"}, 400)
password_hash = db_user.password_hash
if verify_password(password, password_hash):
expiration = int(time.time()) + JWT_SESSION_LENGTH
encoded_jwt = create_encoded_jwt(user, expiration, current_app.jwt_token)
@ -222,3 +220,49 @@ def login():
set_jwt_cookie(response, JWT_COOKIE_NAME, encoded_jwt, expiration)
return response
return make_response({"message": "Login failed"}, 400)
@AuthBp.route("/users")
def get_users():
exports = User.select(User.username).order_by(User.username).dicts().iterator()
return jsonify([e for e in exports])
@AuthBp.route("/users", methods=["POST"])
def create_user():
HASH_ITERATIONS = current_app.frigate_config.auth.hash_iterations
request_data = request.get_json()
password_hash = hash_password(request_data["password"], iterations=HASH_ITERATIONS)
User.insert(
{
User.username: request_data["username"],
User.password_hash: password_hash,
}
).execute()
return jsonify({"username": request_data["username"]})
@AuthBp.route("/users/<username>", methods=["DELETE"])
def delete_user(username: str):
User.delete_by_id(username).execute()
return jsonify({"success": True})
@AuthBp.route("/users/<username>/password", methods=["PUT"])
def update_password(username: str):
HASH_ITERATIONS = current_app.frigate_config.auth.hash_iterations
request_data = request.get_json()
password_hash = hash_password(request_data["password"], iterations=HASH_ITERATIONS)
User.set_by_id(
username,
{
User.password_hash: password_hash,
},
)
return jsonify({"success": True})

View File

@ -3,6 +3,7 @@ import datetime
import logging
import multiprocessing as mp
import os
import secrets
import shutil
import signal
import sys
@ -19,6 +20,7 @@ from playhouse.sqliteq import SqliteQueueDatabase
from pydantic import ValidationError
from frigate.api.app import create_app
from frigate.api.auth import hash_password
from frigate.comms.config_updater import ConfigPublisher
from frigate.comms.detections_updater import DetectionProxy
from frigate.comms.dispatcher import Communicator, Dispatcher
@ -49,6 +51,7 @@ from frigate.models import (
Regions,
ReviewSegment,
Timeline,
User,
)
from frigate.object_detection import ObjectDetectProcess
from frigate.object_processing import TrackedObjectProcessor
@ -338,6 +341,7 @@ class FrigateApp:
Regions,
ReviewSegment,
Timeline,
User,
]
self.db.bind(models)
@ -587,6 +591,29 @@ class FrigateApp:
f"The current SHM size of {available_shm}MB is too small, recommend increasing it to at least {min_req_shm}MB."
)
def init_auth(self) -> None:
if self.config.auth.enabled:
if User.select().count() == 0:
password = secrets.token_hex(16)
password_hash = hash_password(
password, iterations=self.config.auth.hash_iterations
)
User.insert(
{
User.username: "admin",
User.password_hash: password_hash,
}
).execute()
logger.info("********************************************************")
logger.info("********************************************************")
logger.info("*** Auth is enabled, but no users exist. ***")
logger.info("*** Created a default user: ***")
logger.info("*** User: admin ***")
logger.info(f"*** Password: {password} ***")
logger.info("********************************************************")
logger.info("********************************************************")
def start(self) -> None:
parser = argparse.ArgumentParser(
prog="Frigate",
@ -664,6 +691,7 @@ class FrigateApp:
self.start_record_cleanup()
self.start_watchdog()
self.check_shm()
self.init_auth()
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
self.stop()

View File

@ -113,3 +113,8 @@ class RecordingsToDelete(Model): # type: ignore[misc]
class Meta:
temporary = True
class User(Model): # type: ignore[misc]
username = CharField(null=False, primary_key=True, max_length=30)
password_hash = CharField(null=False, max_length=120)

View File

@ -0,0 +1,36 @@
"""Peewee migrations -- 025_create_user_table.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
import peewee as pw
SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs):
migrator.sql(
'CREATE TABLE IF NOT EXISTS "user" ("username" VARCHAR(30) NOT NULL PRIMARY KEY, "password_hash" VARCHAR(120) NOT NULL)'
)
def rollback(migrator, database, fake=False, **kwargs):
pass