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.

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)

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.