Phase 3.4: structured rate-limit retry-after + auth error logging

OtpError now carries an optional details object; rate-limit branches in
checkRateLimits compute retry_after_seconds (cooldown delta for OTP_COOLDOWN,
window-roll-out delta for OTP_RATE_LIMIT_PHONE / OTP_RATE_LIMIT_IP) so the
client can disable Kirim OTP / Kirim ulang CTAs with a real countdown.

All four sendAuthError helpers (client, mitra, shared, internal) now surface
err.details and log unhandled (no statusCode) errors at level 50.

New GET /api/shared/config/otp returns the resend cooldown so the OTP screen
can gate the resend countdown without hardcoding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 13:43:56 +08:00
parent 6de541848c
commit fa7071def5
6 changed files with 86 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
import { authenticate } from '../../plugins/auth.js'
import { getAnonymityConfig, getSensitivityConfig } from '../../services/config.service.js'
import { getAnonymityConfig, getSensitivityConfig, getOtpRateLimits } from '../../services/config.service.js'
export const sharedConfigRoutes = async (app) => {
app.get('/anonymity', async (request, reply) => {
@@ -11,4 +11,12 @@ export const sharedConfigRoutes = async (app) => {
const config = await getSensitivityConfig()
return reply.send({ success: true, data: config })
})
app.get('/otp', async (request, reply) => {
const limits = await getOtpRateLimits()
return reply.send({
success: true,
data: { resend_cooldown_seconds: limits.resend_cooldown_seconds },
})
})
}