mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-11 16:17:39 +02:00
internal/frontend : serve static assets (#392)
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
f20d913abe
commit
ebee64b70b
24 changed files with 700 additions and 502 deletions
5
Makefile
5
Makefile
|
@ -49,6 +49,11 @@ tag: ## Create a new git tag to prepare to build a release
|
|||
git tag -sa $(VERSION) -m "$(VERSION)"
|
||||
@echo "Run git push origin $(VERSION) to push your new tag to GitHub."
|
||||
|
||||
.PHONY: frontend
|
||||
frontend: ## Runs go generate on the static assets package.
|
||||
@echo "==> $@"
|
||||
@CGO_ENABLED=0 GO111MODULE=on go generate github.com/pomerium/pomerium/internal/frontend
|
||||
|
||||
.PHONY: build
|
||||
build: ## Builds dynamic executables and/or packages.
|
||||
@echo "==> $@"
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
|
@ -147,6 +147,6 @@ func New(opts config.Options) (*Authenticate, error) {
|
|||
// IdP
|
||||
provider: provider,
|
||||
|
||||
templates: templates.New(),
|
||||
templates: template.Must(frontend.NewTemplates()),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -21,21 +21,10 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
// CSPHeaders are the content security headers added to the service's handlers
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
|
||||
var CSPHeaders = map[string]string{
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'self'" +
|
||||
" 'sha256-z9MsgkMbQjRSLxzAfN55jB3a9pP0PQ4OHFH8b4iDP6s=' " +
|
||||
" 'sha256-qnVkQSG7pWu17hBhIw0kCpfEB3XGvt0mNRa6+uM6OUU=' " +
|
||||
" 'sha256-qOdRsNZhtR+htazbcy7guQl3Cn1cqOw1FcE4d3llae0='; " +
|
||||
"img-src 'self';",
|
||||
"Referrer-Policy": "Same-origin",
|
||||
}
|
||||
|
||||
// Handler returns the authenticate service's handler chain.
|
||||
func (a *Authenticate) Handler() http.Handler {
|
||||
r := httputil.NewRouter()
|
||||
r.Use(middleware.SetHeaders(CSPHeaders))
|
||||
r.Use(middleware.SetHeaders(httputil.HeadersContentSecurityPolicy))
|
||||
r.Use(csrf.Protect(
|
||||
a.cookieSecret,
|
||||
csrf.Secure(a.cookieOptions.Secure),
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -13,9 +14,9 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
|
@ -29,7 +30,7 @@ func testAuthenticate() *Authenticate {
|
|||
auth.sharedKey = cryptutil.NewBase64Key()
|
||||
auth.cookieSecret = cryptutil.NewKey()
|
||||
auth.cookieOptions = &sessions.CookieOptions{Name: "name"}
|
||||
auth.templates = templates.New()
|
||||
auth.templates = template.Must(frontend.NewTemplates())
|
||||
return &auth
|
||||
}
|
||||
|
||||
|
@ -189,7 +190,7 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
|||
a := &Authenticate{
|
||||
sessionStore: tt.sessionStore,
|
||||
provider: tt.provider,
|
||||
templates: templates.New(),
|
||||
templates: template.Must(frontend.NewTemplates()),
|
||||
}
|
||||
u, _ := url.Parse("/sign_out")
|
||||
params, _ := url.ParseQuery(u.RawQuery)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/pomerium/pomerium/authenticate"
|
||||
"github.com/pomerium/pomerium/authorize"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/grpcutil"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
|
@ -169,6 +170,7 @@ func newGlobalRouter(o *config.Options) *mux.Router {
|
|||
mux.Use(middleware.Healthcheck("/ping", version.UserAgent()))
|
||||
mux.HandleFunc("/healthz", httputil.HealthCheck)
|
||||
mux.HandleFunc("/ping", httputil.HealthCheck)
|
||||
mux.PathPrefix("/.pomerium/assets/").Handler(http.StripPrefix("/.pomerium/assets/", frontend.MustAssetHandler()))
|
||||
|
||||
return mux
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -18,6 +18,7 @@ require (
|
|||
github.com/pomerium/go-oidc v2.0.0+incompatible
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v0.9.3
|
||||
github.com/rakyll/statik v0.1.6
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/rs/zerolog v1.16.0
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
|
|
7
go.sum
7
go.sum
|
@ -170,14 +170,14 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
|||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
|
||||
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
||||
github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg=
|
||||
github.com/rs/zerolog v1.16.0 h1:AaELmZdcJHT8m6oZ5py4213cdFK8XGXkB3dFdAQ+P7Q=
|
||||
github.com/rs/zerolog v1.16.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
@ -221,8 +221,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 h1:PDpCLFAH/YIX0QpHPf2eO7L4rC2OOirBrKtXTLLiNTY=
|
||||
golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -306,7 +304,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
|
237
internal/frontend/assets/html/dashboard.go.html
Normal file
237
internal/frontend/assets/html/dashboard.go.html
Normal file
|
@ -0,0 +1,237 @@
|
|||
{{define "dashboard.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Pomerium</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
{{if .Session.Picture }}
|
||||
<img class="icon" src="{{.Session.Picture}}" alt="user image" />
|
||||
{{else}}
|
||||
<img
|
||||
class="icon"
|
||||
src="/.pomerium/assets/img/account_circle-24px.svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
{{end}}
|
||||
|
||||
<form method="POST" action="/.pomerium/sign_out">
|
||||
<section>
|
||||
<h2>Current user</h2>
|
||||
<p class="message">Your current session details.</p>
|
||||
<fieldset>
|
||||
{{if .Session.Name}}
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.Name}}"
|
||||
title="{{.Session.Name}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{else}} {{if .Session.GivenName}}
|
||||
<label>
|
||||
<span>Given Name</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.GivenName}}"
|
||||
title="{{.Session.GivenName}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.FamilyName}}
|
||||
<label>
|
||||
<span>Family Name</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.FamilyName}}"
|
||||
title="{{.Session.FamilyName}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{end}} {{if .Session.Subject}}
|
||||
<label>
|
||||
<span>UserID</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.Subject}}"
|
||||
title="{{.Session.Subject}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.Email}}
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input
|
||||
type="email"
|
||||
class="field"
|
||||
value="{{.Session.Email}}"
|
||||
title="{{.Session.Email}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.User}}
|
||||
<label>
|
||||
<span>User</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.User}}"
|
||||
title="{{.Session.User}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{range $i,$_:= .Session.Groups}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Group</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.}}"
|
||||
title="{{.}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.Expiry}}
|
||||
<label>
|
||||
<span>Expiry</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.Expiry.Time}}"
|
||||
title="{{.Session.Expiry.Time}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.IssuedAt}}
|
||||
<label>
|
||||
<span>Issued</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.IssuedAt.Time}}"
|
||||
title="{{.Session.IssuedAt.Time}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.Issuer}}
|
||||
<label>
|
||||
<span>Issuer</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{ .Session.Issuer}}"
|
||||
title="{{ .Session.Issuer}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{range $i, $_:= .Session.Audience}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Audience</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
title="{{ . }}"
|
||||
value="{{ . }}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{if .Session.ImpersonateEmail}}
|
||||
<label>
|
||||
<span>Impersonating Email</span>
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.Session.ImpersonateEmail}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}} {{range $i,$_:= .Session.ImpersonateGroups}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Impersonating Group</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input
|
||||
type="text"
|
||||
class="field"
|
||||
value="{{.}}"
|
||||
title="{{.}}"
|
||||
disabled
|
||||
/>
|
||||
</label>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
{{ .csrfField }}
|
||||
<button class="button full" type="submit">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{if .IsAdmin}}
|
||||
<form method="POST" action="/.pomerium/impersonate">
|
||||
<section>
|
||||
<h2>Sign-in-as</h2>
|
||||
<p class="message">
|
||||
Administrators can temporarily impersonate another user.
|
||||
</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
class="field"
|
||||
value=""
|
||||
placeholder="user@example.com"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Group</span>
|
||||
<input
|
||||
name="group"
|
||||
type="text"
|
||||
class="field"
|
||||
value=""
|
||||
placeholder="engineering"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
{{ .csrfField }}
|
||||
<button class="button full" type="submit">
|
||||
Impersonate session
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{template "footer.html"}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
35
internal/frontend/assets/html/error.go.html
Normal file
35
internal/frontend/assets/html/error.go.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
{{define "error.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>{{.Code}} - {{.Title}}</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<img
|
||||
class="icon"
|
||||
src="/.pomerium/assets/img/error-24px.svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
<h1 class="title">{{.Title}}</h1>
|
||||
<section>
|
||||
<p class="message">
|
||||
{{if .Message}}{{.Message}}{{end}} {{if .CanDebug}}Troubleshoot
|
||||
your
|
||||
<a href="/.pomerium/">session</a>.{{end}} {{if .RequestID}}
|
||||
Request {{.RequestID}}{{end}}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{{template "footer.html"}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}
|
12
internal/frontend/assets/html/footer.go.html
Normal file
12
internal/frontend/assets/html/footer.go.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{define "footer.html"}}
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io">
|
||||
<img
|
||||
class="powered-by-pomerium"
|
||||
src="/.pomerium/assets/img/pomerium.svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="25"
|
||||
/>
|
||||
</a>
|
||||
</footer>
|
||||
{{end}}
|
12
internal/frontend/assets/html/header.go.html
Normal file
12
internal/frontend/assets/html/header.go.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{define "header.html"}}
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/.pomerium/assets/style/main.css"
|
||||
/>
|
||||
|
||||
{{end}}
|
1
internal/frontend/assets/img/account_circle-24px.svg
Normal file
1
internal/frontend/assets/img/account_circle-24px.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#6e43e8" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
After Width: | Height: | Size: 380 B |
1
internal/frontend/assets/img/error-24px.svg
Normal file
1
internal/frontend/assets/img/error-24px.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#6e43e8" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
|
After Width: | Height: | Size: 249 B |
1
internal/frontend/assets/img/pomerium.svg
Normal file
1
internal/frontend/assets/img/pomerium.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 139 30" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-5 -5.5)"><path d="m10.6 5.5h127.8c3.09 0 5.6 2 5.6 4.39v21.22c0 2.42-2.51 4.39-5.6 4.39h-127.8c-3.09 0-5.6-2-5.6-4.39v-21.22c0-2.42 2.51-4.39 5.6-4.39z" fill="#6e43e8" fill-rule="evenodd"/><g fill="#fff"><path d="m75.4 26.62h-1.46l1.13-2.79-2.25-5.69h1.54l1.42 3.86 1.43-3.87h1.54zm-5.61-2.44a2.42 2.42 0 0 1 -1.5-.55v.37h-1.51v-8.44h1.51v3a2.48 2.48 0 0 1 1.5-.55c1.58 0 2.66 1.28 2.66 3.09s-1.08 3.08-2.66 3.08zm-.32-4.88a1.68 1.68 0 0 0 -1.18.53v2.52a1.65 1.65 0 0 0 1.18.54c.85 0 1.44-.73 1.44-1.8s-.59-1.79-1.44-1.79zm-8.8 4.33a2.38 2.38 0 0 1 -1.5.55c-1.57 0-2.66-1.27-2.66-3.09s1.09-3.09 2.66-3.09a2.44 2.44 0 0 1 1.5.55v-3h1.52v8.45h-1.52zm0-3.8a1.63 1.63 0 0 0 -1.17-.53c-.86 0-1.45.73-1.45 1.79s.59 1.8 1.45 1.8a1.6 1.6 0 0 0 1.17-.54zm-9 1.68a1.69 1.69 0 0 0 1.8 1.49 3.55 3.55 0 0 0 1.76-.56v1.26a4.73 4.73 0 0 1 -2 .46 3 3 0 0 1 -3-3.13 2.87 2.87 0 0 1 2.88-3.03 2.66 2.66 0 0 1 2.59 3 5.53 5.53 0 0 1 0 .56zm1.37-2.34a1.38 1.38 0 0 0 -1.37 1.36h2.57a1.28 1.28 0 0 0 -1.19-1.36zm-5.34.93v3.9h-1.5v-5.9h1.51v.59a2 2 0 0 1 1.45-.69 1.65 1.65 0 0 1 .49.06v1.35a1.83 1.83 0 0 0 -.53-.07 1.87 1.87 0 0 0 -1.41.76zm-6.7 1.41a1.69 1.69 0 0 0 1.76 1.49 3.55 3.55 0 0 0 1.76-.56v1.26a4.73 4.73 0 0 1 -2 .46 3 3 0 0 1 -3-3.13 2.87 2.87 0 0 1 2.88-3.03 2.66 2.66 0 0 1 2.6 3 5.53 5.53 0 0 1 0 .56zm1.37-2.34a1.38 1.38 0 0 0 -1.37 1.36h2.57a1.28 1.28 0 0 0 -1.23-1.36zm-6.67 4.83-1.2-4-1.2 4h-1.3l-2-5.9h1.51l1.19 4 1.19-4h1.37l1.19 4 1.19-4h1.51l-2 5.9zm-9.23.14a2.94 2.94 0 0 1 -3-3.09 3 3 0 1 1 6.07 0 2.94 2.94 0 0 1 -3.07 3.13zm0-4.92c-.88 0-1.49.75-1.49 1.83s.61 1.83 1.49 1.83 1.53-.7 1.53-1.79-.65-1.83-1.53-1.83zm-6.62 1.87h-1.36v2.91h-1.49v-8.07h2.87a2.61 2.61 0 1 1 0 5.2zm-.22-4h-1.14v2.81h1.14a1.38 1.38 0 1 0 0-2.75z" fill-rule="evenodd"/><path d="m132.71 14.9a3.93 3.93 0 0 0 -3.93-3.9h-34.19a3.93 3.93 0 0 0 -3.93 3.92v16.14h2.71v-4.51a5.49 5.49 0 1 1 11 0v4.51h2v-4.51a5.49 5.49 0 1 1 11 0v4.51h2v-4.51a5.49 5.49 0 1 1 11 0v4.51h2.47zm-39.34 4.1a5.49 5.49 0 1 1 11 0zm12.95 0a5.49 5.49 0 1 1 11 0zm12.94 0a5.49 5.49 0 1 1 11 0z"/></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
239
internal/frontend/assets/style/main.css
Normal file
239
internal/frontend/assets/style/main.css
Normal file
|
@ -0,0 +1,239 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: #f8f8ff;
|
||||
}
|
||||
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#info-box {
|
||||
max-width: 480px;
|
||||
width: 480px;
|
||||
margin-top: 200px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
letter-spacing: 0.3px;
|
||||
text-transform: uppercase;
|
||||
color: #32325d;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
text-align: center;
|
||||
background: #f8f8ff;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 15px 0;
|
||||
color: #32325d;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 -30px;
|
||||
padding: 20px 30px 30px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8fb;
|
||||
background-color: #f8f8ff;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 20px;
|
||||
background: #fcfcff;
|
||||
box-shadow: 0 1px 3px 0 rgba(50, 50, 93, 0.15),
|
||||
0 4px 6px 0 rgba(112, 157, 199, 0.15);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 42px;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
fieldset label:not(:last-child) {
|
||||
border-bottom: 1px solid #f0f5fa;
|
||||
}
|
||||
|
||||
fieldset label span {
|
||||
min-width: 125px;
|
||||
padding: 0 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#group::before {
|
||||
display: inline-flex;
|
||||
content: "";
|
||||
height: 15px;
|
||||
background-position: -1000px -1000px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-table;
|
||||
margin-top: -72px;
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon svg {
|
||||
fill: #6e43e8;
|
||||
background: red;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
width: 115px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
p.message {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.field {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
background: transparent;
|
||||
font-weight: 400;
|
||||
color: #31325f;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
fieldset .select::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input {
|
||||
border-style: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #fcfcff;
|
||||
background: #6e43e8;
|
||||
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-weight: 700;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button.half {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.button.full {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1),
|
||||
0 3px 6px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.off-color {
|
||||
background: #5735b5;
|
||||
}
|
||||
|
||||
.powered-by-pomerium {
|
||||
align-items: center;
|
||||
}
|
12
internal/frontend/statik/statik.go
Normal file
12
internal/frontend/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
55
internal/frontend/templates.go
Normal file
55
internal/frontend/templates.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
//go:generate statik -src=./assets -include=*.svg,*.html,*.css,*.js
|
||||
|
||||
package frontend // import "github.com/pomerium/pomerium/internal/frontend"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/pomerium/pomerium/internal/frontend/statik" // load static assets
|
||||
)
|
||||
|
||||
// NewTemplates loads pomerium's templates. Panics on failure.
|
||||
func NewTemplates() (*template.Template, error) {
|
||||
t := template.New("pomerium-templates")
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("internal/frontend: error creating new file system: %w", err)
|
||||
}
|
||||
|
||||
err = fs.Walk(statikFS, "/html", func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||
if !fileInfo.IsDir() {
|
||||
file, err := statikFS.Open(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("internal/frontend: error opening %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("internal/frontend: error reading %s: %w", filePath, err)
|
||||
}
|
||||
t.Parse(string(buf))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MustAssetHandler wraps a call to the embedded static file system and panics
|
||||
// if the error is non-nil. It is intended for use in variable initializations
|
||||
func MustAssetHandler() http.Handler {
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return http.FileServer(statikFS)
|
||||
}
|
63
internal/frontend/templates_test.go
Normal file
63
internal/frontend/templates_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package frontend
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
_ "github.com/pomerium/pomerium/internal/frontend/statik"
|
||||
"github.com/rakyll/statik/fs"
|
||||
)
|
||||
|
||||
func TestTemplatesCompile(t *testing.T) {
|
||||
templates := template.Must(NewTemplates())
|
||||
if templates == nil {
|
||||
t.Errorf("unexpected nil value %#v", templates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTemplates(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testData string
|
||||
want *template.Template
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty statik fs", "", nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs.Register(tt.testData)
|
||||
got, err := NewTemplates()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewTemplates() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewTemplates() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustAssetHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testData string
|
||||
wantPanic bool
|
||||
}{
|
||||
{"empty statik fs", "", true},
|
||||
{"empty statik fs", "", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("The code did not panic")
|
||||
}
|
||||
}()
|
||||
MustAssetHandler()
|
||||
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,3 +7,11 @@ const (
|
|||
// Especially useful when working with single page apps (SPA).
|
||||
HeaderPomeriumResponse = "x-pomerium-intercepted-response"
|
||||
)
|
||||
|
||||
// HeadersContentSecurityPolicy are the content security headers added to the service's handlers
|
||||
// by default includes profile photo exceptions for supported identity providers.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
|
||||
var HeadersContentSecurityPolicy = map[string]string{
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'self'; img-src *;",
|
||||
"Referrer-Policy": "Same-origin",
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
)
|
||||
|
||||
// Error formats creates a HTTP error with code, user friendly (and safe) error
|
||||
|
@ -92,7 +93,7 @@ func ErrorResponse(w http.ResponseWriter, r *http.Request, e error) {
|
|||
RequestID: requestID,
|
||||
CanDebug: canDebug,
|
||||
}
|
||||
templates.New().ExecuteTemplate(w, "error.html", t)
|
||||
template.Must(frontend.NewTemplates()).ExecuteTemplate(w, "error.html", t)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,461 +0,0 @@
|
|||
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// New loads html and style resources directly. Panics on failure.
|
||||
func New() *template.Template {
|
||||
t := template.New("pomerium-templates")
|
||||
template.Must(t.Parse(`
|
||||
{{define "header.html"}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: #F8F8FF;
|
||||
}
|
||||
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
#info-box {
|
||||
max-width: 480px;
|
||||
width: 480px;
|
||||
margin-top: 200px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
letter-spacing: 0.3px;
|
||||
text-transform: uppercase;
|
||||
color: #32325d;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
text-align: center;
|
||||
background: #F8F8FF;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 15px 0;
|
||||
color: #32325d;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 -30px;
|
||||
padding: 20px 30px 30px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8fb;
|
||||
background-color: #F8F8FF;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 20px;
|
||||
background: #FCFCFF;
|
||||
box-shadow: 0 1px 3px 0 rgba(50, 50, 93, 0.15), 0 4px 6px 0 rgba(112, 157, 199, 0.15);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 42px;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
fieldset label:not(:last-child) {
|
||||
border-bottom: 1px solid #f0f5fa;
|
||||
}
|
||||
|
||||
fieldset label span {
|
||||
min-width: 125px;
|
||||
padding: 0 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
#group::before {
|
||||
display: inline-flex;
|
||||
content: '';
|
||||
height: 15px;
|
||||
background-position: -1000px -1000px;
|
||||
background-repeat: no-repeat;
|
||||
// margin-right: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-table;
|
||||
margin-top: -72px;
|
||||
background: #F8F8FF;
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
width: 115px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ok{
|
||||
fill: #6E43E8;
|
||||
}
|
||||
|
||||
.error{
|
||||
fill: #EB292F;
|
||||
}
|
||||
|
||||
p.message {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.field {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
background: transparent;
|
||||
font-weight: 400;
|
||||
color: #31325f;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
}
|
||||
|
||||
fieldset .select::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
pointer-events: none;
|
||||
background: #6E43E8 url("data:image/svg+xml;utf8,<svg viewBox='0 0 140 140' width='24' height='24' xmlns='http://www.w3.org/2000/svg'><g><path d='m121.3,34.6c-1.6-1.6-4.2-1.6-5.8,0l-51,51.1-51.1-51.1c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l53.9,53.9c0.8,0.8 1.8,1.2 2.9,1.2 1,0 2.1-0.4 2.9-1.2l53.9-53.9c1.7-1.6 1.7-4.2 0.1-5.8z' fill='white'/></g></svg>") no-repeat;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
// flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.flex{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #FCFCFF;
|
||||
background: #6E43E8;
|
||||
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-weight: 700;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button.half{
|
||||
flex-grow:0;
|
||||
flex-shrink:0;
|
||||
flex-basis:calc(50% - 10px);
|
||||
}
|
||||
|
||||
.button.full{
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.off-color{
|
||||
background: #5735B5;
|
||||
}
|
||||
|
||||
</style>
|
||||
{{end}}`))
|
||||
|
||||
template.Must(t.Parse(`
|
||||
{{define "error.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>{{.Code}} - {{.Title}}</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<svg class="icon error" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/></svg>
|
||||
<h1 class="title">{{.Title}}</h1>
|
||||
<section>
|
||||
<p class="message">
|
||||
{{if .Message}}{{.Message}}</br>{{end}}
|
||||
{{if .CanDebug}}Troubleshoot your <a href="/.pomerium/">session</a>.</br>{{end}}
|
||||
{{if .RequestID}} Request {{.RequestID}}</br>{{end}}
|
||||
|
||||
</p>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "dashboard.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
|
||||
<head>
|
||||
<title>Pomerium</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
{{if .Session.Picture }}
|
||||
<img class="icon" src="{{.Session.Picture}}" alt="user image">
|
||||
{{else}}
|
||||
<svg class="icon ok" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
|
||||
</svg>
|
||||
{{end}}
|
||||
|
||||
<form method="POST" action="/.pomerium/sign_out">
|
||||
<section>
|
||||
<h2>Current user</h2>
|
||||
<p class="message">Your current session details.</p>
|
||||
<fieldset>
|
||||
{{if .Session.Name}}
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input type="text" class="field" value="{{.Session.Name}}" title="{{.Session.Name}}" disabled>
|
||||
</label>
|
||||
{{else}}
|
||||
{{if .Session.GivenName}}
|
||||
<label>
|
||||
<span>Given Name</span>
|
||||
<input type="text" class="field" value="{{.Session.GivenName}}" title="{{.Session.GivenName}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.FamilyName}}
|
||||
<label>
|
||||
<span>Family Name</span>
|
||||
<input type="text" class="field" value="{{.Session.FamilyName}}" title="{{.Session.FamilyName}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .Session.Subject}}
|
||||
<label>
|
||||
<span>UserID</span>
|
||||
<input type="text" class="field" value="{{.Session.Subject}}" title="{{.Session.Subject}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.Email}}
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input type="email" class="field" value="{{.Session.Email}}" title="{{.Session.Email}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.User}}
|
||||
<label>
|
||||
<span>User</span>
|
||||
<input type="text" class="field" value="{{.Session.User}}" title="{{.Session.User}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{range $i,$_:= .Session.Groups}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Group</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input type="text" class="field" value="{{.}}" title="{{.}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.Expiry}}
|
||||
<label>
|
||||
<span>Expiry</span>
|
||||
<input type="text" class="field" value="{{.Session.Expiry.Time}}" title="{{.Session.Expiry.Time}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.IssuedAt}}
|
||||
<label>
|
||||
<span>Issued</span>
|
||||
<input type="text" class="field" value="{{.Session.IssuedAt.Time}}" title="{{.Session.IssuedAt.Time}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{if .Session.Issuer}}
|
||||
<label>
|
||||
<span>Issuer</span>
|
||||
<input type="text" class="field" value="{{ .Session.Issuer}}" title="{{ .Session.Issuer}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{range $i, $_:= .Session.Audience}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Audience</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input type="text" class="field" title="{{ . }}" value="{{ . }}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
|
||||
{{if .Session.ImpersonateEmail}}
|
||||
<label>
|
||||
<span>Impersonating Email</span>
|
||||
<input type="text" class="field" value="{{.Session.ImpersonateEmail}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
{{range $i,$_:= .Session.ImpersonateGroups}}
|
||||
<label>
|
||||
{{if eq $i 0}}
|
||||
<span>Impersonating Group</span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{end}}
|
||||
<input type="text" class="field" value="{{.}}" title="{{.}}" disabled>
|
||||
</label>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
{{ .csrfField }}
|
||||
<button class="button full" type="submit">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{if .IsAdmin}}
|
||||
<form method="POST" action="/.pomerium/impersonate">
|
||||
<section>
|
||||
<h2>Sign-in-as</h2>
|
||||
<p class="message">Administrators can temporarily impersonate another user.</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" class="field" value="" placeholder="user@example.com">
|
||||
</label>
|
||||
<label>
|
||||
<span>Group</span>
|
||||
<input name="group" type="text" class="field" value="" placeholder="engineering">
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
{{ .csrfField }}
|
||||
<button class="button full" type="submit">Impersonate session</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}`))
|
||||
return t
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplatesCompile(t *testing.T) {
|
||||
templates := New()
|
||||
if templates == nil {
|
||||
t.Errorf("unexpected nil value %#v", templates)
|
||||
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
|
@ -21,6 +20,7 @@ import (
|
|||
func (p *Proxy) registerDashboardHandlers(r *mux.Router) *mux.Router {
|
||||
// dashboard subrouter
|
||||
h := r.PathPrefix(dashboardURL).Subrouter()
|
||||
h.Use(middleware.SetHeaders(httputil.HeadersContentSecurityPolicy))
|
||||
// 1. Retrieve the user session and add it to the request context
|
||||
h.Use(sessions.RetrieveSession(p.sessionStore))
|
||||
// 2. AuthN - Verify the user is authenticated. Set email, group, & id headers
|
||||
|
@ -86,7 +86,7 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
templates.New().ExecuteTemplate(w, "dashboard.html", map[string]interface{}{
|
||||
p.templates.ExecuteTemplate(w, "dashboard.html", map[string]interface{}{
|
||||
"Session": session,
|
||||
"IsAdmin": isAdmin,
|
||||
"csrfField": csrf.TemplateField(r),
|
||||
|
|
|
@ -16,12 +16,12 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
"github.com/pomerium/pomerium/internal/tripper"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
|
@ -132,7 +132,7 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
sessions.NewHeaderStore(encoder, "Pomerium"),
|
||||
sessions.NewQueryParamStore(encoder, "pomerium_session")},
|
||||
signingKey: opts.SigningKey,
|
||||
templates: templates.New(),
|
||||
templates: template.Must(frontend.NewTemplates()),
|
||||
}
|
||||
// errors checked in ValidateOptions
|
||||
p.authorizeURL, _ = urlutil.DeepCopy(opts.AuthorizeURL)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue