Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
H
haiyen-payment
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Giang Tran
haiyen-payment
Commits
fe1dd3b3
Commit
fe1dd3b3
authored
Aug 29, 2026
by
tdgiang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add design spec for admin account management and data permissions
parent
cbc219c9
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
172 additions
and
0 deletions
+172
-0
2026-08-29-admin-account-management-design.md
...owers/specs/2026-08-29-admin-account-management-design.md
+172
-0
No files found.
docs/superpowers/specs/2026-08-29-admin-account-management-design.md
0 → 100644
View file @
fe1dd3b3
# Thiết kế: Quản lý tài khoản & phân quyền (Admin Account Management)
**Ngày:**
2026-08-29
**Trạng thái:**
Chờ review
## 1. Mục tiêu
Thay cơ chế Basic Auth dùng chung 1 cặp user/password (hiện tại) bằng đăng nhập đa
tài khoản có phân quyền:
-
1 tài khoản
**admin**
tạo tài khoản cho
**nhân viên**
.
-
Phải đăng nhập mới được tạo giao dịch và xem lịch sử giao dịch.
-
Phân quyền dữ liệu: nhân viên chỉ thấy giao dịch do chính mình tạo; admin thấy
toàn bộ giao dịch của mọi nhân viên.
## 2. Ngoài phạm vi (không làm trong lần này)
-
Không có vai trò trung gian (ví dụ "quản lý nhóm" xem được nhiều nhân viên nhưng
không phải admin) — chỉ 2 vai trò:
`admin`
và
`staff`
.
-
Không xoá tài khoản (chỉ khoá/mở khoá) — tránh mất liên kết
`createdByUsername`
trên các giao dịch đã tạo.
-
Không đổi flow
`/admin/pay/:merTrxId`
,
`/admin/epay/return`
,
`/admin/epay/ipn`
(các route public cho khách hàng/MegaPay) — vẫn không cần đăng nhập.
-
Không đổi flow
`/epay/*`
gốc (đã ổn định, đã test tiền thật).
-
Không có "quên mật khẩu" tự phục vụ qua email/SMS — nhân viên quên mật khẩu thì
nhờ admin reset.
## 3. Kiến trúc
### 3.1 Đăng nhập — thay Basic Auth bằng session
Repo đã có sẵn hạ tầng
`express-session`
+ Redis (
`config/express.js`
, dùng cho
các flow khác) — tái sử dụng, không thêm session store mới.
-
Đăng nhập qua form thật (
`POST /admin/login`
), không dùng popup Basic Auth
của trình duyệt nữa.
-
Sau khi đăng nhập thành công: lưu
`req.session.userId`
,
`req.session.username`
,
`req.session.role`
vào session.
-
`app/middlewares/basicAuth.js`
(đang dùng) sẽ được
**thay thế**
bởi 2 middleware
mới:
`requireLogin`
(chặn chưa đăng nhập) và
`requireAdmin`
(chặn không phải
admin) — file
`basicAuth.js`
bị xoá khỏi routes, có thể xoá file luôn nếu không
còn nơi nào require.
-
Mật khẩu hash bằng
`bcryptjs`
(thêm dependency mới — chọn bản pure-JS, không
cần compile native, tránh lặp lại vấn đề build native module đã gặp với
`node-rsa`
/
`bcrypt`
gốc trong repo này trước đây).
### 3.2 Model `AdminUser` (mới)
```
js
{
username
:
String
,
// unique, lowercase, trim
passwordHash
:
String
,
// bcryptjs hash
role
:
{
type
:
String
,
enum
:
[
"admin"
,
"staff"
],
default
:
"staff"
},
active
:
{
type
:
Boolean
,
default
:
true
},
createdAt
,
updatedAt
,
// timestamps: true
}
```
### 3.3 Model `AdminTransaction` — thêm 1 field
```
js
createdByUsername
:
{
type
:
String
,
required
:
true
}
```
Giao dịch cũ tạo trước tính năng này (kể cả giao dịch thật đã test tiền thật
trong phiên trước) không có field này — migrate 1 lần: gán
`createdByUsername`
= username của admin đầu tiên (được seed từ
`ADMIN_USER`
/
`ADMIN_PASSWORD`
hiện có trong
`.env`
), để không bị "mất tích"
khỏi mọi view có lọc theo người tạo.
### 3.4 Seed admin đầu tiên
Khi server khởi động: nếu collection
`AdminUser`
chưa có bản ghi
`role: "admin"`
nào, tự tạo 1 tài khoản admin từ
`ADMIN_USER`
/
`ADMIN_PASSWORD`
trong
`.env`
(idempotent — chỉ chạy khi thực sự chưa có admin nào, không ghi đè nếu đã tồn
tại). Sau bước seed này,
`.env`
's
`ADMIN_USER`
/
`ADMIN_PASSWORD`
không còn được
dùng trực tiếp để xác thực nữa — chỉ dùng một lần lúc seed.
### 3.5 Route table
| Method | Path | Auth | Vai trò | Mô tả |
|---|---|---|---|---|
| GET |
`/admin/login`
| Public | - | Trang đăng nhập |
| POST |
`/admin/login`
| Public | - | Xử lý đăng nhập |
| POST |
`/admin/logout`
| requireLogin | any | Đăng xuất |
| GET |
`/admin/transactions`
| requireLogin | any | Lịch sử (tự lọc theo role) |
| GET |
`/admin/transactions/new`
| requireLogin | any | Form tạo mới |
| POST |
`/admin/transactions`
| requireLogin | any | Tạo giao dịch (gán
`createdByUsername`
) |
| GET |
`/admin/accounts`
| requireLogin + requireAdmin | admin | Danh sách tài khoản nhân viên |
| POST |
`/admin/accounts`
| requireLogin + requireAdmin | admin | Tạo tài khoản nhân viên |
| POST |
`/admin/accounts/:id/toggle`
| requireLogin + requireAdmin | admin | Khoá / mở khoá tài khoản |
| POST |
`/admin/accounts/:id/reset-password`
| requireLogin + requireAdmin | admin | Đặt lại mật khẩu nhân viên |
| GET |
`/admin/account/password`
| requireLogin | any | Trang tự đổi mật khẩu |
| POST |
`/admin/account/password`
| requireLogin | any | Xử lý tự đổi mật khẩu |
| GET |
`/admin/pay/:merTrxId`
| Public | - | Không đổi |
| GET |
`/admin/epay/return`
| Public | - | Không đổi |
| POST |
`/admin/epay/ipn`
| Public | - | Không đổi |
### 3.6 Luồng dữ liệu — phân quyền xem giao dịch
1.
`listTransactions`
đọc
`req.session.role`
:
-
`role === "admin"`
: không lọc theo người tạo (trừ khi có query
`?staff=<username>`
để lọc thủ công theo 1 nhân viên cụ thể — dropdown chọn nhân viên trên UI).
-
`role === "staff"`
: luôn lọc
`{ createdByUsername: req.session.username }`
,
bỏ qua mọi query param
`staff`
nếu có (nhân viên không được tự ý xem chéo
bằng cách sửa URL).
2.
View danh sách hiển thị cột "Người tạo" — luôn hiển thị (kể cả cho staff, họ
chỉ thấy tên của chính mình, không gây rối).
3.
`createTransaction`
gán
`createdByUsername = req.session.username`
khi lưu
bản ghi mới — không nhận từ client.
### 3.7 Xử lý lỗi
-
Sai username/password → lỗi chung chung "Sai tên đăng nhập hoặc mật khẩu"
(không tiết lộ username có tồn tại hay không, tránh dò tài khoản).
-
Tài khoản
`active: false`
cố đăng nhập → từ chối, thông báo "Tài khoản đã bị
khoá, liên hệ admin".
-
`requireLogin`
chặn (chưa đăng nhập / session hết hạn) → redirect
`/admin/login`
, không phải lỗi JSON (vì đây là điều hướng trang, không phải
gọi API từ JS — ngoại trừ
`POST /admin/transactions`
gọi qua
`fetch()`
, ở đó
trả JSON
`401`
để trang xử lý và tự redirect bằng JS).
-
`requireAdmin`
chặn (đã đăng nhập nhưng không phải admin) →
`403 Forbidden`
,
trang báo rõ "Bạn không có quyền truy cập trang này".
-
Tạo tài khoản trùng username →
`400`
với thông báo rõ ràng, không crash.
-
Đặt lại mật khẩu / khoá tài khoản cho
`userId`
không tồn tại →
`404`
rõ ràng.
-
Tự đổi mật khẩu: yêu cầu nhập đúng mật khẩu hiện tại trước khi đổi sang mật
khẩu mới (tránh trường hợp máy đang đăng nhập sẵn bị người khác đổi mật khẩu
hộ không hay biết).
### 3.8 Testing
Theo đúng cách đã áp dụng cho các tính năng trước (không có framework test thật
trong repo — script
`scratch/`
chạy qua MongoDB thật):
-
Tạo
`AdminUser`
, hash đúng, đăng nhập đúng mật khẩu → thành công; sai mật khẩu
→ thất bại với thông báo chung chung.
-
`requireLogin`
chặn request chưa có session hợp lệ.
-
`requireAdmin`
chặn tài khoản
`role: "staff"`
truy cập
`/admin/accounts`
,
`/admin/accounts/:id/toggle`
,
`/admin/accounts/:id/reset-password`
→
`403`
.
-
Tạo 2 tài khoản staff (A, B) + mỗi người tạo 1 giao dịch riêng: xác nhận A chỉ
thấy giao dịch của A, B chỉ thấy của B, admin thấy cả hai + lọc theo từng
người đúng.
-
Khoá tài khoản (
`active: false`
) → đăng nhập lại bằng đúng mật khẩu cũ vẫn bị
từ chối.
-
Tự đổi mật khẩu: đổi xong, mật khẩu cũ không đăng nhập được nữa, mật khẩu mới
đăng nhập được.
-
Migrate giao dịch cũ: xác nhận các bản ghi
`AdminTransaction`
không có
`createdByUsername`
trước migrate được gán đúng username admin đầu tiên sau
khi chạy migrate, và admin vẫn thấy được chúng trong lịch sử.
## 4. Việc cần làm khi triển khai (tóm tắt cho bước lập kế hoạch)
-
Thêm dependency
`bcryptjs`
vào
`package.json`
.
-
Model mới
`app/models/AdminUser.js`
.
-
`app/models/AdminTransaction.js`
: thêm field
`createdByUsername`
.
-
Middleware mới
`app/middlewares/requireLogin.js`
,
`app/middlewares/requireAdmin.js`
— thay thế
`app/middlewares/basicAuth.js`
(xoá file cũ nếu không còn dùng).
-
Cơ chế seed admin đầu tiên từ
`.env`
(chạy lúc server khởi động, idempotent).
-
Cơ chế migrate 1 lần cho giao dịch cũ chưa có
`createdByUsername`
.
-
Controller: thêm
`login`
,
`logout`
,
`accountsList`
,
`createAccount`
,
`toggleAccount`
,
`resetAccountPassword`
,
`changeOwnPasswordForm`
,
`changeOwnPassword`
vào
`app/controllers/admin.server.controller.js`
(hoặc
tách file riêng nếu controller quá dài — controller hiện đã ~200 dòng, cân
nhắc tách
`app/controllers/adminAccount.server.controller.js`
lúc lập kế
hoạch chi tiết).
-
Sửa
`createTransaction`
,
`listTransactions`
để gán/lọc theo
`createdByUsername`
.
-
View mới (Swig, theo phong cách đã thiết kế lại cho các trang admin — dashboard
xanh navy, Inter font):
`admin/login.server.view.html`
,
`admin/accounts-list.server.view.html`
,
`admin/change-password.server.view.html`
.
-
`app/routes/admin.server.routes.js`
: cập nhật toàn bộ route table ở mục 3.5.
-
Bộ test cho toàn bộ luồng trước khi coi là hoàn tất (mục 3.8).
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment