From b7b19628866f07ec827832c881da4a1cb2f9ee5b Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Sun, 11 Jul 2021 17:55:47 +0200 Subject: [PATCH] Add support for security keys password --- src/helpers/AdminKeyHelper.ts | 15 ++++++- src/ui/accountSettings/KeySettingsSection.tsx | 41 ++++++++++++++----- src/ui/routes/LoginRoute.tsx | 22 +++++++++- src/ui/widgets/DialogsProvider.tsx | 2 + 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/helpers/AdminKeyHelper.ts b/src/helpers/AdminKeyHelper.ts index aba744a..101afb1 100644 --- a/src/helpers/AdminKeyHelper.ts +++ b/src/helpers/AdminKeyHelper.ts @@ -15,10 +15,12 @@ export interface AdminAccountKey { id: number; name: string; time_add: number; + has_password: boolean; } export interface AuthKey { name: string; id: number; + password: boolean; } export class AdminKeyHelper { @@ -42,8 +44,13 @@ export class AdminKeyHelper { * * @param name The name of the key to create * @param cred The credentials to register + * @param password The password required to use the key */ - static async RegisterKey(name: string, cred: any): Promise { + static async RegisterKey( + name: string, + cred: any, + password: string + ): Promise { const res = { id: cred.id, rawId: ArrayBufferToBase64(cred.rawId), @@ -61,6 +68,7 @@ export class AdminKeyHelper { await serverRequest("keys/register_key", { name: name, key: JSON.stringify(res), + password: password, }); } @@ -96,11 +104,13 @@ export class AdminKeyHelper { * @param mail Target admin account email address * @param key Key used to authenticate * @param cred Response to authentication + * @param password The password associated with the key (if any) */ static async AuthenticateWithKey( mail: string, key: AuthKey, - cred: any + cred: any, + password: string ): Promise { const creds = { id: cred.id, @@ -122,6 +132,7 @@ export class AdminKeyHelper { mail: mail, key_id: key.id, credential: JSON.stringify(creds), + password: password, }); sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token); diff --git a/src/ui/accountSettings/KeySettingsSection.tsx b/src/ui/accountSettings/KeySettingsSection.tsx index c4007ab..c9a89c0 100644 --- a/src/ui/accountSettings/KeySettingsSection.tsx +++ b/src/ui/accountSettings/KeySettingsSection.tsx @@ -1,24 +1,26 @@ import { + Button, + Divider, + IconButton, Table, + TableBody, + TableCell, TableHead, TableRow, - TableCell, - TableBody, - IconButton, - Divider, - Button, + Tooltip, } from "@material-ui/core"; import { Delete } from "@material-ui/icons"; +import LockIcon from "@material-ui/icons/Lock"; import React from "react"; -import { AdminAccount, AccountHelper } from "../../helpers/AccountHelper"; +import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper"; import { AdminAccountKey, AdminKeyHelper } from "../../helpers/AdminKeyHelper"; import { CopyToClipboard } from "../../utils/ClipboardUtils"; import { AsyncWidget } from "../widgets/AsyncWidget"; import { + input, + matAlert, matConfirm, snackbar, - matAlert, - input, } from "../widgets/DialogsProvider"; import { TimestampWidget } from "../widgets/TimestampWidget"; import { SettingsSection } from "./SettingsSection"; @@ -83,7 +85,14 @@ export class KeySettingsSection extends React.Component< 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!"); @@ -139,7 +148,19 @@ export class KeySettingsSection extends React.Component< {this.state.keys.map((key) => ( - {key.name} + {key.name}{" "} + {key.has_password ? ( + + + + ) : ( + <> + )} diff --git a/src/ui/routes/LoginRoute.tsx b/src/ui/routes/LoginRoute.tsx index 84d1684..8ed6cb0 100644 --- a/src/ui/routes/LoginRoute.tsx +++ b/src/ui/routes/LoginRoute.tsx @@ -24,6 +24,7 @@ import React from "react"; import { AccountHelper, AuthOptions } from "../../helpers/AccountHelper"; import { AdminKeyHelper, AuthKey } from "../../helpers/AdminKeyHelper"; import { input, matAlert } from "../widgets/DialogsProvider"; +import LockIcon from "@material-ui/icons/Lock"; function ErrorGettingOptions(p: { message: string }) { return ( @@ -248,10 +249,22 @@ class AuthOptionsWidget extends React.Component< 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( this.props.email, key, - result + result, + password ); document.location.href = document.location.href + ""; @@ -304,7 +317,12 @@ class AuthOptionsWidget extends React.Component< ))} diff --git a/src/ui/widgets/DialogsProvider.tsx b/src/ui/widgets/DialogsProvider.tsx index a4231c8..8309c53 100644 --- a/src/ui/widgets/DialogsProvider.tsx +++ b/src/ui/widgets/DialogsProvider.tsx @@ -27,6 +27,7 @@ export interface TextInputOptions { minLength?: number; maxLength?: number; label: string; + type?: string; } interface AppDiagProvState { @@ -287,6 +288,7 @@ export class ApplicationDialogsProvider extends React.Component< variant="outlined" value={this.state.inputValue} onChange={this.handleInputValueChanged} + type={this.state.inputOptions.type || "text"} />