From baf1a38215b0b8c31507126e858d95a890823132 Mon Sep 17 00:00:00 2001 From: Paul Greenberg Date: Sun, 24 Mar 2024 14:15:54 -0400 Subject: [PATCH] remove settings endpoint --- pkg/authn/handle_http_barcode.go | 72 +++++ pkg/authn/handle_http_portal.go | 27 +- pkg/authn/handle_http_sandbox.go | 2 +- pkg/authn/handle_http_settings.go | 208 ------------ pkg/authn/handle_http_settings_apikeys.go | 96 ------ pkg/authn/handle_http_settings_general.go | 43 --- pkg/authn/handle_http_settings_gpgkeys.go | 132 -------- pkg/authn/handle_http_settings_mfa.go | 349 --------------------- pkg/authn/handle_http_settings_password.go | 55 ---- pkg/authn/handle_http_settings_sshkeys.go | 135 -------- pkg/authn/respond_http.go | 13 +- 11 files changed, 105 insertions(+), 1027 deletions(-) create mode 100644 pkg/authn/handle_http_barcode.go delete mode 100644 pkg/authn/handle_http_settings.go delete mode 100644 pkg/authn/handle_http_settings_apikeys.go delete mode 100644 pkg/authn/handle_http_settings_general.go delete mode 100644 pkg/authn/handle_http_settings_gpgkeys.go delete mode 100644 pkg/authn/handle_http_settings_mfa.go delete mode 100644 pkg/authn/handle_http_settings_password.go delete mode 100644 pkg/authn/handle_http_settings_sshkeys.go diff --git a/pkg/authn/handle_http_barcode.go b/pkg/authn/handle_http_barcode.go new file mode 100644 index 0000000..8ac1694 --- /dev/null +++ b/pkg/authn/handle_http_barcode.go @@ -0,0 +1,72 @@ +// Copyright 2022 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authn + +import ( + "context" + "encoding/base64" + "net/http" + "strings" + + "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/user" + "github.com/skip2/go-qrcode" +) + +func (p *Portal) handleHTTPProfileMfaBarcode(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error { + p.disableClientCache(w) + p.injectRedirectURL(ctx, w, r, rr) + if parsedUser == nil { + if rr.Response.RedirectURL == "" { + return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI) + } + return p.handleHTTPRedirect(ctx, w, r, rr, "/login") + } + + endpoint, err := getEndpoint(r.URL.Path, "/barcode") + if err != nil { + return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) + } + + qrCodeEncoded := strings.TrimPrefix(endpoint, "/mfa/") + qrCodeEncoded = strings.TrimSuffix(qrCodeEncoded, ".png") + codeURI, err := base64.StdEncoding.DecodeString(qrCodeEncoded) + if err != nil { + return p.handleHTTPRenderPlainText(ctx, w, http.StatusBadRequest) + } + png, err := qrcode.Encode(string(codeURI), qrcode.Medium, 256) + if err != nil { + return p.handleHTTPRenderPlainText(ctx, w, http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "image/png") + w.Write(png) + return nil +} + +func (p *Portal) handleHTTPSandboxMfaBarcode(ctx context.Context, w http.ResponseWriter, _ *http.Request, endpoint string) error { + qrCodeEncoded := strings.TrimPrefix(endpoint, "/mfa/barcode/") + qrCodeEncoded = strings.TrimSuffix(qrCodeEncoded, ".png") + codeURI, err := base64.StdEncoding.DecodeString(qrCodeEncoded) + if err != nil { + return p.handleHTTPRenderPlainText(ctx, w, http.StatusBadRequest) + } + png, err := qrcode.Encode(string(codeURI), qrcode.Medium, 256) + if err != nil { + return p.handleHTTPRenderPlainText(ctx, w, http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "image/png") + w.Write(png) + return nil +} diff --git a/pkg/authn/handle_http_portal.go b/pkg/authn/handle_http_portal.go index 4b88947..4784d1f 100644 --- a/pkg/authn/handle_http_portal.go +++ b/pkg/authn/handle_http_portal.go @@ -16,14 +16,37 @@ package authn import ( "context" + "fmt" + "net/http" + "net/url" + "strings" + "github.com/greenpau/go-authcrunch/pkg/requests" "github.com/greenpau/go-authcrunch/pkg/user" addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" "go.uber.org/zap" - "net/http" - "net/url" ) +func getEndpoint(p, s string) (string, error) { + i := strings.Index(p, s) + if i < 0 { + return s, fmt.Errorf("%s is not in %s", p, s) + } + return strings.TrimPrefix(p[i:], s), nil +} + +func getEndpointKeyID(p, s string) (string, error) { + sp, err := getEndpoint(p, s) + if err != nil { + return "", err + } + arr := strings.Split(sp, "/") + if len(arr) != 1 { + return "", fmt.Errorf("invalid key id") + } + return arr[0], nil +} + func (p *Portal) handleHTTPPortal(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error { p.disableClientCache(w) p.injectRedirectURL(ctx, w, r, rr) diff --git a/pkg/authn/handle_http_sandbox.go b/pkg/authn/handle_http_sandbox.go index 8c886a4..1929e54 100644 --- a/pkg/authn/handle_http_sandbox.go +++ b/pkg/authn/handle_http_sandbox.go @@ -114,7 +114,7 @@ func (p *Portal) handleHTTPSandbox(ctx context.Context, w http.ResponseWriter, r case strings.HasPrefix(sandboxPartition, "mfa-app-barcode/"): // Handle App Portal barcode. sandboxPartition = strings.TrimPrefix(sandboxPartition, "mfa-app-barcode/") - return p.handleHTTPMfaBarcode(ctx, w, r, sandboxPartition) + return p.handleHTTPSandboxMfaBarcode(ctx, w, r, sandboxPartition) case sandboxPartition == "terminate": p.sandboxes.Delete(sandboxID) return p.handleHTTPRedirectSeeOther(ctx, w, r, rr, "login") diff --git a/pkg/authn/handle_http_settings.go b/pkg/authn/handle_http_settings.go deleted file mode 100644 index 018adf7..0000000 --- a/pkg/authn/handle_http_settings.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "fmt" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" - addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" - "go.uber.org/zap" - "net/http" - "strings" -) - -func (p *Portal) handleHTTPSettings(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error { - p.disableClientCache(w) - p.injectRedirectURL(ctx, w, r, rr) - if parsedUser == nil { - if rr.Response.RedirectURL == "" { - return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI) - } - return p.handleHTTPRedirect(ctx, w, r, rr, "/login") - } - - usr, err := p.sessions.Get(parsedUser.Claims.ID) - if err != nil { - p.logger.Warn( - "jti session not found", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.String("jti", parsedUser.Claims.ID), - zap.Any("error", err), - zap.String("source_address", addrutil.GetSourceAddress(r)), - ) - return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr) - } - - if permitted := usr.HasRole("authp/admin", "authp/user"); !permitted { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - - switch usr.Authenticator.Method { - case "local": - default: - return p.handleHTTPGeneric(ctx, w, r, rr, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable)) - } - - endpoint, err := getEndpoint(r.URL.Path, "/settings") - if err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - - backend := p.getIdentityStoreByRealm(usr.Authenticator.Realm) - if backend == nil { - p.logger.Warn( - "backend not found", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.String("realm", usr.Authenticator.Realm), - zap.String("jti", parsedUser.Claims.ID), - zap.String("source_address", addrutil.GetSourceAddress(r)), - ) - return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr) - } - - p.logger.Debug( - "Rendering settings page", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.String("realm", usr.Authenticator.Realm), - zap.String("jti", parsedUser.Claims.ID), - zap.String("source_address", addrutil.GetSourceAddress(r)), - zap.String("endpoint", endpoint), - ) - - resp := p.ui.GetArgs() - resp.PageTitle = "Profile" - resp.BaseURL(rr.Upstream.BasePath) - - // Populate username (sub) and email address (email) - rr.User.Username = usr.Claims.Subject - rr.User.Email = usr.Claims.Email - - switch { - case strings.HasPrefix(endpoint, "/password"): - resp.PageTitle = "Password Management" - resp.NavItems = p.config.UI.GetNavigationItems("settings/password") - if p.config.UI.IsDisabledPage("settings/password") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - if err := p.handleHTTPPasswordSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - case strings.HasPrefix(endpoint, "/apikeys"): - resp.PageTitle = "API Key Management" - resp.NavItems = p.config.UI.GetNavigationItems("settings/apikeys") - if p.config.UI.IsDisabledPage("settings/apikeys") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - if err := p.handleHTTPAPIKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - case strings.HasPrefix(endpoint, "/sshkeys"): - resp.PageTitle = "SSH Key Management" - resp.NavItems = p.config.UI.GetNavigationItems("settings/sshkeys") - if p.config.UI.IsDisabledPage("settings/sshkeys") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - if err := p.handleHTTPSSHKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - case strings.HasPrefix(endpoint, "/gpgkeys"): - resp.PageTitle = "GPG Key Management" - resp.NavItems = p.config.UI.GetNavigationItems("settings/gpgkeys") - if p.config.UI.IsDisabledPage("settings/gpgkeys") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - if err := p.handleHTTPGPGKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - case strings.HasPrefix(endpoint, "/mfa/barcode/"): - return p.handleHTTPMfaBarcode(ctx, w, r, endpoint) - case strings.HasPrefix(endpoint, "/mfa"): - resp.PageTitle = "Multi-Factor Authentication" - resp.NavItems = p.config.UI.GetNavigationItems("settings/mfa") - if p.config.UI.IsDisabledPage("settings/mfa") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - if err := p.handleHTTPMfaSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - case strings.HasPrefix(endpoint, "/connected"): - resp.PageTitle = "Connected Accounts" - resp.NavItems = p.config.UI.GetNavigationItems("settings/connected") - if p.config.UI.IsDisabledPage("settings/connected") { - return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) - } - resp.Data["view"] = "connected" - default: - resp.NavItems = p.config.UI.GetNavigationItems("settings/") - if err := p.handleHTTPGeneralSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { - return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) - } - } - content, err := p.ui.Render("settings", resp) - if err != nil { - return p.handleHTTPRenderError(ctx, w, r, rr, err) - } - return p.handleHTTPRenderHTML(ctx, w, http.StatusOK, content.Bytes()) -} - -func getEndpoint(p, s string) (string, error) { - i := strings.Index(p, s) - if i < 0 { - return s, fmt.Errorf("%s is not in %s", p, s) - } - return strings.TrimPrefix(p[i:], s), nil -} - -func getEndpointKeyID(p, s string) (string, error) { - sp, err := getEndpoint(p, s) - if err != nil { - return "", err - } - arr := strings.Split(sp, "/") - if len(arr) != 1 { - return "", fmt.Errorf("invalid key id") - } - return arr[0], nil -} - -func attachView(data map[string]interface{}, entrypoint, action string, status bool) { - if action == "" { - data["view"] = entrypoint - return - } - if status { - data["view"] = fmt.Sprintf("%s-%s-status", entrypoint, action) - return - } - data["view"] = fmt.Sprintf("%s-%s", entrypoint, action) -} - -func attachStatus(data map[string]interface{}, status, statusText string) { - data["status"] = status - data["status_reason"] = statusText -} - -func attachSuccessStatus(data map[string]interface{}, statusText string) { - attachStatus(data, "SUCCESS", statusText) -} - -func attachFailStatus(data map[string]interface{}, statusText string) { - attachStatus(data, "FAIL", statusText) -} diff --git a/pkg/authn/handle_http_settings_apikeys.go b/pkg/authn/handle_http_settings_apikeys.go deleted file mode 100644 index 2f630aa..0000000 --- a/pkg/authn/handle_http_settings_apikeys.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/identity" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" -) - -func (p *Portal) handleHTTPAPIKeysSettings( - ctx context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - var endpoint string - var err error - var action string - var status bool - entrypoint := "apikeys" - data["view"] = entrypoint - endpoint, err = getEndpoint(r.URL.Path, "/"+entrypoint) - if err != nil { - if v, exists := data["endpoint"]; exists { - endpoint = v.(string) - delete(data, "endpoint") - } else { - return err - } - } - switch { - case strings.HasPrefix(endpoint, "/add") && r.Method == "POST": - action = "add" - status = true - if err := validateAPIKeyInputForm(r, rr); err != nil { - attachFailStatus(data, "Bad Request") - break - } - rr.Key.Usage = "api" - if err = store.Request(operator.AddAPIKey, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - data["api_key"] = rr.Response.Payload.(string) - attachSuccessStatus(data, "New API key has been added") - case strings.HasPrefix(endpoint, "/add"): - action = "add" - case strings.HasPrefix(endpoint, "/delete"): - action = "delete" - status = true - keyID, err := getEndpointKeyID(endpoint, "/delete/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.ID = keyID - if err = store.Request(operator.DeleteAPIKey, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed deleting key id %s: %v", keyID, err)) - break - } - attachSuccessStatus(data, fmt.Sprintf("key id %s deleted successfully", keyID)) - default: - // List API Keys. - rr.Key.Usage = "api" - if err = store.Request(operator.GetAPIKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - bundle := rr.Response.Payload.(*identity.APIKeyBundle) - pubKeys := bundle.Get() - if len(pubKeys) > 0 { - data[entrypoint] = pubKeys - } - - } - attachView(data, entrypoint, action, status) - return nil -} diff --git a/pkg/authn/handle_http_settings_general.go b/pkg/authn/handle_http_settings_general.go deleted file mode 100644 index 00026cb..0000000 --- a/pkg/authn/handle_http_settings_general.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "fmt" - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/identity" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" - "net/http" -) - -func (p *Portal) handleHTTPGeneralSettings( - ctx context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - data["view"] = "general" - err := store.Request(operator.GetUser, rr) - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - return nil - } - user := rr.Response.Payload.(*identity.User) - data["metadata"] = user.GetMetadata() - - attachSuccessStatus(data, "User identity has been discovered") - return nil -} diff --git a/pkg/authn/handle_http_settings_gpgkeys.go b/pkg/authn/handle_http_settings_gpgkeys.go deleted file mode 100644 index 95c3554..0000000 --- a/pkg/authn/handle_http_settings_gpgkeys.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/identity" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" -) - -func (p *Portal) handleHTTPGPGKeysSettings( - ctx context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - var action string - var status bool - entrypoint := "gpgkeys" - data["view"] = entrypoint - var endpoint string - var err error - endpoint, err = getEndpoint(r.URL.Path, "/"+entrypoint) - if err != nil { - if v, exists := data["endpoint"]; exists { - endpoint = v.(string) - delete(data, "endpoint") - } else { - return err - } - } - switch { - case strings.HasPrefix(endpoint, "/add") && r.Method == "POST": - // Add GPG key. - action = "add" - status = true - if err := validateKeyInputForm(r, rr); err != nil { - attachFailStatus(data, "Bad Request") - break - } - rr.Key.Usage = "gpg" - if err = store.Request(operator.AddKeyGPG, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - attachSuccessStatus(data, "Public GPG key has been added") - case strings.HasPrefix(endpoint, "/add"): - action = "add" - case strings.HasPrefix(endpoint, "/delete"): - // Delete a particular GPG key. - action = "delete" - status = true - keyID, err := getEndpointKeyID(endpoint, "/delete/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.ID = keyID - if err = store.Request(operator.DeletePublicKey, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed deleting key id %s: %v", keyID, err)) - break - } - attachSuccessStatus(data, fmt.Sprintf("key id %s deleted successfully", keyID)) - case strings.HasPrefix(endpoint, "/view"): - // Get a particular GPG key. - action = "view" - keyID, err := getEndpointKeyID(endpoint, "/view/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.Usage = "gpg" - if err = store.Request(operator.GetPublicKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed fetching key id %s: %v", keyID, err)) - break - } - bundle := rr.Response.Payload.(*identity.PublicKeyBundle) - for _, k := range bundle.Get() { - if k.ID != keyID { - continue - } - var keyMap map[string]interface{} - keyBytes, _ := json.Marshal(k) - json.Unmarshal(keyBytes, &keyMap) - for _, w := range []string{"payload"} { - if _, exists := keyMap[w]; !exists { - continue - } - delete(keyMap, w) - } - prettyKey, _ := json.MarshalIndent(keyMap, "", " ") - attachSuccessStatus(data, "OK") - data["key"] = string(prettyKey) - if k.Payload != "" { - data["pem_key"] = k.Payload - } - break - } - default: - // List GPG Keys. - rr.Key.Usage = "gpg" - if err = store.Request(operator.GetPublicKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - bundle := rr.Response.Payload.(*identity.PublicKeyBundle) - pubKeys := bundle.Get() - if len(pubKeys) > 0 { - data[entrypoint] = pubKeys - } - } - attachView(data, entrypoint, action, status) - return nil -} diff --git a/pkg/authn/handle_http_settings_mfa.go b/pkg/authn/handle_http_settings_mfa.go deleted file mode 100644 index 916280c..0000000 --- a/pkg/authn/handle_http_settings_mfa.go +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "encoding/base64" - "fmt" - "net/http" - "strings" - - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/identity" - "github.com/greenpau/go-authcrunch/pkg/identity/qr" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" - "github.com/greenpau/go-authcrunch/pkg/util" - "github.com/skip2/go-qrcode" - "go.uber.org/zap" -) - -func (p *Portal) handleHTTPMfaBarcode(ctx context.Context, w http.ResponseWriter, _ *http.Request, endpoint string) error { - qrCodeEncoded := strings.TrimPrefix(endpoint, "/mfa/barcode/") - qrCodeEncoded = strings.TrimSuffix(qrCodeEncoded, ".png") - codeURI, err := base64.StdEncoding.DecodeString(qrCodeEncoded) - if err != nil { - return p.handleHTTPRenderPlainText(ctx, w, http.StatusBadRequest) - } - png, err := qrcode.Encode(string(codeURI), qrcode.Medium, 256) - if err != nil { - return p.handleHTTPRenderPlainText(ctx, w, http.StatusInternalServerError) - } - w.Header().Set("Content-Type", "image/png") - w.Write(png) - return nil -} - -func (p *Portal) handleHTTPMfaSettings( - _ context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - var action string - var status bool - entrypoint := "mfa" - data["view"] = entrypoint - var endpoint string - var err error - endpoint, err = getEndpoint(r.URL.Path, "/"+entrypoint) - if err != nil { - if v, exists := data["endpoint"]; exists { - endpoint = v.(string) - delete(data, "endpoint") - } else { - return err - } - } - - switch { - case strings.HasPrefix(endpoint, "/add/u2f") && r.Method == "POST": - // Add U2F token. - action = "add-u2f" - status = true - if err := validateAddU2FTokenForm(r, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("Bad Request: %s", err)) - break - } - if err = store.Request(operator.AddMfaToken, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - attachSuccessStatus(data, "U2F token has been added") - case strings.HasPrefix(endpoint, "/add/u2f"): - // Add U2F token. - action = "add-u2f" - data["webauthn_challenge"] = util.GetRandomStringFromRange(64, 92) - data["webauthn_rp_name"] = "AUTHP" - data["webauthn_user_id"] = usr.Claims.ID - data["webauthn_user_email"] = usr.Claims.Email - data["webauthn_user_verification"] = "discouraged" - data["webauthn_attestation"] = "direct" - if usr.Claims.Name == "" { - data["webauthn_user_display_name"] = usr.Claims.Subject - } else { - data["webauthn_user_display_name"] = usr.Claims.Name - } - case strings.HasPrefix(endpoint, "/add/app") && r.Method == "POST": - // Add Application MFA token. - action = "add-app" - status = true - if err := validateAddMfaTokenForm(r, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("Bad Request: %s", err)) - break - } - if err = store.Request(operator.AddMfaToken, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - attachSuccessStatus(data, "MFA token has been added") - case strings.HasPrefix(endpoint, "/add/app"): - action = "add-app" - qr := qr.NewCode() - qr.Secret = util.GetRandomStringFromRange(64, 92) - qr.Type = "totp" - qr.Label = fmt.Sprintf("AUTHP:%s", usr.Claims.Email) - qr.Period = 30 - qr.Issuer = "AUTHP" - qr.Digits = 6 - if err := qr.Build(); err != nil { - attachFailStatus(data, fmt.Sprintf("Failed creating QR code: %v", err)) - break - } - data["mfa_label"] = qr.Issuer - data["mfa_comment"] = "My Authentication App" - data["mfa_email"] = usr.Claims.Email - data["mfa_type"] = qr.Type - data["mfa_secret"] = qr.Secret - data["mfa_period"] = fmt.Sprintf("%d", qr.Period) - data["mfa_digits"] = fmt.Sprintf("%d", qr.Digits) - data["code_uri"] = qr.Get() - data["code_uri_encoded"] = qr.GetEncoded() - case strings.HasPrefix(endpoint, "/test/app"): - // Test Application MFA token. - action = "test-app" - if r.Method == "POST" { - status = true - } - tokenID, digitCount, err := validateTestMfaTokenURL(endpoint) - data["mfa_token_id"] = tokenID - data["mfa_digits"] = digitCount - if err != nil { - attachFailStatus(data, fmt.Sprintf("Bad Request: %v", err)) - break - } - if r.Method != "POST" { - break - } - // Validate the posted MFA token. - if err := validateMfaAuthTokenForm(r, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("Bad Request: %v", err)) - break - } - if err = store.Request(operator.GetMfaTokens, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - var tokenValidated bool - bundle := rr.Response.Payload.(*identity.MfaTokenBundle) - for _, token := range bundle.Get() { - if token.ID != rr.MfaToken.ID { - continue - } - if err := token.ValidateCode(rr.MfaToken.Passcode); err != nil { - continue - } - tokenValidated = true - attachSuccessStatus(data, fmt.Sprintf("token id %s tested successfully", token.ID)) - break - } - if tokenValidated { - break - } - attachFailStatus(data, "Invalid token passcode") - case strings.HasPrefix(endpoint, "/test/u2f"): - // Test U2F token. - var token *identity.MfaToken - action = "test-u2f" - tokenID, err := validateTestU2FTokenURL(endpoint) - data["mfa_token_id"] = tokenID - if err != nil { - status = true - attachFailStatus(data, fmt.Sprintf("Bad Request: %v", err)) - break - } - // Get a list of U2F tokens. - if err = store.Request(operator.GetMfaTokens, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - bundle := rr.Response.Payload.(*identity.MfaTokenBundle) - for _, t := range bundle.Get() { - if t.ID != tokenID { - continue - } - if t.Type != "u2f" { - continue - } - token = t - break - } - if token == nil { - status = true - attachFailStatus(data, fmt.Sprintf("Bad Request: U2F token id %s not found", tokenID)) - break - } - if r.Method != "POST" { - // Authentication Ceremony parameters. - // Reference: https://www.w3.org/TR/webauthn-2/#sctn-assertion-privacy - if token.Parameters == nil { - status = true - attachFailStatus(data, fmt.Sprintf("Bad Request: U2F token id %s has no U2F parameters", tokenID)) - break - } - validParams := true - for _, k := range []string{"id", "transports", "type"} { - if _, exists := token.Parameters["u2f_"+k]; !exists { - status = true - validParams = false - attachFailStatus(data, fmt.Sprintf("U2F token id %s has no %s U2F parameters", tokenID, k)) - break - } - } - if !validParams { - break - } - data["webauthn_challenge"] = util.GetRandomStringFromRange(64, 92) - data["webauthn_rp_name"] = "AUTHP" - data["webauthn_timeout"] = "60000" - // See https://chromium.googlesource.com/chromium/src/+/refs/heads/main/content/browser/webauth/uv_preferred.md - // data["webauthn_user_verification"] = "preferred" - data["webauthn_user_verification"] = "discouraged" - // data["webauthn_ext_uvm"] = "true" - data["webauthn_ext_uvm"] = "false" - data["webauthn_ext_loc"] = "false" - data["webauthn_tx_auth_simple"] = "Could you please verify yourself?" - var allowedCredentials []map[string]interface{} - allowedCredential := make(map[string]interface{}) - allowedCredential["id"] = token.Parameters["u2f_id"] - allowedCredential["type"] = token.Parameters["u2f_type"] - allowedCredential["transports"] = token.Parameters["u2f_transports"] - allowedCredentials = append(allowedCredentials, allowedCredential) - data["webauthn_credentials"] = allowedCredentials - break - } - // Validate the posted U2F token. - status = true - if err := validateAuthU2FTokenForm(r, rr); err != nil { - p.logger.Warn( - "detected malformed u2f token validation request", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.Any("error", err), - ) - attachFailStatus(data, fmt.Sprintf("Bad Request: %v", err)) - break - } - - if wr, err := token.WebAuthnRequest(rr.WebAuthn.Request); err != nil { - p.logger.Warn( - "u2f token validation failed", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.Any("webauthn_request", wr), - zap.Any("error", err), - ) - attachFailStatus(data, fmt.Sprintf("U2F authentication failed: %v", err)) - break - } else { - p.logger.Debug( - "successfully validated u2f token", - zap.String("session_id", rr.Upstream.SessionID), - zap.String("request_id", rr.ID), - zap.Any("webauthn_request", wr), - ) - } - attachSuccessStatus(data, fmt.Sprintf("U2F token id %s tested successfully", token.ID)) - case strings.HasPrefix(endpoint, "/delete"): - // Delete a particular SSH key. - action = "delete" - status = true - tokenID, err := getEndpointKeyID(endpoint, "/delete/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.MfaToken.ID = tokenID - if err = store.Request(operator.DeleteMfaToken, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed deleting token id %s: %v", tokenID, err)) - break - } - attachSuccessStatus(data, fmt.Sprintf("token id %s deleted successfully", tokenID)) - /* - case strings.HasPrefix(endpoint, "/view"): - // Get a particular SSH key. - action = "view" - keyID, err := getEndpointKeyID(endpoint, "/view/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.Usage = "ssh" - if err = store.Request(operator.GetPublicKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed fetching key id %s: %v", keyID, err)) - break - } - bundle := rr.Response.Payload.(*identity.PublicKeyBundle) - for _, k := range bundle.Get() { - if k.ID != keyID { - continue - } - var keyMap map[string]interface{} - keyBytes, _ := json.Marshal(k) - json.Unmarshal(keyBytes, &keyMap) - for _, w := range []string{"payload", "openssh"} { - if _, exists := keyMap[w]; !exists { - continue - } - delete(keyMap, w) - } - prettyKey, _ := json.MarshalIndent(keyMap, "", " ") - attachSuccessStatus(data, "OK") - data["key"] = string(prettyKey) - if k.Payload != "" { - data["pem_key"] = k.Payload - } - if k.OpenSSH != "" { - data["openssh_key"] = k.OpenSSH - } - break - } - */ - default: - // List MFA Tokens. - if err = store.Request(operator.GetMfaTokens, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - bundle := rr.Response.Payload.(*identity.MfaTokenBundle) - tokens := bundle.Get() - if len(tokens) > 0 { - data["mfa_tokens"] = tokens - } - attachSuccessStatus(data, "OK") - } - attachView(data, entrypoint, action, status) - return nil -} diff --git a/pkg/authn/handle_http_settings_password.go b/pkg/authn/handle_http_settings_password.go deleted file mode 100644 index bacb320..0000000 --- a/pkg/authn/handle_http_settings_password.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "fmt" - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" - "net/http" - "strings" -) - -func (p *Portal) handleHTTPPasswordSettings( - ctx context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - var action string - var status bool - entrypoint := "password" - data["view"] = entrypoint - endpoint, err := getEndpoint(r.URL.Path, "/"+entrypoint) - if err != nil { - return err - } - switch { - case strings.HasPrefix(endpoint, "/edit") && r.Method == "POST": - action = "edit" - if err := validatePasswordChangeForm(r, rr); err != nil { - attachFailStatus(data, "Bad Request") - break - } - if err = store.Request(operator.ChangePassword, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - attachSuccessStatus(data, "Password has been changed") - } - attachView(data, entrypoint, action, status) - return nil -} diff --git a/pkg/authn/handle_http_settings_sshkeys.go b/pkg/authn/handle_http_settings_sshkeys.go deleted file mode 100644 index 5d3ce63..0000000 --- a/pkg/authn/handle_http_settings_sshkeys.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" - "github.com/greenpau/go-authcrunch/pkg/identity" - "github.com/greenpau/go-authcrunch/pkg/ids" - "github.com/greenpau/go-authcrunch/pkg/requests" - "github.com/greenpau/go-authcrunch/pkg/user" -) - -func (p *Portal) handleHTTPSSHKeysSettings( - ctx context.Context, r *http.Request, rr *requests.Request, - usr *user.User, store ids.IdentityStore, data map[string]interface{}, -) error { - var action string - var status bool - entrypoint := "sshkeys" - data["view"] = entrypoint - var endpoint string - var err error - endpoint, err = getEndpoint(r.URL.Path, "/"+entrypoint) - if err != nil { - if v, exists := data["endpoint"]; exists { - endpoint = v.(string) - delete(data, "endpoint") - } else { - return err - } - } - switch { - case strings.HasPrefix(endpoint, "/add") && r.Method == "POST": - // Add SSH key. - action = "add" - status = true - if err := validateKeyInputForm(r, rr); err != nil { - attachFailStatus(data, "Bad Request") - break - } - rr.Key.Usage = "ssh" - if err = store.Request(operator.AddKeySSH, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - attachSuccessStatus(data, "Public SSH key has been added") - case strings.HasPrefix(endpoint, "/add"): - action = "add" - case strings.HasPrefix(endpoint, "/delete"): - // Delete a particular SSH key. - action = "delete" - status = true - keyID, err := getEndpointKeyID(endpoint, "/delete/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.ID = keyID - if err = store.Request(operator.DeletePublicKey, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed deleting key id %s: %v", keyID, err)) - break - } - attachSuccessStatus(data, fmt.Sprintf("key id %s deleted successfully", keyID)) - case strings.HasPrefix(endpoint, "/view"): - // Get a particular SSH key. - action = "view" - keyID, err := getEndpointKeyID(endpoint, "/view/") - if err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - rr.Key.Usage = "ssh" - if err = store.Request(operator.GetPublicKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("failed fetching key id %s: %v", keyID, err)) - break - } - bundle := rr.Response.Payload.(*identity.PublicKeyBundle) - for _, k := range bundle.Get() { - if k.ID != keyID { - continue - } - var keyMap map[string]interface{} - keyBytes, _ := json.Marshal(k) - json.Unmarshal(keyBytes, &keyMap) - for _, w := range []string{"payload", "openssh"} { - if _, exists := keyMap[w]; !exists { - continue - } - delete(keyMap, w) - } - prettyKey, _ := json.MarshalIndent(keyMap, "", " ") - attachSuccessStatus(data, "OK") - data["key"] = string(prettyKey) - if k.Payload != "" { - data["pem_key"] = k.Payload - } - if k.OpenSSH != "" { - data["openssh_key"] = k.OpenSSH - } - break - } - default: - // List SSH Keys. - rr.Key.Usage = "ssh" - if err = store.Request(operator.GetPublicKeys, rr); err != nil { - attachFailStatus(data, fmt.Sprintf("%v", err)) - break - } - bundle := rr.Response.Payload.(*identity.PublicKeyBundle) - pubKeys := bundle.Get() - if len(pubKeys) > 0 { - data[entrypoint] = pubKeys - } - } - attachView(data, entrypoint, action, status) - return nil -} diff --git a/pkg/authn/respond_http.go b/pkg/authn/respond_http.go index 223c961..189f276 100644 --- a/pkg/authn/respond_http.go +++ b/pkg/authn/respond_http.go @@ -16,15 +16,16 @@ package authn import ( "context" + "net/http" + "net/url" + "path" + "strings" + "github.com/greenpau/go-authcrunch/pkg/requests" "github.com/greenpau/go-authcrunch/pkg/user" "github.com/greenpau/go-authcrunch/pkg/util" addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" "go.uber.org/zap" - "net/http" - "net/url" - "path" - "strings" ) func (p *Portal) handleHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { @@ -41,8 +42,6 @@ func (p *Portal) handleHTTP(ctx context.Context, w http.ResponseWriter, r *http. case strings.HasSuffix(r.URL.Path, "/recover"), strings.HasSuffix(r.URL.Path, "/forgot"): // TODO(greenpau): implement password recovery. return p.handleHTTPRecover(ctx, w, r, rr) - case strings.Contains(r.URL.Path, "/settings"): - return p.handleHTTPSettings(ctx, w, r, rr, usr) case strings.HasSuffix(r.URL.Path, "/register"), strings.Contains(r.URL.Path, "/register/"): return p.handleHTTPRegister(ctx, w, r, rr) case strings.HasSuffix(r.URL.Path, "/whoami"): @@ -59,6 +58,8 @@ func (p *Portal) handleHTTP(ctx context.Context, w http.ResponseWriter, r *http. return p.handleHTTPExternalLogin(ctx, w, r, rr, "oauth2") case strings.Contains(r.URL.Path, "/basic/login/"): return p.handleHTTPBasicLogin(ctx, w, r, rr) + case strings.Contains(r.URL.Path, "/barcode/mfa/"): + return p.handleHTTPProfileMfaBarcode(ctx, w, r, rr, usr) case strings.HasSuffix(r.URL.Path, "/logout"): return p.handleHTTPLogout(ctx, w, r, rr, usr) case strings.Contains(r.URL.Path, "/sandbox/"):