Login anak via username + PIN, brute force protection
Belum ada ide. Klik Edit untuk menambahkan.
Login anak via username + PIN (bukan email/password).
Endpoint: POST /api/auth/native-anak
Auth: JWT bearer (30 hari expiry)
Pemakai: App anak (zx-agent)
Keamanan:
/^[a-z0-9_]{3,20}$/, 16 reserved wordsFile:
src/lib/validasi.ts — USERNAME_REGEX, USERNAME_RESERVED, PIN_LEMAH, validasiUsername(), validasiPin(), duplikatUsername()src/app/api/auth/native-anak/route.ts — login endpointsrc/app/api/anak/[id]/pin/route.ts — reset PINsrc/lib/umur.ts — hitungUmur() dari tanggalLahir66 PIN lemah di-blacklist:
Repeated (30):
0000,1111,2222,...,999900000,11111,...,99999000000,111111,...,999999Sequential naik (18):
0123,1234,2345,...,678901234,...,56789012345,...,456789Sequential turun (18):
9876,8765,...,321098765,...,54321987654,...,654321Catatan:
123456 = sequential naik → DITOLAK1234 = sequential naik → juga ada, tapi existing PIN tidak di-retroactive rejectvalidasiPin() di src/lib/validasi.ts3 iterasi fix bug reset PIN:
v2.9.5 — Iterasi 1:
(sukses: Boolean) -> Unit + loading spinner + error display via state.errorv2.9.6 — Iterasi 2:
state.error global bisa ter-clear oleh operasi lain → user tidak lihat pesan error(sukses: Boolean, pesan: String?) -> Unit — pesan langsung dari callback, tampilkan lokal di dialog sebagai pesanPin merahv2.9.7 — Iterasi 3:
DialogEditAnak → ambiguitas dua tombol Simpan (Simpan Edit + Simpan PIN Baru). User tekan tombol Simpan (edit) bukan Simpan PIN Baru → reset PIN tidak ter-triggerDialogResetPin composable terpisah dengan field PIN + tombol "Simpan PIN Baru" yang jelasFile:
LayarAnak.kt — DialogResetPin composable, DialogEditAnak punya tombol "Reset PIN" yang buka dialog terpisahZxVm.kt — resetPinAnak(id, pin, onSelesai: (sukses, pesan) -> Unit)MainActivity.kt — wiring onResetPin = { id, pin, onSelesai -> vm.resetPinAnak(id, pin, onSelesai) }Api.kt — anakResetPin @PATCH("anak/{id}/pin"), AnakResetPinReq(val pin: String)src/app/api/anak/[id]/pin/route.ts — PATCH reset PIN, validasiPin, bcrypt hashIn-memory Map (cukup single instance Coolify):
Map key = username (lowercase)
Value = { gagal: number, lockUntil: number, recent: number[] }Map key = username (lowercase)
Value = { gagal: number, lockUntil: number, recent: number[] }
**Rules:**
- Max 5 percobaan per menit (sliding window)
- 10 gagal total → lockout 15 menit
- Sukses login → reset state
- Lockout response: `429 { error: "Terlalu banyak percobaan. Coba lagi dalam X menit." }`
- Rate limit response: `429 { error: "Terlalu banyak percobaan. Tunggu sebentar." }`
**Catatan:** In-memory = reset jika container restart. Tidak persistent. Cukup untuk single VPS.Rules:
429 { error: "Terlalu banyak percobaan. Coba lagi dalam X menit." }429 { error: "Terlalu banyak percobaan. Tunggu sebentar." }Catatan: In-memory = reset jika container restart. Tidak persistent. Cukup untuk single VPS.