fix(security): owner auth gate, OAuth state cookie binding, 0600 token perms
- Add OWNER_SECRET-based session: signed HMAC cookie, /api/auth/owner login, requireOwner middleware. All Tesla routes refuse 401 without it. - Bind OAuth state to a SameSite=Lax httpOnly cookie at /start, validate match in /callback with constant-time compare. Refuses unmatched callbacks. - Token store now mkdir 0700, writeFile + rename atomic, mode 0600 with defensive chmod. Owner-only on disk. - VIN masked to last 4 in responses; partner-register no longer echoes raw Tesla body to clients; coord bounds checked on send-to-nav. - Client: useTesla also tracks owner status; Connect Tesla button opens an OwnerLoginModal when not authenticated, then continues to Tesla OAuth. Conscious deferrals: - Explicit CSRF tokens on POST routes: mitigated by SameSite=Lax cookies + same-origin CORS. Will revisit if cross-origin clients land. - At-rest token encryption: deferred for single-user app; tokens are on a 0700 Dokku volume readable only by the app uid. Will add AES-GCM if we multi-tenant.
This commit is contained in:
@@ -38,7 +38,8 @@ let writeLock: Promise<unknown> = Promise.resolve();
|
||||
async function load(): Promise<void> {
|
||||
if (loaded) return;
|
||||
try {
|
||||
await fs.mkdir(dataDir, { recursive: true });
|
||||
await fs.mkdir(dataDir, { recursive: true, mode: 0o700 });
|
||||
await fs.chmod(dataDir, 0o700).catch(() => {}); // tighten if it already existed
|
||||
const raw = await fs.readFile(tokenFile, 'utf8');
|
||||
cache = JSON.parse(raw);
|
||||
log.info({ file: tokenFile, users: Object.keys(cache).length }, 'Loaded Tesla tokens');
|
||||
@@ -50,11 +51,17 @@ async function load(): Promise<void> {
|
||||
}
|
||||
|
||||
async function persist(): Promise<void> {
|
||||
// Serialise writes so concurrent set/remove calls don't race.
|
||||
// Serialise writes so concurrent set/remove calls don't race. Write to a
|
||||
// tmpfile then rename for atomicity; owner-only perms via mode + chmod.
|
||||
writeLock = writeLock
|
||||
.then(async () => {
|
||||
await fs.mkdir(dataDir, { recursive: true });
|
||||
await fs.writeFile(tokenFile, JSON.stringify(cache, null, 2));
|
||||
await fs.mkdir(dataDir, { recursive: true, mode: 0o700 });
|
||||
await fs.chmod(dataDir, 0o700).catch(() => {});
|
||||
const tmp = `${tokenFile}.${process.pid}.tmp`;
|
||||
await fs.writeFile(tmp, JSON.stringify(cache, null, 2), { mode: 0o600 });
|
||||
await fs.chmod(tmp, 0o600).catch(() => {});
|
||||
await fs.rename(tmp, tokenFile);
|
||||
await fs.chmod(tokenFile, 0o600).catch(() => {});
|
||||
})
|
||||
.catch(err => log.error({ err: String(err) }, 'Failed to persist Tesla tokens'));
|
||||
await writeLock;
|
||||
|
||||
Reference in New Issue
Block a user