fix: misc

feat-otp-verification
James Elliott 2023-02-12 23:10:24 +11:00
parent ba1ed1252c
commit 526dd8347d
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
8 changed files with 46 additions and 54 deletions

View File

@ -46,16 +46,13 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlUpdateTOTPConfigRecordSignIn: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignIn, tableTOTPConfigurations),
sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations),
sqlUpsertWebauthnDevice: fmt.Sprintf(queryFmtUpsertWebauthnDevice, tableWebauthnDevices),
sqlInsertWebauthnDevice: fmt.Sprintf(queryFmtUpsertInsertDevice, tableWebauthnDevices),
sqlSelectWebauthnDevices: fmt.Sprintf(queryFmtSelectWebauthnDevices, tableWebauthnDevices),
sqlSelectWebauthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByUsername, tableWebauthnDevices),
sqlSelectWebauthnDevicesByRPIDByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByRPIDByUsername, tableWebauthnDevices),
sqlSelectWebauthnDeviceByID: fmt.Sprintf(queryFmtSelectWebauthnDeviceByID, tableWebauthnDevices),
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID: fmt.Sprintf(queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID, tableWebauthnDevices),
sqlUpdateWebauthnDevicePublicKey: fmt.Sprintf(queryFmtUpdateWebauthnDevicePublicKey, tableWebauthnDevices),
sqlUpdateWebauthnDevicePublicKeyByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDevicePublicKeyByUsername, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignInByUsername, tableWebauthnDevices),
sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices),
@ -164,17 +161,14 @@ type SQLProvider struct {
sqlUpdateTOTPConfigRecordSignInByUsername string
// Table: webauthn_devices.
sqlUpsertWebauthnDevice string
sqlInsertWebauthnDevice string
sqlSelectWebauthnDevices string
sqlSelectWebauthnDevicesByUsername string
sqlSelectWebauthnDevicesByRPIDByUsername string
sqlSelectWebauthnDeviceByID string
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID string
sqlUpdateWebauthnDevicePublicKey string
sqlUpdateWebauthnDevicePublicKeyByUsername string
sqlUpdateWebauthnDeviceRecordSignIn string
sqlUpdateWebauthnDeviceRecordSignInByUsername string
sqlDeleteWebauthnDevice string
sqlDeleteWebauthnDeviceByUsername string
@ -847,7 +841,7 @@ func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.Webau
return fmt.Errorf("error encrypting the Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err)
}
if _, err = p.db.ExecContext(ctx, p.sqlUpsertWebauthnDevice,
if _, err = p.db.ExecContext(ctx, p.sqlInsertWebauthnDevice,
device.CreatedAt, device.LastUsedAt,
device.RPID, device.Username, device.Description,
device.KID, device.PublicKey,

View File

@ -31,7 +31,6 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
// Specific alterations to this provider.
// PostgreSQL doesn't have a UPSERT statement but has an ON CONFLICT operation instead.
provider.sqlUpsertWebauthnDevice = fmt.Sprintf(queryFmtUpsertWebauthnDevicePostgreSQL, tableWebauthnDevices)
provider.sqlUpsertDuoDevice = fmt.Sprintf(queryFmtUpsertDuoDevicePostgreSQL, tableDuoDevices)
provider.sqlUpsertTOTPConfig = fmt.Sprintf(queryFmtUpsertTOTPConfigurationPostgreSQL, tableTOTPConfigurations)
provider.sqlUpsertPreferred2FAMethod = fmt.Sprintf(queryFmtUpsertPreferred2FAMethodPostgreSQL, tableUserPreferences)
@ -59,14 +58,13 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
provider.sqlDeleteTOTPConfig = provider.db.Rebind(provider.sqlDeleteTOTPConfig)
provider.sqlSelectTOTPConfigs = provider.db.Rebind(provider.sqlSelectTOTPConfigs)
provider.sqlInsertWebauthnDevice = provider.db.Rebind(provider.sqlInsertWebauthnDevice)
provider.sqlSelectWebauthnDevices = provider.db.Rebind(provider.sqlSelectWebauthnDevices)
provider.sqlSelectWebauthnDevicesByUsername = provider.db.Rebind(provider.sqlSelectWebauthnDevicesByUsername)
provider.sqlSelectWebauthnDevicesByRPIDByUsername = provider.db.Rebind(provider.sqlSelectWebauthnDevicesByRPIDByUsername)
provider.sqlSelectWebauthnDeviceByID = provider.db.Rebind(provider.sqlSelectWebauthnDeviceByID)
provider.sqlUpdateWebauthnDeviceDescriptionByUsernameAndID = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceDescriptionByUsernameAndID)
provider.sqlUpdateWebauthnDevicePublicKey = provider.db.Rebind(provider.sqlUpdateWebauthnDevicePublicKey)
provider.sqlUpdateWebauthnDevicePublicKeyByUsername = provider.db.Rebind(provider.sqlUpdateWebauthnDevicePublicKeyByUsername)
provider.sqlUpdateWebauthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignIn)
provider.sqlUpdateWebauthnDeviceRecordSignInByUsername = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignInByUsername)
provider.sqlDeleteWebauthnDevice = provider.db.Rebind(provider.sqlDeleteWebauthnDevice)
provider.sqlDeleteWebauthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsername)
provider.sqlDeleteWebauthnDeviceByUsernameAndDescription = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsernameAndDescription)

View File

@ -149,11 +149,6 @@ const (
SET public_key = ?
WHERE id = ?;`
queryFmtUpdateWebauthnDevicePublicKeyByUsername = `
UPDATE %s
SET public_key = ?
WHERE username = ? AND kid = ?;`
queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID = `
UPDATE %s
SET description = ?
@ -166,22 +161,9 @@ const (
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE id = ?;`
queryFmtUpdateWebauthnDeviceRecordSignInByUsername = `
UPDATE %s
SET
rpid = ?, last_used_at = ?, sign_count = ?,
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE username = ? AND kid = ?;`
queryFmtUpsertWebauthnDevice = `
REPLACE INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
queryFmtUpsertWebauthnDevicePostgreSQL = `
queryFmtUpsertInsertDevice = `
INSERT INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT (rpid, username, description)
DO UPDATE SET created_at = $1, last_used_at = $2, kid = $6, public_key = $7, attestation_type = $8, transport = $9, aaguid = $10, sign_count = $11, clone_warning = $12;`
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
queryFmtDeleteWebauthnDevice = `
DELETE FROM %s

View File

@ -8,6 +8,7 @@ export async function PostWithOptionalResponse<T = undefined>(path: string, body
if (res.status !== 200 || hasServiceError(res).errored) {
throw new Error(`Failed POST to ${path}. Code: ${res.status}. Message: ${hasServiceError(res).message}`);
}
return toData<T>(res);
}
@ -32,3 +33,21 @@ export async function Get<T = undefined>(path: string): Promise<T> {
}
return d;
}
export async function GetWithOptionalData<T = undefined>(path: string): Promise<T | null> {
const res = await axios.get<ServiceResponse<T>>(path);
if (res.status !== 200 || hasServiceError(res).errored) {
throw new Error(`Failed GET from ${path}. Code: ${res.status}.`);
}
const d = toData<T>(res);
if (d === null) {
return null;
}
if (!d) {
throw new Error("unexpected type of response");
}
return d;
}

View File

@ -1,8 +1,8 @@
import { WebauthnDevice } from "@models/Webauthn";
import { WebauthnDevicesPath } from "@services/Api";
import { Get } from "@services/Client";
import { GetWithOptionalData } from "@services/Client";
// getWebauthnDevices returns the list of webauthn devices for the authenticated user.
export async function getWebauthnDevices(): Promise<WebauthnDevice[]> {
return Get<WebauthnDevice[]>(WebauthnDevicesPath);
export async function getWebauthnDevices(): Promise<WebauthnDevice[] | null> {
return GetWithOptionalData<WebauthnDevice[] | null>(WebauthnDevicesPath);
}

View File

@ -4,10 +4,9 @@ import { Fingerprint } from "@mui/icons-material";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import { Box, Button, Paper, Stack, Tooltip, Typography } from "@mui/material";
import { Box, Button, CircularProgress, Paper, Stack, Tooltip, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import LoadingButton from "@components/LoadingButton";
import { useNotifications } from "@hooks/NotificationsContext";
import { WebauthnDevice } from "@models/Webauthn";
import { deleteDevice, updateDevice } from "@services/Webauthn";
@ -169,26 +168,26 @@ export default function WebauthnDeviceItem(props: Props) {
</Button>
</Tooltip>
<Tooltip title={translate("Edit information for this Webauthn credential")}>
<LoadingButton
loading={loadingEdit}
<Button
variant="outlined"
color="primary"
startIcon={<EditIcon />}
onClick={() => setShowDialogEdit(true)}
startIcon={loadingEdit ? <CircularProgress color="inherit" size={20} /> : <EditIcon />}
onClick={loadingEdit ? undefined : () => setShowDialogEdit(true)}
>
{translate("Edit")}
</LoadingButton>
</Button>
</Tooltip>
<Tooltip title={translate("Remove this Webauthn credential")}>
<LoadingButton
loading={loadingDelete}
<Button
variant="outlined"
color="secondary"
startIcon={<DeleteIcon />}
onClick={() => setShowDialogDelete(true)}
color="primary"
startIcon={
loadingDelete ? <CircularProgress color="inherit" size={20} /> : <DeleteIcon />
}
onClick={loadingDelete ? undefined : () => setShowDialogDelete(true)}
>
{translate("Remove")}
</LoadingButton>
</Button>
</Tooltip>
</Stack>
</Box>

View File

@ -64,11 +64,11 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
setName("");
};
const handleClose = () => {
const handleClose = useCallback(() => {
resetStates();
props.setCancelled();
};
}, [props]);
const finishAttestation = async () => {
if (!result || !result.response) {

View File

@ -15,11 +15,11 @@ interface Props {
export default function WebauthnDevicesStack(props: Props) {
const { t: translate } = useTranslation("settings");
const [devices, setDevices] = useState<WebauthnDevice[]>([]);
const [devices, setDevices] = useState<WebauthnDevice[] | null>(null);
useEffect(() => {
(async function () {
setDevices([]);
setDevices(null);
const devices = await getWebauthnDevices();
setDevices(devices);
})();
@ -27,7 +27,7 @@ export default function WebauthnDevicesStack(props: Props) {
return (
<Fragment>
{devices.length !== 0 ? (
{devices !== null && devices.length !== 0 ? (
<Stack spacing={3}>
{devices.map((x, idx) => (
<WebauthnDeviceItem key={idx} index={idx} device={x} handleEdit={props.incrementRefreshState} />