Add session cookie config (#874)
This allows changing the cookie domain, maxage and the hash/encryption keys via the config file. If no key is provided a new one is generated on each reboot. But if both keys are provided the session cookies are now valid even after a server reboot.
Cette révision appartient à :
Parent
95548878e6
révision
8cab61802c
3 fichiers modifiés avec 54 ajouts et 18 suppressions
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Référencer dans un nouveau ticket