yikart/AiToEarn · error · UnauthorizedException

Unauthorized

Error message

Unauthorized

What it means

The request-context interceptor stores the authenticated user in AsyncLocalStorage per request. The getUser() helper throws Nest's UnauthorizedException (401) when the store has no user, i.e. the request reached a protected endpoint without a valid authenticated session.

Source

Thrown at project/aitoearn-backend/libs/common/src/interceptors/request-context.interceptor.ts:41

const SUPPORTED_LANGUAGES: Locale[] = ['en-US', 'zh-CN']

export function getLocale(): Locale {
  return requestContext.getStore()?.locale || 'en-US'
}

export function getRequestContext(): RequestContextStore | undefined {
  return requestContext.getStore()
}

/**
 * Get authenticated user from request context.
 * Throws UnauthorizedException if user is not authenticated.
 * Use this for protected endpoints that require authentication.
 */
export function getUser(): TokenInfo {
  const user = requestContext.getStore()?.user
  if (!user) {
    throw new UnauthorizedException()
  }
  return user
}

/**
 * Get authenticated user from request context, or undefined if not authenticated.
 * Does not throw. Use this for public endpoints that optionally use user info.
 */
export function getUserOptional(): TokenInfo | undefined {
  return requestContext.getStore()?.user
}

@Injectable()
export class RequestContextInterceptor implements NestInterceptor {
  public intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const locale = this.parseLocale(context)
    const user = this.extractUser(context)
    return requestContext.run({ locale, user }, () => next.handle())

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Attach a valid Authorization: Bearer <token> header to the request
  2. Re-authenticate or refresh the token if expired
  3. Verify the auth middleware/guard runs and populates requestContext.user for this route
  4. Ensure the API key/token matches the environment (CN key with .cn URLs, international key with .ai URLs)

Example fix

// before
const res = await fetch('https://aitoearn.cn/api/v1/user');
// after
const res = await fetch('https://aitoearn.cn/api/v1/user', {
  headers: { Authorization: `Bearer ${token}` },
});
Defensive patterns

Strategy: validation

Validate before calling

const headers = { Authorization: `Bearer ${token}` };
if (!token) throw new Error('Missing auth token: login first');
const res = await fetch(url, { headers });
if (res.status === 401) await reauthenticate();

Type guard

function hasUser(ctx?: { user?: TokenInfo }): ctx is { user: TokenInfo } {
  return !!ctx?.user;
}

Try / catch

try {
  const data = await api.call();
} catch (e) {
  if (e.response?.status === 401) {
    await refreshTokenOrLogin();
    return api.call();
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling an endpoint that calls getUser() without a Bearer token, with an expired/invalid token, or the auth guard/context middleware did not run before the interceptor populated the context.

Common situations: Missing Authorization header in API clients; expired API keys; third-party calls (e.g. webhooks, MCP) that bypass the auth middleware; environment mismatch where a token issued for one environment is used against another (aitoearn.cn vs aitoearn.ai).

Understand the failure class

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/a1ced9ba422dc310. Report an issue: GitHub.