feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
//! OTP storage and rate-limiting.
|
|
|
|
|
//!
|
|
|
|
|
//! Keys
|
|
|
|
|
//! ────
|
|
|
|
|
//! `otp:code:{6-digit-code}` → user_id string, TTL 15 min
|
|
|
|
|
//! `otp:resend:{user_id}` → resend attempt counter, TTL 1 hour (max 3)
|
|
|
|
|
|
|
|
|
|
use redis::AsyncCommands;
|
|
|
|
|
use crate::RedisPool;
|
|
|
|
|
|
|
|
|
|
const OTP_TTL_SECS: u64 = 900; // 15 minutes
|
|
|
|
|
const RESEND_WINDOW_SECS: i64 = 3_600; // 1 hour
|
|
|
|
|
const RESEND_MAX: i64 = 3;
|
|
|
|
|
|
|
|
|
|
// ── Store / verify ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Store OTP code keyed by the code itself → user_id. TTL 15 min.
|
Update backend services: catering_services, companies, developers, gateway, job_seekers, photographers, social_media_managers, tutors, ugc_content_creators, users; update cache (otp, token), contracts (profession_shared, profession_state), db (job_seeker, verification), email; add revision-requested email template; update init-db.sql and start-services.sh
2026-05-08 15:34:29 +02:00
|
|
|
/// Also stores otp:plain:{user_id} → code for dev-test readability.
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
pub async fn set(redis: &mut RedisPool, code: &str, user_id: &str) -> Result<(), redis::RedisError> {
|
|
|
|
|
let key = format!("otp:code:{code}");
|
Update backend services: catering_services, companies, developers, gateway, job_seekers, photographers, social_media_managers, tutors, ugc_content_creators, users; update cache (otp, token), contracts (profession_shared, profession_state), db (job_seeker, verification), email; add revision-requested email template; update init-db.sql and start-services.sh
2026-05-08 15:34:29 +02:00
|
|
|
let plain_key = format!("otp:plain:{user_id}");
|
|
|
|
|
// Store both: code→user_id (for verification) and plain→code (for dev debugging)
|
|
|
|
|
redis.set_ex::<_, _, ()>(&plain_key, code, OTP_TTL_SECS).await?;
|
|
|
|
|
redis.set_ex::<_, _, ()>(key, user_id, OTP_TTL_SECS).await
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Atomically fetch the user_id for this OTP and delete it (single-use).
|
|
|
|
|
/// Returns `None` if the code doesn't exist or has expired.
|
|
|
|
|
pub async fn consume(redis: &mut RedisPool, code: &str) -> Result<Option<String>, redis::RedisError> {
|
|
|
|
|
let key = format!("otp:code:{code}");
|
|
|
|
|
// GETDEL: atomic get + delete (Redis ≥ 6.2)
|
|
|
|
|
redis.get_del(key).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Resend rate limit ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Returns `true` if the user is allowed to request another OTP (< 3 in last hour).
|
|
|
|
|
pub async fn resend_allowed(redis: &mut RedisPool, user_id: &str) -> Result<bool, redis::RedisError> {
|
|
|
|
|
let key = format!("otp:resend:{user_id}");
|
|
|
|
|
let count: i64 = redis.get(&key).await.unwrap_or(0);
|
|
|
|
|
Ok(count < RESEND_MAX)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Increment the resend counter. Call after sending the OTP.
|
|
|
|
|
pub async fn record_resend(redis: &mut RedisPool, user_id: &str) -> Result<(), redis::RedisError> {
|
|
|
|
|
let key = format!("otp:resend:{user_id}");
|
|
|
|
|
let count: i64 = redis.incr(&key, 1i64).await?;
|
|
|
|
|
// Only set expiry on first increment so window is fixed from first request
|
|
|
|
|
if count == 1 {
|
2026-03-22 15:55:29 +01:00
|
|
|
redis.expire::<_, ()>(&key, RESEND_WINDOW_SECS).await?;
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|