{"record":{"id":"84f6fb4fbac3f239","repo":"HeyPuter/puter","slug":"unauthorized-84f6fb","errorCode":"unauthorized","errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"HttpError","httpStatus":401,"severity":"error","filePath":"src/backend/controllers/fs/LegacyFSController.ts","lineNumber":1819,"sourceCode":"        const appUid = getString(body, 'app_uid');\n        if (!appUid)\n            throw new HttpError(400, '`app_uid` is required', {\n                legacyCode: 'bad_request',\n            });\n\n        const actorApp = (actor as { app?: { uid?: string } }).app;\n        if (!actorApp?.uid || actorApp.uid !== appUid) {\n            throw new HttpError(\n                403,\n                'Only the app itself may request its root dir',\n                { legacyCode: 'forbidden' },\n            );\n        }\n        const userId = this.#getActorUserId(req);\n        const username = (actor as { user?: { username?: string } }).user\n            ?.username;\n        if (!username)\n            throw new HttpError(401, 'Unauthorized', {\n                legacyCode: 'unauthorized',\n            });\n\n        const rootPath = `/${username}/AppData/${appUid}`;\n        // Auto-create the AppData/<uid> tree on first call.\n        const entry = await this.services.fs.mkdir(userId, {\n            path: rootPath,\n            createMissingParents: true,\n        });\n        res.json(await toLegacyEntry(this.clients.event, entry));\n    };\n\n    /**\n     * POST /auth/check-app-acl — check whether an app has a given mode of\n     * access to a subject FS entry.\n     */\n    checkAppAcl = async (req: Request, res: Response): Promise<void> => {\n        this.#requireActor(req);","sourceCodeStart":1801,"sourceCodeEnd":1837,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/fs/LegacyFSController.ts#L1801-L1837","documentation":"Thrown by POST /auth/request-app-root-dir after the app ownership check passes, when the app-under-user actor has no user.username. The root path is built as `/<username>/AppData/<appUid>`, so a missing username means the path cannot be constructed and the session is incomplete — rejected as unauthorized.","triggerScenarios":"An app token minted without a bound user context (app present, user missing); a malformed actor in tests; a token that lost its user binding.","commonSituations":"App token generated by a non-user flow; partial actor object; user record missing username after a partial migration.","solutions":["Use an app-under-user token minted via getUserAppToken so the user (with username) is bound.","Verify the user record has a username before issuing the token.","Re-authenticate the user to refresh the session's user binding."],"exampleFix":"// before — app token with no user binding\nawait fetch('/auth/request-app-root-dir', { method:'POST', headers:{Authorization:'Bearer '+appOnlyToken}, body: JSON.stringify({ app_uid }) })\n// after — app-under-user token\nconst { token } = await puter.auth.getUserAppToken(app_uid)\nawait fetch('/auth/request-app-root-dir', { method:'POST', headers:{Authorization:'Bearer '+token}, body: JSON.stringify({ app_uid }) })","handlingStrategy":"validation","validationCode":"// ensure the app-under-user actor has a username before calling\nfunction assertAppHasUsername(actor) {\n  const u = actor?.user?.username;\n  if (typeof u !== 'string' || u.length === 0) {\n    throw new Error('App session lacks user.username; mint an app-under-user token');\n  }\n}","typeGuard":"/** @param {unknown} a @returns {a is { user: { username: string }, app: { uid: string } }} */\nfunction isAppUnderUserWithUsername(a) {\n  return !!a && typeof a === 'object' &&\n    typeof a.user?.username === 'string' && typeof a.app?.uid === 'string';\n}","tryCatchPattern":null,"preventionTips":["Mint the token via getUserAppToken so the user binding is complete.","Re-authenticate the user if the username binding is missing.","In tests, build the actor with a populated user.username."],"tags":["auth","app-session","app-root","unauthorized"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}