diff --git a/cmd/auth-rest/startcmd/start.go b/cmd/auth-rest/startcmd/start.go index 2dc2eae..bde6300 100644 --- a/cmd/auth-rest/startcmd/start.go +++ b/cmd/auth-rest/startcmd/start.go @@ -79,6 +79,11 @@ const ( " For CouchDB, include the username:password@ text if required." + " Alternatively, this can be set with the following environment variable: " + databaseURLEnvKey + staticFilesPathFlagName = "static-path" + staticFilesPathFlagUsage = "Path to the folder where the static files are to be hosted under " + uiEndpoint + "." + + "Alternatively, this can be set with the following environment variable: " + staticFilesPathEnvKey + staticFilesPathEnvKey = "AUTH_REST_STATIC_FILES" + databasePrefixFlagName = "database-prefix" databasePrefixEnvKey = "AUTH_REST_DATABASE_PREFIX" databasePrefixFlagShorthand = "p" @@ -129,6 +134,7 @@ const ( const ( // api + uiEndpoint = "/ui" healthCheckEndpoint = "/healthcheck" ) @@ -143,6 +149,7 @@ type authRestParameters struct { tlsParams *tlsParams oidcParams *oidcParams bootstrapParams *bootstrapParams + staticFiles string } type tlsParams struct { @@ -311,6 +318,7 @@ func createFlags(startCmd *cobra.Command) { startCmd.Flags().StringP(tlsServeCertPathFlagName, "", "", tlsServeCertPathFlagUsage) startCmd.Flags().StringP(tlsServeKeyPathFlagName, "", "", tlsServeKeyPathFlagUsage) startCmd.Flags().StringP(logLevelFlagName, logLevelFlagShorthand, "", logLevelPrefixFlagUsage) + startCmd.Flags().StringP(staticFilesPathFlagName, "", "", staticFilesPathFlagUsage) startCmd.Flags().StringP(databaseTypeFlagName, databaseTypeFlagShorthand, "", databaseTypeFlagUsage) startCmd.Flags().StringP(databaseURLFlagName, databaseURLFlagShorthand, "", databaseURLFlagUsage) startCmd.Flags().StringP(databasePrefixFlagName, databasePrefixFlagShorthand, "", databasePrefixFlagUsage) @@ -340,7 +348,6 @@ func startAuthService(parameters *authRestParameters, srv server) error { logger.Debugf("root ca's %v", rootCAs) router := mux.NewRouter() - // health check router.HandleFunc(healthCheckEndpoint, healthCheckHandler).Methods(http.MethodGet) @@ -374,6 +381,12 @@ Database type: %s Database URL: %s Database prefix: %s`, parameters.hostURL, parameters.databaseType, parameters.databaseURL, parameters.databasePrefix) + // static frontend + router.PathPrefix(uiEndpoint). + Subrouter(). + Methods(http.MethodGet). + HandlerFunc(uiHandler(parameters.staticFiles, http.ServeFile)) + return srv.ListenAndServeTLS( parameters.hostURL, parameters.tlsParams.serveCertPath, @@ -382,6 +395,19 @@ Database prefix: %s`, parameters.hostURL, parameters.databaseType, parameters.da ) } +func uiHandler( + basePath string, + fileServer func(http.ResponseWriter, *http.Request, string)) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == uiEndpoint { + fileServer(w, r, strings.ReplaceAll(basePath+"/index.html", "//", "/")) + return + } + + fileServer(w, r, strings.ReplaceAll(basePath+"/"+r.URL.Path[len(uiEndpoint):], "//", "/")) + } +} + func getOIDCParams(cmd *cobra.Command) (*oidcParams, error) { params := &oidcParams{} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1f21ebc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,35 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } +} diff --git a/pkg/restapi/operation/operations.go b/pkg/restapi/operation/operations.go index 1aa1f80..92d9112 100644 --- a/pkg/restapi/operation/operations.go +++ b/pkg/restapi/operation/operations.go @@ -12,9 +12,11 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "net/url" - "github.com/coreos/go-oidc" + oidc "github.com/coreos/go-oidc" "github.com/google/uuid" "github.com/trustbloc/edge-core/pkg/log" "github.com/trustbloc/edge-core/pkg/storage" @@ -116,6 +118,7 @@ type Operation struct { oidcClientID string oidcClientSecret string oidcCallbackURL string + uiEndpoint string oauth2ConfigFunc func(...string) oauth2Config bootstrapStore storage.Store bootstrapConfig *BootstrapConfig @@ -129,6 +132,7 @@ type Config struct { OIDCClientID string OIDCClientSecret string OIDCCallbackURL string + UIEndpoint string TransientStoreProvider storage.Provider StoreProvider storage.Provider BootstrapConfig *BootstrapConfig @@ -140,7 +144,7 @@ type BootstrapConfig struct { KeyServerURL string } -// New returns rp operation instance. +// New returns hub-auth operation instance. func New(config *Config) (*Operation, error) { svc := &Operation{ client: &http.Client{Transport: &http.Transport{TLSClientConfig: config.TLSConfig}}, @@ -149,6 +153,7 @@ func New(config *Config) (*Operation, error) { oidcClientSecret: config.OIDCClientSecret, oidcCallbackURL: config.OIDCCallbackURL, bootstrapConfig: config.BootstrapConfig, + uiEndpoint: config.UIEndpoint, } // TODO implement retries: https://github.com/trustbloc/hub-auth/issues/45 @@ -329,7 +334,15 @@ func (c *Operation) handleOIDCCallback(w http.ResponseWriter, r *http.Request) { return } - handleAuthResult(w, r, userProfile) + profileBytes, err := json.Marshal(userProfile) + if err != nil { + c.writeErrorResponse(w, http.StatusInternalServerError, + fmt.Sprintf("failed to marshal user profile data : %s", err)) + + return + } + + c.handleAuthResult(w, r, profileBytes) } // TODO onboard user at key server and SDS: https://github.com/trustbloc/hub-auth/issues/38 @@ -365,9 +378,6 @@ func (c *Operation) handleBootstrapDataRequest(w http.ResponseWriter, r *http.Re handleAuthError(w, http.StatusInternalServerError, fmt.Sprintf("failed to query transient store for handle: %s", err)) - return - } - response, err := json.Marshal(&bootstrapData{ SDSURL: c.bootstrapConfig.SDSURL, SDSPrimaryVaultID: profile.SDSPrimaryVaultID, @@ -388,10 +398,16 @@ func (c *Operation) handleBootstrapDataRequest(w http.ResponseWriter, r *http.Re } } -// TODO redirect to the UI: https://github.com/trustbloc/hub-auth/issues/39 -func handleAuthResult(w http.ResponseWriter, r *http.Request, _ *user.Profile) { - http.Redirect(w, r, "", http.StatusFound) -} +func (c *Operation) handleAuthResult(w http.ResponseWriter, r *http.Request, profileBytes []byte) { + handle := url.QueryEscape(uuid.New().String()) + + err := c.transientStore.Put(handle, profileBytes) + if err != nil { + c.writeErrorResponse(w, + http.StatusInternalServerError, fmt.Sprintf("failed to write handle to transient store : %s", err)) + + return + } func handleAuthError(w http.ResponseWriter, status int, msg string) { // todo #issue-25 handle user data diff --git a/pkg/restapi/operation/operations_test.go b/pkg/restapi/operation/operations_test.go index 5a5d63e..6f417fe 100644 --- a/pkg/restapi/operation/operations_test.go +++ b/pkg/restapi/operation/operations_test.go @@ -411,6 +411,30 @@ func TestHandleOIDCCallback(t *testing.T) { svc.handleOIDCCallback(result, newOIDCCallback(state, "code")) require.Equal(t, http.StatusInternalServerError, result.Code) }) + t.Run("PUT error while storing user info while handling callback user", func(t *testing.T) { + id := uuid.New().String() + state := uuid.New().String() + config := config(t) + + config.TransientStoreProvider = &mockstorage.Provider{ + Stores: map[string]storage.Store{ + transientStoreName: &mockstore.MockStore{ + Store: map[string][]byte{ + id: []byte("{}"), + }, + ErrGet: storage.ErrValueNotFound, + ErrPut: errors.New("generic"), + }, + }, + } + + svc, err := New(config) + require.NoError(t, err) + + result := httptest.NewRecorder() + svc.handleAuthResult(result, newOIDCCallback(state, "code"), nil) + require.Equal(t, http.StatusInternalServerError, result.Code) + }) } func TestHandleBootstrapDataRequest(t *testing.T) {