{"record":{"id":"d58c3e3fb3a7014c","repo":"monicahq/monica","slug":"the-user-does-not-belong-to-the-vault-s-account","errorCode":null,"errorMessage":"The user does not belong to the vault's account.","messagePattern":"The user does not belong to the vault's account\\.","errorType":"exception","errorClass":"ModelNotFoundException","httpStatus":404,"severity":"error","filePath":"app/Domains/Contact/ManageReminders/Services/ScheduleContactReminderForUser.php","lineNumber":47,"sourceCode":"            'user_id' => 'required|uuid|exists:users,id',\n        ];\n    }\n\n    /**\n     * Schedule a contact reminder for the given user, on his timezone.\n     * For each user in the vault, a scheduled reminder is created.\n     * This service SHOULD NOT BE CALLED FROM THE CLIENTS, ever.\n     * It is called by other services.\n     */\n    public function execute(array $data): void\n    {\n        $this->validateRules($data);\n        $this->data = $data;\n\n        $this->contactReminder = ContactReminder::findOrFail($this->data['contact_reminder_id']);\n        $this->user = User::findOrFail($this->data['user_id']);\n        if ($this->user->account_id != $this->contactReminder->contact->vault->account_id) {\n            throw new ModelNotFoundException('The user does not belong to the vault\\'s account.');\n        }\n\n        $this->getDate();\n        $this->schedule();\n    }\n\n    /**\n     * A ContactReminder can be either a complete date, or only a day/month.\n     * If it is only a day/month, we need to add a fake year so we can still\n     * manipulate the date as a Carbon object.\n     */\n    private function getDate(): void\n    {\n        if (! $this->contactReminder->year) {\n            $this->upcomingDate = Carbon::parse('1900-'.$this->contactReminder->month.'-'.$this->contactReminder->day);\n        } else {\n            $this->upcomingDate = Carbon::parse($this->contactReminder->year.'-'.$this->contactReminder->month.'-'.$this->contactReminder->day);\n        }","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/monicahq/monica/blob/e08e91734170b6bbd582cb578532c3948196124e/app/Domains/Contact/ManageReminders/Services/ScheduleContactReminderForUser.php#L29-L65","documentation":"ScheduleContactReminderForUser is an internal service (never called from HTTP clients) that schedules a reminder notification for one specific user of a vault. It loads the ContactReminder and User by id, then asserts user.account_id equals the reminder's contact vault account_id; a mismatch aborts with ModelNotFoundException, which Laravel renders as a 404.","triggerScenarios":"Calling execute() with a user_id whose account differs from the account owning contact_reminder_id — e.g. passing the currently authenticated user instead of a member of the vault, or ids produced by factories/seeds belonging to two different accounts.","commonSituations":"Calling code deriving user_id from Auth::user() instead of the vault's member list, test data created across two accounts, or stale ids after account restructuring.","solutions":["Pass a user belonging to the same account as the reminder's contact vault: derive candidates from $reminder->contact->vault->users","Never feed Auth::id() into this service without verifying the authenticated user is a member of that vault","In tests, create account, vault, contact, reminder and user under one account so account_id matches"],"exampleFix":"// before\n$data = [\n    'contact_reminder_id' => $reminder->id,\n    'user_id' => Auth::id(), // may belong to another account -> throws\n];\n\n// after\n$vault = $reminder->contact->vault;\nabort_unless($vault->users->contains(Auth::id()), 403, 'User is not a member of this vault.');\n$data = [\n    'contact_reminder_id' => $reminder->id,\n    'user_id' => Auth::id(), // now guaranteed same account\n];","handlingStrategy":"validation","validationCode":"// Validate before calling: user must belong to the reminder's vault (same account)\n$reminder = ContactReminder::with('contact.vault')->findOrFail($data['contact_reminder_id']);\n$vault = $reminder->contact->vault;\n\nif (! $vault->users()->where('users.id', $data['user_id'])->exists()) {\n    throw new InvalidArgumentException('user_id does not belong to the vault of contact_reminder_id.');\n}","typeGuard":"function isValidReminderRecipient(string $userId, ContactReminder $reminder): bool\n{\n    return $reminder->contact->vault\n        ->users()\n        ->where('users.id', $userId)\n        ->exists(); // membership implies same account\n}","tryCatchPattern":"use Illuminate\\Database\\Eloquent\\ModelNotFoundException;\n\ntry {\n    app(ScheduleContactReminderForUser::class)->execute($data);\n} catch (ModelNotFoundException $e) {\n    // 404-shaped: one of the ids is wrong or crosses accounts — log and re-dispatch without that user\n    report($e);\n}","preventionTips":["Derive recipient ids from the vault's user list, never from the authenticated user unchecked","Never call this service from client code; keep it behind the reminder-scheduling services","Create related factory data under one account in tests","Treat ModelNotFoundException from this service as a data-integrity signal worth alerting on"],"tags":["monica","reminders","authorization","model-not-found"],"backgroundTag":"cross-account-access-denied","analyzedSha":"e08e91734170b6bbd582cb578532c3948196124e","analyzedAt":"2026-08-17T01:36:49.014Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}