import fs from "fs";
import { spawn } from "node:child_process";
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
import { User } from "@prisma/client";
import type { UsersServiceDeps } from "./types";
import { spawnConcatChunks } from "../../utils/server";
const SSH_RSA_KEY_REGEXP =
/^ssh-rsa AAAA[0-9A-Za-z+\/]+[=]{0,3} ([^@]+@[^@]+)$/i;
const addUserSSHKey: ServiceMethodFactory<
UsersServiceDeps,
[User, string, string],
Promise<boolean>
> = ({ request }) => {
return async (user, name, key) => {
if (key.match(SSH_RSA_KEY_REGEXP) == null) {
throw new Error(
"Invalid public key. Please provide a valid SSH RSA public key.",
);
}
const existingKey = await request.prisma.userSSHKey.findFirst({
where: {
key: key,
},
});
if (existingKey != null) {
throw new Error(
"Public key is already registered. Please use another one.",
);
}
const fingerprintSHA256Process = spawn("ssh-keygen", ["-l", "-f", "-"], {
env: { LANG: "C" },
});
fingerprintSHA256Process.stdin.write(key);
const fingerprintSHA256 = await spawnConcatChunks(fingerprintSHA256Process);
const fingerprintMD5Process = spawn(
"ssh-keygen",
["-lf", "-E", "md5", "-"],
{ env: { LANG: "C" } },
);
fingerprintMD5Process.stdin.write(key);
const fingerprintMD5 = await spawnConcatChunks(fingerprintMD5Process);
console.log("fingerprints:", {
sha256: fingerprintSHA256,
md5: fingerprintMD5,
});
const userKey = await request.prisma.userSSHKey.create({
data: {
name: name,
user: {
connect: {
id: user.id,
},
},
key: key,
key_fingerprint: fingerprintSHA256,
revoked: false,
lastUsedAt: null,
},
});
if (userKey == null) {
return false;
}
let line = "";
line += `environment="KEY=${key},KEY_FINGERPRINT=${fingerprintSHA256}",`;
line += `command="ssh_command ${user.username}",`;
line += "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ";
line += `${key}\n`;
fs.appendFileSync("/home/git/.ssh/authorized_keys", line, {
encoding: "utf8",
});
return true;
};
};
export default addUserSSHKey;