forked from mystiq/dex
Use http.FileSystem for web assets
Signed-off-by: Rui Yang <ryang@pivotal.io> Co-authored-by: Aidan Oldershaw <aoldershaw@pivotal.io>
This commit is contained in:
parent
a1adf86e53
commit
10e9054811
6 changed files with 104 additions and 148 deletions
|
@ -74,7 +74,6 @@ web:
|
||||||
http: 127.0.0.1:5556
|
http: 127.0.0.1:5556
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
dir: ./web
|
|
||||||
extra:
|
extra:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
|
||||||
|
@ -144,7 +143,6 @@ logger:
|
||||||
HTTP: "127.0.0.1:5556",
|
HTTP: "127.0.0.1:5556",
|
||||||
},
|
},
|
||||||
Frontend: server.WebConfig{
|
Frontend: server.WebConfig{
|
||||||
Dir: "./web",
|
|
||||||
Extra: map[string]string{
|
Extra: map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
|
@ -274,7 +272,6 @@ web:
|
||||||
http: 127.0.0.1:5556
|
http: 127.0.0.1:5556
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
dir: ./web
|
|
||||||
extra:
|
extra:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
|
||||||
|
@ -352,7 +349,6 @@ logger:
|
||||||
HTTP: "127.0.0.1:5556",
|
HTTP: "127.0.0.1:5556",
|
||||||
},
|
},
|
||||||
Frontend: server.WebConfig{
|
Frontend: server.WebConfig{
|
||||||
Dir: "./web",
|
|
||||||
Extra: map[string]string{
|
Extra: map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
|
|
|
@ -135,7 +135,7 @@ func TestHandleInvalidSAMLCallbacks(t *testing.T) {
|
||||||
func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) {
|
func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) {
|
||||||
memStorage := memory.New(logger)
|
memStorage := memory.New(logger)
|
||||||
|
|
||||||
templates, err := loadTemplates(webConfig{}, "../web/templates")
|
templates, err := loadTemplates(WebConfig{Dir: http.Dir("../web")}, "../web/templates")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to load templates")
|
t.Fatal("failed to load templates")
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ type WebConfig struct {
|
||||||
// * templates - HTML templates controlled by dex.
|
// * templates - HTML templates controlled by dex.
|
||||||
// * themes/(theme) - Static static served at "( issuer URL )/theme".
|
// * themes/(theme) - Static static served at "( issuer URL )/theme".
|
||||||
//
|
//
|
||||||
Dir string
|
Dir http.FileSystem
|
||||||
|
|
||||||
// Defaults to "( issuer URL )/theme/logo.png"
|
// Defaults to "( issuer URL )/theme/logo.png"
|
||||||
LogoURL string
|
LogoURL string
|
||||||
|
@ -203,18 +203,9 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
||||||
supported[respType] = true
|
supported[respType] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
web := webConfig{
|
tmpls, err := loadTemplates(c.Web, issuerURL.Path)
|
||||||
dir: c.Web.Dir,
|
|
||||||
logoURL: c.Web.LogoURL,
|
|
||||||
issuerURL: c.Issuer,
|
|
||||||
issuer: c.Web.Issuer,
|
|
||||||
theme: c.Web.Theme,
|
|
||||||
extra: c.Web.Extra,
|
|
||||||
}
|
|
||||||
|
|
||||||
static, theme, tmpls, err := loadWebConfig(web)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server: failed to load web static: %v", err)
|
return nil, fmt.Errorf("server: failed to load templates: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := c.Now
|
now := c.Now
|
||||||
|
@ -343,8 +334,8 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Health check passed")
|
fmt.Fprintf(w, "Health check passed")
|
||||||
}))
|
}))
|
||||||
handlePrefix("/static", static)
|
handlePrefix("/", http.FileServer(c.Web.Dir))
|
||||||
handlePrefix("/theme", theme)
|
|
||||||
s.mux = r
|
s.mux = r
|
||||||
|
|
||||||
s.startKeyRotation(ctx, rotationStrategy, now)
|
s.startKeyRotation(ctx, rotationStrategy, now)
|
||||||
|
|
|
@ -93,7 +93,7 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
|
||||||
Issuer: s.URL,
|
Issuer: s.URL,
|
||||||
Storage: memory.New(logger),
|
Storage: memory.New(logger),
|
||||||
Web: WebConfig{
|
Web: WebConfig{
|
||||||
Dir: "../web",
|
Dir: http.Dir("../web"),
|
||||||
},
|
},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
PrometheusRegistry: prometheus.NewRegistry(),
|
PrometheusRegistry: prometheus.NewRegistry(),
|
||||||
|
@ -132,7 +132,7 @@ func newTestServerMultipleConnectors(ctx context.Context, t *testing.T, updateCo
|
||||||
Issuer: s.URL,
|
Issuer: s.URL,
|
||||||
Storage: memory.New(logger),
|
Storage: memory.New(logger),
|
||||||
Web: WebConfig{
|
Web: WebConfig{
|
||||||
Dir: "../web",
|
Dir: http.Dir("../web"),
|
||||||
},
|
},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
PrometheusRegistry: prometheus.NewRegistry(),
|
PrometheusRegistry: prometheus.NewRegistry(),
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -22,18 +21,10 @@ const (
|
||||||
tmplError = "error.html"
|
tmplError = "error.html"
|
||||||
tmplDevice = "device.html"
|
tmplDevice = "device.html"
|
||||||
tmplDeviceSuccess = "device_success.html"
|
tmplDeviceSuccess = "device_success.html"
|
||||||
|
tmplHeader = "header.html"
|
||||||
|
tmplFooter = "footer.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
var requiredTmpls = []string{
|
|
||||||
tmplApproval,
|
|
||||||
tmplLogin,
|
|
||||||
tmplPassword,
|
|
||||||
tmplOOB,
|
|
||||||
tmplError,
|
|
||||||
tmplDevice,
|
|
||||||
tmplDeviceSuccess,
|
|
||||||
}
|
|
||||||
|
|
||||||
type templates struct {
|
type templates struct {
|
||||||
loginTmpl *template.Template
|
loginTmpl *template.Template
|
||||||
approvalTmpl *template.Template
|
approvalTmpl *template.Template
|
||||||
|
@ -44,131 +35,93 @@ type templates struct {
|
||||||
deviceSuccessTmpl *template.Template
|
deviceSuccessTmpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
type webConfig struct {
|
|
||||||
dir string
|
|
||||||
logoURL string
|
|
||||||
issuer string
|
|
||||||
theme string
|
|
||||||
issuerURL string
|
|
||||||
extra map[string]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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) (http.Handler, http.Handler, *templates, error) {
|
|
||||||
// fallback to the default theme if the legacy theme name is provided
|
|
||||||
if c.theme == "coreos" || c.theme == "tectonic" {
|
|
||||||
c.theme = ""
|
|
||||||
}
|
|
||||||
if c.theme == "" {
|
|
||||||
c.theme = "light"
|
|
||||||
}
|
|
||||||
if c.issuer == "" {
|
|
||||||
c.issuer = "dex"
|
|
||||||
}
|
|
||||||
if c.dir == "" {
|
|
||||||
c.dir = "./web"
|
|
||||||
}
|
|
||||||
if c.logoURL == "" {
|
|
||||||
c.logoURL = "theme/logo.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dirExists(c.dir); err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("load web dir: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static := http.FileServer(http.Dir(staticDir))
|
|
||||||
theme := http.FileServer(http.Dir(themeDir))
|
|
||||||
|
|
||||||
templates, err := loadTemplates(c, templatesDir)
|
|
||||||
return static, theme, templates, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadTemplates parses the expected templates from the provided directory.
|
// loadTemplates parses the expected templates from the provided directory.
|
||||||
func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
|
func loadTemplates(c WebConfig, issuerPath string) (*templates, error) {
|
||||||
files, err := ioutil.ReadDir(templatesDir)
|
// fallback to the default theme if the legacy theme name is provided
|
||||||
|
if c.Theme == "coreos" || c.Theme == "tectonic" {
|
||||||
|
c.Theme = ""
|
||||||
|
}
|
||||||
|
if c.Theme == "" {
|
||||||
|
c.Theme = "light"
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Issuer == "" {
|
||||||
|
c.Issuer = "dex"
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.LogoURL == "" {
|
||||||
|
c.LogoURL = "theme/logo.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"issuer": func() string { return c.Issuer },
|
||||||
|
"logo": func() string { return c.LogoURL },
|
||||||
|
"url": func(reqPath, assetPath string) string { return relativeURL(issuerPath, reqPath, assetPath) },
|
||||||
|
"theme": func(reqPath, assetPath string) string {
|
||||||
|
return relativeURL(issuerPath, reqPath, path.Join("themes", c.Theme, assetPath))
|
||||||
|
},
|
||||||
|
"lower": strings.ToLower,
|
||||||
|
"extra": func(k string) string { return c.Extra[k] },
|
||||||
|
}
|
||||||
|
|
||||||
|
group := template.New("")
|
||||||
|
|
||||||
|
// load all of our templates individually.
|
||||||
|
// some http.FilSystem implementations don't implement Readdir
|
||||||
|
|
||||||
|
loginTemplate, err := loadTemplate(c.Dir, tmplLogin, funcs, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read dir: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filenames := []string{}
|
approvalTemplate, err := loadTemplate(c.Dir, tmplApproval, funcs, group)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuerURL, err := url.Parse(c.issuerURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing issuerURL: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
funcs := map[string]interface{}{
|
passwordTemplate, err := loadTemplate(c.Dir, tmplPassword, funcs, group)
|
||||||
"issuer": func() string { return c.issuer },
|
|
||||||
"logo": func() string { return c.logoURL },
|
|
||||||
"url": func(reqPath, assetPath string) string { return relativeURL(issuerURL.Path, reqPath, assetPath) },
|
|
||||||
"lower": strings.ToLower,
|
|
||||||
"extra": func(k string) string { return c.extra[k] },
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpls, err := template.New("").Funcs(funcs).ParseFiles(filenames...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse files: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
missingTmpls := []string{}
|
|
||||||
for _, tmplName := range requiredTmpls {
|
oobTemplate, err := loadTemplate(c.Dir, tmplOOB, funcs, group)
|
||||||
if tmpls.Lookup(tmplName) == nil {
|
if err != 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deviceTemplate, err := loadTemplate(c.Dir, tmplDevice, funcs, group)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceSuccessTemplate, err := loadTemplate(c.Dir, tmplDeviceSuccess, funcs, group)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = loadTemplate(c.Dir, tmplHeader, funcs, group)
|
||||||
|
if err != nil {
|
||||||
|
// we don't actually care if this template exists
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = loadTemplate(c.Dir, tmplFooter, funcs, group)
|
||||||
|
if err != nil {
|
||||||
|
// we don't actually care if this template exists
|
||||||
|
}
|
||||||
|
|
||||||
return &templates{
|
return &templates{
|
||||||
loginTmpl: tmpls.Lookup(tmplLogin),
|
loginTmpl: loginTemplate,
|
||||||
approvalTmpl: tmpls.Lookup(tmplApproval),
|
approvalTmpl: approvalTemplate,
|
||||||
passwordTmpl: tmpls.Lookup(tmplPassword),
|
passwordTmpl: passwordTemplate,
|
||||||
oobTmpl: tmpls.Lookup(tmplOOB),
|
oobTmpl: oobTemplate,
|
||||||
errorTmpl: tmpls.Lookup(tmplError),
|
errorTmpl: errorTemplate,
|
||||||
deviceTmpl: tmpls.Lookup(tmplDevice),
|
deviceTmpl: deviceTemplate,
|
||||||
deviceSuccessTmpl: tmpls.Lookup(tmplDeviceSuccess),
|
deviceSuccessTmpl: deviceSuccessTemplate,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,6 +192,22 @@ func relativeURL(serverPath, reqPath, assetPath string) string {
|
||||||
return relativeURL
|
return relativeURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load a template by name from the templates dir
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.ReadFrom(file)
|
||||||
|
contents := buffer.String()
|
||||||
|
|
||||||
|
return group.New(name).Funcs(funcs).Parse(contents)
|
||||||
|
}
|
||||||
|
|
||||||
var scopeDescriptions = map[string]string{
|
var scopeDescriptions = map[string]string{
|
||||||
"offline_access": "Have offline access",
|
"offline_access": "Have offline access",
|
||||||
"profile": "View basic profile information",
|
"profile": "View basic profile information",
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<title>{{ issuer }}</title>
|
<title>{{ issuer }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="{{ url .ReqPath "static/main.css" }}" rel="stylesheet">
|
<link href="{{ url .ReqPath "static/main.css" }}" rel="stylesheet">
|
||||||
<link href="{{ url .ReqPath "theme/styles.css" }}" rel="stylesheet">
|
<link href="{{ theme .ReqPath "theme/styles.css" }}" rel="stylesheet">
|
||||||
<link rel="icon" href="{{ url .ReqPath "theme/favicon.png" }}">
|
<link rel="icon" href="{{ theme .ReqPath "theme/favicon.png" }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="theme-body">
|
<body class="theme-body">
|
||||||
|
|
Loading…
Reference in a new issue