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), sqlUpdateTOTPConfigRecordSignIn: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignIn, tableTOTPConfigurations),
sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations), sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations),
sqlUpsertWebauthnDevice: fmt.Sprintf(queryFmtUpsertWebauthnDevice, tableWebauthnDevices), sqlInsertWebauthnDevice: fmt.Sprintf(queryFmtUpsertInsertDevice, tableWebauthnDevices),
sqlSelectWebauthnDevices: fmt.Sprintf(queryFmtSelectWebauthnDevices, tableWebauthnDevices), sqlSelectWebauthnDevices: fmt.Sprintf(queryFmtSelectWebauthnDevices, tableWebauthnDevices),
sqlSelectWebauthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByUsername, tableWebauthnDevices), sqlSelectWebauthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByUsername, tableWebauthnDevices),
sqlSelectWebauthnDevicesByRPIDByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByRPIDByUsername, tableWebauthnDevices), sqlSelectWebauthnDevicesByRPIDByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByRPIDByUsername, tableWebauthnDevices),
sqlSelectWebauthnDeviceByID: fmt.Sprintf(queryFmtSelectWebauthnDeviceByID, tableWebauthnDevices), sqlSelectWebauthnDeviceByID: fmt.Sprintf(queryFmtSelectWebauthnDeviceByID, tableWebauthnDevices),
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID: fmt.Sprintf(queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID, tableWebauthnDevices), sqlUpdateWebauthnDeviceDescriptionByUsernameAndID: fmt.Sprintf(queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID, tableWebauthnDevices),
sqlUpdateWebauthnDevicePublicKey: fmt.Sprintf(queryFmtUpdateWebauthnDevicePublicKey, tableWebauthnDevices),
sqlUpdateWebauthnDevicePublicKeyByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDevicePublicKeyByUsername, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices), sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignInByUsername, tableWebauthnDevices),
sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices), sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices), sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices), sqlDeleteWebauthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices),
@ -164,17 +161,14 @@ type SQLProvider struct {
sqlUpdateTOTPConfigRecordSignInByUsername string sqlUpdateTOTPConfigRecordSignInByUsername string
// Table: webauthn_devices. // Table: webauthn_devices.
sqlUpsertWebauthnDevice string sqlInsertWebauthnDevice string
sqlSelectWebauthnDevices string sqlSelectWebauthnDevices string
sqlSelectWebauthnDevicesByUsername string sqlSelectWebauthnDevicesByUsername string
sqlSelectWebauthnDevicesByRPIDByUsername string sqlSelectWebauthnDevicesByRPIDByUsername string
sqlSelectWebauthnDeviceByID string sqlSelectWebauthnDeviceByID string
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID string sqlUpdateWebauthnDeviceDescriptionByUsernameAndID string
sqlUpdateWebauthnDevicePublicKey string
sqlUpdateWebauthnDevicePublicKeyByUsername string
sqlUpdateWebauthnDeviceRecordSignIn string sqlUpdateWebauthnDeviceRecordSignIn string
sqlUpdateWebauthnDeviceRecordSignInByUsername string
sqlDeleteWebauthnDevice string sqlDeleteWebauthnDevice string
sqlDeleteWebauthnDeviceByUsername 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) 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.CreatedAt, device.LastUsedAt,
device.RPID, device.Username, device.Description, device.RPID, device.Username, device.Description,
device.KID, device.PublicKey, device.KID, device.PublicKey,

View File

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

View File

@ -149,11 +149,6 @@ const (
SET public_key = ? SET public_key = ?
WHERE id = ?;` WHERE id = ?;`
queryFmtUpdateWebauthnDevicePublicKeyByUsername = `
UPDATE %s
SET public_key = ?
WHERE username = ? AND kid = ?;`
queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID = ` queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID = `
UPDATE %s UPDATE %s
SET description = ? SET description = ?
@ -166,22 +161,9 @@ const (
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE id = ?;` WHERE id = ?;`
queryFmtUpdateWebauthnDeviceRecordSignInByUsername = ` queryFmtUpsertInsertDevice = `
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 = `
INSERT INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning) 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
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;`
queryFmtDeleteWebauthnDevice = ` queryFmtDeleteWebauthnDevice = `
DELETE FROM %s 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) { if (res.status !== 200 || hasServiceError(res).errored) {
throw new Error(`Failed POST to ${path}. Code: ${res.status}. Message: ${hasServiceError(res).message}`); throw new Error(`Failed POST to ${path}. Code: ${res.status}. Message: ${hasServiceError(res).message}`);
} }
return toData<T>(res); return toData<T>(res);
} }
@ -32,3 +33,21 @@ export async function Get<T = undefined>(path: string): Promise<T> {
} }
return d; 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 { WebauthnDevice } from "@models/Webauthn";
import { WebauthnDevicesPath } from "@services/Api"; 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. // getWebauthnDevices returns the list of webauthn devices for the authenticated user.
export async function getWebauthnDevices(): Promise<WebauthnDevice[]> { export async function getWebauthnDevices(): Promise<WebauthnDevice[] | null> {
return Get<WebauthnDevice[]>(WebauthnDevicesPath); 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 DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; 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 { useTranslation } from "react-i18next";
import LoadingButton from "@components/LoadingButton";
import { useNotifications } from "@hooks/NotificationsContext"; import { useNotifications } from "@hooks/NotificationsContext";
import { WebauthnDevice } from "@models/Webauthn"; import { WebauthnDevice } from "@models/Webauthn";
import { deleteDevice, updateDevice } from "@services/Webauthn"; import { deleteDevice, updateDevice } from "@services/Webauthn";
@ -169,26 +168,26 @@ export default function WebauthnDeviceItem(props: Props) {
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title={translate("Edit information for this Webauthn credential")}> <Tooltip title={translate("Edit information for this Webauthn credential")}>
<LoadingButton <Button
loading={loadingEdit}
variant="outlined" variant="outlined"
color="primary" color="primary"
startIcon={<EditIcon />} startIcon={loadingEdit ? <CircularProgress color="inherit" size={20} /> : <EditIcon />}
onClick={() => setShowDialogEdit(true)} onClick={loadingEdit ? undefined : () => setShowDialogEdit(true)}
> >
{translate("Edit")} {translate("Edit")}
</LoadingButton> </Button>
</Tooltip> </Tooltip>
<Tooltip title={translate("Remove this Webauthn credential")}> <Tooltip title={translate("Remove this Webauthn credential")}>
<LoadingButton <Button
loading={loadingDelete}
variant="outlined" variant="outlined"
color="secondary" color="primary"
startIcon={<DeleteIcon />} startIcon={
onClick={() => setShowDialogDelete(true)} loadingDelete ? <CircularProgress color="inherit" size={20} /> : <DeleteIcon />
}
onClick={loadingDelete ? undefined : () => setShowDialogDelete(true)}
> >
{translate("Remove")} {translate("Remove")}
</LoadingButton> </Button>
</Tooltip> </Tooltip>
</Stack> </Stack>
</Box> </Box>

View File

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

View File

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