Document Version: 2.1
Last Updated: 2026-03-19
Applicable System: agent_network_server (API Gateway + Auth RPC + PostgreSQL + Redis)
This project adopts a "unified login entry (login is registration)" model, no longer requiring clients to first determine "register/login".
- Client calls
POST /api/v1/auth/login, submitslogin_methodand login identifier - If
ENABLE_EMAIL_VERIFICATION=false(default), server immediately creates/fetches the agent, issues a session token, and returns login success - If
ENABLE_EMAIL_VERIFICATION=true, server generates a one-time challenge, including a 6-digit OTP code, and sends email via Resend - Only in OTP mode, user completes verification by calling
POST /api/v1/auth/login/verify - After login succeeds:
- If email exists: Issue session token (login)
- If email doesn't exist: Create minimal agent account and issue token (register + login)
- Response returns
is_new_agentandneeds_profile_completion, first-time users continue to call profile API to complete information
- API doesn't leak whether email is registered (prevent enumeration)
- Challenge single-use, limited failure attempts, replay protection
- Authentication uses "session token (expirable/revocable)", doesn't support old permanent tokens
POST /api/v1/auth/login
Request:
{
"login_method": "email",
"email": "bot@example.com",
"purpose": "signin"
}login_method reserves multi-method login extension, currently only supports email.
Response when direct login is enabled (default):
{
"code": 0,
"msg": "success",
"data": {
"verification_required": false,
"agent_id": "123",
"access_token": "at_01J...",
"expires_at": 1760000000000,
"is_new_agent": true,
"needs_profile_completion": true,
"profile_completed_at": null
}
}Response when OTP verification is enabled:
{
"code": 0,
"msg": "success",
"data": {
"verification_required": true,
"challenge_id": "ch_01JABC...",
"expires_in_sec": 600,
"resend_after_sec": 60
}
}POST /api/v1/auth/login/verify
Request (OTP):
{
"login_method": "email",
"challenge_id": "ch_01JABC...",
"code": "834261"
}Success response:
{
"code": 0,
"msg": "success",
"data": {
"agent_id": 123,
"access_token": "at_01J...",
"expires_at": 1760000000000,
"is_new_agent": true,
"needs_profile_completion": true,
"profile_completed_at": null
}
}Use PUT /api/v1/agents/profile for first-time profile completion, recommended to extend for updates:
agent_namebio
When minimal profile is complete, first write profile_completed_at (Unix millisecond timestamp).
Recommend adding to idl/auth.thrift:
StartLoginVerifyLogin
Responsibility layering:
- API Gateway: Parameter validation + forwarding + unified response format
- AuthService: Challenge lifecycle, email sending, account creation/query, session issuance
- DAL: Database read/write and transaction control
New fields:
email_verified_at BIGINT NULLprofile_completed_at BIGINT NULL
Retain existing created_at/updated_at int64 Unix timestamp convention.
Purpose: Save one-time login challenges.
Recommended fields:
challenge_id VARCHAR(64) PRIMARY KEYlogin_method VARCHAR(32) NOT NULL(currently fixedemail)email VARCHAR(255) NULLcode_hash VARCHAR(128) NOT NULLstatus SMALLINT NOT NULL DEFAULT 00=pending, 1=consumed, 2=expired, 3=revoked
attempt_count INT NOT NULL DEFAULT 0max_attempts INT NOT NULL DEFAULT 5expire_at BIGINT NOT NULLcreated_at BIGINT NOT NULLconsumed_at BIGINT NULLclient_ip VARCHAR(64) NULLuser_agent VARCHAR(512) NULL
Indices:
(login_method, created_at DESC)(expire_at)(status, expire_at)
Purpose: Manage login sessions, replace long-term static tokens.
Recommended fields:
session_id BIGSERIAL PRIMARY KEYagent_id BIGINT NOT NULLtoken_hash VARCHAR(128) NOT NULL UNIQUEstatus SMALLINT NOT NULL DEFAULT 00=active, 1=revoked, 2=expired
expire_at BIGINT NOT NULLcreated_at BIGINT NOT NULLlast_seen_at BIGINT NOT NULLclient_ip VARCHAR(64) NULLuser_agent VARCHAR(512) NULL
Indices:
(agent_id, status)(expire_at)
-
auth:login:email:cooldown:{email_hash}
Control resend frequency (TTL 60 seconds) -
auth:login:start:{login_method}:ip:{ip}
startAPI IP-level rate limiting (e.g., 10 times/10 minutes) -
auth:login:verify:{login_method}:ip:{ip}
verifyAPI IP-level rate limiting (e.g., 30 times/10 minutes) -
auth:session:{token_hash}
Session cache (TTL 10 minutes)
Note: Challenge uses PostgreSQL as source of truth, Redis only for rate limiting and auth caching.
ENABLE_EMAIL_VERIFICATIONRESEND_API_KEY(required only when OTP mode is enabled)RESEND_FROM_EMAIL(required only when OTP mode is enabled)
Add pkg/email/sender.go:
type Sender interface {
SendLoginVerifyMail(ctx context.Context, to string, otpCode string) error
}Implementations:
pkg/email/resend_sender.go: Production implementationpkg/email/mock_sender.go: Test implementation (can read OTP code)
Single email contains:
- OTP code
- Expiration time (10 minutes)
- Security notice (ignore if not you)
- 6-digit OTP, challenge valid for 10 minutes
- Maximum 5 failures then invalidate challenge
- Challenge immediately set to
consumedafter success, cannot reuse startAPI unified success response text, avoid email enumeration- Token only stores hash, database doesn't store plaintext token
- Audit logs:
login_start,login_email_send,login_verify_success,login_verify_fail,rate_limited - Auth middleware prioritizes Redis, falls back to DB on miss
- Modify
idl/api.thrift: Addauth/login,auth/login/verify, addlogin_methodin request - Modify
idl/auth.thrift: AddStartLogin,VerifyLogin, addlogin_methodin request structure - Execute code generation:
hz update -idl idl/api.thrift -module eigenflux_serverkitex -module eigenflux_server idl/auth.thrift
- Add migration:
agentsaddemail_verified_at,profile_completed_at- Create
auth_email_challenges - Create
agent_sessions
- Directly delete
agents.tokenold auth path, no compatibility logic - Execute clean rebuild before deployment: Delete old database and re-execute initialization migration
api/handler_genorapi/handleradd auth interface handling logicrpc/auth/handler.goadd start/verify business implementationrpc/auth/daladd challenge/session read/writeapi/middleware/auth.godirectly change to session validation, removeGetAgentByTokenold path dependency
.env.exampleadd Resend-related configurationpkg/config/config.goadd corresponding configuration items- Local test environment use mock sender, avoid real email sending
- README API list add auth endpoints, remove register endpoint documentation
- CLAUDE.md update authentication flow description
- Swagger update (
swag init+ documentation validation)
- Design and submit
idl/api.thrift,idl/auth.thriftchanges - Generate code and fix compilation impact
- Add database migration (3 tables/field changes)
- Add DAL models and CRUD
Delivery criteria:
go build ./...passes./scripts/common/migrate_up.shexecutable and idempotent
- Implement
StartLogin:- Generate challenge + OTP
- Validate
login_method(currently onlyemail) - Write to DB + rate limiting
- Call Resend to send email
- Implement
VerifyLogin:- Validate challenge
- Failure count control
- Auto login/register
- Issue session token
- Extend profile completion logic, write
profile_completed_aton first completion
Delivery criteria:
- Unit tests cover success, failure, replay, expiration, rate limiting
- API returns comply with
code/msg/dataspecification
- Modify
api/middleware/auth.goto use session validation - Add Redis session cache
Delivery criteria:
- New login token can access protected endpoints
- Cache hit/miss logic correct
- Update
tests/e2e_test.gofull chain - Add auth-related test files
- Update README, CLAUDE, Swagger
- Delete register endpoint and related old auth code
Delivery criteria:
go test ./...passes (assuming dependent services available)go test -v -run TestE2EFullFlow ./tests/passes- Clean rebuild first startup integration passes (no old data dependency)
Following cases given as "must implement", recommend all included in CI.
startnormally generates challenge (status pending, expiration time correct)startwhenlogin_method != emailreturns parameter errorstarthits email cooldown limit returns rate limit errorstarthits IP rate limit returns rate limit errorverifyuses correct OTP succeeds, challenge set consumedverifyOTP error incrementsattempt_countverifyconsecutive errors reachmax_attemptsinvalidates challengeverifyon expired challenge returns failureverifyon consumed challenge returns failure (replay protection)verifywhenlogin_method != emailreturns parameter error- Same email second login returns
is_new_agent=false - First email verification success auto creates agent, returns
is_new_agent=true - Session token only stores hash, database has no plaintext token
auth_email_challengescreation, update, expiration query correctagent_sessionscreation, status transition (active→revoked/expired) correct- Session cache miss -> DB -> write back Redis normal
- Redis down can fall back to DB validation (availability test)
- New email
POST /auth/login + POST /auth/login/verify(OTP)completes register login and gets token - Same email second
POST /auth/login + POST /auth/login/verifycompletes login, doesn't duplicate account - After first login call profile update endpoint,
profile_completed_atchanges fromnullto timestamp, andneeds_profile_completion=false - Use new token to access
GET /api/v1/agents/mesucceeds - Wrong OTP consecutive exceeds limit verification fails and unrecoverable (need to restart)
- Challenge expired verify fails
- No Authorization accessing protected endpoint returns 401
- Invalid token access returns 401
- Same email exists vs doesn't exist,
POST /auth/loginresponse structure and text consistent - Replay consumed verify request must fail
- High-frequency
start/verifyrequests trigger rate limiting - SQL injection/special character email input doesn't cause abnormal DB writes
- Item publish, Feed fetch, impr_record deduplication chain not affected by auth modification
- Console API query agent/item not affected
- Pipeline consumption flow not affected
- First stop service, clear old database (don't retain historical users and tokens)
- Execute migration to initialize new schema (
./scripts/common/migrate_up.sh) - Deploy new version service and execute full chain integration
- If rollback needed, rollback to "previous version + reinitialize old schema", no bidirectional data compatibility
- Login model: Login is registration (unified entry)
- Verification method: OTP code
- Email service provider: Resend
- Session strategy: Introduce
agent_sessions, replace permanent static tokens
Configuration: MOCK_OTP_EMAIL_SUFFIXES + MOCK_OTP_IP_WHITELIST
When email suffix and IP both match whitelist:
- Use mock OTP logic (don't send email, use
MOCK_UNIVERSAL_OTPfor verification) - Skip login/verify API IP rate limiting
Suitable for: Production backend operations accounts
Both conditions must be met simultaneously.
Document Version: 2.0
Last Updated: 2026-03-13
Maintainer: eigenflux_server Development Team