diff --git a/pkg/authn/api_delete_user_api_key.go b/pkg/authn/api_delete_user_api_key.go new file mode 100644 index 0000000..bb1dd6d --- /dev/null +++ b/pkg/authn/api_delete_user_api_key.go @@ -0,0 +1,54 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// DeleteUserAPIKey deletes API key from user identity. +func (p *Portal) DeleteUserAPIKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "api" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.DeleteAPIKey, rr); err != nil { + resp["message"] = "Profile API failed to delete user api key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + + resp["entry"] = rr.Key.ID + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_delete_user_gpg_key.go b/pkg/authn/api_delete_user_gpg_key.go new file mode 100644 index 0000000..9c7a514 --- /dev/null +++ b/pkg/authn/api_delete_user_gpg_key.go @@ -0,0 +1,54 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// DeleteUserGPGKey deletes GPG key from user identity. +func (p *Portal) DeleteUserGPGKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "gpg" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.DeletePublicKey, rr); err != nil { + resp["message"] = "Profile API failed to delete user GPG key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + + resp["entry"] = rr.Key.ID + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_delete_user_multi_factor_authenticator.go b/pkg/authn/api_delete_user_multi_factor_authenticator.go index 314b0e3..878c708 100644 --- a/pkg/authn/api_delete_user_multi_factor_authenticator.go +++ b/pkg/authn/api_delete_user_multi_factor_authenticator.go @@ -43,7 +43,6 @@ func (p *Portal) DeleteUserMultiFactorVerifier( return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) } - // Get MFA Token if err := backend.Request(operator.DeleteMfaToken, rr); err != nil { resp["message"] = "Profile API failed to delete user multi factor authenticator" return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) diff --git a/pkg/authn/api_delete_user_ssh_key.go b/pkg/authn/api_delete_user_ssh_key.go new file mode 100644 index 0000000..33182f5 --- /dev/null +++ b/pkg/authn/api_delete_user_ssh_key.go @@ -0,0 +1,54 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// DeleteUserSSHKey deletes SSH key from user identity. +func (p *Portal) DeleteUserSSHKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "ssh" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.DeletePublicKey, rr); err != nil { + resp["message"] = "Profile API failed to delete user SSH key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + + resp["entry"] = rr.Key.ID + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_fetch_debug.go b/pkg/authn/api_fetch_debug.go new file mode 100644 index 0000000..5bb889d --- /dev/null +++ b/pkg/authn/api_fetch_debug.go @@ -0,0 +1,48 @@ +// Copyright 2024 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" + "net/http" + + "github.com/greenpau/go-authcrunch/pkg/ids" + "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/user" +) + +// FetchDebug fetches debug information. +func (p *Portal) FetchDebug( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore) error { + + entry := make(map[string]interface{}) + database := map[string]interface{}{ + "name": "localdb", + "host": "localhost", + "port": 5432, + "engine": "postgresql", + } + entry["version"] = "1.0.0" + entry["database"] = database + resp["entry"] = entry + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_fetch_user_api_keys.go b/pkg/authn/api_fetch_user_api_keys.go new file mode 100644 index 0000000..6f9ecc8 --- /dev/null +++ b/pkg/authn/api_fetch_user_api_keys.go @@ -0,0 +1,76 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// FetchUserAPIKeys fetches API keys from user identity. +func (p *Portal) FetchUserAPIKeys( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore) error { + + rr.Key.Usage = "api" + if err := backend.Request(operator.GetAPIKeys, rr); err != nil { + resp["message"] = "Profile API failed to get API keys" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + bundle := rr.Response.Payload.(*identity.APIKeyBundle) + resp["entries"] = bundle.Get() + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} + +// FetchUserAPIKey fetches API key from user identity. +func (p *Portal) FetchUserAPIKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "api" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.GetAPIKey, rr); err != nil { + resp["message"] = "Profile API failed to get API key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + token := rr.Response.Payload.(*identity.APIKey) + resp["entry"] = token + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_fetch_user_gpg_keys.go b/pkg/authn/api_fetch_user_gpg_keys.go new file mode 100644 index 0000000..6f53260 --- /dev/null +++ b/pkg/authn/api_fetch_user_gpg_keys.go @@ -0,0 +1,76 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// FetchUserGPGKeys fetches GPG keys from user identity. +func (p *Portal) FetchUserGPGKeys( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore) error { + + rr.Key.Usage = "gpg" + if err := backend.Request(operator.GetPublicKeys, rr); err != nil { + resp["message"] = "Profile API failed to get GPG keys" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + bundle := rr.Response.Payload.(*identity.PublicKeyBundle) + resp["entries"] = bundle.Get() + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} + +// FetchUserGPGKey fetches GPG key from user identity. +func (p *Portal) FetchUserGPGKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "gpg" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.GetPublicKey, rr); err != nil { + resp["message"] = "Profile API failed to get GPG key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + token := rr.Response.Payload.(*identity.PublicKey) + resp["entry"] = token + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/api_fetch_user_multi_factor_authenticators.go b/pkg/authn/api_fetch_user_multi_factor_authenticators.go index ae64c0a..5301ded 100644 --- a/pkg/authn/api_fetch_user_multi_factor_authenticators.go +++ b/pkg/authn/api_fetch_user_multi_factor_authenticators.go @@ -46,7 +46,7 @@ func (p *Portal) FetchUserMultiFactorVerifiers( return handleAPIProfileResponse(w, rr, http.StatusOK, resp) } -// FetchUserMultiFactorVerifier fetches app multi factor authenticators from user identity. +// FetchUserMultiFactorVerifier fetches app multi factor authenticator from user identity. func (p *Portal) FetchUserMultiFactorVerifier( ctx context.Context, w http.ResponseWriter, diff --git a/pkg/authn/api_fetch_user_ssh_keys.go b/pkg/authn/api_fetch_user_ssh_keys.go new file mode 100644 index 0000000..a6de5d5 --- /dev/null +++ b/pkg/authn/api_fetch_user_ssh_keys.go @@ -0,0 +1,76 @@ +// Copyright 2024 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" + "net/http" + + "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" +) + +// FetchUserSSHKeys fetches SSH keys from user identity. +func (p *Portal) FetchUserSSHKeys( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore) error { + + rr.Key.Usage = "ssh" + if err := backend.Request(operator.GetPublicKeys, rr); err != nil { + resp["message"] = "Profile API failed to get SSH keys" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + bundle := rr.Response.Payload.(*identity.PublicKeyBundle) + resp["entries"] = bundle.Get() + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} + +// FetchUserSSHKey fetches SSH key from user identity. +func (p *Portal) FetchUserSSHKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "ssh" + if v, exists := bodyData["id"]; exists { + rr.Key.ID = v.(string) + } else { + resp["message"] = "Profile API did not find id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.GetPublicKey, rr); err != nil { + resp["message"] = "Profile API failed to get SSH key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + token := rr.Response.Payload.(*identity.PublicKey) + resp["entry"] = token + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/enums/operator/operator.go b/pkg/authn/enums/operator/operator.go index d2be939..206c04d 100644 --- a/pkg/authn/enums/operator/operator.go +++ b/pkg/authn/enums/operator/operator.go @@ -30,8 +30,12 @@ const ( ChangePassword // GetPublicKeys operator signals the retrieval of public keys. GetPublicKeys + // GetPublicKey operator signals the retrieval of a single public key. + GetPublicKey // GetAPIKeys operator signals the retrieval of API keys. GetAPIKeys + // GetAPIKey operator signals the retrieval of a single API key. + GetAPIKey // AddKeySSH operator signals the addition of an SSH public key. AddKeySSH // AddKeyGPG operator signals the addition of an GPG public key. @@ -77,6 +81,8 @@ func (e Type) String() string { return "ChangePassword" case GetPublicKeys: return "GetPublicKeys" + case GetPublicKey: + return "GetPublicKey" case AddKeySSH: return "AddKeySSH" case AddKeyGPG: @@ -101,6 +107,8 @@ func (e Type) String() string { return "DeleteUser" case GetAPIKeys: return "GetAPIKeys" + case GetAPIKey: + return "GetAPIKey" case AddAPIKey: return "AddAPIKey" case DeleteAPIKey: diff --git a/pkg/authn/handle_api_profile.go b/pkg/authn/handle_api_profile.go index e46ce0c..de2e5d4 100644 --- a/pkg/authn/handle_api_profile.go +++ b/pkg/authn/handle_api_profile.go @@ -94,6 +94,7 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r } switch reqKind { + case "fetch_debug": case "fetch_user_dashboard_data": case "delete_user_multi_factor_authenticator": case "fetch_user_multi_factor_authenticators": @@ -101,6 +102,15 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r case "fetch_user_app_multi_factor_authenticator_code": case "test_user_app_multi_factor_authenticator": case "add_user_app_multi_factor_authenticator": + case "fetch_user_api_keys": + case "fetch_user_api_key": + case "delete_user_api_key": + case "fetch_user_ssh_keys": + case "fetch_user_ssh_key": + case "delete_user_ssh_key": + case "fetch_user_gpg_keys": + case "fetch_user_gpg_key": + case "delete_user_gpg_key": default: resp["message"] = "Profile API recieved unsupported request type" return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) @@ -144,6 +154,8 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r rr.User.Email = usr.Claims.Email switch reqKind { + case "fetch_debug": + return p.FetchDebug(ctx, w, r, rr, parsedUser, resp, usr, backend) case "fetch_user_dashboard_data": return p.FetchUserDashboardData(ctx, w, r, rr, parsedUser, resp, usr, backend) case "fetch_user_multi_factor_authenticators": @@ -158,6 +170,24 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r return p.TestUserAppMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) case "add_user_app_multi_factor_authenticator": return p.AddUserAppMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "fetch_user_api_keys": + return p.FetchUserAPIKeys(ctx, w, r, rr, parsedUser, resp, usr, backend) + case "fetch_user_api_key": + return p.FetchUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "delete_user_api_key": + return p.DeleteUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "fetch_user_ssh_keys": + return p.FetchUserSSHKeys(ctx, w, r, rr, parsedUser, resp, usr, backend) + case "fetch_user_ssh_key": + return p.FetchUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "delete_user_ssh_key": + return p.DeleteUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "fetch_user_gpg_keys": + return p.FetchUserGPGKeys(ctx, w, r, rr, parsedUser, resp, usr, backend) + case "fetch_user_gpg_key": + return p.FetchUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "delete_user_gpg_key": + return p.DeleteUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) } // Default response diff --git a/pkg/errors/database.go b/pkg/errors/database.go index bd1ebd2..bfa1655 100644 --- a/pkg/errors/database.go +++ b/pkg/errors/database.go @@ -33,10 +33,12 @@ const ( ErrAddPublicKey StandardError = "failed adding %s public key: %v" ErrDeletePublicKey StandardError = "failed deleting %q key: %v" ErrGetPublicKeys StandardError = "failed getting %q keys: %v" + ErrGetPublicKey StandardError = "failed getting %q key: %v" ErrAddAPIKey StandardError = "failed adding %s key: %v" ErrDeleteAPIKey StandardError = "failed deleting %q key: %v" ErrGetAPIKeys StandardError = "failed getting %q keys: %v" + ErrGetAPIKey StandardError = "failed getting %q key: %v" ErrChangeUserPassword StandardError = "failed change user password: %v" ErrUpdateUserPassword StandardError = "failed updating user password: %v" diff --git a/pkg/identity/database.go b/pkg/identity/database.go index 1f488fa..a3d01c8 100644 --- a/pkg/identity/database.go +++ b/pkg/identity/database.go @@ -543,6 +543,30 @@ func (db *Database) GetPublicKeys(r *requests.Request) error { return nil } +// GetPublicKey returns a public key associated with a user. +func (db *Database) GetPublicKey(r *requests.Request) error { + db.mu.RLock() + defer db.mu.RUnlock() + user, err := db.validateUserIdentity(r.User.Username, r.User.Email) + if err != nil { + return errors.ErrGetPublicKey.WithArgs(r.Key.Usage, err) + } + for _, k := range user.PublicKeys { + if k.Usage != r.Key.Usage { + continue + } + if k.Disabled { + continue + } + if k.ID != r.Key.ID { + continue + } + r.Response.Payload = k + return nil + } + return errors.ErrGetPublicKey.WithArgs(r.Key.Usage, "not found") +} + // DeletePublicKey deletes a public key associated with a user by key id. func (db *Database) DeletePublicKey(r *requests.Request) error { db.mu.Lock() @@ -639,6 +663,30 @@ func (db *Database) GetAPIKeys(r *requests.Request) error { return nil } +// GetAPIKey returns an API key associated with a user. +func (db *Database) GetAPIKey(r *requests.Request) error { + db.mu.RLock() + defer db.mu.RUnlock() + user, err := db.validateUserIdentity(r.User.Username, r.User.Email) + if err != nil { + return errors.ErrGetAPIKey.WithArgs(r.Key.Usage, err) + } + for _, k := range user.APIKeys { + if k.Usage != r.Key.Usage { + continue + } + if k.Disabled { + continue + } + if k.ID != r.Key.ID { + continue + } + r.Response.Payload = k + return nil + } + return errors.ErrGetAPIKey.WithArgs(r.Key.Usage, "not found") +} + // ChangeUserPassword change user password. func (db *Database) ChangeUserPassword(r *requests.Request) error { db.mu.Lock() diff --git a/pkg/identity/public_key.go b/pkg/identity/public_key.go index 64a5c36..b40a505 100644 --- a/pkg/identity/public_key.go +++ b/pkg/identity/public_key.go @@ -22,14 +22,16 @@ import ( "encoding/hex" "encoding/pem" "fmt" + "strconv" + "strings" + "time" + "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/tagging" "github.com/greenpau/go-authcrunch/pkg/util" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/ssh" - "strconv" - "strings" - "time" ) var supportedPublicKeyTypes = map[string]bool{ @@ -48,17 +50,20 @@ type PublicKey struct { ID string `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"` Usage string `json:"usage,omitempty" xml:"usage,omitempty" yaml:"usage,omitempty"` // Type is any of the following: dsa, rsa, ecdsa, ed25519 - Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"` - Fingerprint string `json:"fingerprint,omitempty" xml:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` - FingerprintMD5 string `json:"fingerprint_md5,omitempty" xml:"fingerprint_md5,omitempty" yaml:"fingerprint_md5,omitempty"` - Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` - Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` - OpenSSH string `json:"openssh,omitempty" xml:"openssh,omitempty" yaml:"openssh,omitempty"` - Expired bool `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"` - ExpiredAt time.Time `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"` - Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` - DisabledAt time.Time `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` + Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"` + Fingerprint string `json:"fingerprint,omitempty" xml:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` + FingerprintMD5 string `json:"fingerprint_md5,omitempty" xml:"fingerprint_md5,omitempty" yaml:"fingerprint_md5,omitempty"` + Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` + Description string `json:"description,omitempty" xml:"description,omitempty" yaml:"description,omitempty"` + Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` + OpenSSH string `json:"openssh,omitempty" xml:"openssh,omitempty" yaml:"openssh,omitempty"` + Expired bool `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"` + ExpiredAt time.Time `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"` + Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` + DisabledAt time.Time `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` + Tags []tagging.Tag `json:"tags,omitempty" xml:"tags,omitempty" yaml:"tags,omitempty"` + Labels []string `json:"labels,omitempty" xml:"labels,omitempty" yaml:"labels,omitempty"` } // NewPublicKeyBundle returns an instance of PublicKeyBundle. @@ -87,11 +92,14 @@ func (b *PublicKeyBundle) Size() int { // NewPublicKey returns an instance of PublicKey. func NewPublicKey(r *requests.Request) (*PublicKey, error) { p := &PublicKey{ - Comment: r.Key.Comment, - ID: util.GetRandomString(40), - Payload: r.Key.Payload, - Usage: r.Key.Usage, - CreatedAt: time.Now().UTC(), + Comment: r.Key.Comment, + ID: util.GetRandomString(40), + Payload: r.Key.Payload, + Usage: r.Key.Usage, + CreatedAt: time.Now().UTC(), + Description: r.Key.Description, + Tags: r.Key.Tags, + Labels: r.Key.Labels, } if err := p.parse(); err != nil { return nil, err diff --git a/pkg/ids/local/authenticator.go b/pkg/ids/local/authenticator.go index b4be3d3..375e069 100644 --- a/pkg/ids/local/authenticator.go +++ b/pkg/ids/local/authenticator.go @@ -202,6 +202,13 @@ func (sa *Authenticator) GetPublicKeys(r *requests.Request) error { return sa.db.GetPublicKeys(r) } +// GetPublicKey returns a public keys associated with a user. +func (sa *Authenticator) GetPublicKey(r *requests.Request) error { + sa.mux.Lock() + defer sa.mux.Unlock() + return sa.db.GetPublicKey(r) +} + // AddAPIKey adds API key for a user. func (sa *Authenticator) AddAPIKey(r *requests.Request) error { sa.mux.Lock() @@ -223,6 +230,13 @@ func (sa *Authenticator) GetAPIKeys(r *requests.Request) error { return sa.db.GetAPIKeys(r) } +// GetAPIKey returns API key associated with a user. +func (sa *Authenticator) GetAPIKey(r *requests.Request) error { + sa.mux.Lock() + defer sa.mux.Unlock() + return sa.db.GetAPIKey(r) +} + // AddMfaToken adds MFA token to a user. func (sa *Authenticator) AddMfaToken(r *requests.Request) error { sa.mux.Lock() diff --git a/pkg/ids/store.go b/pkg/ids/store.go index 8383c28..5fec29b 100644 --- a/pkg/ids/store.go +++ b/pkg/ids/store.go @@ -16,6 +16,7 @@ package ids import ( "encoding/json" + "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" "github.com/greenpau/go-authcrunch/pkg/authn/icons" "github.com/greenpau/go-authcrunch/pkg/errors" diff --git a/pkg/requests/requests.go b/pkg/requests/requests.go index 3b85cfa..52629e0 100644 --- a/pkg/requests/requests.go +++ b/pkg/requests/requests.go @@ -99,12 +99,15 @@ type User struct { // Key holds crypto key attributes. type Key struct { - ID string `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"` - Prefix string `json:"prefix,omitempty" xml:"prefix,omitempty" yaml:"prefix,omitempty"` - Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` - Usage string `json:"usage,omitempty" xml:"usage,omitempty" yaml:"usage,omitempty"` - Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` - Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` + ID string `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"` + Prefix string `json:"prefix,omitempty" xml:"prefix,omitempty" yaml:"prefix,omitempty"` + Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` + Usage string `json:"usage,omitempty" xml:"usage,omitempty" yaml:"usage,omitempty"` + Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` + Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` + Description string `json:"description,omitempty" xml:"description,omitempty" yaml:"description,omitempty"` + Tags []tagging.Tag `json:"tags,omitempty" xml:"tags,omitempty" yaml:"tags,omitempty"` + Labels []string `json:"labels,omitempty" xml:"labels,omitempty" yaml:"labels,omitempty"` } // MfaToken holds MFA token attributes.