HeyPuter/puter · error · HttpError

unauthorized

unauthorized

Error message

Unauthorized

What it means

`POST /notif/mark-ack` could not resolve a user ID from `req.actor.user.id`. Although the route is decorated with `requireUserActor: true` (which should block unauthenticated requests earlier in the pipeline), this is a defensive secondary guard. If reached, the actor is missing or malformed.

Source

Thrown at src/backend/controllers/notification/NotificationController.ts:62

        // Fires per notification interaction, so the ceiling stays
        // generous — it is here to catch a loop, not to pace a user.
        rateLimit: {
            scope: 'notification-mark',
            limit: 300,
            window: 60_000,
            key: 'user',
        },
    })
    async markAck(req: Request, res: Response): Promise<void> {
        const uid = req.body?.uid;
        if (typeof uid !== 'string' || uid.length === 0) {
            throw new HttpError(400, '`uid` must be a non-empty string', {
                legacyCode: 'bad_request',
            });
        }
        const userId = req.actor?.user?.id;
        if (!userId)
            throw new HttpError(401, 'Unauthorized', {
                legacyCode: 'unauthorized',
            });

        const notifService = this.services.notification as unknown as
            NotificationService | undefined;
        if (notifService?.markAcknowledged) {
            await notifService.markAcknowledged(uid, userId);
        } else {
            // Fallback: direct store call if service isn't wired
            await (
                this.stores as Record<string, unknown> as {
                    notification: {
                        markAcknowledged: (
                            uid: string,
                            userId: number,
                        ) => Promise<boolean>;
                    };
                }

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Ensure the request includes a valid user session token or full-access API token.
  2. If this fires despite correct auth, check that the `requireUserActor` middleware is wired for this route.
  3. Re-authenticate to obtain a fresh token and retry.
  4. Verify the actor middleware populates `req.actor.user.id` for your token type.

Example fix

// before — calling without a valid session
await fetch('/api/notif/mark-ack', {
  method: 'POST',
  body: JSON.stringify({ uid }),
});

// after — include auth token
await fetch('/api/notif/mark-ack', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
  },
  body: JSON.stringify({ uid }),
});
Defensive patterns

Strategy: validation

Validate before calling

// Verify auth state before calling
if (!sessionToken) {
  redirectToLogin();
  return;
}
await fetch('/api/notif/mark-ack', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
  },
  body: JSON.stringify({ uid }),
});

Try / catch

try {
  await fetch('/api/notif/mark-ack', { /* ... with auth ... */ });
} catch (e) {
  if (e.code === 'unauthorized') {
    redirectToLogin();
  } else throw e;
}

Prevention

When it happens

Trigger: The route middleware that enforces `requireUserActor` was bypassed or misconfigured; the auth token resolved to an actor without a `user` property (e.g., an app-only actor); a session expired between middleware and handler execution.

Common situations: A misconfigured middleware chain that lets an anonymous request through; a stale or partially-valid token; an extension that overrode the actor shape; running tests without proper auth setup.

Understand the failure class

Related errors


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