Fix/tap hold wake and remount - #13
Merged
Merged
Conversation
aide-user was a bare cookie carrying an account id. Setting it to somebody else's id made you that account: no password, no session, and nothing in any route to get past, because every route trusts whatever userIdFrom() returns. Account ids are eight hex characters and appear in ordinary responses, so this was not a theoretical leak away from being an account takeover of anyone whose id you had seen. It carries the same HMAC as the login session now, with the id inside the signed payload so a signature cannot be lifted from one account onto another. It still confers a weaker identity — no password was ever presented for it — but it can no longer be invented, only replayed by whoever already holds the cookie. Both cookies are HttpOnly, both expire, and both are Secure outside development: they are bearer tokens for an account that can move money, and they must not travel in clear text. The signing key no longer falls back to a string in this repository. A default that ships here is a default an attacker also has, and with it they can mint a cookie for any account on the platform — so in production a missing SESSION_SECRET now refuses to start instead. It also no longer falls back to MONNIFY_SECRET_KEY: reusing a payment provider's credential to sign cookies means one leak costs both. DEPLOY NOTE: SESSION_SECRET must be set in the environment before this ships, or the app will not boot. Existing cookies are invalidated by the new format, so everyone signs in again once.
lib/store/applications.ts began with `const owner = () => worker.id`, and every function in it called that instead of asking who was signed in. So there was, in effect, one worker. Your application list was the demo worker's. Applying applied as them. An employer reviewing applicants saw that account's name, skills and bio attached to whatever had been applied for, and hiring, rejecting or marking paid acted on them no matter who had actually applied. Payment coverage checked their wallet, so one person's confirmed money could release another person's gig. The same constant appeared again in the external-jobs route, where one worker's web-scan results were everyone's. The account is a parameter everywhere now. That is the point: leaving it out is a type error rather than a silent fallback to the wrong person. Employers act on a named applicant, resolved through a new by_job index — the one you named, the only one there is, or a refusal asking which. Message threads were worse, because the system prompt tells employers to send credentials through them. The route asked `acc.role === "worker"` and returned "worker", making every worker a party to every gig's conversation, and the agent tools carried their own copy of the same check. Both now ask whether this account is THIS thread's hired worker, or the employer who actually posted the gig. Ownership no longer rests on display names, which two accounts can share. Gigs record employerAccountId and messages record authorAccountId; where neither exists — the seeded demo gigs, rows written before these fields — the checks fall back to the old name comparison for reads and refuse outright for writes, because refusing is the safe direction. The Convex layer already keyed applications by accountId. None of this needed new storage; it needed the server to stop answering "who?" with a constant.
Three things the platform could do in one direction only. Each one is a write, so each one asks the same question first: is this yours? Withdrawing an application is allowed while it is still just an application. Once the assessment clock has started it is refused — an attempt row means the questions have been handed out, and deleting the record would be a way to quietly retake a test meant to be taken once. Cancelled applications are refused too, or the permanent lockout could be erased by withdrawing and applying again. Removing a gig is refused once anyone has been hired or paid on it: at that point the gig is the record of work that was agreed, and taking it away would strand the worker's application and their onboarding thread with nothing to point at. Pending applications go with the gig, because an application to a listing that no longer exists is a job the worker can never hear about again. The employer is told the count before they confirm, aloud as well as on screen. Messages can only be deleted by the account that wrote them, checked against the stored author id rather than a display name. Rows written before that field existed carry no author, so nobody can delete them — refusing is the safe direction when ownership is unknown. All three are reachable by voice as well as by button, since a worker who cannot see the screen cannot press "Withdraw". Refusals are spoken, not just displayed: a button that silently does nothing is worse than one that is not there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.