simple authkey based authentication between electron and local-server

This commit is contained in:
sawka 2022-12-20 16:22:05 -08:00
parent f0b9bc6eb8
commit 71980589bb
9 changed files with 61 additions and 12 deletions

View File

@ -60,7 +60,7 @@ node_modules/.bin/webpack --config webpack.electron.prod.js
(cd ../mshell; GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-darwin.arm64 main-mshell.go)
(cd ../mshell; GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-linux.amd64 main-mshell.go)
(cd ../mshell; GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-linux.arm64 main-mshell.go)
(cd ../sh2-server; GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/scripthaus-local-server cmd/main-server.go)
(cd ../sh2-server; GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/prompt-local-server cmd/main-server.go)
node_modules/.bin/electron-forge make
```

View File

@ -9,12 +9,15 @@ import {handleJsonFetchResponse} from "./util";
import * as winston from "winston";
import * as util from "util";
import {sprintf} from "sprintf-js";
import {v4 as uuidv4} from "uuid";
const PromptAppPathVarName = "PROMPT_APP_PATH";
const AuthKeyFile = "prompt.authkey";
let isDev = (process.env.PROMPT_DEV != null);
let scHome = getPromptHomeDir();
ensureDir(scHome);
let DistDir = (isDev ? "dist-dev" : "dist");
let GlobalAuthKey = "";
// these are either "darwin/amd64" or "darwin/arm64"
// normalize darwin/x64 to darwin/amd64 for GOARCH compatibility
@ -94,6 +97,22 @@ function ensureDir(dir) {
fs.mkdirSync(dir, {recursive: true, mode: 0o700});
}
function readAuthKey() {
let homeDir = getPromptHomeDir();
let authKeyFileName = path.join(homeDir, AuthKeyFile);
if (!fs.existsSync(authKeyFileName)) {
let authKeyStr = String(uuidv4());
fs.writeFileSync(authKeyFileName, authKeyStr, 0o600);
return authKeyStr;
}
let authKeyData = fs.readFileSync(authKeyFileName);
let authKeyStr = String(authKeyData);
if (authKeyStr == null || authKeyStr == "") {
throw new Error("cannot read authkey");
}
return authKeyStr.trim();
}
let app = electron.app;
app.setName("Prompt");
@ -248,7 +267,8 @@ function mainResizeHandler(e) {
console.log("resize/move", win.getBounds());
let winSize = {width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x};
let url = "http://localhost:8080/api/set-winsize";
fetch(url, {method: "post", body: JSON.stringify(winSize)}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => {
let fetchHeaders = getFetchHeaders();
fetch(url, {method: "post", body: JSON.stringify(winSize), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => {
console.log("error setting winsize", err)
});
}
@ -302,6 +322,11 @@ electron.ipcMain.on("get-id", (event) => {
return;
});
electron.ipcMain.on("get-authkey", (event) => {
event.returnValue = GlobalAuthKey;
return;
});
electron.ipcMain.on("local-server-status", (event) => {
event.returnValue = (localServerProc != null);
return;
@ -327,9 +352,16 @@ function getContextMenu() : any {
return menu;
}
function getFetchHeaders() {
return {
"x-authkey": GlobalAuthKey,
};
}
function getClientData() {
let url = "http://localhost:8080/api/get-client-data";
return fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
let fetchHeaders = getFetchHeaders();
return fetch(url, {headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
if (data == null) {
return null;
}
@ -422,6 +454,7 @@ async function sleep(ms) {
// ====== MAIN ====== //
(async () => {
GlobalAuthKey = readAuthKey();
try {
await runLocalServer();
}

View File

@ -298,7 +298,8 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
this.rtnStateDiffFetched = true;
let usp = new URLSearchParams({sessionid: line.sessionid, cmdid: line.cmdid});
let url = "http://localhost:8080/api/rtnstate?" + usp.toString();
fetch(url).then((resp) => {
let fetchHeaders = GlobalModel.getFetchHeaders();
fetch(url, {headers: fetchHeaders}).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for /api/rtnstate: %d %s", resp.status, resp.statusText));
}

View File

@ -92,6 +92,7 @@ type KeyModsType = {
type ElectronApi = {
getId : () => string,
getAuthKey : () => string,
getLocalServerStatus : () => boolean,
restartLocalServer : () => boolean,
onTCmd : (callback : (mods : KeyModsType) => void) => void,
@ -1543,10 +1544,12 @@ class Model {
debugCmds : number = 0;
debugSW : OV<boolean> = mobx.observable.box(false);
localServerRunning : OV<boolean>;
authKey : string;
constructor() {
this.clientId = getApi().getId();
this.ws = new WSControl(this.clientId, (message : any) => this.runUpdate(message, false));
this.authKey = getApi().getAuthKey();
this.ws = new WSControl(this.clientId, this.authKey, (message : any) => this.runUpdate(message, false));
this.ws.reconnect();
this.inputModel = new InputModel();
let isLocalServerRunning = getApi().getLocalServerStatus();
@ -1565,6 +1568,12 @@ class Model {
document.addEventListener("keydown", this.docKeyDownHandler.bind(this));
}
getFetchHeaders() : Record<string, string> {
return {
"x-authkey": this.authKey,
};
}
docKeyDownHandler(e : any) {
if (isModKeyPress(e)) {
return;
@ -1979,7 +1988,8 @@ class Model {
}
}
let url = sprintf("http://localhost:8080/api/run-command");
fetch(url, {method: "post", body: JSON.stringify(cmdPk)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
let fetchHeaders = this.getFetchHeaders();
fetch(url, {method: "post", body: JSON.stringify(cmdPk), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
mobx.action(() => {
let update = data.data;
if (update != null) {
@ -2062,7 +2072,8 @@ class Model {
this.windows.set(newWin.sessionId + "/" + newWin.windowId, newWin);
let usp = new URLSearchParams({sessionid: newWin.sessionId, windowid: newWin.windowId});
let url = new URL("http://localhost:8080/api/get-window?" + usp.toString());
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
let fetchHeaders = GlobalModel.getFetchHeaders();
fetch(url, {headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
if (data.data == null) {
console.log("null window returned from get-window");
return;

View File

@ -2,6 +2,7 @@ let {contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld("api", {
getId: () => ipcRenderer.sendSync("get-id"),
getAuthKey: () => ipcRenderer.sendSync("get-authkey"),
getLocalServerStatus: () => ipcRenderer.sendSync("local-server-status"),
restartLocalServer: () => ipcRenderer.sendSync("restart-server"),
onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),

View File

@ -4,7 +4,6 @@ import {createRoot} from 'react-dom/client';
import {sprintf} from "sprintf-js";
import {Terminal} from 'xterm';
import {Main} from "./main";
import {WSControl} from "./ws";
import {GlobalModel} from "./model";
import {v4 as uuidv4} from "uuid";
@ -43,4 +42,4 @@ document.addEventListener("DOMContentLoaded", () => {
(window as any).mobx = mobx;
(window as any).sprintf = sprintf;
console.log("SCRIPTHAUS", VERSION)
console.log("PROMPT", VERSION)

View File

@ -245,7 +245,8 @@ class TermWrap {
this.terminal.reset();
let url = this._getReloadUrl();
let ptyOffset = 0;
fetch(url).then((resp) => {
let fetchHeaders = GlobalModel.getFetchHeaders();
fetch(url, {headers: fetchHeaders}).then((resp) => {
if (!resp.ok) {
mobx.action(() => { this.loadError.set(true); })();
this.dataUpdates = [];

View File

@ -203,6 +203,7 @@ type WatchScreenPacketType = {
sessionid : string,
screenid : string,
connect : boolean,
authkey : string,
};
type TermWinSize = {

View File

@ -15,10 +15,12 @@ class WSControl {
watchSessionId : string = null;
watchScreenId : string = null;
wsLog : mobx.IObservableArray<string> = mobx.observable.array([], {name: "wsLog"})
authKey : string;
constructor(clientId : string, messageCallback : (any) => void) {
constructor(clientId : string, authKey : string, messageCallback : (any) => void) {
this.messageCallback = messageCallback;
this.clientId = clientId;
this.authKey = authKey;
this.open = mobx.observable.box(false, {name: "WSOpen"});
setInterval(this.sendPing, 5000);
}
@ -174,7 +176,7 @@ class WSControl {
}
sendWatchScreenPacket(connect : boolean) {
let pk : WatchScreenPacketType = {"type": "watchscreen", connect: connect, sessionid: null, screenid: null};
let pk : WatchScreenPacketType = {"type": "watchscreen", connect: connect, sessionid: null, screenid: null, authkey: this.authKey};
if (this.watchSessionId != null) {
pk.sessionid = this.watchSessionId;
}