mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Security: refactor 'redirect_to' cookie to use 'Secure' flag (#19787)
* Refactor redirect_to cookie with secure flag in middleware * Refactor redirect_to cookie with secure flag in api/login * Refactor redirect_to cookie with secure flag in api/login_oauth * Removed the deletion of 'Set-Cookie' header to prevent logout * Removed the deletion of 'Set-Cookie' at top of api/login.go * Add HttpOnly flag on redirect_to cookies where missing * Refactor duplicated code * Add tests * Refactor cookie options * Replace local function for deleting cookie * Delete redundant calls Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
This commit is contained in:
parent
a3c99f4871
commit
c5f906f472
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ var getViewIndex = func() string {
|
|||||||
return ViewIndex
|
return ViewIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRedirectTo(redirectTo string) error {
|
func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
|
||||||
to, err := url.Parse(redirectTo)
|
to, err := url.Parse(redirectTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return login.ErrInvalidRedirectTo
|
return login.ErrInvalidRedirectTo
|
||||||
@ -36,12 +35,20 @@ func validateRedirectTo(redirectTo string) error {
|
|||||||
if to.IsAbs() {
|
if to.IsAbs() {
|
||||||
return login.ErrAbsoluteRedirectTo
|
return login.ErrAbsoluteRedirectTo
|
||||||
}
|
}
|
||||||
if setting.AppSubUrl != "" && !strings.HasPrefix(to.Path, "/"+setting.AppSubUrl) {
|
if hs.Cfg.AppSubUrl != "" && !strings.HasPrefix(to.Path, "/"+hs.Cfg.AppSubUrl) {
|
||||||
return login.ErrInvalidRedirectTo
|
return login.ErrInvalidRedirectTo
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions {
|
||||||
|
return middleware.CookieOptions{
|
||||||
|
Path: hs.Cfg.AppSubUrl + "/",
|
||||||
|
Secure: hs.Cfg.CookieSecure,
|
||||||
|
SameSite: hs.Cfg.CookieSameSite,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
||||||
viewData, err := setIndexViewData(hs, c)
|
viewData, err := setIndexViewData(hs, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,7 +69,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
//therefore the loginError should be passed to the view data
|
//therefore the loginError should be passed to the view data
|
||||||
//and the view should return immediately before attempting
|
//and the view should return immediately before attempting
|
||||||
//to login again via OAuth and enter to a redirect loop
|
//to login again via OAuth and enter to a redirect loop
|
||||||
deleteCookie(c, LoginErrorCookieName)
|
middleware.DeleteCookie(c.Resp, LoginErrorCookieName, hs.cookieOptionsFromCfg)
|
||||||
viewData.Settings["loginError"] = loginError
|
viewData.Settings["loginError"] = loginError
|
||||||
c.HTML(200, getViewIndex(), viewData)
|
c.HTML(200, getViewIndex(), viewData)
|
||||||
return
|
return
|
||||||
@ -79,13 +86,13 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
if err := validateRedirectTo(redirectTo); err != nil {
|
if err := hs.validateRedirectTo(redirectTo); err != nil {
|
||||||
viewData.Settings["loginError"] = err.Error()
|
viewData.Settings["loginError"] = err.Error()
|
||||||
c.HTML(200, getViewIndex(), viewData)
|
c.HTML(200, getViewIndex(), viewData)
|
||||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
||||||
c.Redirect(redirectTo)
|
c.Redirect(redirectTo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -168,12 +175,12 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
if err := validateRedirectTo(redirectTo); err == nil {
|
if err := hs.validateRedirectTo(redirectTo); err == nil {
|
||||||
result["redirectUrl"] = redirectTo
|
result["redirectUrl"] = redirectTo
|
||||||
} else {
|
} else {
|
||||||
log.Info("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
log.Info("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
||||||
}
|
}
|
||||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.MApiLoginPost.Inc()
|
metrics.MApiLoginPost.Inc()
|
||||||
@ -223,28 +230,13 @@ func tryGetEncryptedCookie(ctx *models.ReqContext, cookieName string) (string, b
|
|||||||
return string(decryptedError), err == nil
|
return string(decryptedError), err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteCookie(ctx *models.ReqContext, cookieName string) {
|
|
||||||
ctx.SetCookie(cookieName, "", -1, setting.AppSubUrl+"/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName string, value string, maxAge int) error {
|
func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName string, value string, maxAge int) error {
|
||||||
encryptedError, err := util.Encrypt([]byte(value), setting.SecretKey)
|
encryptedError, err := util.Encrypt([]byte(value), setting.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := http.Cookie{
|
middleware.WriteCookie(ctx.Resp, cookieName, hex.EncodeToString(encryptedError), 60, hs.cookieOptionsFromCfg)
|
||||||
Name: cookieName,
|
|
||||||
MaxAge: 60,
|
|
||||||
Value: hex.EncodeToString(encryptedError),
|
|
||||||
HttpOnly: true,
|
|
||||||
Path: setting.AppSubUrl + "/",
|
|
||||||
Secure: hs.Cfg.CookieSecure,
|
|
||||||
}
|
|
||||||
if hs.Cfg.CookieSameSite != http.SameSiteDefaultMode {
|
|
||||||
cookie.SameSite = hs.Cfg.CookieSameSite
|
|
||||||
}
|
|
||||||
http.SetCookie(ctx.Resp, &cookie)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
"github.com/grafana/grafana/pkg/login"
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@ -69,7 +70,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hashedState := hashStatecode(state, setting.OAuthService.OAuthInfos[name].ClientSecret)
|
hashedState := hashStatecode(state, setting.OAuthService.OAuthInfos[name].ClientSecret)
|
||||||
hs.writeCookie(ctx.Resp, OauthStateCookieName, hashedState, 60, hs.Cfg.CookieSameSite)
|
middleware.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, 60, hs.cookieOptionsFromCfg)
|
||||||
if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
|
if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
|
||||||
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
|
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
|
||||||
} else {
|
} else {
|
||||||
@ -81,8 +82,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
|
|||||||
cookieState := ctx.GetCookie(OauthStateCookieName)
|
cookieState := ctx.GetCookie(OauthStateCookieName)
|
||||||
|
|
||||||
// delete cookie
|
// delete cookie
|
||||||
ctx.Resp.Header().Del("Set-Cookie")
|
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.cookieOptionsFromCfg)
|
||||||
hs.deleteCookie(ctx.Resp, OauthStateCookieName, hs.Cfg.CookieSameSite)
|
|
||||||
|
|
||||||
if cookieState == "" {
|
if cookieState == "" {
|
||||||
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
|
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
|
||||||
@ -217,7 +217,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
|
|||||||
metrics.MApiLoginOAuth.Inc()
|
metrics.MApiLoginOAuth.Inc()
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
||||||
ctx.Redirect(redirectTo)
|
ctx.Redirect(redirectTo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -225,25 +225,6 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
|
|||||||
ctx.Redirect(setting.AppSubUrl + "/")
|
ctx.Redirect(setting.AppSubUrl + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) deleteCookie(w http.ResponseWriter, name string, sameSite http.SameSite) {
|
|
||||||
hs.writeCookie(w, name, "", -1, sameSite)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hs *HTTPServer) writeCookie(w http.ResponseWriter, name string, value string, maxAge int, sameSite http.SameSite) {
|
|
||||||
cookie := http.Cookie{
|
|
||||||
Name: name,
|
|
||||||
MaxAge: maxAge,
|
|
||||||
Value: value,
|
|
||||||
HttpOnly: true,
|
|
||||||
Path: setting.AppSubUrl + "/",
|
|
||||||
Secure: hs.Cfg.CookieSecure,
|
|
||||||
}
|
|
||||||
if sameSite != http.SameSiteDefaultMode {
|
|
||||||
cookie.SameSite = sameSite
|
|
||||||
}
|
|
||||||
http.SetCookie(w, &cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashStatecode(code, seed string) string {
|
func hashStatecode(code, seed string) string {
|
||||||
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
||||||
return hex.EncodeToString(hashBytes[:])
|
return hex.EncodeToString(hashBytes[:])
|
||||||
|
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -134,6 +135,7 @@ func TestLoginViewRedirect(t *testing.T) {
|
|||||||
Cfg: setting.NewCfg(),
|
Cfg: setting.NewCfg(),
|
||||||
License: models.OSSLicensingService{},
|
License: models.OSSLicensingService{},
|
||||||
}
|
}
|
||||||
|
hs.Cfg.CookieSecure = true
|
||||||
|
|
||||||
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) {
|
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) {
|
||||||
c.IsSignedIn = true
|
c.IsSignedIn = true
|
||||||
@ -192,15 +194,15 @@ func TestLoginViewRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range redirectCases {
|
for _, c := range redirectCases {
|
||||||
setting.AppUrl = c.appURL
|
hs.Cfg.AppUrl = c.appURL
|
||||||
setting.AppSubUrl = c.appSubURL
|
hs.Cfg.AppSubUrl = c.appSubURL
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
t.Run(c.desc, func(t *testing.T) {
|
||||||
cookie := http.Cookie{
|
cookie := http.Cookie{
|
||||||
Name: "redirect_to",
|
Name: "redirect_to",
|
||||||
MaxAge: 60,
|
MaxAge: 60,
|
||||||
Value: c.url,
|
Value: c.url,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Path: setting.AppSubUrl + "/",
|
Path: hs.Cfg.AppSubUrl + "/",
|
||||||
Secure: hs.Cfg.CookieSecure,
|
Secure: hs.Cfg.CookieSecure,
|
||||||
SameSite: hs.Cfg.CookieSameSite,
|
SameSite: hs.Cfg.CookieSameSite,
|
||||||
}
|
}
|
||||||
@ -211,6 +213,19 @@ func TestLoginViewRedirect(t *testing.T) {
|
|||||||
location, ok := sc.resp.Header()["Location"]
|
location, ok := sc.resp.Header()["Location"]
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, location[0], c.url)
|
assert.Equal(t, location[0], c.url)
|
||||||
|
|
||||||
|
setCookie, ok := sc.resp.Header()["Set-Cookie"]
|
||||||
|
assert.True(t, ok, "Set-Cookie exists")
|
||||||
|
assert.Greater(t, len(setCookie), 0)
|
||||||
|
var redirectToCookieFound bool
|
||||||
|
expCookieValue := fmt.Sprintf("redirect_to=%v; Path=%v; Max-Age=60; HttpOnly; Secure", c.url, hs.Cfg.AppSubUrl+"/")
|
||||||
|
for _, cookieValue := range setCookie {
|
||||||
|
if cookieValue == expCookieValue {
|
||||||
|
redirectToCookieFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, redirectToCookieFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseString, err := getBody(sc.resp)
|
responseString, err := getBody(sc.resp)
|
||||||
@ -235,6 +250,7 @@ func TestLoginPostRedirect(t *testing.T) {
|
|||||||
License: models.OSSLicensingService{},
|
License: models.OSSLicensingService{},
|
||||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||||
}
|
}
|
||||||
|
hs.Cfg.CookieSecure = true
|
||||||
|
|
||||||
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response {
|
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response {
|
||||||
cmd := dtos.LoginCommand{
|
cmd := dtos.LoginCommand{
|
||||||
@ -286,15 +302,15 @@ func TestLoginPostRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range redirectCases {
|
for _, c := range redirectCases {
|
||||||
setting.AppUrl = c.appURL
|
hs.Cfg.AppUrl = c.appURL
|
||||||
setting.AppSubUrl = c.appSubURL
|
hs.Cfg.AppSubUrl = c.appSubURL
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
t.Run(c.desc, func(t *testing.T) {
|
||||||
cookie := http.Cookie{
|
cookie := http.Cookie{
|
||||||
Name: "redirect_to",
|
Name: "redirect_to",
|
||||||
MaxAge: 60,
|
MaxAge: 60,
|
||||||
Value: c.url,
|
Value: c.url,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Path: setting.AppSubUrl + "/",
|
Path: hs.Cfg.AppSubUrl + "/",
|
||||||
Secure: hs.Cfg.CookieSecure,
|
Secure: hs.Cfg.CookieSecure,
|
||||||
SameSite: hs.Cfg.CookieSameSite,
|
SameSite: hs.Cfg.CookieSameSite,
|
||||||
}
|
}
|
||||||
@ -310,6 +326,19 @@ func TestLoginPostRedirect(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
assert.Equal(t, c.url, redirectURL)
|
assert.Equal(t, c.url, redirectURL)
|
||||||
}
|
}
|
||||||
|
// assert redirect_to cookie is deleted
|
||||||
|
setCookie, ok := sc.resp.Header()["Set-Cookie"]
|
||||||
|
assert.True(t, ok, "Set-Cookie exists")
|
||||||
|
assert.Greater(t, len(setCookie), 0)
|
||||||
|
var redirectToCookieFound bool
|
||||||
|
expCookieValue := fmt.Sprintf("redirect_to=; Path=%v; Max-Age=0; HttpOnly; Secure", hs.Cfg.AppSubUrl+"/")
|
||||||
|
for _, cookieValue := range setCookie {
|
||||||
|
if cookieValue == expCookieValue {
|
||||||
|
redirectToCookieFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, redirectToCookieFound)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func notAuthorized(c *m.ReqContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+c.Req.RequestURI), 0, setting.AppSubUrl+"/", nil, false, true)
|
WriteCookie(c.Resp, "redirect_to", url.QueryEscape(setting.AppSubUrl+c.Req.RequestURI), 0, newCookieOptions)
|
||||||
|
|
||||||
c.Redirect(setting.AppSubUrl + "/login")
|
c.Redirect(setting.AppSubUrl + "/login")
|
||||||
}
|
}
|
||||||
|
43
pkg/middleware/cookie.go
Normal file
43
pkg/middleware/cookie.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CookieOptions struct {
|
||||||
|
Path string
|
||||||
|
Secure bool
|
||||||
|
SameSite http.SameSite
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCookieOptions() CookieOptions {
|
||||||
|
return CookieOptions{
|
||||||
|
Path: setting.AppSubUrl + "/",
|
||||||
|
Secure: setting.CookieSecure,
|
||||||
|
SameSite: setting.CookieSameSite,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCookieOptionsFunc func() CookieOptions
|
||||||
|
|
||||||
|
func DeleteCookie(w http.ResponseWriter, name string, getCookieOptionsFunc GetCookieOptionsFunc) {
|
||||||
|
WriteCookie(w, name, "", -1, getCookieOptionsFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, getCookieOptionsFunc GetCookieOptionsFunc) {
|
||||||
|
options := getCookieOptionsFunc()
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
MaxAge: maxAge,
|
||||||
|
Value: value,
|
||||||
|
HttpOnly: true,
|
||||||
|
Path: options.Path,
|
||||||
|
Secure: options.Secure,
|
||||||
|
}
|
||||||
|
if options.SameSite != http.SameSiteDefaultMode {
|
||||||
|
cookie.SameSite = options.SameSite
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
}
|
@ -2,7 +2,6 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -253,20 +252,7 @@ func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetimeDays in
|
|||||||
maxAge = int(maxAgeHours.Seconds())
|
maxAge = int(maxAgeHours.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Resp.Header().Del("Set-Cookie")
|
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions)
|
||||||
cookie := http.Cookie{
|
|
||||||
Name: setting.LoginCookieName,
|
|
||||||
Value: url.QueryEscape(value),
|
|
||||||
HttpOnly: true,
|
|
||||||
Path: setting.AppSubUrl + "/",
|
|
||||||
Secure: setting.CookieSecure,
|
|
||||||
MaxAge: maxAge,
|
|
||||||
}
|
|
||||||
if setting.CookieSameSite != http.SameSiteDefaultMode {
|
|
||||||
cookie.SameSite = setting.CookieSameSite
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(ctx.Resp, &cookie)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDefaultResponseHeaders() macaron.Handler {
|
func AddDefaultResponseHeaders() macaron.Handler {
|
||||||
|
Loading…
Reference in New Issue
Block a user