Skip to content
This repository has been archived by the owner on Apr 11, 2023. It is now read-only.

Commit

Permalink
feat: GNAP flow closes popup window before wallet redirect
Browse files Browse the repository at this point in the history
Signed-off-by: Filip Burlacu <[email protected]>
  • Loading branch information
Filip Burlacu committed Jun 29, 2022
1 parent 1d3d7e6 commit 6a40146
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 11 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion cmd/auth-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions cmd/auth-rest/static/gnapRedirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redirecting...</title>
</head>

<script>
window.opener.location.href = '{{.RedirectURI}}';
window.top.close();
</script>

<body>

</body>
</html>
3 changes: 2 additions & 1 deletion images/auth-rest/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 19 additions & 5 deletions pkg/restapi/gnap/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"net/url"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -139,6 +142,7 @@ func New(config *Config) (*Operation, error) {
transientStore: transientStore,
tlsConfig: config.TLSConfig,
interactionHandler: config.InteractionHandler,
closePopupHTML: config.ClosePopupHTML,
}, nil
}

Expand Down Expand Up @@ -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()

Expand All @@ -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) {
Expand Down
75 changes: 71 additions & 4 deletions pkg/restapi/gnap/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"regexp"
"strings"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
})

Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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": [{
Expand All @@ -1211,4 +1262,20 @@ const (
}
]
}`
staticHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redirecting...</title>
</head>
<script>
window.opener.location.href = '{{.RedirectURI}}';
window.top.close();
</script>
<body>
</body>
</html>`
)
14 changes: 14 additions & 0 deletions test/bdd/pkg/gnap/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 6a40146

Please sign in to comment.