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 all 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
6 changes: 6 additions & 0 deletions backend/authschemes/webauthn/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type WebAuthnRegistrationInfo struct {
CredentialCreatedDate time.Time
}

type WebAuthnUpdateCredentialInfo struct {
UserID int64
CredentialName string
NewCredentialName string
}

type AShirtWebauthnExtension struct {
CredentialName string `json:"credentialName"`
CredentialCreatedDate time.Time `json:"credentialCreatedDate"`
Expand Down
45 changes: 45 additions & 0 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 @@ -237,6 +238,20 @@ func (a WebAuthn) BindRoutes(r *mux.Router, bridge authschemes.AShirtAuthBridge)
return nil, a.deleteCredential(callingUserID, credentialName, bridge)
}))

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())
Expand Down Expand Up @@ -336,6 +351,36 @@ func (a WebAuthn) deleteCredential(userID int64, credentialName string, bridge a
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 == CreateCredential {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/authschemes/webauthn/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ export async function listWebauthnCredentials(): Promise<CredentialList> {
export async function deleteWebauthnCredential(i: { credentialName: string }): Promise<CredentialList> {
return await req('DELETE', `/auth/webauthn/credential/${i.credentialName}`)
}

export async function modifyCredentialName(i: { credentialName: string, newCredentialName: string }): Promise<void> {
return await req('PUT', `/auth/webauthn/credential`, i)
}
55 changes: 46 additions & 9 deletions frontend/src/authschemes/webauthn/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import SettingsSection from 'src/components/settings_section'
import classnames from 'classnames/bind'
import { useForm, useFormField } from 'src/helpers/use_form'
import { renderModals, useModal, useWiredData } from 'src/helpers'
import { beginAddCredential, deleteWebauthnCredential, finishAddCredential, listWebauthnCredentials } from '../services'
import { beginAddCredential, deleteWebauthnCredential, finishAddCredential, listWebauthnCredentials, modifyCredentialName } from '../services'
import Table from 'src/components/table'
import Button from 'src/components/button'
import Button, { ButtonGroup } from 'src/components/button'
import { BuildReloadBus } from 'src/helpers/reload_bus'
import ModalForm from 'src/components/modal_form'
import { convertToCredentialCreationOptions, encodeAsB64 } from '../helpers'
Expand Down Expand Up @@ -45,6 +45,7 @@ const CredentialList = (props: {
})

const deleteModal = useModal<{ credentialName: string }>(mProps => <DeleteCredentialModal {...mProps} />, wiredCredentials.reload)
const modifyModal = useModal<{ credentialName: string }>(mProps => <EditCredentialModal {...mProps} />, wiredCredentials.reload)

return (<>
{wiredCredentials.render(data => {
Expand All @@ -57,18 +58,21 @@ const CredentialList = (props: {
<tr key={credentialName}>
<td>{credentialName}</td>
<td>{toEnUSDate(dateCreated)}</td>
<td>
<Button small danger onClick={() => {
deleteModal.show({ credentialName })
}}>
Delete
</Button>
<td className={cx('button-cell')}>
<ButtonGroup className={cx('row-buttons')}>
<Button small onClick={() => {
modifyModal.show({ credentialName })
}}>Edit</Button>
<Button danger small onClick={() => {
deleteModal.show({ credentialName })
}}>Delete</Button>
</ButtonGroup>
</td>
</tr>
)
})}
</Table>
{renderModals(deleteModal)}
{renderModals(deleteModal, modifyModal)}
</div>
)
})}
Expand Down Expand Up @@ -153,3 +157,36 @@ const DeleteCredentialModal = (props: {
onRequestClose={props.onRequestClose}
/>
)

const EditCredentialModal = (props: {
credentialName: string,
onRequestClose: () => void,
}) => {
const credentialName = useFormField("")

const formComponentProps = useForm({
fields: [credentialName],
handleSubmit: async () => {
if (credentialName.value === '') {
return Promise.reject(new Error("Credential name must be populated"))
}
await modifyCredentialName({
newCredentialName: credentialName.value,
credentialName: props.credentialName,
})
},
onSuccess: props.onRequestClose
})

return (
<ModalForm
title={"Edit Credential Name"}
submitText={"Edit"}
cancelText="Cancel"
onRequestClose={props.onRequestClose}
{...formComponentProps}
>
<Input label="New Credential name" {...credentialName} />
</ModalForm>
)
}
8 changes: 8 additions & 0 deletions frontend/src/authschemes/webauthn/settings/stylesheet.styl
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@
.security-credentials-section
*
margin-bottom: 5px

.button-cell
position: relative

.row-buttons
position: absolute
right: 5px
top: 10px