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 datetime import datetime
|
||||||
from pathlib import Path
|
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 import Limiter
|
||||||
from flask_limiter.util import get_remote_address
|
from flask_limiter.util import get_remote_address
|
||||||
from joserfc import jwt
|
from joserfc import jwt
|
||||||
|
from peewee import DoesNotExist
|
||||||
|
|
||||||
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
||||||
|
from frigate.models import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -204,17 +206,13 @@ def login():
|
|||||||
content = request.get_json()
|
content = request.get_json()
|
||||||
user = content["user"]
|
user = content["user"]
|
||||||
password = content["password"]
|
password = content["password"]
|
||||||
password_hash = next(
|
|
||||||
(
|
try:
|
||||||
u.password_hash
|
db_user: User = User.get_by_id(user)
|
||||||
for u in current_app.frigate_config.auth.users
|
except DoesNotExist:
|
||||||
if u.user == user
|
return make_response({"message": "Login failed"}, 400)
|
||||||
),
|
|
||||||
None,
|
password_hash = db_user.password_hash
|
||||||
)
|
|
||||||
# if the user wasn't found in the config
|
|
||||||
if password_hash is None:
|
|
||||||
make_response({"message": "Login failed"}, 400)
|
|
||||||
if verify_password(password, password_hash):
|
if verify_password(password, password_hash):
|
||||||
expiration = int(time.time()) + JWT_SESSION_LENGTH
|
expiration = int(time.time()) + JWT_SESSION_LENGTH
|
||||||
encoded_jwt = create_encoded_jwt(user, expiration, current_app.jwt_token)
|
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)
|
set_jwt_cookie(response, JWT_COOKIE_NAME, encoded_jwt, expiration)
|
||||||
return response
|
return response
|
||||||
return make_response({"message": "Login failed"}, 400)
|
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 logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
@ -19,6 +20,7 @@ from playhouse.sqliteq import SqliteQueueDatabase
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.api.app import create_app
|
from frigate.api.app import create_app
|
||||||
|
from frigate.api.auth import hash_password
|
||||||
from frigate.comms.config_updater import ConfigPublisher
|
from frigate.comms.config_updater import ConfigPublisher
|
||||||
from frigate.comms.detections_updater import DetectionProxy
|
from frigate.comms.detections_updater import DetectionProxy
|
||||||
from frigate.comms.dispatcher import Communicator, Dispatcher
|
from frigate.comms.dispatcher import Communicator, Dispatcher
|
||||||
@ -49,6 +51,7 @@ from frigate.models import (
|
|||||||
Regions,
|
Regions,
|
||||||
ReviewSegment,
|
ReviewSegment,
|
||||||
Timeline,
|
Timeline,
|
||||||
|
User,
|
||||||
)
|
)
|
||||||
from frigate.object_detection import ObjectDetectProcess
|
from frigate.object_detection import ObjectDetectProcess
|
||||||
from frigate.object_processing import TrackedObjectProcessor
|
from frigate.object_processing import TrackedObjectProcessor
|
||||||
@ -338,6 +341,7 @@ class FrigateApp:
|
|||||||
Regions,
|
Regions,
|
||||||
ReviewSegment,
|
ReviewSegment,
|
||||||
Timeline,
|
Timeline,
|
||||||
|
User,
|
||||||
]
|
]
|
||||||
self.db.bind(models)
|
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."
|
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:
|
def start(self) -> None:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="Frigate",
|
prog="Frigate",
|
||||||
@ -664,6 +691,7 @@ class FrigateApp:
|
|||||||
self.start_record_cleanup()
|
self.start_record_cleanup()
|
||||||
self.start_watchdog()
|
self.start_watchdog()
|
||||||
self.check_shm()
|
self.check_shm()
|
||||||
|
self.init_auth()
|
||||||
|
|
||||||
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|||||||
@ -113,3 +113,8 @@ class RecordingsToDelete(Model): # type: ignore[misc]
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
temporary = True
|
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