fix: security & privacy (rate limiter fix, csrf session cookie, logout on server, drafts stale token passw, login timing, password over 72b, JWT_SECRET complexity, SMTP TLS, CSV export neutralised, fonts bundled, upload metadata, hourly cleanup job, privacy.md report)
This commit is contained in:
1 parent
e2812ee0b9
commit
ea98c5ec05
118 files changed
+3828
-130
No files matched your search
@@ -0,0 +1,5 @@
|
||||
package image
|
||||
|
||||
type MetadataStripper interface {
|
||||
Strip(contentType string, data []byte) ([]byte, error)
|
||||
}
|
||||
@@ -15,6 +15,8 @@ var ErrInviteAlreadyAccepted = errors.New("invite already accepted")
|
||||
|
||||
const Lifetime = 3 * 24 * time.Hour
|
||||
|
||||
const Retention = 30 * 24 * time.Hour
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
@@ -42,3 +44,13 @@ func (i Invite) StatusAt(now time.Time) Status {
|
||||
}
|
||||
return StatusExpired
|
||||
}
|
||||
|
||||
func (i Invite) IsStaleAt(now time.Time) bool {
|
||||
switch i.StatusAt(now) {
|
||||
case StatusAccepted:
|
||||
return now.After(i.AcceptedAt.Add(Retention))
|
||||
case StatusExpired:
|
||||
return now.After(i.ExpiresAt.Add(Retention))
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package retention
|
||||
|
||||
type Result struct {
|
||||
RevokedSessions int
|
||||
UnverifiedUsers int
|
||||
Invites int
|
||||
}
|
||||
|
||||
func (r Result) Total() int {
|
||||
return r.RevokedSessions + r.UnverifiedUsers + r.Invites
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidSessionID = errors.New("invalid session id")
|
||||
|
||||
const MaxIDLength = 64
|
||||
|
||||
type Revocation struct {
|
||||
ID string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func ValidateID(id string) error {
|
||||
if id == "" || len(id) > MaxIDLength {
|
||||
return ErrInvalidSessionID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package session
|
||||
|
||||
import "time"
|
||||
|
||||
type Repository interface {
|
||||
Revoke(revocation Revocation) error
|
||||
IsRevoked(id string) (bool, error)
|
||||
DeleteExpired(now time.Time) (int, error)
|
||||
}
|
||||
@@ -18,9 +18,19 @@ var ErrInvalidDisplayName = errors.New("invalid display name")
|
||||
var ErrUsernameTaken = errors.New("username taken")
|
||||
var ErrInvalidLookup = errors.New("invalid user lookup")
|
||||
var ErrVerificationEmailNotSent = errors.New("verification email not sent")
|
||||
var ErrPasswordTooLong = errors.New("password too long")
|
||||
|
||||
const MaxLookupIDs = 100
|
||||
|
||||
const MaxPasswordBytes = 72
|
||||
|
||||
func ValidatePassword(password string) error {
|
||||
if len(password) > MaxPasswordBytes {
|
||||
return ErrPasswordTooLong
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
@@ -45,6 +55,22 @@ type User struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
const UnverifiedRetention = 7 * 24 * time.Hour
|
||||
|
||||
func (u User) IsStaleUnverifiedAt(now time.Time) bool {
|
||||
if u.IsVerified {
|
||||
return false
|
||||
}
|
||||
deadline := u.VerificationExpiration
|
||||
if deadline.IsZero() {
|
||||
deadline = u.CreatedAt
|
||||
}
|
||||
if deadline.IsZero() {
|
||||
return false
|
||||
}
|
||||
return now.After(deadline.Add(UnverifiedRetention))
|
||||
}
|
||||
|
||||
func (u User) NameOrUsername() string {
|
||||
if trimmed := strings.TrimSpace(u.DisplayName); trimmed != "" {
|
||||
return trimmed
|
||||
|
||||
Reference in new issue
Block a user