fix(security): redesign password reset tokens using crypto/rand with server-side storage
- Replace SHA512-based deterministic token with 32-byte crypto/rand token - Store tokens server-side with 1-hour expiry and single-use semantics - Remove genToken (previously broken due to time.Add immutability bug) - Add CSRF double-submit cookie protection to change/lost/reset forms - Remove token from form action URL (use hidden fields only, POST body) - Add MailFrom field and SMTP_FROM env var for configurable sender address - Add SMTP_PASSWORD_FILE env var for secure SMTP password loading - Add PUBLIC_URL env var and --public-url flag for configurable reset link domain - Use generic error messages in handlers to avoid information disclosure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a2f368eb02
commit
57775bbf89
9 changed files with 193 additions and 83 deletions
26
main.go
26
main.go
|
|
@ -17,13 +17,14 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
const BASEURL = "https://ldap.nemunai.re"
|
||||
var myPublicURL = "https://ldap.nemunai.re"
|
||||
|
||||
var myLDAP = LDAP{
|
||||
Host: "localhost",
|
||||
Port: 389,
|
||||
BaseDN: "dc=example,dc=com",
|
||||
MailPort: 587,
|
||||
MailFrom: "noreply@nemunai.re",
|
||||
}
|
||||
|
||||
type ResponseWriterPrefix struct {
|
||||
|
|
@ -70,8 +71,11 @@ func main() {
|
|||
var bind = flag.String("bind", "127.0.0.1:8080", "Bind port/socket")
|
||||
var baseURL = flag.String("baseurl", "/", "URL prepended to each URL")
|
||||
var configfile = flag.String("config", "", "path to the configuration file")
|
||||
var publicURL = flag.String("public-url", myPublicURL, "Public base URL used in password reset emails")
|
||||
flag.Parse()
|
||||
|
||||
myPublicURL = *publicURL
|
||||
|
||||
// Sanitize options
|
||||
log.Println("Checking paths...")
|
||||
if *baseURL != "/" {
|
||||
|
|
@ -141,9 +145,25 @@ func main() {
|
|||
if val, ok := os.LookupEnv("SMTP_USER"); ok {
|
||||
myLDAP.MailUser = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok {
|
||||
if val, ok := os.LookupEnv("SMTP_PASSWORD_FILE"); ok {
|
||||
if fd, err := os.Open(val); err != nil {
|
||||
log.Fatal(err)
|
||||
} else if cnt, err := os.ReadFile(val); err != nil {
|
||||
fd.Close()
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
fd.Close()
|
||||
myLDAP.MailPassword = string(cnt)
|
||||
}
|
||||
} else if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok {
|
||||
myLDAP.MailPassword = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_FROM"); ok {
|
||||
myLDAP.MailFrom = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("PUBLIC_URL"); ok {
|
||||
myPublicURL = val
|
||||
}
|
||||
|
||||
if flag.NArg() > 0 {
|
||||
switch flag.Arg(0) {
|
||||
|
|
@ -164,7 +184,7 @@ func main() {
|
|||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
fmt.Printf("Reset link for %s: %s/reset?l=%s&t=%s", dn, BASEURL, login, token)
|
||||
fmt.Printf("Reset link for %s: %s/reset?l=%s&t=%s", dn, myPublicURL, login, token)
|
||||
return
|
||||
case "serve":
|
||||
case "server":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue