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.*
|
click == 8.1.*
|
||||||
Flask == 3.0.*
|
Flask == 3.0.*
|
||||||
|
Flask_Limiter == 3.6.*
|
||||||
imutils == 0.5.*
|
imutils == 0.5.*
|
||||||
joserfc == 0.9.*
|
joserfc == 0.9.*
|
||||||
markupsafe == 2.1.*
|
markupsafe == 2.1.*
|
||||||
|
|||||||
@ -13,8 +13,9 @@ from flask import Blueprint, Flask, current_app, jsonify, make_response, request
|
|||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
from peewee import operator
|
from peewee import operator
|
||||||
from playhouse.sqliteq import SqliteQueueDatabase
|
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.event import EventBp
|
||||||
from frigate.api.export import ExportBp
|
from frigate.api.export import ExportBp
|
||||||
from frigate.api.media import MediaBp
|
from frigate.api.media import MediaBp
|
||||||
@ -86,6 +87,11 @@ def create_app(
|
|||||||
app.camera_error_image = None
|
app.camera_error_image = None
|
||||||
app.stats_emitter = stats_emitter
|
app.stats_emitter = stats_emitter
|
||||||
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
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)
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ 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, make_response, request
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
from joserfc import jwt
|
from joserfc import jwt
|
||||||
|
|
||||||
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
|
||||||
@ -19,6 +21,15 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
AuthBp = Blueprint("auth", __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:
|
def get_jwt_secret() -> str:
|
||||||
jwt_secret = None
|
jwt_secret = None
|
||||||
@ -179,6 +190,7 @@ def auth():
|
|||||||
|
|
||||||
|
|
||||||
@AuthBp.route("/login", methods=["POST"])
|
@AuthBp.route("/login", methods=["POST"])
|
||||||
|
@limiter.limit(get_rate_limit, deduct_when=lambda response: response.status_code == 400)
|
||||||
def login():
|
def login():
|
||||||
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
|
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
|
||||||
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length
|
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length
|
||||||
|
|||||||
@ -125,7 +125,6 @@ class UserConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
class AuthConfig(FrigateBaseModel):
|
class AuthConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=False, title="Enable authentication")
|
enabled: bool = Field(default=False, title="Enable authentication")
|
||||||
# TODO: validation for cookie names
|
|
||||||
cookie_name: str = Field(
|
cookie_name: str = Field(
|
||||||
default="jwt.token", title="Name for jwt token cookie", pattern=r"^[a-z]_*$"
|
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",
|
title="Refresh the session if it is going to expire in this many seconds",
|
||||||
ge=30,
|
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")
|
users: Optional[List[UserConfig]] = Field(default=[], title="Users")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import ActivityIndicator from "./indicators/activity-indicator";
|
import ActivityIndicator from "./indicators/activity-indicator";
|
||||||
import axios from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import { Toaster } from "./ui/sonner";
|
import { Toaster } from "./ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
@ -56,9 +56,27 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
|||||||
);
|
);
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Login failed", {
|
if (axios.isAxiosError(error)) {
|
||||||
position: "top-center",
|
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);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user