gitroomhq/postiz-app · error · HttpException

Unauthorized

Error message

Unauthorized

What it means

The admin controller's assertSuperAdmin guard throws HttpException('Unauthorized', 400) when the authenticated user lacks the isSuperAdmin flag. It protects all super-admin-only endpoints (listErrors, listPlatforms, getStats). Note the status code is 400 (Bad Request) even though the message says Unauthorized, which is misleading.

Solutions

  1. Verify the user row in the database actually has isSuperAdmin=true (User table) and re-login
  2. For self-hosted setups, set the IS_SUPER_ADMIN env variable to your email/ID before registering so the first user is promoted
  3. If you are the admin but still blocked, clear cookies/session and re-authenticate so GetUserFromRequest resolves the full user
  4. If you maintain the code, consider throwing 401/403 instead of 400 for semantic correctness

Example fix

// before
if (!user?.isSuperAdmin) {
  throw new HttpException('Unauthorized', 400);
}
// after
if (!user?.isSuperAdmin) {
  throw new HttpException('Unauthorized', HttpStatus.FORBIDDEN);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const me = await api.getMe();
if (!me?.isSuperAdmin) {
  throw new Error('This action requires a super admin account');
}

Type guard

const isSuperAdmin = (u: User | null | undefined): u is User & { isSuperAdmin: true } =>
  Boolean(u?.isSuperAdmin);

Try / catch

try { await adminApi.getStats(); } catch (e) { if (e instanceof HttpException && e.message === 'Unauthorized') showPermissionError(); else throw e; }

Prevention

When it happens

Trigger: Calling GET /admin/errors, /admin/platforms or /admin/stats with a session/user whose isSuperAdmin property is false or undefined. Happens for regular org users, expired sessions that resolve to a partial user object, or self-hosted instances where no super admin was configured.

Common situations: Self-hosting Postiz without setting IS_SUPER_ADMIN for the first user; logging in with a normal account and navigating to admin routes; token from a user record where isSuperAdmin was never set in the database.

Understand the failure class

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/5860fe16b79997c6. Report an issue: GitHub.

Appendix: source

Thrown at apps/backend/src/api/routes/admin.controller.ts:24

} from '@nestjs/common';
import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';
import { User } from '@prisma/client';
import { ApiTags } from '@nestjs/swagger';
import { ErrorsService } from '@gitroom/nestjs-libraries/database/prisma/errors/errors.service';
import { AdminStatsService } from '@gitroom/nestjs-libraries/database/prisma/admin-stats/admin-stats.service';
import dayjs from 'dayjs';

@ApiTags('Admin')
@Controller('/admin')
export class AdminController {
  constructor(
    private _errorsService: ErrorsService,
    private _adminStatsService: AdminStatsService
  ) {}

  private assertSuperAdmin(user: User) {
    if (!user?.isSuperAdmin) {
      throw new HttpException('Unauthorized', 400);
    }
  }

  @Get('/errors')
  async listErrors(
    @GetUserFromRequest() user: User,
    @Query('page') page?: string,
    @Query('limit') limit?: string,
    @Query('platform') platform?: string,
    @Query('email') email?: string,
    @Query('unknownFirst') unknownFirst?: string
  ) {
    this.assertSuperAdmin(user);
    return this._errorsService.listErrors({
      page: page ? parseInt(page, 10) : 0,
      limit: limit ? parseInt(limit, 10) : 20,
      platform: platform || undefined,
      email: email || undefined,

View on GitHub (pinned to 0f1647f749)