{"record":{"id":"ad4ab66cac246f33","repo":"jumpserver/jumpserver","slug":"face-vector-is-not-set","errorCode":null,"errorMessage":"Face vector is not set.","messagePattern":"Face vector is not set\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"apps/users/models/user/_face.py","lineNumber":24,"sourceCode":"from django.core.exceptions import ValidationError\n\nfrom common.utils import (\n    get_logger,\n)\n\nlogger = get_logger(__file__)\n\n\nclass FaceMixin:\n    face_vector = None\n\n    @property\n    def is_face_code_set(self):\n        return self.face_vector is not None\n\n    def get_face_vector(self) -> list[float]:\n        if not self.face_vector:\n            raise ValidationError(\"Face vector is not set.\")\n        return self._decode_base64_vector(str(self.face_vector))\n\n    def check_face(self, code, distance_threshold=None, similarity_threshold=None) -> bool:\n        distance = self.compare_euclidean_distance(code)\n        similarity = self.compare_cosine_similarity(code)\n\n        distance_threshold = distance_threshold or settings.FACE_RECOGNITION_DISTANCE_THRESHOLD\n        similarity_threshold = similarity_threshold or settings.FACE_RECOGNITION_COSINE_THRESHOLD\n\n        return distance < distance_threshold and similarity > similarity_threshold\n\n    def compare_euclidean_distance(self, base64_vector: str) -> float:\n        target_vector = self._decode_base64_vector(base64_vector)\n        current_vector = self.get_face_vector()\n        return self._calculate_euclidean_distance(current_vector, target_vector)\n\n    def compare_cosine_similarity(self, base64_vector: str) -> float:\n        target_vector = self._decode_base64_vector(base64_vector)","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/jumpserver/jumpserver/blob/6ec464fabd61b95912d539455a3a5f15f5c59fe0/apps/users/models/user/_face.py#L6-L42","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\ndef verify(user, code):\n    return user.compare_cosine_similarity(code) > 0.9\n\n# after\ndef verify(user, code):\n    if not user.is_face_code_set:\n        raise ValueError('Face not enrolled; please capture your face first')\n    return user.compare_cosine_similarity(code) > 0.9","handlingStrategy":"type-guard","validationCode":"if not user.is_face_code_set:\n    return redirect('face-enrollment')  # instead of calling compare APIs","typeGuard":"def can_verify_face(user) -> bool:\n    return user.is_face_code_set  # property: face_vector is not None","tryCatchPattern":"from django.core.exceptions import ValidationError\ntry:\n    vector = user.get_face_vector()\nexcept ValidationError:\n    # prompt enrollment instead of failing comparison\n    ...","preventionTips":["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"],"tags":["face-recognition","biometric-auth","django","validation-error","precondition"],"backgroundTag":"missing-biometric-enrollment","analyzedSha":"6ec464fabd61b95912d539455a3a5f15f5c59fe0","analyzedAt":"2026-08-28T11:33:00.925Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}