package main import ( "crypto/rand" "encoding/base64" "encoding/json" "fmt" "net/http" "os" "strings" "riedstra.dev/mitch/go-website/users" ) type Auth struct { Users []*users.SiteUser `json:"Users"` // How long are JWTs valid? LoginHours int `json:"LoginHours"` // JWT secret TokenKey string `json:"TokenKey"` // Are cookies HTTP only? HTTPOnly bool `json:"HTTPOnly"` // Do they require HTTPs? Secure bool `json:"SecureCookie"` // See https://pkg.go.dev/net/http#SameSite // You probably want this set to 3 SameSiteStrict http.SameSite `json:"SameSiteStrict"` } func GenTokenKey() string { r := make([]byte, 16) // 128 bits _, err := rand.Read(r) if err != nil { // Not my favorite thing, but I consider this to be a // critical issue panic(fmt.Errorf("reading random bytes: %w", err)) } return base64.RawURLEncoding.EncodeToString(r) } func (a *App) ReadAuth(fn string) error { //nolint auth := &Auth{ Users: []*users.SiteUser{ { Username: "admin", Password: "admin", }, }, LoginHours: 1, TokenKey: GenTokenKey(), HTTPOnly: true, Secure: true, SameSiteStrict: http.SameSiteStrictMode, } var dec *json.Decoder fh, err := os.Open(fn) if err != nil { if strings.Contains(err.Error(), "no such file") || strings.Contains(err.Error(), "system cannot find the file specified") { goto write } return fmt.Errorf("opening %s: %w", fn, err) } dec = json.NewDecoder(fh) dec.DisallowUnknownFields() err = dec.Decode(auth) if err != nil { return fmt.Errorf("decoding file %s: %w", fn, err) } err = fh.Close() if err != nil { return fmt.Errorf("closing file %s: %w", fn, err) } for _, u := range auth.Users { err = u.SetPasswordHashIfNecessary() if err != nil { return fmt.Errorf("setting password hash for: %s: %w", u.Username, err) } } write: fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("opening file %s: %w", fn, err) } enc := json.NewEncoder(fh) enc.SetIndent("", " ") err = enc.Encode(auth) if err != nil { return fmt.Errorf("encoding to file %s: %w", fn, err) } a.auth = auth return nil }