264 lines
8.1 KiB
PHP
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;
|
|
}
|
|
}
|