Trust Is a Boundary, Not a Badge
OpenStock has a login flow, protected pages, persistent watchlists, scheduled alerts, external data providers, and open-source licensing. Those facts are relevant to trust, but they are not interchangeable proof of it. The question here is narrower and more useful: which boundaries protect personal state, which claims are supported by the captured implementation, and which outcomes require runtime or deployment evidence? That distinction separates authentication from authorization, detection from delivery, and source availability from dependency independence.
Authentication proves identity at some boundaries
Authentication answers, “Who is this user?” Better Auth is configured with a MongoDB adapter and email/password accounts. The application sets password-length and composition rules, supports automatic sign-in, and connects password-reset handling to email. The middleware checks for a session cookie and redirects requests without one away from protected paths, while allowing public authentication pages and static or framework resources to remain reachable.
The authenticated root layout and watchlist page add server-side session lookups. The layout obtains the current session before rendering the main application shell. The watchlist page redirects if no session exists, then takes the user ID from the session before loading watchlist items and alerts. These checks establish a normal route-level boundary: a person who reaches the page through the ordinary flow is served a personal context rather than an arbitrary user’s list.
That is meaningful, but it is not the whole authorization story. Authorization answers a different question: “Is this authenticated caller allowed to perform this specific action on this specific record?” A route can be protected while an action invoked from that route still accepts an identifier supplied by the caller. A session can be valid while a mutation does not verify that the target record belongs to that session’s user.
The captured watchlist actions illustrate the distinction. Adding and removing items take a userId argument, and normal retrieval filters records by that user ID. The interface can pass the expected ID from the authenticated context. The functions themselves do not demonstrate that they independently obtain or verify the current session before changing data. The alert actions have a related boundary: creation includes a user ID, but deletion and active-state updates operate by alert ID without an evident ownership check in the action body.
This is a static code-review observation, not a claim that a deployed instance is exploitable. Middleware, framework behavior, deployment controls, and calling conventions may add context not visible in the repository. The precise conclusion is that the source demonstrates page-level authentication and ordinary user-scoped queries, but not defense-in-depth ownership checks at every mutation boundary. Personal state is only as isolated as both layers make it.
Personal records need an explicit ownership model
A watchlist record contains a user ID, symbol, company label, and added timestamp. An alert contains a user ID, symbol, target price, condition, active and triggered flags, expiry, and creation metadata. These fields express intended ownership, but ownership becomes a security property only when every read and write enforces the relationship.
For reads, the model is comparatively clear. A user’s watchlist query searches by user ID. The normal page gets that ID from the session. The compound unique index prevents duplicate symbols for one user without preventing another user from saving the same public symbol. This is a sensible data model for personal attention: the company is public, the relationship is private.
For writes, the trust boundary is more distributed. A client component can call an action with a user ID or alert ID.