mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
add brute force protection on login
This commit is contained in:
parent
bf35d16a89
commit
115d2bc35a
@ -1,5 +1,6 @@
|
||||
click == 8.1.*
|
||||
Flask == 3.0.*
|
||||
Flask_Limiter == 3.6.*
|
||||
imutils == 0.5.*
|
||||
joserfc == 0.9.*
|
||||
markupsafe == 2.1.*
|
||||
|
||||
@ -13,8 +13,9 @@ from flask import Blueprint, Flask, current_app, jsonify, make_response, request
|
||||
from markupsafe import escape
|
||||
from peewee import operator
|
||||
from playhouse.sqliteq import SqliteQueueDatabase
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from frigate.api.auth import AuthBp, get_jwt_secret
|
||||
from frigate.api.auth import AuthBp, get_jwt_secret, limiter
|
||||
from frigate.api.event import EventBp
|
||||
from frigate.api.export import ExportBp
|
||||
from frigate.api.media import MediaBp
|
||||
@ -86,6 +87,11 @@ def create_app(
|
||||
app.camera_error_image = None
|
||||
app.stats_emitter = stats_emitter
|
||||
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
||||
# initialize the rate limiter for the login endpoint
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
|
||||
limiter.init_app(app)
|
||||
if frigate_config.auth.failed_login_rate_limit is None:
|
||||
limiter.enabled = False
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Blueprint, current_app, make_response, request
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
from joserfc import jwt
|
||||
|
||||
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
||||
@ -19,6 +21,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
AuthBp = Blueprint("auth", __name__)
|
||||
|
||||
limiter = Limiter(
|
||||
lambda: get_remote_address,
|
||||
storage_uri="memory://",
|
||||
)
|
||||
|
||||
|
||||
def get_rate_limit():
|
||||
return current_app.frigate_config.auth.failed_login_rate_limit
|
||||
|
||||
|
||||
def get_jwt_secret() -> str:
|
||||
jwt_secret = None
|
||||
@ -179,6 +190,7 @@ def auth():
|
||||
|
||||
|
||||
@AuthBp.route("/login", methods=["POST"])
|
||||
@limiter.limit(get_rate_limit, deduct_when=lambda response: response.status_code == 400)
|
||||
def login():
|
||||
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
|
||||
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length
|
||||
|
||||
@ -125,7 +125,6 @@ class UserConfig(FrigateBaseModel):
|
||||
|
||||
class AuthConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=False, title="Enable authentication")
|
||||
# TODO: validation for cookie names
|
||||
cookie_name: str = Field(
|
||||
default="jwt.token", title="Name for jwt token cookie", pattern=r"^[a-z]_*$"
|
||||
)
|
||||
@ -137,6 +136,10 @@ class AuthConfig(FrigateBaseModel):
|
||||
title="Refresh the session if it is going to expire in this many seconds",
|
||||
ge=30,
|
||||
)
|
||||
failed_login_rate_limit: Optional[str] = Field(
|
||||
default="1/second;5/minute;20/hour",
|
||||
title="Rate limits for failed login attempts.",
|
||||
)
|
||||
users: Optional[List[UserConfig]] = Field(default=[], title="Users")
|
||||
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Input } from "./ui/input";
|
||||
import { Button } from "./ui/button";
|
||||
import ActivityIndicator from "./indicators/activity-indicator";
|
||||
import axios from "axios";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { Toaster } from "./ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@ -56,9 +56,27 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
||||
);
|
||||
window.location.href = "/";
|
||||
} catch (error) {
|
||||
toast.error("Login failed", {
|
||||
position: "top-center",
|
||||
});
|
||||
if (axios.isAxiosError(error)) {
|
||||
const err = error as AxiosError;
|
||||
if (err.response?.status === 429) {
|
||||
toast.error("Exceeded rate limit. Try again in 1 minute.", {
|
||||
position: "top-center",
|
||||
});
|
||||
} else if (err.response?.status === 400) {
|
||||
toast.error("Login failed", {
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
toast.error("Unknown error. Check logs.", {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast.error("Unknown error. Check console logs.", {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user