mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-20 05:11:48 +00:00
fix(cursor): guard raw-settings save race and enforce daemon preconditions
This commit is contained in:
1 parent
0e55db88a5
commit
9a76f866b0
5 files changed
+157
-3
No files matched your search
@@ -20,6 +20,44 @@ import cursorSettingsRoutes from './cursor-settings-routes';
|
||||
|
||||
const router = Router();
|
||||
|
||||
interface DaemonStartPreconditionInput {
|
||||
enabled: boolean;
|
||||
authenticated: boolean;
|
||||
tokenExpired?: boolean;
|
||||
}
|
||||
|
||||
interface DaemonStartPreconditionError {
|
||||
status: number;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export function getDaemonStartPreconditionError(
|
||||
input: DaemonStartPreconditionInput
|
||||
): DaemonStartPreconditionError | null {
|
||||
if (!input.enabled) {
|
||||
return {
|
||||
status: 400,
|
||||
error: 'Cursor integration is disabled. Enable it before starting daemon.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!input.authenticated) {
|
||||
return {
|
||||
status: 401,
|
||||
error: 'Cursor authentication required. Import credentials before starting daemon.',
|
||||
};
|
||||
}
|
||||
|
||||
if (input.tokenExpired) {
|
||||
return {
|
||||
status: 401,
|
||||
error: 'Cursor credentials expired. Re-authenticate before starting daemon.',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mount settings sub-routes
|
||||
router.use('/settings', cursorSettingsRoutes);
|
||||
|
||||
@@ -125,6 +163,21 @@ router.get('/models', async (_req: Request, res: Response): Promise<void> => {
|
||||
router.post('/daemon/start', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const cursorConfig = getCursorConfig();
|
||||
const authStatus = checkAuthStatus();
|
||||
const preconditionError = getDaemonStartPreconditionError({
|
||||
enabled: cursorConfig.enabled,
|
||||
authenticated: authStatus.authenticated,
|
||||
tokenExpired: authStatus.expired ?? false,
|
||||
});
|
||||
|
||||
if (preconditionError) {
|
||||
res.status(preconditionError.status).json({
|
||||
success: false,
|
||||
error: preconditionError.error,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await startDaemon({
|
||||
port: cursorConfig.port,
|
||||
ghost_mode: cursorConfig.ghost_mode,
|
||||
|
||||
@@ -162,9 +162,17 @@ router.put('/raw', (req: Request, res: Response): void => {
|
||||
|
||||
const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
|
||||
|
||||
// Check for conflict if file exists and expectedMtime provided
|
||||
if (fs.existsSync(settingsPath) && expectedMtime) {
|
||||
// For existing files, expectedMtime is required to prevent blind overwrite races.
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
const stat = fs.statSync(settingsPath);
|
||||
if (typeof expectedMtime !== 'number' || !Number.isFinite(expectedMtime)) {
|
||||
res.status(409).json({
|
||||
error: 'File metadata not loaded. Refresh and retry.',
|
||||
mtime: stat.mtimeMs,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.abs(stat.mtimeMs - expectedMtime) > 1000) {
|
||||
res.status(409).json({ error: 'File modified externally', mtime: stat.mtimeMs });
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user