go-ssb-room/web/utils.go

119 lines
3.2 KiB
Go

// SPDX-License-Identifier: MIT
package web
import (
"errors"
"fmt"
"html/template"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"github.com/gorilla/securecookie"
"github.com/go-kit/kit/log/level"
"github.com/gorilla/mux"
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
"go.mindeco.de/logging"
)
//go:generate go run -tags=dev embedded_generate.go
func TemplateFuncs(m *mux.Router) template.FuncMap {
return template.FuncMap{
"urlTo": NewURLTo(m),
"inc": func(i int) int { return i + 1 },
}
}
func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
l := logging.Logger("helper.URLTo") // TOOD: inject in a scoped way
return func(routeName string, ps ...interface{}) *url.URL {
route := appRouter.Get(routeName)
if route == nil {
level.Warn(l).Log("msg", "no such route", "route", routeName, "params", ps)
return &url.URL{}
}
var params []string
for _, p := range ps {
switch v := p.(type) {
case string:
params = append(params, v)
case int:
params = append(params, strconv.Itoa(v))
case int64:
params = append(params, strconv.FormatInt(v, 10))
default:
level.Error(l).Log("msg", "invalid param type", "param", p, "route", routeName)
logging.CheckFatal(errors.New("invalid param"))
}
}
u, err := route.URLPath(params...)
if err != nil {
level.Error(l).Log("msg", "failed to create URL",
"route", routeName,
"params", params,
"error", err)
return &url.URL{}
}
return u
}
}
// LoadOrCreateCookieSecrets either parses the bytes from $repo/web/cookie-secret or creates a new file with suitable keys in it
func LoadOrCreateCookieSecrets(repo repo.Interface) ([]securecookie.Codec, error) {
secretPath := repo.GetPath("web", "cookie-secret")
err := os.MkdirAll(filepath.Dir(secretPath), 0700)
if err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("failed to create folder for cookie secret: %w", err)
}
// load the existing data
secrets, err := ioutil.ReadFile(secretPath)
if err != nil {
if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to load cookie secrets: %w", err)
}
// create new keys, save them and return the codec
hashKey := securecookie.GenerateRandomKey(32)
blockKey := securecookie.GenerateRandomKey(32)
data := append(hashKey, blockKey...)
err = ioutil.WriteFile(secretPath, data, 0600)
if err != nil {
return nil, err
}
sc := securecookie.CodecsFromPairs(hashKey, blockKey)
return sc, nil
}
// secrets should contain multiple of 64byte (to enable key rotation as supported by gorilla)
if n := len(secrets); n%64 != 0 {
return nil, fmt.Errorf("expected multiple of 64bytes in cookie secret file but got: %d", n)
}
// range over the secrets []byte in chunks of 64 bytes
// and slice it into 32byte pairs
var pairs [][]byte
// the increment/next part (which usually is i++)
// is the multiple comma assigment (a,b = b+1,a-1)
// so chunk is the next 64 bytes and then it slices of the first 64 bytes of secrets for the next iteration
for chunk := secrets[:64]; len(secrets) >= 64; chunk, secrets = secrets[:64], secrets[64:] {
pairs = append(pairs,
chunk[0:32], // hash key
chunk[32:64], // block key
)
}
sc := securecookie.CodecsFromPairs(pairs...)
return sc, nil
}