Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/account/ssh-keys/ssh-key-adder.tsx
1503 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Button, Input, Modal } from "antd";
7
import { useState } from "react";
8
import { useIntl } from "react-intl";
9
import { A, Icon } from "@cocalc/frontend/components";
10
import { labels } from "@cocalc/frontend/i18n";
11
import { compute_fingerprint } from "./fingerprint";
12
import ShowError from "@cocalc/frontend/components/error";
13
14
const 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;
22
23
const ALLOWED_SSH_TYPES_DESCRIPTION =
24
ALLOWED_SSH_TYPES.slice(0, -1).join(", ") +
25
", or " +
26
ALLOWED_SSH_TYPES[ALLOWED_SSH_TYPES.length - 1];
27
28
// Removes all new lines and trims the output
29
// Newlines are simply illegal in SSH keys
30
const normalize_key = (value) =>
31
value
32
.trim()
33
.split(/[\r\n]+/)
34
.join("");
35
36
// Splits an SSH key into its parts. Doesn't allow options
37
// Assumes the key has valid formatting ie.
38
// <key-type>[space]<public-key>[space]<comment>
39
interface ParsedKey {
40
type?: string;
41
pubkey?: string;
42
source?: string;
43
comments?: string;
44
error?: string;
45
value: string;
46
}
47
const parse_key = function (value: string): ParsedKey {
48
const parts: string[] = value.split(/\s+/);
49
const type = parts[0];
50
const pubkey = parts[1];
51
const source = parts[2];
52
const comments = parts.slice(3).join(" ");
53
54
return { value, type, pubkey, source, comments };
55
};
56
57
const validate_key = function (value): ParsedKey {
58
const key = parse_key(value);
59
if (!ALLOWED_SSH_TYPES.includes(key.type as any)) {
60
key.error = "Invalid key or type not supported";
61
} else {
62
delete key.error;
63
}
64
// TODO: Use some validation library?
65
return key;
66
};
67
68
interface Props {
69
add_ssh_key: Function;
70
style?: React.CSSProperties;
71
extra?: React.JSX.Element;
72
size?;
73
}
74
75
export default function SSHKeyAdder({
76
add_ssh_key,
77
style,
78
extra,
79
size,
80
}: Props) {
81
const [add, setAdd] = useState<boolean>(false);
82
const intl = useIntl();
83
const [keyTitle, setKeyTitle] = useState<string>("");
84
const [keyValue, setKeyValue] = useState<string>("");
85
const [error, setError] = useState<string>("");
86
87
const button = (
88
<Button size={size} onClick={() => setAdd(!add)}>
89
<Icon name="plus-circle" /> Add SSH Key...
90
</Button>
91
);
92
93
if (!add) {
94
return button;
95
}
96
97
const addKey = intl.formatMessage({
98
id: "account.ssh-key-adder.button",
99
defaultMessage: "Add SSH Key",
100
});
101
102
function cancelAndClose() {
103
setKeyTitle("");
104
setKeyValue("");
105
setError("");
106
setAdd(false);
107
}
108
109
function submit_form(e?): void {
110
let title;
111
e?.preventDefault();
112
try {
113
const validated_key = validate_key(normalize_key(keyValue));
114
if (validated_key.error != null) {
115
setError(validated_key.error);
116
return;
117
} else {
118
setError("");
119
}
120
121
if (keyTitle) {
122
title = keyTitle;
123
} else {
124
title = validated_key.source;
125
}
126
127
const { value } = validated_key;
128
129
add_ssh_key({
130
title,
131
value,
132
fingerprint: compute_fingerprint(validated_key.pubkey),
133
});
134
135
cancelAndClose();
136
} catch (err) {
137
setError(`${err}`);
138
}
139
}
140
141
return (
142
<>
143
{button}
144
<Modal
145
open
146
onCancel={cancelAndClose}
147
title={
148
<>
149
<Icon name="plus-circle" />{" "}
150
{intl.formatMessage(
151
{
152
id: "account.ssh-key-adder.title",
153
defaultMessage: "Add an <A>SSH key</A>",
154
},
155
{
156
A: (c) => (
157
<A href="https://doc.cocalc.com/account/ssh.html">{c}</A>
158
),
159
},
160
)}
161
</>
162
}
163
style={style}
164
footer={[
165
<Button onClick={() => cancelAndClose()} key="close">
166
{intl.formatMessage(labels.cancel)}
167
</Button>,
168
<Button
169
key="add"
170
type="primary"
171
onClick={submit_form}
172
disabled={keyValue.length < 10}
173
>
174
{addKey}
175
</Button>,
176
]}
177
>
178
{extra && extra}
179
<div>
180
Title
181
<Input
182
id="ssh-title"
183
value={keyTitle}
184
onChange={(e) => setKeyTitle(e.target.value)}
185
placeholder={intl.formatMessage({
186
id: "account.ssh-key-adder.placeholder",
187
defaultMessage:
188
"Choose a name for this ssh key to help you keep track of it...",
189
})}
190
/>
191
<div style={{ marginTop: "15px" }}>
192
Key
193
<Input.TextArea
194
value={keyValue}
195
rows={8}
196
placeholder={`Begins with ${ALLOWED_SSH_TYPES_DESCRIPTION}`}
197
onChange={(e) => setKeyValue((e.target as any).value)}
198
onKeyDown={(e) => {
199
if (e.keyCode == 13) {
200
e.preventDefault();
201
submit_form();
202
}
203
}}
204
style={{ resize: "vertical" }}
205
/>
206
</div>
207
</div>
208
209
<ShowError
210
error={error}
211
setError={setError}
212
style={{ marginTop: "15px" }}
213
/>
214
</Modal>
215
</>
216
);
217
}
218
219