MFA fix, added IP blacklist, update webmail client
This commit is contained in:
@@ -132,6 +132,33 @@ func (d *DB) LogAuthAttempt(authType, identifier, ipAddress string, success bool
|
||||
return err
|
||||
}
|
||||
|
||||
// CountRecentFailedAttempts counts failed esrv_auth_logs rows for one identifier
|
||||
// (independent of which IP each attempt came from — a distributed credential-
|
||||
// stuffing attempt against a single account should still trip this) within
|
||||
// authType and since the given cutoff, powering the per-account lockout in
|
||||
// internal/webui/login.go and webmail_login.go.
|
||||
func (d *DB) CountRecentFailedAttempts(authType, identifier string, since time.Time) (int, error) {
|
||||
var n int
|
||||
// created_at is populated by SQLite's own CURRENT_TIMESTAMP: a plain
|
||||
// "YYYY-MM-DD HH:MM:SS" UTC string, space-separated, no fractional seconds, no
|
||||
// offset. modernc.org/sqlite instead binds a Go time.Time query parameter as
|
||||
// RFC3339Nano with a zone offset (e.g. "2026-08-14T06:57:50.497566315+01:00") —
|
||||
// a live check confirmed this by inserting a time.Time into a real column and
|
||||
// reading the stored text back. That format is structurally different from
|
||||
// CURRENT_TIMESTAMP's own (different separator, precision, and offset), so a
|
||||
// plain text >= comparison between the two doesn't reflect chronological order at
|
||||
// all (confirmed: it silently matched zero rows). Two Go-bound time.Time values
|
||||
// compared against each other DO work correctly, since the driver formats both
|
||||
// identically — this only breaks when one side is a raw SQL CURRENT_TIMESTAMP
|
||||
// default and the other is a Go-bound parameter, which happens on THIS column but
|
||||
// nowhere else in this codebase (checked every other DATETIME comparison).
|
||||
// Formatting since into CURRENT_TIMESTAMP's exact layout makes both sides match.
|
||||
err := d.QueryRow(`SELECT COUNT(*) FROM esrv_auth_logs
|
||||
WHERE auth_type = ? AND identifier = ? AND success = 0 AND created_at >= ?`,
|
||||
authType, identifier, since.UTC().Format("2006-01-02 15:04:05")).Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func parseTime(s string) (time.Time, error) {
|
||||
for _, layout := range []string{"2006-01-02 15:04:05.999999999-07:00", "2006-01-02 15:04:05", time.RFC3339} {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
|
||||
Reference in New Issue
Block a user