update HR dan Payroll
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class UserSessionService
|
||||
{
|
||||
private $CI;
|
||||
private $durations=array(24,72,168);
|
||||
|
||||
public function __construct(){ $this->CI=&get_instance(); }
|
||||
|
||||
public function begin($user)
|
||||
{
|
||||
$hours=$this->duration($user->session_duration_hours??24);
|
||||
$limit=$this->deviceLimit($user->max_active_devices??1);
|
||||
$loginAt=date('Y-m-d H:i:s');
|
||||
$expiresAt=date('Y-m-d H:i:s',time()+$hours*3600);
|
||||
$result=array('allowed'=>true,'login_key'=>bin2hex(random_bytes(32)),'login_at'=>$loginAt,'expires_at'=>$expiresAt,'duration_hours'=>$hours,'device_limit'=>$limit);
|
||||
if(!$this->ready())return$result;
|
||||
|
||||
$db=$this->CI->db;$db->trans_begin();
|
||||
try{
|
||||
$db->query('SELECT id FROM users WHERE id=? FOR UPDATE',array((int)$user->id));
|
||||
$this->expireStale((int)$user->id);
|
||||
$active=(int)$db->where(array('user_id'=>(int)$user->id,'status'=>'active'))->where('expires_at >',date('Y-m-d H:i:s'))->count_all_results('user_login_sessions');
|
||||
if($active>=$limit){
|
||||
$this->attempt((int)$user->id,(string)$user->username,'device_limit','Batas perangkat aktif telah tercapai.',$active,$limit,null);
|
||||
if(!$db->trans_status())throw new RuntimeException('Log pembatasan perangkat gagal disimpan.');
|
||||
$db->trans_commit();
|
||||
return array('allowed'=>false,'active_count'=>$active,'device_limit'=>$limit,'message'=>'Akun ini sudah aktif pada '.$active.' perangkat (batas '.$limit.'). Akhiri salah satu sesi aktif melalui menu Profil sebelum login di perangkat lain.');
|
||||
}
|
||||
$device=$this->device();
|
||||
$db->insert('user_login_sessions',array(
|
||||
'user_id'=>(int)$user->id,'login_key'=>$result['login_key'],'session_id_hash'=>$this->sessionHash(),
|
||||
'login_at'=>$loginAt,'expires_at'=>$expiresAt,'last_seen_at'=>$loginAt,'ip_address'=>$device['ip'],
|
||||
'user_agent'=>$device['agent'],'device_type'=>$device['type'],'platform'=>$device['platform'],
|
||||
'browser'=>$device['browser'],'status'=>'active'
|
||||
));
|
||||
$sessionId=(int)$db->insert_id();
|
||||
$this->attempt((int)$user->id,(string)$user->username,'success','Login berhasil.',$active+1,$limit,$sessionId);
|
||||
if(!$db->trans_status())throw new RuntimeException('Session login gagal dicatat.');
|
||||
$db->trans_commit();return$result;
|
||||
}catch(Throwable$e){$db->trans_rollback();throw$e;}
|
||||
}
|
||||
|
||||
public function recordInvalid($user,$username)
|
||||
{
|
||||
if(!$this->attemptsReady())return;
|
||||
$id=$user?(int)$user->id:null;$limit=$user?$this->deviceLimit($user->max_active_devices??1):1;
|
||||
$this->attempt($id,substr((string)$username,0,100),'invalid_credentials','Username atau password tidak valid.',0,$limit,null);
|
||||
}
|
||||
|
||||
public function validateCurrent()
|
||||
{
|
||||
$userId=(int)$this->CI->session->userdata('user_id');if($userId<1)return array('valid'=>false,'reason'=>'invalid');
|
||||
$expires=(string)$this->CI->session->userdata('auth_expires_at');
|
||||
if($expires!==''&&strtotime($expires)<=time()){ $this->endCurrent('expired','Masa login 24 jam atau sesuai pengaturan telah berakhir.');return array('valid'=>false,'reason'=>'expired'); }
|
||||
|
||||
if(!$this->ready()){
|
||||
if($expires===''){
|
||||
$loginAt=date('Y-m-d H:i:s');$expires=date('Y-m-d H:i:s',time()+86400);
|
||||
$this->CI->session->set_userdata(array('auth_started_at'=>$loginAt,'auth_expires_at'=>$expires));
|
||||
}
|
||||
return array('valid'=>true);
|
||||
}
|
||||
|
||||
$loginKey=(string)$this->CI->session->userdata('auth_login_key');
|
||||
if($loginKey==='')return$this->adoptExisting($userId);
|
||||
$row=$this->CI->db->where(array('user_id'=>$userId,'login_key'=>$loginKey))->get('user_login_sessions')->row();
|
||||
if(!$row||$row->status!=='active')return array('valid'=>false,'reason'=>'revoked');
|
||||
if(strtotime($row->expires_at)<=time()){
|
||||
$this->CI->db->where('id',$row->id)->update('user_login_sessions',array('status'=>'expired','ended_at'=>date('Y-m-d H:i:s'),'end_reason'=>'Batas waktu login tercapai.'));
|
||||
return array('valid'=>false,'reason'=>'expired');
|
||||
}
|
||||
$device=$this->device();
|
||||
$this->CI->db->where(array('id'=>$row->id,'status'=>'active'))->update('user_login_sessions',array('session_id_hash'=>$this->sessionHash(),'last_seen_at'=>date('Y-m-d H:i:s'),'ip_address'=>$device['ip']));
|
||||
$this->CI->session->set_userdata(array('auth_started_at'=>$row->login_at,'auth_expires_at'=>$row->expires_at));
|
||||
return array('valid'=>true,'row'=>$row);
|
||||
}
|
||||
|
||||
private function adoptExisting($userId)
|
||||
{
|
||||
$user=$this->CI->db->get_where('users',array('id'=>$userId))->row();if(!$user)return array('valid'=>false,'reason'=>'invalid');
|
||||
$created=$this->begin($user);if(!$created['allowed'])return array('valid'=>false,'reason'=>'device_limit');
|
||||
$this->CI->session->set_userdata(array('auth_login_key'=>$created['login_key'],'auth_started_at'=>$created['login_at'],'auth_expires_at'=>$created['expires_at']));
|
||||
return array('valid'=>true,'adopted'=>true);
|
||||
}
|
||||
|
||||
public function endCurrent($status='logged_out',$reason='Logout oleh pengguna.')
|
||||
{
|
||||
if(!$this->ready())return;
|
||||
$key=(string)$this->CI->session->userdata('auth_login_key');$user=(int)$this->CI->session->userdata('user_id');if($key===''||$user<1)return;
|
||||
$this->CI->db->where(array('user_id'=>$user,'login_key'=>$key,'status'=>'active'))->update('user_login_sessions',array('status'=>$status,'ended_at'=>date('Y-m-d H:i:s'),'end_reason'=>substr($reason,0,255)));
|
||||
}
|
||||
|
||||
public function activeSessions($userId)
|
||||
{
|
||||
if(!$this->ready())return array();$this->expireStale((int)$userId);$current=(string)$this->CI->session->userdata('auth_login_key');
|
||||
$rows=$this->CI->db->where('user_id',(int)$userId)->where_in('status',array('active','logged_out','expired','revoked'))->order_by("status='active'",'DESC',false)->order_by('last_seen_at','DESC')->limit(100)->get('user_login_sessions')->result();
|
||||
foreach($rows as$row)$row->is_current=hash_equals((string)$row->login_key,$current);
|
||||
return$rows;
|
||||
}
|
||||
|
||||
public function attempts($userId)
|
||||
{
|
||||
if(!$this->attemptsReady())return array();return$this->CI->db->where('user_id',(int)$userId)->order_by('attempted_at','DESC')->limit(100)->get('user_login_attempts')->result();
|
||||
}
|
||||
|
||||
public function revoke($id,$userId)
|
||||
{
|
||||
if(!$this->ready())throw new RuntimeException('Migration keamanan session belum dijalankan.');
|
||||
$row=$this->CI->db->where(array('id'=>(int)$id,'user_id'=>(int)$userId))->get('user_login_sessions')->row();
|
||||
if(!$row)throw new RuntimeException('Session perangkat tidak ditemukan.');
|
||||
if($row->status==='active')$this->CI->db->where('id',$row->id)->update('user_login_sessions',array('status'=>'revoked','ended_at'=>date('Y-m-d H:i:s'),'end_reason'=>'Diakhiri dari pengaturan perangkat.'));
|
||||
$this->deleteNativeSession($row->session_id_hash);
|
||||
return(array('current'=>hash_equals((string)$row->login_key,(string)$this->CI->session->userdata('auth_login_key'))));
|
||||
}
|
||||
|
||||
public function revokeOthers($userId)
|
||||
{
|
||||
if(!$this->ready())return;$current=(string)$this->CI->session->userdata('auth_login_key');
|
||||
$rows=$this->CI->db->where(array('user_id'=>(int)$userId,'status'=>'active'))->where('login_key !=',$current)->get('user_login_sessions')->result();
|
||||
foreach($rows as$row){$this->CI->db->where('id',$row->id)->update('user_login_sessions',array('status'=>'revoked','ended_at'=>date('Y-m-d H:i:s'),'end_reason'=>'Password akun diubah.'));$this->deleteNativeSession($row->session_id_hash);}
|
||||
}
|
||||
|
||||
public function activeCount($userId)
|
||||
{
|
||||
if(!$this->ready())return 1;$this->expireStale((int)$userId);return(int)$this->CI->db->where(array('user_id'=>(int)$userId,'status'=>'active'))->where('expires_at >',date('Y-m-d H:i:s'))->count_all_results('user_login_sessions');
|
||||
}
|
||||
|
||||
private function expireStale($userId)
|
||||
{
|
||||
if(!$this->ready())return;$this->CI->db->where(array('user_id'=>(int)$userId,'status'=>'active'))->where('expires_at <=',date('Y-m-d H:i:s'))->update('user_login_sessions',array('status'=>'expired','ended_at'=>date('Y-m-d H:i:s'),'end_reason'=>'Batas waktu login tercapai.'));
|
||||
}
|
||||
|
||||
private function attempt($userId,$username,$result,$reason,$active,$limit,$sessionId)
|
||||
{
|
||||
if(!$this->attemptsReady())return;$device=$this->device();$this->CI->db->insert('user_login_attempts',array('user_id'=>$userId?:null,'username'=>substr($username,0,100),'attempted_at'=>date('Y-m-d H:i:s'),'ip_address'=>$device['ip'],'user_agent'=>$device['agent'],'device_type'=>$device['type'],'platform'=>$device['platform'],'browser'=>$device['browser'],'result'=>$result,'reason'=>substr($reason,0,255),'active_session_count'=>max(0,(int)$active),'device_limit'=>$this->deviceLimit($limit),'login_session_id'=>$sessionId));
|
||||
}
|
||||
|
||||
private function deleteNativeSession($hash)
|
||||
{
|
||||
if($hash===''||!$this->CI->db->table_exists('ci_sessions'))return;$this->CI->db->query('DELETE FROM ci_sessions WHERE SHA2(id,256)=?',array($hash));
|
||||
}
|
||||
|
||||
private function sessionHash(){return hash('sha256',(string)session_id());}
|
||||
private function duration($hours){$hours=(int)$hours;return in_array($hours,$this->durations,true)?$hours:24;}
|
||||
private function deviceLimit($limit){return max(1,min(10,(int)$limit));}
|
||||
private function ready(){return$this->CI->db->table_exists('user_login_sessions')&&$this->CI->db->field_exists('session_duration_hours','users')&&$this->CI->db->field_exists('max_active_devices','users');}
|
||||
private function attemptsReady(){return$this->CI->db->table_exists('user_login_attempts');}
|
||||
private function device()
|
||||
{
|
||||
$agent=substr((string)$this->CI->input->user_agent(),0,512);$browser='Browser lain';$platform='Perangkat lain';$type='Desktop';
|
||||
foreach(array('Edg/'=>'Microsoft Edge','OPR/'=>'Opera','Chrome/'=>'Google Chrome','Firefox/'=>'Mozilla Firefox','Safari/'=>'Safari')as$needle=>$label)if(stripos($agent,$needle)!==false){$browser=$label;break;}
|
||||
foreach(array('Windows'=>'Windows','Android'=>'Android','iPhone'=>'iOS','iPad'=>'iPadOS','Macintosh'=>'macOS','Linux'=>'Linux')as$needle=>$label)if(stripos($agent,$needle)!==false){$platform=$label;break;}
|
||||
if(preg_match('/iPad|Tablet/i',$agent))$type='Tablet';elseif(preg_match('/Mobile|Android|iPhone/i',$agent))$type='Mobile';
|
||||
return array('ip'=>substr((string)$this->CI->input->ip_address(),0,45),'agent'=>$agent,'browser'=>$browser,'platform'=>$platform,'type'=>$type);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user