db = $db ?? Database::connect(); } /** * @return array{ * ok: true, * token: string, * username: string, * admin_user_id: int, * group_names: list, * group_ids: list * }|array{ok: false, reason: 'skip'|'invalid'|'no_group'|'no_proxy'} */ /** * Cari id_pegawai dari identifier login (username pegawai atau NIP). */ public function resolvePegawaiIdFromCredentials(string $username): ?int { $username = trim($username); if ($username === '') { return null; } $p = $this->db->table('pegawai')->select('id_pegawai')->where('username', $username)->get()->getRowArray(); if ($p !== null && ! empty($p['id_pegawai'])) { return (int) $p['id_pegawai']; } $p = $this->db->table('pegawai')->select('id_pegawai')->where('nip', $username)->get()->getRowArray(); if ($p !== null && ! empty($p['id_pegawai'])) { return (int) $p['id_pegawai']; } return null; } /** * Pegawai yang di baris `admin_users` punya `id_pegawai` sama + aktif + punya grup Ion * → panel memakai RBAC grup (supervisor, HRD, dll.), bukan mode pegawai panel tipis. * * @return array{admin_user_id: int, username: string, group_names: list}|null */ public function findLinkedAdminForPegawaiId(int $pegawaiId): ?array { if ($pegawaiId <= 0 || ! $this->db->tableExists('admin_users')) { return null; } if (! $this->db->fieldExists('id_pegawai', 'admin_users')) { return null; } $row = $this->db->table('admin_users') ->where('id_pegawai', $pegawaiId) ->where('active', 1) ->orderBy('id', 'ASC') ->get() ->getRowArray(); if ($row === null) { return null; } $userId = (int) ($row['id'] ?? 0); if ($userId <= 0) { return null; } $groups = $this->loadAdminGroupsForUser($userId); if ($this->db->tableExists('admin_users_groups') && $groups['names'] === []) { return null; } return [ 'admin_user_id' => $userId, 'username' => (string) ($row['username'] ?? ''), 'group_names' => $groups['names'], ]; } public function tryLogin(string $username, string $password): array { if ($username === '' || $password === '') { return ['ok' => false, 'reason' => 'skip']; } if (! $this->db->tableExists('admin_users')) { return ['ok' => false, 'reason' => 'skip']; } $row = $this->db->table('admin_users') ->where('username', $username) ->where('active', 1) ->get() ->getRowArray(); if ($row === null || empty($row['password'])) { return ['ok' => false, 'reason' => 'invalid']; } $hash = (string) $row['password']; if (! $this->verifyAdminPassword($password, $hash)) { return ['ok' => false, 'reason' => 'invalid']; } $userId = (int) $row['id']; $groups = $this->loadAdminGroupsForUser($userId); if ($this->db->tableExists('admin_users_groups') && $groups === []) { return ['ok' => false, 'reason' => 'no_group']; } $proxyId = $this->resolveProxyPegawaiId($groups, $row); if ($proxyId === null) { return ['ok' => false, 'reason' => 'no_proxy']; } $mobile = new MobileJsonService($this->db); $token = $mobile->issueTokenForPegawaiId($proxyId); if ($token === null) { return ['ok' => false, 'reason' => 'no_proxy']; } $this->db->table('admin_users')->where('id', $userId)->update([ 'last_login' => time(), ]); return [ 'ok' => true, 'token' => $token, 'username' => $username, 'admin_user_id' => $userId, 'group_names' => $groups['names'], 'group_ids' => $groups['ids'], ]; } /** * @return array{names: list, ids: list} */ private function loadAdminGroupsForUser(int $userId): array { if (! $this->db->tableExists('admin_users_groups') || ! $this->db->tableExists('admin_groups')) { return ['names' => [], 'ids' => []]; } $rows = $this->db->table('admin_users_groups ug') ->select('g.id, g.name') ->join('admin_groups g', 'g.id = ug.group_id', 'inner') ->where('ug.user_id', $userId) ->orderBy('g.id', 'ASC') ->get() ->getResultArray(); $names = []; $ids = []; foreach ($rows as $r) { $ids[] = (int) ($r['id'] ?? 0); $names[] = (string) ($r['name'] ?? ''); } return ['names' => $names, 'ids' => $ids]; } /** * Proxy pegawai untuk token API. Bisa diarahkan per grup (mis. HRD) lewat .env. * * @param array{names: list, ids: list} $groups * @param array|null $adminRow Baris admin_users (untuk id_pegawai per-akun). */ private function resolveProxyPegawaiId(array $groups, ?array $adminRow = null): ?int { if ($adminRow !== null && $this->db->fieldExists('id_pegawai', 'admin_users')) { $assigned = (int) ($adminRow['id_pegawai'] ?? 0); if ($assigned > 0 && $this->pegawaiExists($assigned)) { return $assigned; } } $fromEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID'); if (is_string($fromEnv) && $fromEnv !== '') { $id = (int) $fromEnv; if ($id > 0 && $this->pegawaiExists($id)) { return $id; } } $hrdOnly = in_array('hrd', array_map('strtolower', $groups['names']), true) && ! in_array('webmaster', array_map('strtolower', $groups['names']), true); if ($hrdOnly) { $hrdEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID_HRD'); if (is_string($hrdEnv) && $hrdEnv !== '') { $hid = (int) $hrdEnv; if ($hid > 0 && $this->pegawaiExists($hid)) { return $hid; } } } $super = $this->db->table('pegawai') ->select('id_pegawai') ->where('super_akses', 'true') ->orderBy('id_pegawai', 'ASC') ->limit(1) ->get() ->getRowArray(); if ($super !== null && ! empty($super['id_pegawai'])) { return (int) $super['id_pegawai']; } $any = $this->db->table('pegawai') ->select('id_pegawai') ->orderBy('id_pegawai', 'ASC') ->limit(1) ->get() ->getRowArray(); if ($any !== null && ! empty($any['id_pegawai'])) { return (int) $any['id_pegawai']; } return null; } private function verifyAdminPassword(string $plain, string $hash): bool { if (str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$2b$')) { return password_verify($plain, $hash); } if (strlen($hash) === 32 && ctype_xdigit($hash)) { return md5($plain) === $hash; } return password_verify($plain, $hash); } private function pegawaiExists(int $id): bool { return $this->db->table('pegawai')->where('id_pegawai', $id)->countAllResults() > 0; } }