security: Try to set cookies secure, with force override option

This commit is contained in:
jamesread 2026-02-26 23:43:50 +00:00
parent 4744169aa0
commit 03da2ff2e7
3 changed files with 23 additions and 4 deletions

View File

@ -158,7 +158,12 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
return connect.NewResponse(ret), nil
}
func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool) {
func (api *oliveTinAPI) cookieSecure(header http.Header) bool {
useTLS := header.Get("X-Forwarded-Proto") == "https"
return useTLS || api.cfg.Security.ForceSecureCookies
}
func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool, secure bool) {
if match {
user := api.cfg.FindUserByUsername(req.Username)
if user != nil {
@ -171,6 +176,8 @@ func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest,
MaxAge: 31556952,
HttpOnly: true,
Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
}
response.Header().Set("Set-Cookie", cookie.String())
}
@ -192,7 +199,7 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
}
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: match})
api.applyLocalLoginResult(req.Msg, response, match)
api.applyLocalLoginResult(req.Msg, response, match, api.cookieSecure(req.Header()))
return response, nil
}
@ -389,6 +396,7 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
}).Info("Logout: User logged out")
response := connect.NewResponse(&apiv1.LogoutResponse{})
secure := api.cookieSecure(req.Header())
// Clear the local authentication cookie by setting it to expire
localCookie := &http.Cookie{
@ -397,6 +405,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true,
Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
}
response.Header().Set("Set-Cookie", localCookie.String())
@ -407,6 +417,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true,
Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
}
response.Header().Add("Set-Cookie", oauth2Cookie.String())

View File

@ -108,14 +108,20 @@ func randString(nByte int) (string, error) {
return base64.URLEncoding.EncodeToString(b), nil
}
func (h *OAuth2Handler) cookieSecure(r *http.Request) bool {
useTLS := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
return useTLS || h.cfg.Security.ForceSecureCookies
}
func (h *OAuth2Handler) setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
cookie := &http.Cookie{
Name: name,
Value: value,
MaxAge: 900, // 15 minutes
Secure: r.TLS != nil,
Secure: h.cookieSecure(r),
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)

View File

@ -107,13 +107,14 @@ type PrometheusConfig struct {
DefaultGoMetrics bool `koanf:"defaultGoMetrics"`
}
// SecurityConfig allows users to fine tune the security related HTTP headers.
// SecurityConfig allows users to fine tune the security related HTTP headers and cookie options.
type SecurityConfig struct {
HeaderContentSecurityPolicy bool `koanf:"headerContentSecurityPolicy"`
ContentSecurityPolicy string `koanf:"contentSecurityPolicy"`
HeaderXContentTypeOptions bool `koanf:"headerXContentTypeOptions"`
HeaderXFrameOptions bool `koanf:"headerXFrameOptions"`
XFrameOptions string `koanf:"xFrameOptions"`
ForceSecureCookies bool `koanf:"forceSecureCookies"`
}
// Config is the global config used through the whole app.