fix(users): allow demo login without redis
This commit is contained in:
parent
9b3bc98b38
commit
ac3fef72a1
2 changed files with 89 additions and 10 deletions
|
|
@ -407,11 +407,13 @@ async fn login(
|
|||
State(state): State<AppState>,
|
||||
Json(payload): Json<LoginPayload>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
|
||||
let email = payload.email.to_lowercase();
|
||||
let email = payload.email.to_lowercase();
|
||||
let is_demo_account = is_dummy_account_email(&email);
|
||||
let mut redis = state.redis.clone();
|
||||
|
||||
// Rate limit: max 10 login attempts per 15 min per email
|
||||
if !cache::rate_limit::check_login(&mut redis, &email).await.unwrap_or(true) {
|
||||
// Demo logins must not depend on Redis because the live demo path needs to work
|
||||
// even when cache persistence is degraded.
|
||||
if !is_demo_account && !cache::rate_limit::check_login(&mut redis, &email).await.unwrap_or(true) {
|
||||
return Err(err(StatusCode::TOO_MANY_REQUESTS, "Too many login attempts. Try again in 15 minutes.", "RATE_LIMITED"));
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +426,6 @@ async fn login(
|
|||
}
|
||||
|
||||
// Allow demo accounts to login without email verification
|
||||
let is_demo_account = is_dummy_account_email(&email);
|
||||
if !user.email_verified && !is_demo_account {
|
||||
return Err(err(StatusCode::UNAUTHORIZED, "Email not verified. Check your inbox.", "EMAIL_NOT_VERIFIED"));
|
||||
}
|
||||
|
|
@ -450,9 +451,14 @@ async fn login(
|
|||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "TOKEN_ERROR"))?;
|
||||
|
||||
// Refresh token → Redis (30-day TTL)
|
||||
cache::token::store_refresh(&mut redis, &tokens.refresh_token, &user.id.to_string())
|
||||
.await
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "CACHE_ERROR"))?;
|
||||
if let Err(e) = cache::token::store_refresh(&mut redis, &tokens.refresh_token, &user.id.to_string()).await {
|
||||
tracing::warn!(
|
||||
error = %e,
|
||||
user_id = %user.id,
|
||||
email = %user.email,
|
||||
"Failed to store refresh token in Redis; continuing with access token only"
|
||||
);
|
||||
}
|
||||
|
||||
let cookie = format!(
|
||||
"nxtgauge_refresh_token={}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=2592000",
|
||||
|
|
@ -549,9 +555,14 @@ async fn refresh(
|
|||
)
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "TOKEN_ERROR"))?;
|
||||
|
||||
cache::token::store_refresh(&mut redis, &tokens.refresh_token, &user.id.to_string())
|
||||
.await
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "CACHE_ERROR"))?;
|
||||
if let Err(e) = cache::token::store_refresh(&mut redis, &tokens.refresh_token, &user.id.to_string()).await {
|
||||
tracing::warn!(
|
||||
error = %e,
|
||||
user_id = %user.id,
|
||||
email = %user.email,
|
||||
"Failed to store refresh token in Redis; continuing with access token only"
|
||||
);
|
||||
}
|
||||
|
||||
let new_cookie = format!(
|
||||
"nxtgauge_refresh_token={}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=2592000",
|
||||
|
|
|
|||
68
scripts/bootstrap-live-compat.sql
Normal file
68
scripts/bootstrap-live-compat.sql
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
-- Compatibility bootstrap for empty production databases.
|
||||
-- Run after scripts/init-db.sql and before/after scripts/seed.sql.
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_role_assignments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||
approved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(user_id, role_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_assignments_user_id
|
||||
ON user_role_assignments(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS role_sidebar_configs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
audience VARCHAR(50) NOT NULL,
|
||||
config_json JSONB NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_role_sidebar_per_role_audience
|
||||
ON role_sidebar_configs(role_id, audience) WHERE is_active = true;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS role_runtime_configs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
config_json JSONB NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_role_runtime_per_role
|
||||
ON role_runtime_configs(role_id) WHERE is_active = true;
|
||||
|
||||
INSERT INTO user_role_assignments (id, user_id, role_id, status, approved_at, created_at)
|
||||
SELECT id, user_id, role_id, status, approved_at, created_at
|
||||
FROM user_roles
|
||||
ON CONFLICT (user_id, role_id) DO NOTHING;
|
||||
|
||||
INSERT INTO role_sidebar_configs (id, role_id, audience, config_json, version, is_active, updated_at)
|
||||
SELECT id, role_id, audience, config_json, version, is_active, updated_at
|
||||
FROM dashboard_configs
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO role_runtime_configs (id, role_id, config_json, version, is_active, updated_at)
|
||||
SELECT id, role_id, config_json, version, is_active, updated_at
|
||||
FROM runtime_configs
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO pricing_packages (name, role_key, package_type, tracecoins_amount, price_inr, description, is_active)
|
||||
VALUES
|
||||
('Starter Credits', 'COMPANY', 'TRACECOIN_BUNDLE', 100, 49900, 'Starter company credit bundle for demo purchases.', true),
|
||||
('Growth Credits', 'COMPANY', 'TRACECOIN_BUNDLE', 300, 129900, 'Growth company credit bundle for regular hiring.', true),
|
||||
('Scale Credits', 'COMPANY', 'TRACECOIN_BUNDLE', 750, 299900, 'Scale company credit bundle for higher-volume recruitment.', true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Reference in a new issue