Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add button to update key/credential name #834

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions backend/authschemes/webauthn/dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ package webauthn

import "time"

type ListKeysOutput struct {
Keys []KeyEntry `json:"keys"`
type ListCredentialsOutput struct {
Credentials []CredentialEntry `json:"credentials"`
}

type KeyEntry struct {
KeyName string `json:"keyName"`
DateCreated time.Time `json:"dateCreated"`
type CredentialEntry struct {
CredentialName string `json:"credentialName"`
DateCreated time.Time `json:"dateCreated"`
}
36 changes: 21 additions & 15 deletions backend/authschemes/webauthn/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ import (
type RegistrationType int

const (
// CreateOrLinkKey reflects the usecase where
CreateKey RegistrationType = iota
LinkKey
AddKey
// CreateOrLinkCredential reflects the usecase where
CreateCredential RegistrationType = iota
LinkCredential
AddCredential
)

type WebAuthnRegistrationInfo struct {
Email string
Username string
FirstName string
LastName string
KeyName string
UserID int64
RegistrationType RegistrationType
ExistingCredentials []AShirtWebauthnCredential
KeyCreatedDate time.Time
Email string
Username string
FirstName string
LastName string
CredentialName string
UserID int64
RegistrationType RegistrationType
ExistingCredentials []AShirtWebauthnCredential
CredentialCreatedDate time.Time
}

type WebAuthnUpdateCredentialInfo struct {
UserID int64
CredentialName string
NewCredentialName string
}

type AShirtWebauthnExtension struct {
KeyName string `json:"keyName"`
KeyCreatedDate time.Time `json:"keyCreatedDate"`
CredentialName string `json:"credentialName"`
CredentialCreatedDate time.Time `json:"credentialCreatedDate"`
}

type AShirtWebauthnCredential struct {
Expand Down
113 changes: 79 additions & 34 deletions backend/authschemes/webauthn/webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"strings"
"time"

"github.com/gorilla/mux"
"github.com/theparanoids/ashirt-server/backend"
Expand Down Expand Up @@ -107,8 +108,8 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
Username: dr.FromBody("username").Required().AsString(),
FirstName: dr.FromBody("firstName").Required().AsString(),
LastName: dr.FromBody("lastName").Required().AsString(),
KeyName: dr.FromBody("keyName").Required().AsString(),
RegistrationType: CreateKey,
CredentialName: dr.FromBody("credentialName").Required().AsString(),
RegistrationType: CreateCredential,
}
if dr.Error != nil {
return nil, dr.Error
Expand Down Expand Up @@ -194,9 +195,9 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
dr := remux.DissectJSONRequest(r)
info := WebAuthnRegistrationInfo{
Username: dr.FromBody("username").Required().AsString(),
KeyName: dr.FromBody("keyName").Required().AsString(),
CredentialName: dr.FromBody("credentialName").Required().AsString(),
UserID: callingUserId,
RegistrationType: LinkKey,
RegistrationType: LinkCredential,
}
if dr.Error != nil {
return nil, dr.Error
Expand All @@ -222,39 +223,53 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
})
}))

remux.Route(r, "GET", "/keys", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
remux.Route(r, "GET", "/credentials", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
callingUserID := middleware.UserID(r.Context())
return a.getKeys(callingUserID, bridge)
return a.getCredentials(callingUserID, bridge)
}))

remux.Route(r, "DELETE", "/key/{keyName}", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
remux.Route(r, "DELETE", "/credential/{credentialName}", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
callingUserID := middleware.UserID(r.Context())
dr := remux.DissectJSONRequest(r)
keyName := dr.FromURL("keyName").Required().AsString()
credentialName := dr.FromURL("credentialName").Required().AsString()
if dr.Error != nil {
return nil, dr.Error
}
return nil, a.deleteKey(callingUserID, keyName, bridge)
return nil, a.deleteCredential(callingUserID, credentialName, bridge)
}))

remux.Route(r, "POST", "/key/add/begin", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
remux.Route(r, "PUT", "/credential", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
callingUserID := middleware.UserID(r.Context())
dr := remux.DissectJSONRequest(r)
if dr.Error != nil {
return nil, dr.Error
}
info := WebAuthnUpdateCredentialInfo{
NewCredentialName: dr.FromBody("newCredentialName").Required().AsString(),
CredentialName: dr.FromBody("credentialName").Required().AsString(),
UserID: callingUserID,
}
return nil, a.updateCredentialName(info, bridge)
}))

remux.Route(r, "POST", "/credential/add/begin", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
remux.JSONHandler(func(r *http.Request) (interface{}, error) {
auth, err := bridge.FindUserAuthByContext(r.Context())
if err != nil {
return nil, backend.DatabaseErr(err)
}

dr := remux.DissectJSONRequest(r)
keyName := dr.FromBody("keyName").Required().AsString()
credentialName := dr.FromBody("credentialName").Required().AsString()
if dr.Error != nil {
return nil, dr.Error
}

info := WebAuthnRegistrationInfo{
Username: auth.Username,
KeyName: keyName,
CredentialName: credentialName,
UserID: auth.UserID,
RegistrationType: AddKey,
RegistrationType: AddCredential,
}

creds, err := a.getExistingCredentials(auth)
Expand All @@ -267,7 +282,7 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
}).ServeHTTP(w, r)
}))

remux.Route(r, "POST", "/key/add/finish", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
remux.Route(r, "POST", "/credential/add/finish", remux.JSONHandler(func(r *http.Request) (interface{}, error) {
_, encodedCreds, err := a.validateRegistrationComplete(r, bridge)
if err != nil {
return nil, backend.WrapError("Unable to validate registration data", err)
Expand All @@ -280,18 +295,18 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
userAuth.JSONData = helpers.Ptr(string(encodedCreds))
err = bridge.UpdateAuthForUser(userAuth)
if err != nil {
return nil, backend.WrapError("Unable to update keys", err)
return nil, backend.WrapError("Unable to update credentials", err)
}

// We might want to return a full list of keys. TODO: check if we want that
// We might want to return a full list of credentials. TODO: check if we want that
return nil, nil
}))
}

func (a WebAuthn) getKeys(userID int64, bridge authschemes.AShirtAuthBridge) (*ListKeysOutput, error) {
func (a WebAuthn) getCredentials(userID int64, bridge authschemes.AShirtAuthBridge) (*ListCredentialsOutput, error) {
auth, err := bridge.FindUserAuthByUserID(userID)
if err != nil {
return nil, backend.WrapError("Unable to get keys", err)
return nil, backend.WrapError("Unable to get credentials", err)
}

webauthRawCreds := []byte(*auth.JSONData)
Expand All @@ -300,17 +315,17 @@ func (a WebAuthn) getKeys(userID int64, bridge authschemes.AShirtAuthBridge) (*L
return nil, backend.WebauthnLoginError(err, "Unable to parse webauthn credentials")
}

results := helpers.Map(creds, func(cred AShirtWebauthnCredential) KeyEntry {
return KeyEntry{
KeyName: cred.KeyName,
DateCreated: cred.KeyCreatedDate,
results := helpers.Map(creds, func(cred AShirtWebauthnCredential) CredentialEntry {
return CredentialEntry{
CredentialName: cred.CredentialName,
DateCreated: cred.CredentialCreatedDate,
}
})
output := ListKeysOutput{results}
output := ListCredentialsOutput{results}
return &output, nil
}

func (a WebAuthn) deleteKey(userID int64, keyName string, bridge authschemes.AShirtAuthBridge) error {
func (a WebAuthn) deleteCredential(userID int64, credentialName string, bridge authschemes.AShirtAuthBridge) error {
auth, err := bridge.FindUserAuthByUserID(userID)
if err != nil {
return backend.WrapError("Unable to find user", err)
Expand All @@ -323,11 +338,11 @@ func (a WebAuthn) deleteKey(userID int64, keyName string, bridge authschemes.ASh
}

results := helpers.Filter(creds, func(cred AShirtWebauthnCredential) bool {
return cred.KeyName != keyName
return cred.CredentialName != credentialName
})
encodedCreds, err := json.Marshal(results)
if err != nil {
return backend.WrapError("Unable to delete key", err)
return backend.WrapError("Unable to delete credential", err)
}
auth.JSONData = helpers.Ptr(string(encodedCreds))

Expand All @@ -336,14 +351,44 @@ func (a WebAuthn) deleteKey(userID int64, keyName string, bridge authschemes.ASh
return nil
}

func (a WebAuthn) updateCredentialName(info WebAuthnUpdateCredentialInfo, bridge authschemes.AShirtAuthBridge) error {
userAuth, err := bridge.FindUserAuthByUserID(info.UserID)
if err != nil {
return backend.WrapError("Unable to find user", err)
}
webauthRawCreds := []byte(*userAuth.JSONData)
var creds []AShirtWebauthnCredential
if err = json.Unmarshal(webauthRawCreds, &creds); err != nil {
return backend.WebauthnLoginError(err, "Unable to parse webauthn credentials")
}
matchingIndex, _ := helpers.Find(creds, func(item AShirtWebauthnCredential) bool {
return string(item.CredentialName) == string(info.CredentialName)
})
if matchingIndex == -1 {
return backend.WrapError("Could not find matching credential", err)
}
creds[matchingIndex].CredentialName = info.NewCredentialName
creds[matchingIndex].CredentialCreatedDate = time.Now()

encodedCreds, err := json.Marshal(creds)
if err != nil {
return backend.WrapError("Unable to encode credentials", err)
}
userAuth.JSONData = helpers.Ptr(string(encodedCreds))
if err = bridge.UpdateAuthForUser(userAuth); err != nil {
return backend.WrapError("Unable to update credential", err)
}
return nil
}

func (a WebAuthn) beginRegistration(w http.ResponseWriter, r *http.Request, bridge authschemes.AShirtAuthBridge, info WebAuthnRegistrationInfo) (*protocol.CredentialCreation, error) {
var user webauthnUser
if info.RegistrationType == CreateKey {
user = makeNewWebAuthnUser(info.FirstName, info.LastName, info.Email, info.Username, info.KeyName)
} else if info.RegistrationType == LinkKey {
user = makeLinkingWebAuthnUser(info.UserID, info.Username, info.KeyName)
} else { // Add Key
user = makeAddKeyWebAuthnUser(info.UserID, info.KeyName, info.Username, info.ExistingCredentials)
if info.RegistrationType == CreateCredential {
user = makeNewWebAuthnUser(info.FirstName, info.LastName, info.Email, info.Username, info.CredentialName)
} else if info.RegistrationType == LinkCredential {
user = makeLinkingWebAuthnUser(info.UserID, info.Username, info.CredentialName)
} else { // Add Credential
user = makeAddCredentialWebAuthnUser(info.UserID, info.CredentialName, info.Username, info.ExistingCredentials)
}

credExcludeList := make([]protocol.CredentialDescriptor, len(user.Credentials))
Expand Down Expand Up @@ -422,8 +467,8 @@ func (a WebAuthn) validateRegistrationComplete(r *http.Request, bridge authschem
}

data.UserData.Credentials = append(data.UserData.Credentials, wrapCredential(*cred, AShirtWebauthnExtension{
KeyName: data.UserData.KeyName,
KeyCreatedDate: data.UserData.KeyCreatedDate,
CredentialName: data.UserData.CredentialName,
CredentialCreatedDate: data.UserData.CredentialCreatedDate,
}))

encodedCreds, err := json.Marshal(data.UserData.Credentials)
Expand Down
52 changes: 26 additions & 26 deletions backend/authschemes/webauthn/webauthnuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,42 @@ import (
)

type webauthnUser struct {
UserID []byte
AuthnID []byte
UserName string
IconURL string
Credentials []AShirtWebauthnCredential
FirstName string
LastName string
Email string
KeyName string
KeyCreatedDate time.Time
UserID []byte
AuthnID []byte
UserName string
IconURL string
Credentials []AShirtWebauthnCredential
FirstName string
LastName string
Email string
CredentialName string
CredentialCreatedDate time.Time
}

func makeNewWebAuthnUser(firstName, lastName, email, username, keyName string) webauthnUser {
func makeNewWebAuthnUser(firstName, lastName, email, username, credentialName string) webauthnUser {
return webauthnUser{
AuthnID: []byte(uuid.New().String()),
UserName: username,
FirstName: firstName,
LastName: lastName,
Email: email,
KeyName: keyName,
KeyCreatedDate: time.Now(),
AuthnID: []byte(uuid.New().String()),
UserName: username,
FirstName: firstName,
LastName: lastName,
Email: email,
CredentialName: credentialName,
CredentialCreatedDate: time.Now(),
}
}

func makeLinkingWebAuthnUser(userID int64, username, keyName string) webauthnUser {
func makeLinkingWebAuthnUser(userID int64, username, credentialName string) webauthnUser {
return webauthnUser{
UserID: i64ToByteSlice(userID),
AuthnID: []byte(uuid.New().String()),
UserName: username,
KeyName: keyName,
KeyCreatedDate: time.Now(),
UserID: i64ToByteSlice(userID),
AuthnID: []byte(uuid.New().String()),
UserName: username,
CredentialName: credentialName,
CredentialCreatedDate: time.Now(),
}
}

func makeAddKeyWebAuthnUser(userID int64, username, keyName string, creds []AShirtWebauthnCredential) webauthnUser {
user := makeLinkingWebAuthnUser(userID, username, keyName)
func makeAddCredentialWebAuthnUser(userID int64, username, credentialName string, creds []AShirtWebauthnCredential) webauthnUser {
user := makeLinkingWebAuthnUser(userID, username, credentialName)
user.Credentials = creds
return user
}
Expand Down