stop reading the owner's vnc password in the main process
The password lives in the owner's ~/.vnc, and the sidecar is the process that writes it — together with the rfbauth file x11vnc actually authenticates against. Officer read the plaintext half directly and answered with it before ever asking the sidecar, which is both a secret the proxy has no business opening and a way to hand out a password that no longer matches: if `passwd` went missing while `password` survived, officer kept serving the old plaintext and the desktop refused every login. `vnc:ensure-password` reconciles the pair, so ask it every time and delete the reader. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,19 @@
|
|||||||
import { createRouter } from '../../create-router';
|
import { createRouter } from '../../create-router';
|
||||||
import { getVncPassword } from './vnc-config';
|
|
||||||
import * as sidecar from '@@/sidecar-registry';
|
import * as sidecar from '@@/sidecar-registry';
|
||||||
|
|
||||||
export const desktopRouter = createRouter();
|
export const desktopRouter = createRouter();
|
||||||
|
|
||||||
// The desktop UI asks for the password before it can open the WebSocket — and that WebSocket is what
|
// The desktop UI asks for the password before it can open the WebSocket — and that WebSocket is what
|
||||||
// starts the VNC session. So this cannot wait for a session to exist: on a fresh install nothing has
|
// starts the VNC session. So this cannot wait for a session to exist: on a fresh install nothing has
|
||||||
// ever written the password, and answering "not configured" deadlocked the page permanently. Ask the
|
// ever written the password, and answering "not configured" deadlocked the page permanently.
|
||||||
// sidecar to provision it instead; it owns the .vnc directory and the call is idempotent.
|
//
|
||||||
|
// Officer does not read the password file itself. It lives in the owner's ~/.vnc, next to the rfbauth
|
||||||
|
// file x11vnc authenticates against, and the sidecar is the process that writes both — so it is the
|
||||||
|
// process that answers for them too. `vnc:ensure-password` is idempotent: it returns the existing
|
||||||
|
// pair when both files are already there, and provisions them when they are not.
|
||||||
desktopRouter.get('/vnc-password', async (ctx) => {
|
desktopRouter.get('/vnc-password', async (ctx) => {
|
||||||
const user = ctx.get('user');
|
const user = ctx.get('user');
|
||||||
|
|
||||||
const existing = await getVncPassword(user.email);
|
|
||||||
if (existing) return ctx.json({ password: existing });
|
|
||||||
|
|
||||||
if (!sidecar.isVncConnected()) {
|
if (!sidecar.isVncConnected()) {
|
||||||
return ctx.json({ error: 'VNC sidecar is not connected' }, 503);
|
return ctx.json({ error: 'VNC sidecar is not connected' }, 503);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { join } from 'node:path';
|
|
||||||
import { getOwnerHomeDir } from '@@/data-path';
|
|
||||||
|
|
||||||
const getVncDir = (email: string): string => join(getOwnerHomeDir(email), '.vnc');
|
|
||||||
|
|
||||||
export async function getVncPassword(email: string): Promise<string | null> {
|
|
||||||
const file = Bun.file(join(getVncDir(email), 'password'));
|
|
||||||
if (!(await file.exists())) return null;
|
|
||||||
return (await file.text()).trim();
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user