Files
bij/app/Services/Admin/AdminUsersLoginService.php
2026-04-21 05:59:39 +07:00

264 lines
8.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Admin;
use App\Services\Mobile\MobileJsonService;
use CodeIgniter\Database\BaseConnection;
use Config\Database;
/**
* Login admin memakai Ion Auth CI3: `admin_users` + relasi `admin_users_groups` / `admin_groups`.
* Password: bcrypt ($2a$/$2y$) atau legacy MD5 (32 hex).
* Setelah sukses, token API ditulis ke baris pegawai "proxy" (sama seperti sebelumnya).
*/
class AdminUsersLoginService
{
protected BaseConnection $db;
public function __construct(?BaseConnection $db = null)
{
$this->db = $db ?? Database::connect();
}
/**
* @return array{
* ok: true,
* token: string,
* username: string,
* admin_user_id: int,
* group_names: list<string>,
* group_ids: list<int>
* }|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<string>}|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<string>, ids: list<int>}
*/
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<string>, ids: list<int>} $groups
* @param array<string, mixed>|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;
}
}