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

  1. Check the is_face_code_set property before any comparison and redirect the user to face enrollment
  2. Re-enroll the face (capture flow) so face_vector is populated
  3. If the vector column was lost (migration/restore), verify the _face.py storage backend and re-capture
  4. 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

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


AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28). Data as JSON: /api/errors/ad4ab66cac246f33. Report an issue: GitHub.