HeyPuter/puter · critical · HttpError

unauthorized

unauthorized

Error message

Authentication required

What it means

NotificationDriver's #requireUserActor() pulls the actor from Context (ALS); if there is no actor at all, it throws 401 unauthorized. Every notification method runs this first, so an unauthenticated driver/call hits it immediately. This is distinct from the subsequent 403s for missing user or app-actor access.

Source

Thrown at src/backend/drivers/notification/NotificationDriver.ts:210

        if (!uid)
            throw new HttpError(400, 'Missing `uid`', {
                legacyCode: 'bad_request',
            });
        const ok = await this.stores.notification.markAcknowledged(
            uid,
            actor.user.id,
        );
        return { success: ok };
    }

    // -- Permissions -------------------------------------------------

    #requireUserActor(): Actor & {
        user: { id: number; uuid: string; username: string };
    } {
        const actor = Context.get('actor') as Actor | undefined;
        if (!actor)
            throw new HttpError(401, 'Authentication required', {
                legacyCode: 'unauthorized',
            });
        if (!actor.user?.id)
            throw new HttpError(403, 'User actor required', {
                legacyCode: 'forbidden',
            });
        // App-under-user actors are not allowed for notifications.
        if (actor.app)
            throw new HttpError(403, 'App actors cannot access notifications', {
                legacyCode: 'forbidden',
            });
        return actor as Actor & {
            user: { id: number; uuid: string; username: string };
        };
    }

    // -- Serialization -----------------------------------------------

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Ensure the request carries a valid user access token before calling notification methods.
  2. Re-authenticate / refresh the token if it has expired.
  3. For server-internal callers, set the actor in Context (or use a system token that resolves to one) before invoking the driver.

Example fix

// before — no token on the request
await fetch('/drivers/call', { method:'POST', body: JSON.stringify({ interface:'puter-notifications', method:'select' }) });

// after — attach a valid token
await fetch('/drivers/call', {
  method:'POST',
  headers: { Authorization:`Bearer ${token}` },
  body: JSON.stringify({ interface:'puter-notifications', method:'select' }),
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure a valid token is attached before calling notification methods.
if (!token) {
  throw new Error('authentication required for notifications');
}
await callWithAuth(token);

Try / catch

try {
  await notifications.select({});
} catch (e) {
  if (e.status === 401 && e.code === 'unauthorized') {
    // session expired — re-authenticate, then retry
    await reauth();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any puter-notifications method (create/read/select/mark_shown/mark_acknowledged) without an authenticated session in the request context — e.g. a /drivers/call with no or invalid token, or an internal invocation that forgot to set the actor.

Common situations: Expired or missing access token; token not attached to the request; calling the driver directly server-side without establishing a Context actor; session timed out in the GUI.

Understand the failure class

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/8ccf4e6cbcb431e3. Report an issue: GitHub.