git.iko.soy/typeable/oauth2-proxy
on master help sign in
Update templates to bulma framework
commit801edeba230c485a508c222e91ec762066682404 authorJoel Speed <joel.speed@hotmail.co.uk> dateSat 6 Feb 2021, 17:20 (6 years ago) parent465789b
2 files changed+222−167
oauthproxy.go+47−28 templates.go+175−139
oauthproxy.go+47−28
@@ -478,7 +478,7 @@ func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
478478
479479 switch path := req.URL.Path; {
480480 case path == p.RobotsPath:
481− p.RobotsTxt(rw)
481+ p.RobotsTxt(rw, req)
482482 case p.IsAllowedRequest(req):
483483 p.SkipAuthProxy(rw, req)
484484 case path == p.SignInPath:
@@ -499,30 +499,49 @@ func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
499499}
500500
501501// RobotsTxt disallows scraping pages from the OAuthProxy
502−func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
502+func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter, req *http.Request) {
503503 _, err := fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
504504 if err != nil {
505505 logger.Printf("Error writing robots.txt: %v", err)
506− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
506+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
507507 return
508508 }
509509 rw.WriteHeader(http.StatusOK)
510510}
511511
512512// ErrorPage writes an error response
513−func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
513+func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code int, title string, message string) {
514+ redirectURL, err := p.getAppRedirect(req)
515+ if err != nil {
516+ logger.Errorf("Error obtaining redirect: %v", err)
517+ }
518+ if redirectURL == p.SignInPath || redirectURL == "" {
519+ redirectURL = "/"
520+ }
521+
514522 rw.WriteHeader(code)
523+
524+ // We allow unescaped template.HTML since it is user configured options
525+ /* #nosec G203 */
515526 t := struct {
516527 Title string
517528 Message string
518529 ProxyPrefix string
530+ StatusCode int
531+ Redirect string
532+ Footer template.HTML
533+ Version string
519534 }{
520− Title: fmt.Sprintf("%d %s", code, title),
535+ Title: title,
521536 Message: message,
522537 ProxyPrefix: p.ProxyPrefix,
538+ StatusCode: code,
539+ Redirect: redirectURL,
540+ Footer: template.HTML(p.Footer),
541+ Version: VERSION,
523542 }
524− err := p.templates.ExecuteTemplate(rw, "error.html", t)
525− if err != nil {
543+
544+ if err := p.templates.ExecuteTemplate(rw, "error.html", t); err != nil {
526545 logger.Printf("Error rendering error.html template: %v", err)
527546 http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
528547 }
@@ -570,7 +589,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
570589 err := p.ClearSessionCookie(rw, req)
571590 if err != nil {
572591 logger.Printf("Error clearing session cookie: %v", err)
573− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
592+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
574593 return
575594 }
576595 rw.WriteHeader(code)
@@ -578,7 +597,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
578597 redirectURL, err := p.getAppRedirect(req)
579598 if err != nil {
580599 logger.Errorf("Error obtaining redirect: %v", err)
581− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
600+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
582601 return
583602 }
584603
@@ -611,7 +630,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
611630 err = p.templates.ExecuteTemplate(rw, "sign_in.html", t)
612631 if err != nil {
613632 logger.Printf("Error rendering sign_in.html template: %v", err)
614− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
633+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
615634 }
616635}
617636
@@ -639,7 +658,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
639658 redirect, err := p.getAppRedirect(req)
640659 if err != nil {
641660 logger.Errorf("Error obtaining redirect: %v", err)
642− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
661+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
643662 return
644663 }
645664
@@ -649,7 +668,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
649668 err = p.SaveSession(rw, req, session)
650669 if err != nil {
651670 logger.Printf("Error saving session: %v", err)
652− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
671+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
653672 return
654673 }
655674 http.Redirect(rw, req, redirect, http.StatusFound)
@@ -688,7 +707,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
688707 err = json.NewEncoder(rw).Encode(userInfo)
689708 if err != nil {
690709 logger.Printf("Error encoding user info: %v", err)
691− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
710+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
692711 }
693712}
694713
@@ -697,13 +716,13 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
697716 redirect, err := p.getAppRedirect(req)
698717 if err != nil {
699718 logger.Errorf("Error obtaining redirect: %v", err)
700− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
719+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
701720 return
702721 }
703722 err = p.ClearSessionCookie(rw, req)
704723 if err != nil {
705724 logger.Errorf("Error clearing session cookie: %v", err)
706− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
725+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
707726 return
708727 }
709728 http.Redirect(rw, req, redirect, http.StatusFound)
@@ -715,14 +734,14 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
715734 nonce, err := encryption.Nonce()
716735 if err != nil {
717736 logger.Errorf("Error obtaining nonce: %v", err)
718− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
737+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
719738 return
720739 }
721740 p.SetCSRFCookie(rw, req, nonce)
722741 redirect, err := p.getAppRedirect(req)
723742 if err != nil {
724743 logger.Errorf("Error obtaining redirect: %v", err)
725− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
744+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
726745 return
727746 }
728747 redirectURI := p.getOAuthRedirectURI(req)
@@ -738,34 +757,34 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
738757 err := req.ParseForm()
739758 if err != nil {
740759 logger.Errorf("Error while parsing OAuth2 callback: %v", err)
741− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
760+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
742761 return
743762 }
744763 errorString := req.Form.Get("error")
745764 if errorString != "" {
746765 logger.Errorf("Error while parsing OAuth2 callback: %s", errorString)
747− p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", errorString)
766+ p.ErrorPage(rw, req, http.StatusForbidden, "Permission Denied", errorString)
748767 return
749768 }
750769
751770 session, err := p.redeemCode(req)
752771 if err != nil {
753772 logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
754− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
773+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
755774 return
756775 }
757776
758777 err = p.enrichSessionState(req.Context(), session)
759778 if err != nil {
760779 logger.Errorf("Error creating session during OAuth2 callback: %v", err)
761− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
780+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
762781 return
763782 }
764783
765784 state := strings.SplitN(req.Form.Get("state"), ":", 2)
766785 if len(state) != 2 {
767786 logger.Error("Error while parsing OAuth2 state: invalid length")
768− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Invalid State")
787+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", "Invalid State")
769788 return
770789 }
771790 nonce := state[0]
@@ -773,13 +792,13 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
773792 c, err := req.Cookie(p.CSRFCookieName)
774793 if err != nil {
775794 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unable to obtain CSRF cookie")
776− p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", err.Error())
795+ p.ErrorPage(rw, req, http.StatusForbidden, "Permission Denied", err.Error())
777796 return
778797 }
779798 p.ClearCSRFCookie(rw, req)
780799 if c.Value != nonce {
781800 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: CSRF token mismatch, potential attack")
782− p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", "CSRF Failed")
801+ p.ErrorPage(rw, req, http.StatusForbidden, "Permission Denied", "CSRF Failed")
783802 return
784803 }
785804
@@ -797,13 +816,13 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
797816 err := p.SaveSession(rw, req, session)
798817 if err != nil {
799818 logger.Errorf("Error saving session state for %s: %v", remoteAddr, err)
800− p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
819+ p.ErrorPage(rw, req, http.StatusInternalServerError, "Internal Server Error", err.Error())
801820 return
802821 }
803822 http.Redirect(rw, req, redirect, http.StatusFound)
804823 } else {
805824 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")
806− p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", "Invalid Account")
825+ p.ErrorPage(rw, req, http.StatusForbidden, "Permission Denied", "Invalid Account")
807826 }
808827}
809828
@@ -885,12 +904,12 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
885904 }
886905
887906 case ErrAccessDenied:
888− p.ErrorPage(rw, http.StatusUnauthorized, "Permission Denied", "Unauthorized")
907+ p.ErrorPage(rw, req, http.StatusUnauthorized, "Permission Denied", "Unauthorized")
889908
890909 default:
891910 // unknown error
892911 logger.Errorf("Unexpected internal error: %v", err)
893− p.ErrorPage(rw, http.StatusInternalServerError,
912+ p.ErrorPage(rw, req, http.StatusInternalServerError,
894913 "Internal Error", "Internal Error")
895914 }
896915}
templates.go+175−139
@@ -28,138 +28,90 @@ func getTemplates() *template.Template {
2828 t, err := template.New("foo").Parse(`{{define "sign_in.html"}}
2929<!DOCTYPE html>
3030<html lang="en" charset="utf-8">
31−<head>
32− <title>Sign In</title>
33− <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
34− <style>
35− body {
36− font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
37− font-size: 14px;
38− line-height: 1.42857143;
39− color: #333;
40− background: #f0f0f0;
41− }
42− .signin {
43− display:block;
44− margin:20px auto;
45− max-width:400px;
46− background: #fff;
47− border:1px solid #ccc;
48− border-radius: 10px;
49− padding: 20px;
50− }
51− .center {
52− text-align:center;
53− }
54− .btn {
55− color: #fff;
56− background-color: #428bca;
57− border: 1px solid #357ebd;
58− -webkit-border-radius: 4;
59− -moz-border-radius: 4;
60− border-radius: 4px;
61− font-size: 14px;
62− padding: 6px 12px;
63− text-decoration: none;
64− cursor: pointer;
65− }
31+ <head>
32+ <meta charset="utf-8">
33+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
34+ <title>Sign In</title>
35+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
6636
67− .btn:hover {
68− background-color: #3071a9;
69− border-color: #285e8e;
70− text-decoration: none;
71− }
72− label {
73− display: inline-block;
74− max-width: 100%;
75− margin-bottom: 5px;
76− font-weight: 700;
77− }
78− input {
79− display: block;
80− width: 100%;
81− height: 34px;
82− padding: 6px 12px;
83− font-size: 14px;
84− line-height: 1.42857143;
85− color: #555;
86− background-color: #fff;
87− background-image: none;
88− border: 1px solid #ccc;
89− border-radius: 4px;
90− -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
91− box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
92− -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
93− -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
94− transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
95− margin:0;
96− box-sizing: border-box;
97− }
98− footer {
99− display:block;
100− font-size:10px;
101− color:#aaa;
102− text-align:center;
103− margin-bottom:10px;
104− }
105− footer a {
106− display:inline-block;
107− height:25px;
108− line-height:25px;
109− color:#aaa;
110− text-decoration:underline;
111− }
112− footer a:hover {
113− color:#aaa;
114− }
115− </style>
116−</head>
117−<body>
118− <div class="signin center">
119− <form method="GET" action="{{.ProxyPrefix}}/start">
120− <input type="hidden" name="rd" value="{{.Redirect}}">
121− {{ if .SignInMessage }}
122− <p>{{.SignInMessage}}</p>
123− {{ end}}
124− <button type="submit" class="btn">Sign in with {{.ProviderName}}</button><br/>
125− </form>
126− </div>
127−
128− {{ if .CustomLogin }}
129− <div class="signin">
130− <form method="POST" action="{{.ProxyPrefix}}/sign_in">
131− <input type="hidden" name="rd" value="{{.Redirect}}">
132− <label for="username">Username:</label><input type="text" name="username" id="username" size="10"><br/>
133− <label for="password">Password:</label><input type="password" name="password" id="password" size="10"><br/>
134− <button type="submit" class="btn">Sign In</button>
135− </form>
136− </div>
137− {{ end }}
138− <script>
139− if (window.location.hash) {
140− (function() {
141− var inputs = document.getElementsByName('rd');
142− for (var i = 0; i < inputs.length; i++) {
143− // Add hash, but make sure it is only added once
144− var idx = inputs[i].value.indexOf('#');
145− if (idx >= 0) {
146− // Remove existing hash from URL
147− inputs[i].value = inputs[i].value.substr(0, idx);
148− }
149− inputs[i].value += window.location.hash;
150− }
151− })();
152− }
153− </script>
154− <footer>
155− {{ if eq .Footer "-" }}
156− {{ else if eq .Footer ""}}
157− Secured with <a href="https://github.com/oauth2-proxy/oauth2-proxy#oauth2_proxy">OAuth2 Proxy</a> version {{.Version}}
158− {{ else }}
159− {{.Footer}}
160− {{ end }}
37+ <style>
38+ body {
39+ height: 100vh;
40+ }
41+ .sign-in-box {
42+ max-width: 400px;
43+ margin: 1.25rem auto;
44+ }
45+ footer a {
46+ text-decoration: underline;
47+ }
48+ </style>
49+
50+ <script>
51+ if (window.location.hash) {
52+ (function() {
53+ var inputs = document.getElementsByName('rd');
54+ for (var i = 0; i < inputs.length; i++) {
55+ // Add hash, but make sure it is only added once
56+ var idx = inputs[i].value.indexOf('#');
57+ if (idx >= 0) {
58+ // Remove existing hash from URL
59+ inputs[i].value = inputs[i].value.substr(0, idx);
60+ }
61+ inputs[i].value += window.location.hash;
62+ }
63+ })();
64+ }
65+ </script>
66+ </head>
67+ <body class="has-background-light">
68+ <section class="section">
69+ <div class="box block sign-in-box has-text-centered">
70+ <form method="GET" action="{{.ProxyPrefix}}/start">
71+ <input type="hidden" name="rd" value="{{.Redirect}}">
72+ {{ if .SignInMessage }}
73+ <p class="block">{{.SignInMessage}}</p>
74+ {{ end}}
75+ <button type="submit" class="button block is-primary">Sign in with {{.ProviderName}}</button>
76+ </form>
77+
78+ {{ if .CustomLogin }}
79+ <hr>
80+
81+ <form method="POST" action="{{.ProxyPrefix}}/sign_in" class="block">
82+ <input type="hidden" name="rd" value="{{.Redirect}}">
83+
84+ <div class="field">
85+ <label class="label" for="username">Username</label>
86+ <div class="control">
87+ <input class="input" type="email" placeholder="e.g. userx@example.com" name="username" id="username">
88+ </div>
89+ </div>
90+
91+ <div class="field">
92+ <label class="label" for="password">Password</label>
93+ <div class="control">
94+ <input class="input" type="password" placeholder="********" name="password" id="password">
95+ </div>
96+ </div>
97+ <button class="button is-primary">Sign in</button>
98+ {{ end }}
99+ </form>
100+ </div>
101+ </section>
102+
103+ <footer class="footer has-text-grey has-background-light is-size-7">
104+ <div class="content has-text-centered">
105+ {{ if eq .Footer "-" }}
106+ {{ else if eq .Footer ""}}
107+ <p>Secured with <a href="https://github.com/oauth2-proxy/oauth2-proxy#oauth2_proxy" class="has-text-grey">OAuth2 Proxy</a> version {{.Version}}</p>
108+ {{ else }}
109+ <p>{{.Footer}}</p>
110+ {{ end }}
111+ </div>
161112 </footer>
162−</body>
113+
114+ </body>
163115</html>
164116{{end}}`)
165117 if err != nil {
@@ -170,16 +122,100 @@ func getTemplates() *template.Template {
170122<!DOCTYPE html>
171123<html lang="en" charset="utf-8">
172124<head>
173− <title>{{.Title}}</title>
174− <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
125+ <meta charset="utf-8">
126+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
127+ <title>{{.StatusCode}} {{.Title}}</title>
128+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
129+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css">
130+
131+ <script type="text/javascript">
132+ document.addEventListener('DOMContentLoaded', function() {
133+ let cardToggles = document.getElementsByClassName('card-toggle');
134+ for (let i = 0; i < cardToggles.length; i++) {
135+ cardToggles[i].addEventListener('click', e => {
136+ e.currentTarget.parentElement.parentElement.childNodes[3].classList.toggle('is-hidden');
137+ });
138+ }
139+ });
140+ </script>
141+
142+ <style>
143+ body {
144+ height: 100vh;
145+ }
146+ .error-box {
147+ margin: 1.25rem auto;
148+ max-width: 600px;
149+ }
150+ .status-code {
151+ font-size: 12rem;
152+ font-weight: 600;
153+ }
154+ #more-info.card {
155+ border: 1px solid #f0f0f0;
156+ }
157+ footer a {
158+ text-decoration: underline;
159+ }
160+ </style>
175161</head>
176−<body>
177− <h2>{{.Title}}</h2>
178− <p>{{.Message}}</p>
179− <hr>
180− <p><a href="{{.ProxyPrefix}}/sign_in">Sign In</a></p>
181−</body>
182−</html>{{end}}`)
162+<body class="has-background-light">
163+ <section class="section">
164+ <div class="box block error-box has-text-centered">
165+ <div class="status-code">{{.StatusCode}}</div>
166+ <div class="block">
167+ <h1 class="subtitle is-1">{{.Title}}</h1>
168+ </div>
169+
170+ {{ if .Message }}
171+ <div id="more-info" class="block card is-fullwidth is-shadowless">
172+ <header class="card-header is-shadowless">
173+ <p class="card-header-title">More Info</p>
174+ <a class="card-header-icon card-toggle">
175+ <i class="fa fa-angle-down"></i>
176+ </a>
177+ </header>
178+ <div class="card-content has-text-left is-hidden">
179+ <div class="content">
180+ {{.Message}}
181+ </div>
182+ </div>
183+ </div>
184+ {{ end }}
185+
186+ <hr>
187+
188+ <div class="columns">
189+ <div class="column">
190+ <form method="GET" action="{{.Redirect}}">
191+ <button type="submit" class="button is-danger is-fullwidth">Go back</button>
192+ </form>
193+ </div>
194+ <div class="column">
195+ <form method="GET" action="{{.ProxyPrefix}}/sign_in">
196+ <input type="hidden" name="rd" value="{{.Redirect}}">
197+ <button type="submit" class="button is-primary is-fullwidth">Sign in</button>
198+ </form>
199+ </div>
200+ </div>
201+
202+ </div>
203+ </section>
204+
205+ <footer class="footer has-text-grey has-background-light is-size-7">
206+ <div class="content has-text-centered">
207+ {{ if eq .Footer "-" }}
208+ {{ else if eq .Footer ""}}
209+ <p>Secured with <a href="https://github.com/oauth2-proxy/oauth2-proxy#oauth2_proxy" class="has-text-grey">OAuth2 Proxy</a> version {{.Version}}</p>
210+ {{ else }}
211+ <p>{{.Footer}}</p>
212+ {{ end }}
213+ </div>
214+ </footer>
215+
216+ </body>
217+</html>
218+{{end}}`)
183219 if err != nil {
184220 logger.Fatalf("failed parsing template %s", err)
185221 }