feat: standardize passwords at eight characters
This commit is contained in:
@@ -69,7 +69,7 @@ The built-in Actions job token can be disabled or lack registry scope on self-ho
|
|||||||
- `JWT_SECRET`: a unique random string of at least 32 characters.
|
- `JWT_SECRET`: a unique random string of at least 32 characters.
|
||||||
- `FRONTEND_ORIGIN`: the exact public application URL, for example `https://hub.example.com`.
|
- `FRONTEND_ORIGIN`: the exact public application URL, for example `https://hub.example.com`.
|
||||||
- `SUPERADMIN_EMAIL`: email address for the initial platform administrator.
|
- `SUPERADMIN_EMAIL`: email address for the initial platform administrator.
|
||||||
- `SUPERADMIN_PASSWORD`: password for that administrator (at least 12 characters).
|
- `SUPERADMIN_PASSWORD`: password for that administrator (at least 8 characters).
|
||||||
- `AUTH_RATE_LIMIT_MAX` and `AUTH_RATE_LIMIT_WINDOW_SECONDS` are optional login and public-auth throttling controls (defaults: 10 attempts per 900 seconds per source IP).
|
- `AUTH_RATE_LIMIT_MAX` and `AUTH_RATE_LIMIT_WINDOW_SECONDS` are optional login and public-auth throttling controls (defaults: 10 attempts per 900 seconds per source IP).
|
||||||
- `JWT_SESSION_TTL`, `INVITATION_TTL_HOURS`, `PASSWORD_RESET_TTL_HOURS`, `BUNNY_EMBED_TOKEN_TTL_SECONDS`, and `AUDIT_LOG_PAGE_SIZE` tune operating policy without changing code.
|
- `JWT_SESSION_TTL`, `INVITATION_TTL_HOURS`, `PASSWORD_RESET_TTL_HOURS`, `BUNNY_EMBED_TOKEN_TTL_SECONDS`, and `AUDIT_LOG_PAGE_SIZE` tune operating policy without changing code.
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
|
|
||||||
if (!signedInUser) {
|
if (!signedInUser) {
|
||||||
setError(isRegistering
|
setError(isRegistering
|
||||||
? 'Não foi possível criar sua conta. Use um e-mail válido e uma senha com pelo menos 12 caracteres.'
|
? 'Não foi possível criar sua conta. Use um e-mail válido e uma senha com pelo menos 8 caracteres.'
|
||||||
: 'Credenciais inválidas. Verifique seu e-mail e senha.');
|
: 'Credenciais inválidas. Verifique seu e-mail e senha.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{isRegistering && <input type="text" placeholder="Seu nome" value={name} onChange={(event) => setName(event.target.value)} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required autoFocus />}
|
{isRegistering && <input type="text" placeholder="Seu nome" value={name} onChange={(event) => setName(event.target.value)} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required autoFocus />}
|
||||||
<input type="email" placeholder="Seu e-mail" value={email} onChange={(event) => setEmail(event.target.value)} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required autoFocus={!isRegistering} />
|
<input type="email" placeholder="Seu e-mail" value={email} onChange={(event) => setEmail(event.target.value)} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required autoFocus={!isRegistering} />
|
||||||
<input type="password" placeholder="Sua senha" value={password} onChange={(event) => setPassword(event.target.value)} minLength={12} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required />
|
<input type="password" placeholder="Sua senha" value={password} onChange={(event) => setPassword(event.target.value)} minLength={8} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <div className="bg-red-500/10 border border-red-500/20 rounded-[8px] p-3"><p className="text-red-400 text-caption font-semibold tracking-caption text-center">{error}</p></div>}
|
{error && <div className="bg-red-500/10 border border-red-500/20 rounded-[8px] p-3"><p className="text-red-400 text-caption font-semibold tracking-caption text-center">{error}</p></div>}
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ export const AccessTokenPage: React.FC<{ mode: 'invite' | 'reset' }> = ({ mode }
|
|||||||
} catch { setError('Este link é inválido, expirou ou não pôde ser usado.'); }
|
} catch { setError('Este link é inválido, expirou ou não pôde ser usado.'); }
|
||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
};
|
};
|
||||||
return <main className="min-h-screen pt-32 px-6 flex justify-center"><form onSubmit={submit} className="w-full max-w-md rounded-2xl border border-white/10 bg-zinc-950 p-7 space-y-4"><h1 className="text-2xl font-bold">{mode === 'invite' ? 'Criar seu acesso' : 'Redefinir senha'}</h1>{mode === 'invite' && <input required value={name} onChange={(event) => setName(event.target.value)} placeholder="Seu nome" className="w-full rounded-xl bg-zinc-900 p-3" />}<input required minLength={12} type="password" value={password} onChange={(event) => setPassword(event.target.value)} placeholder="Nova senha (mínimo 12 caracteres)" className="w-full rounded-xl bg-zinc-900 p-3" />{error && <p className="text-sm text-red-400">{error}</p>}<button disabled={!token || saving} className="w-full rounded-xl bg-orange-500 p-3 font-semibold">{saving ? 'Salvando...' : mode === 'invite' ? 'Criar conta' : 'Redefinir senha'}</button></form></main>;
|
return <main className="min-h-screen pt-32 px-6 flex justify-center"><form onSubmit={submit} className="w-full max-w-md rounded-2xl border border-white/10 bg-zinc-950 p-7 space-y-4"><h1 className="text-2xl font-bold">{mode === 'invite' ? 'Criar seu acesso' : 'Redefinir senha'}</h1>{mode === 'invite' && <input required value={name} onChange={(event) => setName(event.target.value)} placeholder="Seu nome" className="w-full rounded-xl bg-zinc-900 p-3" />}<input required minLength={8} type="password" value={password} onChange={(event) => setPassword(event.target.value)} placeholder="Nova senha (mínimo 8 caracteres)" className="w-full rounded-xl bg-zinc-900 p-3" />{error && <p className="text-sm text-red-400">{error}</p>}<button disabled={!token || saving} className="w-full rounded-xl bg-orange-500 p-3 font-semibold">{saving ? 'Salvando...' : mode === 'invite' ? 'Criar conta' : 'Redefinir senha'}</button></form></main>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const environmentSchema = z.object({
|
|||||||
APP_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
APP_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||||
JWT_SECRET: z.string().min(32).default('development-only-secret-change-before-production'),
|
JWT_SECRET: z.string().min(32).default('development-only-secret-change-before-production'),
|
||||||
SUPERADMIN_EMAIL: optionalEnvironmentValue(z.string().email()),
|
SUPERADMIN_EMAIL: optionalEnvironmentValue(z.string().email()),
|
||||||
SUPERADMIN_PASSWORD: optionalEnvironmentValue(z.string().min(12)),
|
SUPERADMIN_PASSWORD: optionalEnvironmentValue(z.string().min(8)),
|
||||||
SUPERADMIN_NAME: z.string().min(1).max(120).default('Compor HUB Superadmin'),
|
SUPERADMIN_NAME: z.string().min(1).max(120).default('Compor HUB Superadmin'),
|
||||||
AUTH_RATE_LIMIT_MAX: z.coerce.number().int().min(1).max(1000).default(10),
|
AUTH_RATE_LIMIT_MAX: z.coerce.number().int().min(1).max(1000).default(10),
|
||||||
AUTH_RATE_LIMIT_WINDOW_SECONDS: z.coerce.number().int().min(60).max(86_400).default(900),
|
AUTH_RATE_LIMIT_WINDOW_SECONDS: z.coerce.number().int().min(60).max(86_400).default(900),
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import { sendWelcomeEmail } from '../services/email.js';
|
|||||||
|
|
||||||
const credentialsSchema = z.object({
|
const credentialsSchema = z.object({
|
||||||
email: z.string().email().transform((email) => email.toLowerCase()),
|
email: z.string().email().transform((email) => email.toLowerCase()),
|
||||||
password: z.string().min(12).max(200),
|
password: z.string().min(8).max(200),
|
||||||
});
|
});
|
||||||
|
|
||||||
const registerSchema = credentialsSchema.extend({
|
const registerSchema = credentialsSchema.extend({
|
||||||
name: z.string().trim().min(2).max(120),
|
name: z.string().trim().min(2).max(120),
|
||||||
});
|
});
|
||||||
const tokenPasswordSchema = z.object({ token: z.string().min(20), password: z.string().min(12).max(200), name: z.string().trim().min(2).max(120).optional() });
|
const tokenPasswordSchema = z.object({ token: z.string().min(20), password: z.string().min(8).max(200), name: z.string().trim().min(2).max(120).optional() });
|
||||||
|
|
||||||
type UserRow = {
|
type UserRow = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user