diff --git a/config/default_config.yml b/config/default_config.yml index 5bf06eb3..6d2ae126 100644 --- a/config/default_config.yml +++ b/config/default_config.yml @@ -21,6 +21,16 @@ web_address: nyaa.pantsu.cat auth_token_expiration: 1000 # EnableSecureCSRF : Enable CSRF https mode : True if website support https, false otherwise (eg. testing locally: false) enable_secure_csrf: true +# the default config for session cookies +cookies: + # DomainName : The host domain so the cookies can be shared across subdomains + domain_name: pantsu.cat + # MaxAge : The expiration time of sessions cookies in seconds (default: 7 days) + max_age: 604800 + # HashKey : 64 byte key used to authenticate cookies using HMAC. Leave blank for a random key after each restart. + hash_key: + # EncryptionKey : 32 byte key used to encrypt values. Leave blank for a random key after each restart. + encryption_key: # the default config for bittorrent scraping scraper: addr: :9999 diff --git a/config/types.go b/config/types.go index edee876c..d5ae08ab 100644 --- a/config/types.go +++ b/config/types.go @@ -15,6 +15,8 @@ type Config struct { DBLogMode string `json:"db_logmode" yaml:"db_logmode,omitempty"` Version string `json:"version" yaml:"version,omitempty"` Build string `yaml:"-"` + // cookies config + Cookies CookiesConfig `yaml:"cookies,flow,omitempty"` // tracker scraper config (required) Scrape ScraperConfig `json:"scraper" yaml:"scraper,flow,omitempty"` // cache config @@ -41,6 +43,14 @@ type Config struct { Models ModelsConfig `yaml:"models,flow,omitempty"` } +// CookiesConfig : Config struct for session cookies +type CookiesConfig struct { + DomainName string `yaml:"domain_name,omitempty"` + MaxAge int `yaml:"max_age,omitempty"` + HashKey string `yaml:"hash_key,omitempty"` + EncryptionKey string `yaml:"encryption_key,omitempty"` +} + // CacheConfig is config struct for caching strategy type CacheConfig struct { Dialect string `yaml:"dialect,omitempty"` diff --git a/service/user/cookie_helper.go b/service/user/cookie_helper.go index 76f6258c..a37e95a8 100644 --- a/service/user/cookie_helper.go +++ b/service/user/cookie_helper.go @@ -2,6 +2,7 @@ package userService import ( "errors" + "fmt" "net/http" "strconv" "time" @@ -21,8 +22,6 @@ import ( const ( // CookieName : Name of cookie CookieName = "session" - // Domain name : The host domain so these can be shared across sukebei and nyaa - DomainName = "pantsu.cat" // UserContextKey : key for user context UserContextKey = "nyaapantsu.user" @@ -30,8 +29,18 @@ const ( // If you want to keep login cookies between restarts you need to make these permanent var cookieHandler = securecookie.New( - securecookie.GenerateRandomKey(64), - securecookie.GenerateRandomKey(32)) + getOrGenerateKey(config.Conf.Cookies.HashKey, 64), + getOrGenerateKey(config.Conf.Cookies.EncryptionKey, 32)) + +func getOrGenerateKey(key string, requiredLen int) []byte { + data := []byte(key) + if len(data) == 0 { + data = securecookie.GenerateRandomKey(requiredLen) + } else if len(data) != requiredLen { + panic(fmt.Sprintf("failed to load cookie key. required key length is %d bytes and the provided key length is %d bytes.", requiredLen, len(data))) + } + return data +} // DecodeCookie : Encoding & Decoding of the cookie value func DecodeCookie(cookieValue string) (uint, error) { @@ -49,8 +58,7 @@ func DecodeCookie(cookieValue string) (uint, error) { } // EncodeCookie : Encoding of the cookie value -func EncodeCookie(userID uint) (string, error) { - validUntil := timeHelper.FewDaysLater(7) // 1 week +func EncodeCookie(userID uint, validUntil time.Time) (string, error) { value := map[string]string{ "u": strconv.FormatUint(uint64(userID), 10), "t": strconv.FormatInt(validUntil.Unix(), 10), @@ -60,13 +68,9 @@ func EncodeCookie(userID uint) (string, error) { // ClearCookie : Erase cookie session func ClearCookie(w http.ResponseWriter) (int, error) { - domain := DomainName - if config.Conf.Environment == "DEVELOPMENT" { - domain = "" - } cookie := &http.Cookie{ Name: CookieName, - Domain: domain, + Domain: getDomainName(), Value: "", Path: "/", HttpOnly: true, @@ -103,21 +107,20 @@ func SetCookieHandler(w http.ResponseWriter, r *http.Request, email string, pass return http.StatusUnauthorized, errors.New("Account banned") } - encoded, err := EncodeCookie(user.ID) + maxAge := getMaxAge() + validUntil := timeHelper.FewDurationLater(time.Duration(maxAge) * time.Second) + encoded, err := EncodeCookie(user.ID, validUntil) if err != nil { return http.StatusInternalServerError, err } - domain := DomainName - if config.Conf.Environment == "DEVELOPMENT" { - domain = "" - } + cookie := &http.Cookie{ Name: CookieName, - Domain: domain, + Domain: getDomainName(), Value: encoded, Path: "/", HttpOnly: true, - MaxAge: 2592000, // One month + MaxAge: maxAge, } http.SetCookie(w, cookie) // also set response header for convenience @@ -176,12 +179,25 @@ func CurrentUser(r *http.Request) (model.User, error) { return user, nil } +func getDomainName() string { + domain := config.Conf.Cookies.DomainName + if config.Conf.Environment == "DEVELOPMENT" { + domain = "" + } + return domain +} + +func getMaxAge() int { + return config.Conf.Cookies.MaxAge +} + func getUserFromContext(r *http.Request) model.User { if rv := context.Get(r, UserContextKey); rv != nil { return rv.(model.User) } return model.User{} } + func setUserToContext(r *http.Request, val model.User) { context.Set(r, UserContextKey, val) }