Skip to content

Commit

Permalink
Use http.Filesystem for web config
Browse files Browse the repository at this point in the history
  • Loading branch information
Joshua Winters authored and vito committed Nov 16, 2018
1 parent 2425c6e commit 8b0aef4
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 135 deletions.
32 changes: 11 additions & 21 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ type WebConfig struct {
// * templates - HTML templates controlled by dex.
// * themes/(theme) - Static static served at "( issuer URL )/theme".
//
Dir string
Dir http.FileSystem

// Defaults to "( issuer URL )/theme/logo.png"
LogoURL string
Expand Down Expand Up @@ -173,33 +173,24 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
supported[respType] = true
}

web := webConfig{
dir: c.Web.Dir,
logoURL: c.Web.LogoURL,
issuerURL: c.Issuer,
issuer: c.Web.Issuer,
theme: c.Web.Theme,
if c.Now == nil {
c.Now = time.Now
}

static, theme, tmpls, err := loadWebConfig(web)
templates, err := loadTemplates(c.Web, c.Issuer)
if err != nil {
return nil, fmt.Errorf("server: failed to load web static: %v", err)
}

now := c.Now
if now == nil {
now = time.Now
return nil, fmt.Errorf("server: failed to templates: %v", err)
}

s := &Server{
issuerURL: *issuerURL,
connectors: make(map[string]Connector),
storage: newKeyCacher(c.Storage, now),
storage: newKeyCacher(c.Storage, c.Now),
supportedResponseTypes: supported,
idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour),
skipApproval: c.SkipApprovalScreen,
now: now,
templates: tmpls,
now: c.Now,
templates: templates,
logger: c.Logger,
}

Expand Down Expand Up @@ -281,12 +272,11 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
handleFunc("/callback/{connector}", s.handleConnectorCallback)
handleFunc("/approval", s.handleApproval)
handleFunc("/healthz", s.handleHealth)
handlePrefix("/static", static)
handlePrefix("/theme", theme)
handlePrefix("/", http.FileServer(c.Web.Dir))
s.mux = r

s.startKeyRotation(ctx, rotationStrategy, now)
s.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), now)
s.startKeyRotation(ctx, rotationStrategy, c.Now)
s.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), c.Now)

return s, nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
Issuer: s.URL,
Storage: memory.New(logger),
Web: WebConfig{
Dir: "../web",
Dir: http.Dir("../web"),
},
Logger: logger,
PrometheusRegistry: prometheus.NewRegistry(),
Expand Down
176 changes: 66 additions & 110 deletions server/templates.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package server

import (
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"net/url"
"path"
"path/filepath"
"sort"
"strings"
Expand All @@ -20,14 +21,6 @@ const (
tmplError = "error.html"
)

var requiredTmpls = []string{
tmplApproval,
tmplLogin,
tmplPassword,
tmplOOB,
tmplError,
}

type templates struct {
loginTmpl *template.Template
approvalTmpl *template.Template
Expand All @@ -37,133 +30,96 @@ type templates struct {
}

type webConfig struct {
dir string
logoURL string
issuer string
theme string
issuerURL string
themeDir http.FileSystem
staticDir http.FileSystem
templatesDir http.FileSystem
logoURL string
issuer string
theme string
issuerURL string
}

func join(base, path string) string {
b := strings.HasSuffix(base, "/")
p := strings.HasPrefix(path, "/")
switch {
case b && p:
return base + path[1:]
case b || p:
return base + path
default:
return base + "/" + path
}
func join(basepath string, paths ...string) string {
u, _ := url.Parse(basepath)
u.Path = path.Join(append([]string{u.Path}, paths...)...)
return u.String()
}

func dirExists(dir string) error {
stat, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("directory %q does not exist", dir)
}
return fmt.Errorf("stat directory %q: %v", dir, err)
}
if !stat.IsDir() {
return fmt.Errorf("path %q is a file not a directory", dir)
}
return nil
}
// loadTemplates parses the expected templates from the provided directory.
func loadTemplates(c WebConfig, issuerURL string) (*templates, error) {

// loadWebConfig returns static assets, theme assets, and templates used by the frontend by
// reading the directory specified in the webConfig.
//
// The directory layout is expected to be:
//
// ( web directory )
// |- static
// |- themes
// | |- (theme name)
// |- templates
//
func loadWebConfig(c webConfig) (static, theme http.Handler, templates *templates, err error) {
if c.theme == "" {
c.theme = "coreos"
}
if c.issuer == "" {
c.issuer = "dex"
}
if c.dir == "" {
c.dir = "./web"
}
if c.logoURL == "" {
c.logoURL = join(c.issuerURL, "theme/logo.png")
if c.Theme == "" {
c.Theme = "coreos"
}

if err := dirExists(c.dir); err != nil {
return nil, nil, nil, fmt.Errorf("load web dir: %v", err)
if c.Issuer == "" {
c.Issuer = "dex"
}

staticDir := filepath.Join(c.dir, "static")
templatesDir := filepath.Join(c.dir, "templates")
themeDir := filepath.Join(c.dir, "themes", c.theme)

for _, dir := range []string{staticDir, templatesDir, themeDir} {
if err := dirExists(dir); err != nil {
return nil, nil, nil, fmt.Errorf("load dir: %v", err)
}
if c.LogoURL == "" {
c.LogoURL = join(issuerURL, "themes", c.Theme, "logo.png")
}

static = http.FileServer(http.Dir(staticDir))
theme = http.FileServer(http.Dir(themeDir))
funcs := template.FuncMap{
"issuer": func() string { return c.Issuer },
"logo": func() string { return c.LogoURL },
"static": func(s string) string { return join(issuerURL, "static", s) },
"theme": func(s string) string { return join(issuerURL, "themes", c.Theme, s) },
"lower": strings.ToLower,
}

templates, err = loadTemplates(c, templatesDir)
return
}
group := template.New("")

// loadTemplates parses the expected templates from the provided directory.
func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
files, err := ioutil.ReadDir(templatesDir)
loginTemplate, err := loadTemplate(c.Dir, tmplLogin, funcs, group)
if err != nil {
return nil, fmt.Errorf("read dir: %v", err)
return nil, err
}

filenames := []string{}
for _, file := range files {
if file.IsDir() {
continue
}
filenames = append(filenames, filepath.Join(templatesDir, file.Name()))
}
if len(filenames) == 0 {
return nil, fmt.Errorf("no files in template dir %q", templatesDir)
approvalTemplate, err := loadTemplate(c.Dir, tmplApproval, funcs, group)
if err != nil {
return nil, err
}

funcs := map[string]interface{}{
"issuer": func() string { return c.issuer },
"logo": func() string { return c.logoURL },
"url": func(s string) string { return join(c.issuerURL, s) },
"lower": strings.ToLower,
passwordTemplate, err := loadTemplate(c.Dir, tmplPassword, funcs, group)
if err != nil {
return nil, err
}

tmpls, err := template.New("").Funcs(funcs).ParseFiles(filenames...)
oobTemplate, err := loadTemplate(c.Dir, tmplOOB, funcs, group)
if err != nil {
return nil, fmt.Errorf("parse files: %v", err)
}
missingTmpls := []string{}
for _, tmplName := range requiredTmpls {
if tmpls.Lookup(tmplName) == nil {
missingTmpls = append(missingTmpls, tmplName)
}
return nil, err
}
if len(missingTmpls) > 0 {
return nil, fmt.Errorf("missing template(s): %s", missingTmpls)

errorTemplate, err := loadTemplate(c.Dir, tmplError, funcs, group)
if err != nil {
return nil, err
}

loadTemplate(c.Dir, "header.html", funcs, group)
loadTemplate(c.Dir, "footer.html", funcs, group)

return &templates{
loginTmpl: tmpls.Lookup(tmplLogin),
approvalTmpl: tmpls.Lookup(tmplApproval),
passwordTmpl: tmpls.Lookup(tmplPassword),
oobTmpl: tmpls.Lookup(tmplOOB),
errorTmpl: tmpls.Lookup(tmplError),
loginTmpl: loginTemplate,
approvalTmpl: approvalTemplate,
passwordTmpl: passwordTemplate,
oobTmpl: oobTemplate,
errorTmpl: errorTemplate,
}, nil
}

func loadTemplate(dir http.FileSystem, name string, funcs template.FuncMap, group *template.Template) (*template.Template, error) {
file, err := dir.Open(filepath.Join("templates", name))
if err != nil {
return nil, err
}

var buffer bytes.Buffer
buffer.ReadFrom(file)
contents := buffer.String()

return group.New(name).Funcs(funcs).Parse(contents)
}

var scopeDescriptions = map[string]string{
"offline_access": "Have offline access",
"profile": "View basic profile information",
Expand Down
6 changes: 3 additions & 3 deletions web/templates/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ issuer }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url "static/main.css" }}" rel="stylesheet">
<link href="{{ url "theme/styles.css" }}" rel="stylesheet">
<link rel="icon" href="{{ url "theme/favicon.png" }}">
<link href="{{ static "main.css" }}" rel="stylesheet">
<link href="{{ theme "styles.css" }}" rel="stylesheet">
<link rel="icon" href="{{ theme "favicon.png" }}">
</head>

<body class="theme-body">
Expand Down

0 comments on commit 8b0aef4

Please sign in to comment.