Path: blob/master/src/packages/frontend/account/ssh-keys/ssh-key-adder.tsx
1503 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Button, Input, Modal } from "antd";6import { useState } from "react";7import { useIntl } from "react-intl";8import { A, Icon } from "@cocalc/frontend/components";9import { labels } from "@cocalc/frontend/i18n";10import { compute_fingerprint } from "./fingerprint";11import ShowError from "@cocalc/frontend/components/error";1213const ALLOWED_SSH_TYPES = [14"ssh-rsa",15"ssh-dss",16"ssh-ed25519",17"ecdsa-sha2-nistp256",18"ecdsa-sha2-nistp384",19"ecdsa-sha2-nistp521",20] as const;2122const ALLOWED_SSH_TYPES_DESCRIPTION =23ALLOWED_SSH_TYPES.slice(0, -1).join(", ") +24", or " +25ALLOWED_SSH_TYPES[ALLOWED_SSH_TYPES.length - 1];2627// Removes all new lines and trims the output28// Newlines are simply illegal in SSH keys29const normalize_key = (value) =>30value31.trim()32.split(/[\r\n]+/)33.join("");3435// Splits an SSH key into its parts. Doesn't allow options36// Assumes the key has valid formatting ie.37// <key-type>[space]<public-key>[space]<comment>38interface ParsedKey {39type?: string;40pubkey?: string;41source?: string;42comments?: string;43error?: string;44value: string;45}46const parse_key = function (value: string): ParsedKey {47const parts: string[] = value.split(/\s+/);48const type = parts[0];49const pubkey = parts[1];50const source = parts[2];51const comments = parts.slice(3).join(" ");5253return { value, type, pubkey, source, comments };54};5556const validate_key = function (value): ParsedKey {57const key = parse_key(value);58if (!ALLOWED_SSH_TYPES.includes(key.type as any)) {59key.error = "Invalid key or type not supported";60} else {61delete key.error;62}63// TODO: Use some validation library?64return key;65};6667interface Props {68add_ssh_key: Function;69style?: React.CSSProperties;70extra?: React.JSX.Element;71size?;72}7374export default function SSHKeyAdder({75add_ssh_key,76style,77extra,78size,79}: Props) {80const [add, setAdd] = useState<boolean>(false);81const intl = useIntl();82const [keyTitle, setKeyTitle] = useState<string>("");83const [keyValue, setKeyValue] = useState<string>("");84const [error, setError] = useState<string>("");8586const button = (87<Button size={size} onClick={() => setAdd(!add)}>88<Icon name="plus-circle" /> Add SSH Key...89</Button>90);9192if (!add) {93return button;94}9596const addKey = intl.formatMessage({97id: "account.ssh-key-adder.button",98defaultMessage: "Add SSH Key",99});100101function cancelAndClose() {102setKeyTitle("");103setKeyValue("");104setError("");105setAdd(false);106}107108function submit_form(e?): void {109let title;110e?.preventDefault();111try {112const validated_key = validate_key(normalize_key(keyValue));113if (validated_key.error != null) {114setError(validated_key.error);115return;116} else {117setError("");118}119120if (keyTitle) {121title = keyTitle;122} else {123title = validated_key.source;124}125126const { value } = validated_key;127128add_ssh_key({129title,130value,131fingerprint: compute_fingerprint(validated_key.pubkey),132});133134cancelAndClose();135} catch (err) {136setError(`${err}`);137}138}139140return (141<>142{button}143<Modal144open145onCancel={cancelAndClose}146title={147<>148<Icon name="plus-circle" />{" "}149{intl.formatMessage(150{151id: "account.ssh-key-adder.title",152defaultMessage: "Add an <A>SSH key</A>",153},154{155A: (c) => (156<A href="https://doc.cocalc.com/account/ssh.html">{c}</A>157),158},159)}160</>161}162style={style}163footer={[164<Button onClick={() => cancelAndClose()} key="close">165{intl.formatMessage(labels.cancel)}166</Button>,167<Button168key="add"169type="primary"170onClick={submit_form}171disabled={keyValue.length < 10}172>173{addKey}174</Button>,175]}176>177{extra && extra}178<div>179Title180<Input181id="ssh-title"182value={keyTitle}183onChange={(e) => setKeyTitle(e.target.value)}184placeholder={intl.formatMessage({185id: "account.ssh-key-adder.placeholder",186defaultMessage:187"Choose a name for this ssh key to help you keep track of it...",188})}189/>190<div style={{ marginTop: "15px" }}>191Key192<Input.TextArea193value={keyValue}194rows={8}195placeholder={`Begins with ${ALLOWED_SSH_TYPES_DESCRIPTION}`}196onChange={(e) => setKeyValue((e.target as any).value)}197onKeyDown={(e) => {198if (e.keyCode == 13) {199e.preventDefault();200submit_form();201}202}}203style={{ resize: "vertical" }}204/>205</div>206</div>207208<ShowError209error={error}210setError={setError}211style={{ marginTop: "15px" }}212/>213</Modal>214</>215);216}217218219