Skip to content

Commit

Permalink
Merge pull request #6 from go-seatbelt/version-public-assets
Browse files Browse the repository at this point in the history
all: version public assets, improve sessions
  • Loading branch information
bentranter authored May 19, 2022
2 parents 88fd9a8 + 9765c10 commit b0fe295
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 21 deletions.
4 changes: 4 additions & 0 deletions example/public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ html, body {
font-size: 16px;
margin: 0;
}

.title {
font-size: 30px;
}
2 changes: 1 addition & 1 deletion example/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{ define "title" }}Home{{ end }}

{{ define "content" }}
<h1>Hello!</h1>
<h1 class="title">Hello!</h1>
<p>Welcome to Seatbelt. This is a sample application that shows off all the framework can do.</p>
<a href="/session">Set and view session data</a>
{{ end }}
5 changes: 3 additions & 2 deletions example/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href='{{ versionpath "/public/css/style.css" }}'/>
{{ csrfMetaTags }}
<title>{{ block "title" . }}test{{ end }}</title>
<link rel="stylesheet" href="/public/css/style.css"/>
</head>
<body>
{{ block "content" . }}<p>Hello</p>{{ end }}
<script src="/public/js/main.js"></script>
<script src='{{ versionpath "/public/js/main.js" }}'></script>
</body>
</html>
16 changes: 10 additions & 6 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ func (r *Render) parseTemplates() error {
dirfs := os.DirFS(r.dir)

// Parse the layout template in order to clone it for each template later.
ltpl, err := template.New("layout").ParseFS(dirfs, "layout.html")
if err != nil {
log.Fatalln("[error] parsing layout template", err)
return err
}
// Because template functions may be defined in the layout, we also need
// to add them prior to actually doing any parsing.
basetpl := template.New("layout")

// Define an initial map of template funcs as no-ops. This will be passed
// to templates **before** parsing in order to prevent a "function not
Expand All @@ -86,7 +84,13 @@ func (r *Render) parseTemplates() error {
}
}
}
ltpl.Funcs(noopFuncMap)
basetpl.Funcs(noopFuncMap)
}

ltpl, err := basetpl.ParseFS(dirfs, "layout.html")
if err != nil {
log.Fatalln("[error] parsing layout template", err)
return err
}

rootDir := "."
Expand Down
38 changes: 36 additions & 2 deletions seatbelt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/go-seatbelt/seatbelt/handler"
Expand Down Expand Up @@ -174,9 +176,16 @@ type Option struct {
// The directory containing your HTML templates.
TemplateDir string

// The signing key for the cookie session store.
// The signing key for the session cookie store.
SigningKey string

// The session name for the session cookie. Default is "_session".
SessionName string

// The MaxAge for the session cookie. Default is 365 days. Pass -1 for no
// max age.
SessionMaxAge int

// Request-contextual HTML functions.
Funcs func(w http.ResponseWriter, r *http.Request) template.FuncMap

Expand Down Expand Up @@ -241,6 +250,28 @@ func defaultTemplateFuncs(session *session.Session) func(w http.ResponseWriter,
"flashes": func() map[string]interface{} {
return session.Flashes(w, r)
},
// versionpath takes a filepath and returns the same filepath with
// a query parameter appended that contains the unix timestamp of
// that file's last modified time. This should be used for files
// that might change between page loads (JavaScript and CSS files,
// images, etc).
"versionpath": func(path string) string {
path = filepath.Clean(path)

// Leading `/` characters will just break local filepath
// resolution, so we remove it if it exists.
fi, err := os.Stat(strings.TrimPrefix(path, "/"))
if err == nil {
path = path + "?" + strconv.Itoa(int(fi.ModTime().Unix()))
} else {
fmt.Printf("seatbelt: error getting file info at path %s: %v\n", path, err)
}

return path
},
"csrfMetaTags": func() template.HTML {
return template.HTML(`<meta name="csrf-token" content="` + csrf.Token(r) + `">`)
},
}
}
}
Expand All @@ -263,7 +294,10 @@ func New(opts ...Option) *App {
mux := chi.NewRouter()
mux.Use(csrf.Protect(signingKey))

sess := session.New(signingKey)
sess := session.New(signingKey, session.Options{
Name: opt.SessionName,
MaxAge: opt.SessionMaxAge,
})

app := &App{
mux: mux,
Expand Down
47 changes: 37 additions & 10 deletions session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,44 @@ func init() {
// A Session manages setting and getting data from the cookie that stores the
// session data.
type Session struct {
sc *securecookie.SecureCookie
sc *securecookie.SecureCookie
name string
}

// Options to customize the behaviour of the session.
type Options struct {
// The name of the cookie (default is "_session").
Name string

// MaxAge of the cookie before expiry (default is 365 days). Set it to
// -1 for no expiry.
MaxAge int
}

// New creates a new session with the given key.
func New(secret []byte) *Session {
func New(secret []byte, opts ...Options) *Session {
var o Options
for _, opt := range opts {
o = opt
}

if o.Name == "" {
o.Name = defaultSessionName
}
switch o.MaxAge {
case 0:
// Default to one year, since some browsers don't set their cookies
// with the same defaults.
o.MaxAge = defaultMaxAge
case -1:
o.MaxAge = 0
}

sc := securecookie.New(secret, nil)
// Default to one year for new cookies, since some browsers don't set
// their cookies with the same defaults.
sc.MaxAge(defaultMaxAge)
sc.MaxAge(o.MaxAge)
return &Session{
sc: sc,
sc: sc,
name: o.Name,
}
}

Expand Down Expand Up @@ -79,7 +106,7 @@ func (s *Session) fromReq(r *http.Request) *session {
}
}

cookie, err := r.Cookie(defaultSessionName)
cookie, err := r.Cookie(s.name)
if err != nil {
// The only error that can be returned by r.Cookie() is ErrNoCookie,
// so if the error is not nil, that means that the cookie doesn't
Expand All @@ -91,7 +118,7 @@ func (s *Session) fromReq(r *http.Request) *session {
}

ss := &session{}
if err := s.sc.Decode(defaultSessionName, cookie.Value, ss); err != nil {
if err := s.sc.Decode(s.name, cookie.Value, ss); err != nil {
log.Println("[error] failed to decode session from cookie:", err)
ss.init()
return ss
Expand All @@ -106,14 +133,14 @@ func (s *Session) saveCtx(w http.ResponseWriter, r *http.Request, session *sessi
r2 := r.Clone(ctx)
*r = *r2

encoded, err := s.sc.Encode(defaultSessionName, session)
encoded, err := s.sc.Encode(s.name, session)
if err != nil {
log.Println("error encoding cookie:", err)
return
}

http.SetCookie(w, &http.Cookie{
Name: defaultSessionName,
Name: s.name,
MaxAge: defaultMaxAge,
Expires: time.Now().UTC().Add(time.Duration(defaultMaxAge * time.Second)),
Value: encoded,
Expand Down

0 comments on commit b0fe295

Please sign in to comment.