safety: enforce JWT secret min length, validate order symbol input
- Require JWT_SECRET >= 32 characters at startup (prevents weak secrets) - Add SECURITY comment documenting auth.rs login as dev-only stub - Add symbol input validation in OrderForm: uppercase, alphanumeric with dots/hyphens/underscores, max 20 chars, visual error state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,11 @@ export function OrderForm() {
|
||||
|
||||
const submitOrder = useApiPost<unknown, OrderPayload>('/trading/orders');
|
||||
|
||||
const symbolValid = /^[A-Z0-9._-]{1,20}$/.test(symbol);
|
||||
|
||||
function handleSubmit(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!symbolValid) return;
|
||||
const qty = parseFloat(quantity);
|
||||
if (isNaN(qty) || qty <= 0) return;
|
||||
|
||||
@@ -52,8 +55,14 @@ export function OrderForm() {
|
||||
<input
|
||||
type="text"
|
||||
value={symbol}
|
||||
onChange={(e) => setSymbol(e.target.value)}
|
||||
className="w-full px-2 py-1.5 text-sm rounded border border-[var(--color-border)] bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] focus:border-[var(--color-accent)] outline-none"
|
||||
onChange={(e) => setSymbol(e.target.value.toUpperCase())}
|
||||
maxLength={20}
|
||||
pattern="[A-Z0-9._-]+"
|
||||
className={`w-full px-2 py-1.5 text-sm rounded border bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] outline-none ${
|
||||
symbolValid
|
||||
? 'border-[var(--color-border)] focus:border-[var(--color-accent)]'
|
||||
: 'border-[var(--color-red)]'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,10 +16,13 @@ async fn main() -> Result<()> {
|
||||
|
||||
let config = GatewayConfig::from_env();
|
||||
|
||||
// Fail fast if JWT secret is not configured
|
||||
// Fail fast if JWT secret is not configured or too short
|
||||
if config.jwt_secret.is_empty() {
|
||||
bail!("JWT_SECRET environment variable must be set (non-empty) for token validation");
|
||||
}
|
||||
if config.jwt_secret.len() < 32 {
|
||||
bail!("JWT_SECRET must be at least 32 characters for adequate security");
|
||||
}
|
||||
|
||||
let listen_addr = config.listen_addr.clone();
|
||||
|
||||
|
||||
@@ -21,12 +21,13 @@ pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/login", routing::post(login))
|
||||
}
|
||||
|
||||
// SECURITY: This is a development-only login stub. Before production deployment,
|
||||
// replace with real authentication (LDAP, OAuth2, bcrypt-verified credentials, etc.).
|
||||
// Currently accepts any non-empty username/password combination.
|
||||
async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(body): Json<LoginRequest>,
|
||||
) -> Result<Json<LoginResponse>, AppError> {
|
||||
// Validate credentials against the configured secret
|
||||
// In production, this would check against a user database or auth service
|
||||
if body.username.is_empty() || body.password.is_empty() {
|
||||
return Err(AppError::BadRequest("Username and password required".into()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user