Add support for security keys password

This commit is contained in:
Pierre HUBERT 2021-07-11 17:55:47 +02:00
parent 0cd6de200d
commit b7b1962886
4 changed files with 66 additions and 14 deletions

View File

@ -15,10 +15,12 @@ export interface AdminAccountKey {
id: number; id: number;
name: string; name: string;
time_add: number; time_add: number;
has_password: boolean;
} }
export interface AuthKey { export interface AuthKey {
name: string; name: string;
id: number; id: number;
password: boolean;
} }
export class AdminKeyHelper { export class AdminKeyHelper {
@ -42,8 +44,13 @@ export class AdminKeyHelper {
* *
* @param name The name of the key to create * @param name The name of the key to create
* @param cred The credentials to register * @param cred The credentials to register
* @param password The password required to use the key
*/ */
static async RegisterKey(name: string, cred: any): Promise<void> { static async RegisterKey(
name: string,
cred: any,
password: string
): Promise<void> {
const res = { const res = {
id: cred.id, id: cred.id,
rawId: ArrayBufferToBase64(cred.rawId), rawId: ArrayBufferToBase64(cred.rawId),
@ -61,6 +68,7 @@ export class AdminKeyHelper {
await serverRequest("keys/register_key", { await serverRequest("keys/register_key", {
name: name, name: name,
key: JSON.stringify(res), key: JSON.stringify(res),
password: password,
}); });
} }
@ -96,11 +104,13 @@ export class AdminKeyHelper {
* @param mail Target admin account email address * @param mail Target admin account email address
* @param key Key used to authenticate * @param key Key used to authenticate
* @param cred Response to authentication * @param cred Response to authentication
* @param password The password associated with the key (if any)
*/ */
static async AuthenticateWithKey( static async AuthenticateWithKey(
mail: string, mail: string,
key: AuthKey, key: AuthKey,
cred: any cred: any,
password: string
): Promise<any> { ): Promise<any> {
const creds = { const creds = {
id: cred.id, id: cred.id,
@ -122,6 +132,7 @@ export class AdminKeyHelper {
mail: mail, mail: mail,
key_id: key.id, key_id: key.id,
credential: JSON.stringify(creds), credential: JSON.stringify(creds),
password: password,
}); });
sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token); sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token);

View File

@ -1,24 +1,26 @@
import { import {
Button,
Divider,
IconButton,
Table, Table,
TableBody,
TableCell,
TableHead, TableHead,
TableRow, TableRow,
TableCell, Tooltip,
TableBody,
IconButton,
Divider,
Button,
} from "@material-ui/core"; } from "@material-ui/core";
import { Delete } from "@material-ui/icons"; import { Delete } from "@material-ui/icons";
import LockIcon from "@material-ui/icons/Lock";
import React from "react"; import React from "react";
import { AdminAccount, AccountHelper } from "../../helpers/AccountHelper"; import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper";
import { AdminAccountKey, AdminKeyHelper } from "../../helpers/AdminKeyHelper"; import { AdminAccountKey, AdminKeyHelper } from "../../helpers/AdminKeyHelper";
import { CopyToClipboard } from "../../utils/ClipboardUtils"; import { CopyToClipboard } from "../../utils/ClipboardUtils";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { import {
input,
matAlert,
matConfirm, matConfirm,
snackbar, snackbar,
matAlert,
input,
} from "../widgets/DialogsProvider"; } from "../widgets/DialogsProvider";
import { TimestampWidget } from "../widgets/TimestampWidget"; import { TimestampWidget } from "../widgets/TimestampWidget";
import { SettingsSection } from "./SettingsSection"; import { SettingsSection } from "./SettingsSection";
@ -83,7 +85,14 @@ export class KeySettingsSection extends React.Component<
minLength: 2, minLength: 2,
}); });
await AdminKeyHelper.RegisterKey(name, credential); const password = await input({
label: "Key password (10 characters min)",
maxLength: 4000,
minLength: 10,
type: "password",
});
await AdminKeyHelper.RegisterKey(name, credential, password);
snackbar("Successfully enrolled a new key!"); snackbar("Successfully enrolled a new key!");
@ -139,7 +148,19 @@ export class KeySettingsSection extends React.Component<
{this.state.keys.map((key) => ( {this.state.keys.map((key) => (
<TableRow key={key.id}> <TableRow key={key.id}>
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{key.name} {key.name}{" "}
{key.has_password ? (
<Tooltip
disableFocusListener
title="This key requires a password to be used"
placement="top"
arrow
>
<LockIcon fontSize="inherit" />
</Tooltip>
) : (
<></>
)}
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
<TimestampWidget time={key.time_add} /> <TimestampWidget time={key.time_add} />

View File

@ -24,6 +24,7 @@ import React from "react";
import { AccountHelper, AuthOptions } from "../../helpers/AccountHelper"; import { AccountHelper, AuthOptions } from "../../helpers/AccountHelper";
import { AdminKeyHelper, AuthKey } from "../../helpers/AdminKeyHelper"; import { AdminKeyHelper, AuthKey } from "../../helpers/AdminKeyHelper";
import { input, matAlert } from "../widgets/DialogsProvider"; import { input, matAlert } from "../widgets/DialogsProvider";
import LockIcon from "@material-ui/icons/Lock";
function ErrorGettingOptions(p: { message: string }) { function ErrorGettingOptions(p: { message: string }) {
return ( return (
@ -248,10 +249,22 @@ class AuthOptionsWidget extends React.Component<
const result = await navigator.credentials.get(challenge); const result = await navigator.credentials.get(challenge);
const password = key.password
? await input({
label: "Key password",
message:
"A password is required to sign in using this key.",
minLength: 2,
title: "Login with " + key.name,
type: "password",
})
: "";
await AdminKeyHelper.AuthenticateWithKey( await AdminKeyHelper.AuthenticateWithKey(
this.props.email, this.props.email,
key, key,
result result,
password
); );
document.location.href = document.location.href + ""; document.location.href = document.location.href + "";
@ -304,7 +317,12 @@ class AuthOptionsWidget extends React.Component<
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={key.name} primary={key.name}
secondary="Sign in using this security key" secondary={
"Sign in using this security key. " +
(key.password
? "A password is associated with this key."
: "")
}
/> />
</ListItem> </ListItem>
))} ))}

View File

@ -27,6 +27,7 @@ export interface TextInputOptions {
minLength?: number; minLength?: number;
maxLength?: number; maxLength?: number;
label: string; label: string;
type?: string;
} }
interface AppDiagProvState { interface AppDiagProvState {
@ -287,6 +288,7 @@ export class ApplicationDialogsProvider extends React.Component<
variant="outlined" variant="outlined"
value={this.state.inputValue} value={this.state.inputValue}
onChange={this.handleInputValueChanged} onChange={this.handleInputValueChanged}
type={this.state.inputOptions.type || "text"}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>