diff --git a/go.mod b/go.mod
index 2b1522e8..3a224243 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module go.pinniped.dev
-go 1.14
+go 1.16
require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
@@ -26,6 +26,7 @@ require (
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
+ github.com/tdewolff/minify/v2 v2.9.18
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
diff --git a/go.sum b/go.sum
index a57507b9..eb6dfbd2 100644
--- a/go.sum
+++ b/go.sum
@@ -118,6 +118,7 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -840,6 +841,7 @@ github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc=
github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -1150,6 +1152,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tdewolff/minify/v2 v2.9.18 h1:j5Is0sOGp4cxm0o3HgvHCWCvTtmKnfB0qv0FCRbmgZY=
+github.com/tdewolff/minify/v2 v2.9.18/go.mod h1:0y0mXZnisZm8HcgQvAV0btxa1IgecGam90zMuHqEZuc=
+github.com/tdewolff/parse/v2 v2.5.18 h1:d67Ql/Pe36JcJZ7J2MY8upx6iTxbxGS9lzwyFGtMmd0=
+github.com/tdewolff/parse/v2 v2.5.18/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
+github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
+github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.7.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
diff --git a/internal/oidc/callback/callback_handler.go b/internal/oidc/callback/callback_handler.go
index d585c962..8b9ab93e 100644
--- a/internal/oidc/callback/callback_handler.go
+++ b/internal/oidc/callback/callback_handler.go
@@ -18,6 +18,7 @@ import (
"go.pinniped.dev/internal/oidc/csrftoken"
"go.pinniped.dev/internal/oidc/downstreamsession"
"go.pinniped.dev/internal/oidc/provider"
+ "go.pinniped.dev/internal/oidc/provider/formposthtml"
"go.pinniped.dev/internal/plog"
)
@@ -35,7 +36,7 @@ func NewHandler(
stateDecoder, cookieDecoder oidc.Decoder,
redirectURI string,
) http.Handler {
- return securityheader.Wrap(httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
+ handler := httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
state, err := validateRequest(r, stateDecoder, cookieDecoder)
if err != nil {
return err
@@ -97,7 +98,8 @@ func NewHandler(
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
return nil
- }))
+ })
+ return securityheader.WrapWithCustomCSP(handler, formposthtml.ContentSecurityPolicy())
}
func authcode(r *http.Request) string {
diff --git a/internal/oidc/callback/callback_handler_test.go b/internal/oidc/callback/callback_handler_test.go
index 4d749355..23912944 100644
--- a/internal/oidc/callback/callback_handler_test.go
+++ b/internal/oidc/callback/callback_handler_test.go
@@ -149,7 +149,7 @@ func TestCallbackEndpoint(t *testing.T) {
csrfCookie: happyCSRFCookie,
wantStatus: http.StatusOK,
wantContentType: "text/html;charset=UTF-8",
- wantBodyFormResponseRegexp: ``,
+ wantBodyFormResponseRegexp: `(.+)
`,
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
wantDownstreamIDTokenUsername: upstreamUsername,
wantDownstreamIDTokenGroups: upstreamGroupMembership,
diff --git a/internal/oidc/oidc.go b/internal/oidc/oidc.go
index d92a0f1b..d29979c8 100644
--- a/internal/oidc/oidc.go
+++ b/internal/oidc/oidc.go
@@ -14,6 +14,7 @@ import (
"go.pinniped.dev/internal/oidc/csrftoken"
"go.pinniped.dev/internal/oidc/jwks"
"go.pinniped.dev/internal/oidc/provider"
+ "go.pinniped.dev/internal/oidc/provider/formposthtml"
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/pkce"
)
@@ -217,7 +218,7 @@ func FositeOauth2Helper(
MinParameterEntropy: fosite.MinParameterEntropy,
}
- return compose.Compose(
+ provider := compose.Compose(
oauthConfig,
oauthStore,
&compose.CommonStrategy{
@@ -233,6 +234,8 @@ func FositeOauth2Helper(
compose.OAuth2PKCEFactory,
TokenExchangeFactory,
)
+ provider.(*fosite.Fosite).FormPostHTMLTemplate = formposthtml.Template()
+ return provider
}
// FositeErrorForLog generates a list of information about the provided Fosite error that can be
diff --git a/internal/oidc/provider/formposthtml/form_post.css b/internal/oidc/provider/formposthtml/form_post.css
new file mode 100644
index 00000000..c65c2fc7
--- /dev/null
+++ b/internal/oidc/provider/formposthtml/form_post.css
@@ -0,0 +1,87 @@
+/* Copyright 2021 the Pinniped contributors. All Rights Reserved. */
+/* SPDX-License-Identifier: Apache-2.0 */
+
+body {
+ font-family: "Metropolis-Light", Helvetica, sans-serif;
+}
+
+h1 {
+ font-size: 20px;
+}
+
+.state {
+ position: absolute;
+ top: 100px;
+ left: 50%;
+ width: 400px;
+ height: 80px;
+ margin-top: -40px;
+ margin-left: -200px;
+ font-size: 14px;
+ line-height: 24px;
+}
+
+button {
+ margin: -10px;
+ padding: 10px;
+ text-align: left;
+ width: 100%;
+ display: inline;
+ border: none;
+ background: none;
+ cursor: pointer;
+ transition: all .1s;
+}
+
+button:hover {
+ background-color: #eee;
+ transform: scale(1.01);
+}
+
+button:active {
+ background-color: #ddd;
+ transform: scale(.99);
+}
+
+code {
+ word-wrap: break-word;
+ hyphens: auto;
+ hyphenate-character: '';
+ font-size: 12px;
+ font-family: monospace;
+ color: #333;
+}
+
+.copy-icon {
+ float: left;
+ width: 36px;
+ height: 36px;
+ padding-top: 2px;
+ padding-right: 10px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ /*
+ This is the "copy-to-clipboard-line.svg" icon from Clarity (https://clarity.design/):
+ https://github.com/vmware/clarity-assets/blob/master/icons/essential/copy-to-clipboard-line.svg
+ */
+ background-image: url("data:image/svg+xml,%3Csvg version='1.1' width='36' height='36' viewBox='0 0 36 36' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Ecopy-to-clipboard-line%3C/title%3E%3Cpath d='M22.6,4H21.55a3.89,3.89,0,0,0-7.31,0H13.4A2.41,2.41,0,0,0,11,6.4V10H25V6.4A2.41,2.41,0,0,0,22.6,4ZM23,8H13V6.25A.25.25,0,0,1,13.25,6h2.69l.12-1.11A1.24,1.24,0,0,1,16.61,4a2,2,0,0,1,3.15,1.18l.09.84h2.9a.25.25,0,0,1,.25.25Z' class='clr-i-outline clr-i-outline-path-1'%3E%3C/path%3E%3Cpath d='M33.25,18.06H21.33l2.84-2.83a1,1,0,1,0-1.42-1.42L17.5,19.06l5.25,5.25a1,1,0,0,0,.71.29,1,1,0,0,0,.71-1.7l-2.84-2.84H33.25a1,1,0,0,0,0-2Z' class='clr-i-outline clr-i-outline-path-2'%3E%3C/path%3E%3Cpath d='M29,16h2V6.68A1.66,1.66,0,0,0,29.35,5H27.08V7H29Z' class='clr-i-outline clr-i-outline-path-3'%3E%3C/path%3E%3Cpath d='M29,31H7V7H9V5H6.64A1.66,1.66,0,0,0,5,6.67V31.32A1.66,1.66,0,0,0,6.65,33H29.36A1.66,1.66,0,0,0,31,31.33V22.06H29Z' class='clr-i-outline clr-i-outline-path-4'%3E%3C/path%3E%3Crect x='0' y='0' width='36' height='36' fill-opacity='0'/%3E%3C/svg%3E");
+}
+
+@keyframes loader {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+#loading {
+ content: '';
+ box-sizing: border-box;
+ width: 80px;
+ height: 80px;
+ margin-top: -40px;
+ margin-left: -40px;
+ border-radius: 50%;
+ border: 2px solid #fff;
+ border-top-color: #1b3951;
+ animation: loader .6s linear infinite;
+}
diff --git a/internal/oidc/provider/formposthtml/form_post.gohtml b/internal/oidc/provider/formposthtml/form_post.gohtml
new file mode 100644
index 00000000..92be18d2
--- /dev/null
+++ b/internal/oidc/provider/formposthtml/form_post.gohtml
@@ -0,0 +1,34 @@
+
+
+
You have successfully logged in. You may now close this tab.
+To finish logging in, paste this authorization code into your command-line session:
+ +You have successfully logged in. You may now close this tab.
+To finish logging in, paste this authorization code into your command-line session:
+ +