providers/session: Provide option for insecure session cookie

Closes #28. Also increase security of secure session cookie.
This commit is contained in:
Sasha Koshka 2024-12-27 01:10:28 -05:00
parent 851931d518
commit b6e4c719ca

View File

@ -1,5 +1,6 @@
package session package session
import "log"
import "time" import "time"
import "net/http" import "net/http"
import "html/template" import "html/template"
@ -10,6 +11,7 @@ import "git.tebibyte.media/sashakoshka/go-util/container"
import shttp "git.tebibyte.media/sashakoshka/step/http" import shttp "git.tebibyte.media/sashakoshka/step/http"
const sessionIDCookieName = "step-session-id" const sessionIDCookieName = "step-session-id"
const secureSessionIDCookieName = "__Host-step-session-id"
const defaultLifetime = 48 * time.Hour const defaultLifetime = 48 * time.Hour
var _ step.FuncProviderFor = new(Provider) var _ step.FuncProviderFor = new(Provider)
@ -19,7 +21,8 @@ var _ step.Trimmer = new(Provider)
// Provider provides session functions. // Provider provides session functions.
type Provider struct { type Provider struct {
Lifetime time.Duration Lifetime time.Duration
InsecureCookie bool
sessions usync.RWLocker[sessionMap] sessions usync.RWLocker[sessionMap]
} }
@ -53,6 +56,15 @@ func (this *Provider) Configure (config step.Meta) error {
if err != nil { return err } if err != nil { return err }
this.Lifetime = lifetime this.Lifetime = lifetime
} }
if insecureCookieStr := config.Get("session.insecure-cookie"); insecureCookieStr != "" {
if insecureCookieStr != "true" && insecureCookieStr != "false" {
return step.ErrTypeMismatch
}
this.InsecureCookie = insecureCookieStr == "true"
if this.InsecureCookie {
log.Println("!!! session.insecure-cookie is active, this is not recommended")
}
}
return nil return nil
} }
@ -61,6 +73,7 @@ func (this *Provider) FuncMapFor (document *step.Document) template.FuncMap {
stat := &state { stat := &state {
document: document, document: document,
sessions: &this.sessions, sessions: &this.sessions,
insecureCookie: this.InsecureCookie,
} }
return template.FuncMap { return template.FuncMap {
"sessionHTTP": stat.funcSessionHTTP, "sessionHTTP": stat.funcSessionHTTP,
@ -72,6 +85,7 @@ type state struct {
document *step.Document document *step.Document
sessions *usync.RWLocker[sessionMap] sessions *usync.RWLocker[sessionMap]
lifetime time.Duration lifetime time.Duration
insecureCookie bool
} }
func (this *state) funcSessionHTTP ( func (this *state) funcSessionHTTP (
@ -81,8 +95,10 @@ func (this *state) funcSessionHTTP (
*Session, *Session,
error, error,
) { ) {
cookieName := sessionIDCookieName
if !this.insecureCookie { cookieName = secureSessionIDCookieName }
var id uuid.UUID var id uuid.UUID
if cookie, err := req.Cookie(sessionIDCookieName); err == nil { if cookie, err := req.Cookie(cookieName); err == nil {
if parsed, err := uuid.Parse(cookie.Value); err == nil { if parsed, err := uuid.Parse(cookie.Value); err == nil {
id = parsed id = parsed
} }
@ -108,14 +124,16 @@ func (this *state) funcSessionHTTP (
result = session result = session
} }
cookie := &http.Cookie { cookie := &http.Cookie {
Name: sessionIDCookieName, Name: cookieName,
Value: result.ID().String(), Value: result.ID().String(),
Expires: expiration, Expires: expiration,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
Path: "/", Path: "/",
} }
if !this.insecureCookie {
cookie.Secure = true
cookie.HttpOnly = true
}
underlyingRes := shttp.UnderlyingResponseWriter(res) underlyingRes := shttp.UnderlyingResponseWriter(res)
http.SetCookie(underlyingRes, cookie) http.SetCookie(underlyingRes, cookie)
return result, nil return result, nil