feat: TOTP 2차 인증, 관리자 메뉴/대시보드 및 의존성 반영

- robthree/twofactorauth, Auth 설정·TotpService·2FA 뷰·라우트
- member TOTP 컬럼 DDL(login_tables, member_add_totp.sql)
- 관리자 메뉴·레이아웃·필터·대시보드 등 연관 변경
- env 샘플에 auth.requireTotp 주석

Made-with: Cursor
This commit is contained in:
taekyoungc
2026-03-26 15:29:55 +09:00
parent d36217920f
commit a3f92cd322
32 changed files with 1416 additions and 66 deletions

View File

@@ -90,8 +90,8 @@ class Access extends BaseController
}
$requestedLevel = (int) $requestRow->mar_requested_level;
if ($requestedLevel === Roles::LEVEL_SUPER_ADMIN) {
return redirect()->to(site_url('admin/access/approvals'))->with('error', 'super admin 역할 요청은 승인할 수 없습니다.');
if ($requestedLevel === Roles::LEVEL_SUPER_ADMIN || $requestedLevel === Roles::LEVEL_HEADQUARTERS_ADMIN) {
return redirect()->to(site_url('admin/access/approvals'))->with('error', '상위 관리자 역할 요청은 승인할 수 없습니다.');
}
$db = db_connect();

View File

@@ -22,7 +22,7 @@ class DesignatedShop extends BaseController
private function isSuperAdmin(): bool
{
return (int) session()->get('mb_level') === Roles::LEVEL_SUPER_ADMIN;
return Roles::isSuperAdminEquivalent((int) session()->get('mb_level'));
}
private function isLocalAdmin(): bool

View File

@@ -19,7 +19,7 @@ class LocalGovernment extends BaseController
private function isSuperAdmin(): bool
{
return (int) session()->get('mb_level') === Roles::LEVEL_SUPER_ADMIN;
return Roles::isSuperAdminEquivalent((int) session()->get('mb_level'));
}
/**
@@ -29,7 +29,7 @@ class LocalGovernment extends BaseController
{
if (! $this->isSuperAdmin()) {
return redirect()->to(site_url('admin'))
->with('error', '지자체 관리는 super admin만 접근할 수 있습니다.');
->with('error', '지자체 관리는 상위 관리자만 접근할 수 있습니다.');
}
$list = $this->lgModel->orderBy('lg_idx', 'DESC')->findAll();
@@ -47,7 +47,7 @@ class LocalGovernment extends BaseController
{
if (! $this->isSuperAdmin()) {
return redirect()->to(site_url('admin/local-governments'))
->with('error', '지자체 등록은 super admin만 가능합니다.');
->with('error', '지자체 등록은 상위 관리자만 가능합니다.');
}
return view('admin/layout', [
@@ -63,7 +63,7 @@ class LocalGovernment extends BaseController
{
if (! $this->isSuperAdmin()) {
return redirect()->to(site_url('admin/local-governments'))
->with('error', '지자체 등록은 super admin만 가능합니다.');
->with('error', '지자체 등록은 상위 관리자만 가능합니다.');
}
$rules = [

View File

@@ -205,8 +205,8 @@ class Menu extends BaseController
return '';
}
$levels = array_map('intval', $levels);
// super admin(4)은 DB 저장 대상 아님. 1,2,3은 그대로 저장
$levels = array_filter($levels, static fn ($v) => $v > 0 && $v !== \Config\Roles::LEVEL_SUPER_ADMIN);
// super/본부(4·5)는 mm_level 저장 대상 아님. 1,2,3은 그대로 저장
$levels = array_filter($levels, static fn ($v) => $v > 0 && ! \Config\Roles::isSuperAdminEquivalent($v));
return implode(',', array_values($levels));
}

View File

@@ -9,12 +9,12 @@ use Config\Roles;
class SelectLocalGovernment extends BaseController
{
/**
* 지자체 선택 화면 (super admin 전용)
* 지자체 선택 화면 (super·본부 관리자)
*/
public function index()
{
if ((int) session()->get('mb_level') !== Roles::LEVEL_SUPER_ADMIN) {
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 super admin만 사용할 수 있습니다.');
if (! Roles::isSuperAdminEquivalent((int) session()->get('mb_level'))) {
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 상위 관리자만 사용할 수 있습니다.');
}
$list = model(LocalGovernmentModel::class)
@@ -35,8 +35,8 @@ class SelectLocalGovernment extends BaseController
*/
public function store()
{
if ((int) session()->get('mb_level') !== Roles::LEVEL_SUPER_ADMIN) {
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 super admin만 사용할 수 있습니다.');
if (! Roles::isSuperAdminEquivalent((int) session()->get('mb_level'))) {
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 상위 관리자만 사용할 수 있습니다.');
}
$lgIdx = (int) $this->request->getPost('lg_idx');

View File

@@ -177,7 +177,7 @@ class User extends BaseController
/**
* 현재 로그인한 관리자가 부여 가능한 역할 목록.
* super admin만 super admin(4) 부여 가능, 그 외는 1~3만 허용.
* super/본부만 4·5 부여 가능, 지자체 관리자는 1~3만.
*
* @return array<int,string>
*/
@@ -185,10 +185,11 @@ class User extends BaseController
{
$levelNames = $this->roles->levelNames;
$myLevel = (int) session()->get('mb_level');
if ($myLevel === Roles::LEVEL_SUPER_ADMIN) {
if (Roles::isSuperAdminEquivalent($myLevel)) {
return $levelNames;
}
unset($levelNames[Roles::LEVEL_SUPER_ADMIN]);
unset($levelNames[Roles::LEVEL_SUPER_ADMIN], $levelNames[Roles::LEVEL_HEADQUARTERS_ADMIN]);
return $levelNames;
}