mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
move users to database instead of config
This commit is contained in:
parent
7cf3abc850
commit
fafc0aab2f
@ -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})
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
36
migrations/025_create_user_table.py
Normal file
36
migrations/025_create_user_table.py
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user