diff --git a/Makefile b/Makefile
index f7c531e..7abbacd 100644
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,7 @@ unit-test: generate-unit-test-key
auth-rest:
@echo "Building auth-rest"
@mkdir -p ./.build/bin
+ @cp -r ${AUTH_REST_PATH}/static ./.build/bin/
@cd ${AUTH_REST_PATH} && go build -o ../../.build/bin/auth-rest main.go
.PHONY: auth-vue
diff --git a/cmd/auth-rest/startcmd/start.go b/cmd/auth-rest/startcmd/start.go
index 27a9078..56e43e2 100644
--- a/cmd/auth-rest/startcmd/start.go
+++ b/cmd/auth-rest/startcmd/start.go
@@ -519,6 +519,7 @@ func startAuthService(parameters *authRestParameters, srv server) error {
AccessPolicyConfig: gnapAPConfig,
InteractionHandler: interact,
UIEndpoint: uiEndpoint,
+ ClosePopupHTML: parameters.staticFiles + "/gnapRedirect.html",
StartupTimeout: parameters.startupTimeout,
OIDC: &oidcmodel.Config{
CallbackURL: parameters.oidcParams.callbackURL,
@@ -544,7 +545,7 @@ Database prefix: %s`, parameters.hostURL, parameters.databaseType, parameters.da
router.PathPrefix(uiEndpoint).
Subrouter().
Methods(http.MethodGet).
- HandlerFunc(uiHandler(parameters.staticFiles, http.ServeFile))
+ HandlerFunc(uiHandler(parameters.staticFiles+"/auth-vue", http.ServeFile))
return srv.ListenAndServe(
parameters.hostURL,
diff --git a/cmd/auth-rest/static/gnapRedirect.html b/cmd/auth-rest/static/gnapRedirect.html
new file mode 100644
index 0000000..2d88efa
--- /dev/null
+++ b/cmd/auth-rest/static/gnapRedirect.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+
+
diff --git a/images/auth-rest/Dockerfile b/images/auth-rest/Dockerfile
index ff7ffe8..f2992f0 100644
--- a/images/auth-rest/Dockerfile
+++ b/images/auth-rest/Dockerfile
@@ -27,8 +27,9 @@ RUN make auth-rest
FROM alpine:${ALPINE_VER} as base
COPY --from=auth /go/src/github.com/trustbloc/auth/.build/bin/auth-rest /usr/local/bin
+COPY --from=auth /go/src/github.com/trustbloc/auth/.build/bin/static /usr/local/static
COPY ./.build/bin/auth-vue /usr/local/static/auth-vue
-ENV AUTH_REST_STATIC_FILES=/usr/local/static/auth-vue
+ENV AUTH_REST_STATIC_FILES=/usr/local/static
# set up nsswitch.conf for Go's "netgo" implementation
# - https://github.com/golang/go/blob/go1.9.1/src/net/conf.go#L194-L275
diff --git a/pkg/restapi/gnap/operations.go b/pkg/restapi/gnap/operations.go
index 2af223a..7479adf 100644
--- a/pkg/restapi/gnap/operations.go
+++ b/pkg/restapi/gnap/operations.go
@@ -12,6 +12,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
+ "html/template"
"io/ioutil"
"net/http"
"net/url"
@@ -76,6 +77,7 @@ type Operation struct {
authHandler *authhandler.AuthHandler
interactionHandler api.InteractionHandler
uiEndpoint string
+ closePopupHTML string
authProviders []authProvider
oidcProvidersConfig map[string]*oidcmodel.ProviderConfig
cachedOIDCProviders map[string]oidcProvider
@@ -91,6 +93,7 @@ type Config struct {
StoreProvider storage.Provider
AccessPolicyConfig *accesspolicy.Config
BaseURL string
+ ClosePopupHTML string
InteractionHandler api.InteractionHandler
UIEndpoint string
OIDC *oidcmodel.Config
@@ -139,6 +142,7 @@ func New(config *Config) (*Operation, error) {
transientStore: transientStore,
tlsConfig: config.TLSConfig,
interactionHandler: config.InteractionHandler,
+ closePopupHTML: config.ClosePopupHTML,
}, nil
}
@@ -397,8 +401,7 @@ func (o *Operation) oidcCallbackHandler(w http.ResponseWriter, r *http.Request)
return
}
- // TODO: redirect to auth frontend that saves the redirect URI (with interactRef
- // and responseHash) in vueX then closes the popup
+ // TODO: validate clientURI for security
q := clientURI.Query()
@@ -407,9 +410,20 @@ func (o *Operation) oidcCallbackHandler(w http.ResponseWriter, r *http.Request)
clientURI.RawQuery = q.Encode()
- // redirect to consumer url
- http.Redirect(w, r, clientURI.String(), http.StatusFound)
- logger.Debugf("redirected to: %s", clientURI.String())
+ redirect := clientURI.String()
+
+ t, err := template.ParseFiles(o.closePopupHTML)
+ if err != nil {
+ o.writeErrorResponse(w, http.StatusInternalServerError, "failed to parse template : %s", err.Error())
+
+ return
+ }
+
+ if err := t.Execute(w, map[string]interface{}{
+ "RedirectURI": redirect,
+ }); err != nil {
+ logger.Errorf(fmt.Sprintf("failed execute html template: %s", err.Error()))
+ }
}
func (o *Operation) authContinueHandler(w http.ResponseWriter, req *http.Request) {
diff --git a/pkg/restapi/gnap/operations_test.go b/pkg/restapi/gnap/operations_test.go
index 1d7962d..fcfa4c7 100644
--- a/pkg/restapi/gnap/operations_test.go
+++ b/pkg/restapi/gnap/operations_test.go
@@ -18,6 +18,9 @@ import (
"net/http"
"net/http/httptest"
"net/url"
+ "os"
+ "regexp"
+ "strings"
"testing"
"github.com/google/uuid"
@@ -442,6 +445,11 @@ func TestOIDCCallbackHandler(t *testing.T) {
code := uuid.New().String()
config := config(t)
+ templatePath, deleteTmp := tmpStaticHTML(t)
+ defer deleteTmp()
+
+ config.ClosePopupHTML = templatePath
+
o, err := New(config)
require.NoError(t, err)
@@ -503,7 +511,7 @@ func TestOIDCCallbackHandler(t *testing.T) {
result := httptest.NewRecorder()
o.oidcCallbackHandler(result, newOIDCCallback(state, code))
- require.Equal(t, http.StatusFound, result.Code)
+ require.Equal(t, http.StatusOK, result.Code)
// TODO validate redirect url
})
@@ -844,7 +852,14 @@ func TestOIDCCallbackHandler(t *testing.T) {
}
func Test_Full_Flow(t *testing.T) {
- o, err := New(config(t))
+ conf := config(t)
+
+ templatePath, deleteTmp := tmpStaticHTML(t)
+ defer deleteTmp()
+
+ conf.ClosePopupHTML = templatePath
+
+ o, err := New(conf)
require.NoError(t, err)
authResp := &gnap.AuthResponse{}
@@ -950,11 +965,23 @@ func Test_Full_Flow(t *testing.T) {
o.oidcCallbackHandler(rw, newOIDCCallback(state, code))
- require.Equal(t, http.StatusFound, rw.Code)
+ require.Equal(t, http.StatusOK, rw.Code)
- redirectURL, err := url.Parse(rw.Header().Get("location"))
+ body := rw.Body.Bytes()
+
+ rx := regexp.MustCompile("window.opener.location.href = '(.*)';")
+ res := rx.FindStringSubmatch(string(body))
+
+ u := res[1]
+
+ u = strings.ReplaceAll(u, "\\u0026", "\u0026")
+ u = strings.ReplaceAll(u, "\\/", "/")
+
+ redirectURL, err := url.Parse(u)
require.NoError(t, err)
+ require.Contains(t, redirectURL.String(), "interact_ref")
+
interactRef = redirectURL.Query().Get("interact_ref")
require.NotEqual(t, "", interactRef)
}
@@ -1188,6 +1215,30 @@ func clientKey(t *testing.T) (*jwk.JWK, *gnap.ClientKey) {
return &privJWK, &ck
}
+func tmpStaticHTML(t *testing.T) (string, func()) {
+ t.Helper()
+
+ f, err := os.CreateTemp("", "tmpfile-*.html")
+ require.NoError(t, err)
+
+ defer func() {
+ e := f.Close()
+ if e != nil {
+ fmt.Printf("failed to close tmpfile: %s", e.Error())
+ }
+ }()
+
+ _, err = f.Write([]byte(staticHTML))
+ require.NoError(t, err)
+
+ return f.Name(), func() {
+ e := os.Remove(f.Name())
+ if e != nil {
+ fmt.Printf("failed to delete tmpfile: %s", e.Error())
+ }
+ }
+}
+
const (
accessPolicyConf = `{
"access-types": [{
@@ -1211,4 +1262,20 @@ const (
}
]
}`
+ staticHTML = `
+
+
+
+Redirecting...
+
+
+
+
+
+
+
+`
)
diff --git a/test/bdd/pkg/gnap/steps.go b/test/bdd/pkg/gnap/steps.go
index c815ef4..55d6d20 100644
--- a/test/bdd/pkg/gnap/steps.go
+++ b/test/bdd/pkg/gnap/steps.go
@@ -11,9 +11,11 @@ import (
"crypto/elliptic"
"crypto/rand"
"fmt"
+ "io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
+ "regexp"
"strings"
"github.com/cucumber/godog"
@@ -297,6 +299,18 @@ func (s *Steps) interactRedirect() error {
clientRedirect := result.Header.Get("Location")
+ body, err := ioutil.ReadAll(result.Body)
+ if err != nil {
+ return fmt.Errorf("failed to read result body: %w", err)
+ }
+
+ rx := regexp.MustCompile("window.opener.location.href = '(.*)';")
+ res := rx.FindStringSubmatch(string(body))
+
+ clientRedirect = res[1]
+ clientRedirect = strings.Replace(clientRedirect, "\\u0026", "\u0026", -1)
+ clientRedirect = strings.Replace(clientRedirect, "\\/", "/", -1)
+
// TODO validate the client finishURL
if !strings.HasPrefix(clientRedirect, mockClientFinishURI) {
return fmt.Errorf(