Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/account/ssh-keys/ssh-key-adder.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Button, Card, Input, Space } from "antd";6import { useState } from "react";7import { useIntl } from "react-intl";89import { A, ErrorDisplay, Icon } from "@cocalc/frontend/components";10import { labels } from "@cocalc/frontend/i18n";1112import { compute_fingerprint } from "./fingerprint";1314const ALLOWED_SSH_TYPES = [15"ssh-rsa",16"ssh-dss",17"ssh-ed25519",18"ecdsa-sha2-nistp256",19"ecdsa-sha2-nistp384",20"ecdsa-sha2-nistp521",21] as const;2223const ALLOWED_SSH_TYPES_DESCRIPTION =24ALLOWED_SSH_TYPES.slice(0, -1).join(", ") +25", or " +26ALLOWED_SSH_TYPES[ALLOWED_SSH_TYPES.length - 1];2728// Removes all new lines and trims the output29// Newlines are simply illegal in SSH keys30const normalize_key = (value) =>31value32.trim()33.split(/[\r\n]+/)34.join("");3536// Splits an SSH key into its parts. Doesn't allow options37// Assumes the key has valid formatting ie.38// <key-type>[space]<public-key>[space]<comment>39interface ParsedKey {40type?: string;41pubkey?: string;42source?: string;43comments?: string;44error?: string;45value: string;46}47const parse_key = function (value: string): ParsedKey {48const parts: string[] = value.split(/\s+/);49const type = parts[0];50const pubkey = parts[1];51const source = parts[2];52const comments = parts.slice(3).join(" ");5354return { value, type, pubkey, source, comments };55};5657const validate_key = function (value): ParsedKey {58const key = parse_key(value);59if (!ALLOWED_SSH_TYPES.includes(key.type as any)) {60key.error = "Invalid key or type not supported";61} else {62delete key.error;63}64// TODO: Use some validation library?65return key;66};6768interface Props {69add_ssh_key: Function;70toggleable?: boolean;71style?: React.CSSProperties;72extra?: JSX.Element;73}7475export const SSHKeyAdder: React.FC<Props> = (props: Props) => {76const { add_ssh_key, toggleable, style, extra } = props;77const intl = useIntl();78const [key_title, set_key_title] = useState<string>("");79const [key_value, set_key_value] = useState<string>("");80const [show_panel, set_show_panel] = useState<boolean>(false);81const [error, set_error] = useState<undefined | string>(undefined);8283const addKey = intl.formatMessage({84id: "account.ssh-key-adder.button",85defaultMessage: "Add SSH Key",86});8788function cancel_and_close() {89set_key_title("");90set_key_value("");91set_show_panel(!toggleable);92set_error(undefined);93}9495function submit_form(e?): void {96let title;97e?.preventDefault();98try {99const validated_key = validate_key(normalize_key(key_value));100if (validated_key.error != null) {101set_error(validated_key.error);102return;103} else {104set_error(undefined);105}106107if (key_title) {108title = key_title;109} else {110title = validated_key.source;111}112113const { value } = validated_key;114115add_ssh_key({116title,117value,118fingerprint: compute_fingerprint(validated_key.pubkey),119});120121cancel_and_close();122} catch (err) {123set_error(err.toString());124}125}126127function render_panel() {128return (129<Card130title={131<>132<Icon name="plus-circle" />{" "}133{intl.formatMessage(134{135id: "account.ssh-key-adder.title",136defaultMessage: "Add an <A>SSH key</A>",137},138{139A: (c) => (140<A href="https://doc.cocalc.com/account/ssh.html">{c}</A>141),142},143)}144</>145}146style={style}147>148{extra && extra}149<div>150Title151<Input152id="ssh-title"153value={key_title}154onChange={(e) => set_key_title(e.target.value)}155placeholder={intl.formatMessage({156id: "account.ssh-key-adder.placeholder",157defaultMessage:158"Choose a name for this ssh key to help you keep track of it...",159})}160/>161<div style={{ marginTop: "15px" }}>162Key163<Input.TextArea164value={key_value}165rows={8}166placeholder={`Begins with ${ALLOWED_SSH_TYPES_DESCRIPTION}`}167onChange={(e) => set_key_value((e.target as any).value)}168onKeyDown={(e) => {169if (e.keyCode == 13) {170e.preventDefault();171submit_form();172}173}}174style={{ resize: "vertical" }}175/>176</div>177</div>178<div style={{ marginTop: "15px" }}>179<Space>180{toggleable ? (181<Button onClick={cancel_and_close}>182{intl.formatMessage(labels.cancel)}183</Button>184) : undefined}185<Button186type="primary"187onClick={submit_form}188disabled={key_value.length < 10}189>190{addKey}191</Button>192</Space>193{error && (194<ErrorDisplay195error={error}196onClose={() => set_error(undefined)}197style={{ marginTop: "10px" }}198/>199)}200</div>201</Card>202);203}204205function render_open_button() {206return (207<Button onClick={() => set_show_panel(true)} style={style}>208<Icon name="terminal" /> {addKey}...209</Button>210);211}212213if (!toggleable || show_panel) {214return render_panel();215} else {216return render_open_button();217}218};219220221