mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-11 17:47:37 +03:00
use verify endpoint for existing password verification
avoid /login side effects (creating a new session)
This commit is contained in:
parent
d654ac2755
commit
96731ead74
@ -55,6 +55,7 @@ def require_admin_by_default():
|
|||||||
"/auth",
|
"/auth",
|
||||||
"/auth/first_time_login",
|
"/auth/first_time_login",
|
||||||
"/login",
|
"/login",
|
||||||
|
"/auth/verify",
|
||||||
# Authenticated user endpoints (allow_any_authenticated)
|
# Authenticated user endpoints (allow_any_authenticated)
|
||||||
"/logout",
|
"/logout",
|
||||||
"/profile",
|
"/profile",
|
||||||
@ -751,6 +752,30 @@ def login(request: Request, body: AppPostLoginBody):
|
|||||||
return JSONResponse(content={"message": "Login failed"}, status_code=401)
|
return JSONResponse(content={"message": "Login failed"}, status_code=401)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/auth/verify", dependencies=[Depends(allow_public())])
|
||||||
|
@limiter.limit(limit_value=rateLimiter.get_limit)
|
||||||
|
def verify(request: Request, body: AppPostLoginBody):
|
||||||
|
"""Verify credentials without creating a session.
|
||||||
|
|
||||||
|
This endpoint is used for password change verification and other
|
||||||
|
credential validation scenarios that don't require session creation.
|
||||||
|
"""
|
||||||
|
user = body.user
|
||||||
|
password = body.password
|
||||||
|
|
||||||
|
try:
|
||||||
|
db_user: User = User.get_by_id(user)
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(content={"message": "Verification failed"}, status_code=401)
|
||||||
|
|
||||||
|
password_hash = db_user.password_hash
|
||||||
|
if verify_password(password, password_hash):
|
||||||
|
return JSONResponse(
|
||||||
|
content={"message": "Verification successful"}, status_code=200
|
||||||
|
)
|
||||||
|
return JSONResponse(content={"message": "Verification failed"}, status_code=401)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users", dependencies=[Depends(require_role(["admin"]))])
|
@router.get("/users", dependencies=[Depends(require_role(["admin"]))])
|
||||||
def get_users():
|
def get_users():
|
||||||
exports = (
|
exports = (
|
||||||
@ -863,6 +888,8 @@ async def update_password(
|
|||||||
}
|
}
|
||||||
).where(User.username == username).execute()
|
).where(User.username == username).execute()
|
||||||
|
|
||||||
|
response = JSONResponse(content={"success": True})
|
||||||
|
|
||||||
# If user changed their own password, issue a new JWT to keep them logged in
|
# If user changed their own password, issue a new JWT to keep them logged in
|
||||||
if current_username == username:
|
if current_username == username:
|
||||||
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
|
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
|
||||||
@ -873,13 +900,12 @@ async def update_password(
|
|||||||
encoded_jwt = create_encoded_jwt(
|
encoded_jwt = create_encoded_jwt(
|
||||||
username, current_role, expiration, request.app.jwt_token
|
username, current_role, expiration, request.app.jwt_token
|
||||||
)
|
)
|
||||||
response = JSONResponse(content={"success": True})
|
# Set new JWT cookie on response
|
||||||
set_jwt_cookie(
|
set_jwt_cookie(
|
||||||
response, JWT_COOKIE_NAME, encoded_jwt, expiration, JWT_COOKIE_SECURE
|
response, JWT_COOKIE_NAME, encoded_jwt, expiration, JWT_COOKIE_SECURE
|
||||||
)
|
)
|
||||||
return response
|
|
||||||
|
|
||||||
return JSONResponse(content={"success": True})
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import axios from "axios";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import SetPasswordDialog from "../overlay/SetPasswordDialog";
|
import SetPasswordDialog from "../overlay/SetPasswordDialog";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { verifyPassword } from "@/utils/authUtil";
|
||||||
|
|
||||||
type AccountSettingsProps = {
|
type AccountSettingsProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -51,15 +52,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
|||||||
|
|
||||||
const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
|
const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
|
||||||
if (!profile?.username || profile.username === "anonymous") return false;
|
if (!profile?.username || profile.username === "anonymous") return false;
|
||||||
try {
|
return verifyPassword(profile.username, oldPassword);
|
||||||
const response = await axios.post("login", {
|
|
||||||
user: profile.username,
|
|
||||||
password: oldPassword,
|
|
||||||
});
|
|
||||||
return response.status === 200;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordSave = async (password: string, oldPassword?: string) => {
|
const handlePasswordSave = async (password: string, oldPassword?: string) => {
|
||||||
|
|||||||
@ -66,6 +66,7 @@ import { supportedLanguageKeys } from "@/lib/const";
|
|||||||
|
|
||||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||||
import { MdCategory } from "react-icons/md";
|
import { MdCategory } from "react-icons/md";
|
||||||
|
import { verifyPassword } from "@/utils/authUtil";
|
||||||
|
|
||||||
type GeneralSettingsProps = {
|
type GeneralSettingsProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -120,15 +121,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
|
|
||||||
const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
|
const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
|
||||||
if (!profile?.username || profile.username === "anonymous") return false;
|
if (!profile?.username || profile.username === "anonymous") return false;
|
||||||
try {
|
return verifyPassword(profile.username, oldPassword);
|
||||||
const response = await axios.post("login", {
|
|
||||||
user: profile.username,
|
|
||||||
password: oldPassword,
|
|
||||||
});
|
|
||||||
return response.status === 200;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordSave = async (password: string, oldPassword?: string) => {
|
const handlePasswordSave = async (password: string, oldPassword?: string) => {
|
||||||
|
|||||||
24
web/src/utils/authUtil.ts
Normal file
24
web/src/utils/authUtil.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a user's password without creating a session.
|
||||||
|
* This is used for password change verification.
|
||||||
|
*
|
||||||
|
* @param username - The username to verify
|
||||||
|
* @param password - The password to verify
|
||||||
|
* @returns true if credentials are valid, false otherwise
|
||||||
|
*/
|
||||||
|
export async function verifyPassword(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await axios.post("auth/verify", {
|
||||||
|
user: username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
return response.status === 200;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,6 +37,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import DeleteRoleDialog from "@/components/overlay/DeleteRoleDialog";
|
import DeleteRoleDialog from "@/components/overlay/DeleteRoleDialog";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
|
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
|
||||||
|
import { verifyPassword } from "@/utils/authUtil";
|
||||||
|
|
||||||
type AuthenticationViewProps = {
|
type AuthenticationViewProps = {
|
||||||
section?: "users" | "roles";
|
section?: "users" | "roles";
|
||||||
@ -73,15 +74,7 @@ export default function AuthenticationView({
|
|||||||
const onVerifyOldPassword = useCallback(
|
const onVerifyOldPassword = useCallback(
|
||||||
async (oldPassword: string): Promise<boolean> => {
|
async (oldPassword: string): Promise<boolean> => {
|
||||||
if (!selectedUser) return false;
|
if (!selectedUser) return false;
|
||||||
try {
|
return verifyPassword(selectedUser, oldPassword);
|
||||||
const response = await axios.post("login", {
|
|
||||||
user: selectedUser,
|
|
||||||
password: oldPassword,
|
|
||||||
});
|
|
||||||
return response.status === 200;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[selectedUser],
|
[selectedUser],
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user