jumpserver/jumpserver · error · ValidationError
Face vector is not set.
Error message
Face vector is not set.
What it means
Raised by User.get_face_vector() when the user's stored face_vector attribute is None/empty. It is the precondition check before decoding the Base64 faceprint used for face authentication (distance/similarity comparison). Since face login/disabling flows call compare_euclidean_distance and compare_cosine_similarity, any comparison against a user who never enrolled a face fails here.
Source
Thrown at apps/users/models/user/_face.py:24
from django.core.exceptions import ValidationError
from common.utils import (
get_logger,
)
logger = get_logger(__file__)
class FaceMixin:
face_vector = None
@property
def is_face_code_set(self):
return self.face_vector is not None
def get_face_vector(self) -> list[float]:
if not self.face_vector:
raise ValidationError("Face vector is not set.")
return self._decode_base64_vector(str(self.face_vector))
def check_face(self, code, distance_threshold=None, similarity_threshold=None) -> bool:
distance = self.compare_euclidean_distance(code)
similarity = self.compare_cosine_similarity(code)
distance_threshold = distance_threshold or settings.FACE_RECOGNITION_DISTANCE_THRESHOLD
similarity_threshold = similarity_threshold or settings.FACE_RECOGNITION_COSINE_THRESHOLD
return distance < distance_threshold and similarity > similarity_threshold
def compare_euclidean_distance(self, base64_vector: str) -> float:
target_vector = self._decode_base64_vector(base64_vector)
current_vector = self.get_face_vector()
return self._calculate_euclidean_distance(current_vector, target_vector)
def compare_cosine_similarity(self, base64_vector: str) -> float:
target_vector = self._decode_base64_vector(base64_vector)View on GitHub (pinned to 6ec464fabd)
Solutions
- Check the is_face_code_set property before any comparison and redirect the user to face enrollment
- Re-enroll the face (capture flow) so face_vector is populated
- If the vector column was lost (migration/restore), verify the _face.py storage backend and re-capture
- Guard in the view: catch ValidationError and show 'face not enrolled' instead of a raw error
Example fix
# before
def verify(user, code):
return user.compare_cosine_similarity(code) > 0.9
# after
def verify(user, code):
if not user.is_face_code_set:
raise ValueError('Face not enrolled; please capture your face first')
return user.compare_cosine_similarity(code) > 0.9 Defensive patterns
Strategy: type-guard
Validate before calling
if not user.is_face_code_set:
return redirect('face-enrollment') # instead of calling compare APIs Type guard
def can_verify_face(user) -> bool:
return user.is_face_code_set # property: face_vector is not None Try / catch
from django.core.exceptions import ValidationError
try:
vector = user.get_face_vector()
except ValidationError:
# prompt enrollment instead of failing comparison
... Prevention
- Always gate face comparison with is_face_code_set
- After enrollment, assert face_vector was persisted before marking the user as face-enabled
- Log enrollment failures so empty vectors are visible early
When it happens
Trigger: Calling user.compare_euclidean_distance(code) / compare_cosine_similarity(code) / user.check_face(code) when user.face_vector is None — e.g. face login attempt for a user without face enrollment, or submitting the face-disable form when no vector is stored.
Common situations: User cleared their face data previously, face enrollment silently failed so the vector was never saved, database restored/migrated without the face column data, or a frontend bug letting un-enrolled users reach the face-verification step.
Related errors
- Facial comparison failed
- This account is inactive.
- User does not exist
- UKey SN mismatch
- Connection token expired at: {}
AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28).
Data as JSON: /api/errors/ad4ab66cac246f33.
Report an issue: GitHub.