Welcome to The Crimson Market Developers Portal

Build powerful integrations with The Crimson Market API. Create OAuth applications, manage your credentials, and access our comprehensive documentation to bring trading functionality to your platform.

Developers Documentation

TCM OAuth Integration Docs

SDK-backed guidance for @crimsoncorp/oauth-react, plus portal-specific setup details for apps, scopes, and redirect registration.

Roles RBAC Integration

Request the OAuth roles scope, persist trusted platform roles, and map them to service-local permissions.

SDK snapshot 21344f2c

Use the OAuth roles scope when a service needs trusted TCM platform roles for local RBAC.

Contract

  • roles exposes platform roles from Portal user platformRoles.
  • roles does not expose tenant, community, or self-service role-management documents.
  • Returned roles are simple strings such as admin and tester.
  • Permissions are not sent through OAuth. Each service maps roles to local code-level permissions.
  • Unknown role names must be ignored by the service.

When the scope is granted, /oauth/userinfo includes:

{
  "sub": "user-id",
  "userName": "alice",
  "roles": ["admin"]
}

If the user has no platform roles, roles may be omitted or returned as an empty array.

OAuth Client Setup

Enable the roles scope on the OAuth client in the Developers portal. Keep profile enabled because it is required for normal user identity.

Recommended scope for platform RBAC services:

scope: "profile roles"

If the service also needs email or other fields, request the narrowest allowed scope set:

scope: "profile email roles"

The requested scopes must already be included in the client's allowed scopes, otherwise Portal rejects the authorization request.

Exchange Route

Persist userInfo.roles into the app-local session during the server exchange.

const route = createTcmOAuthExchangeRoute({
  oauth: {
    apiBaseUrl: process.env.TCM_OAUTH_API_URL!,
    clientId: process.env.NEXT_PUBLIC_TCM_CLIENT_ID!,
    clientSecret: process.env.TCM_OAUTH_CLIENT_SECRET!,
    callbackPath: "/auth/tcm/callback",
  },
  async onResolvedUser({ userInfo, tokenSet }) {
    const user = await upsertUserFromTcm(userInfo);

    return {
      body: { userId: user.id },
      session: {
        sub: user.id,
        roles: Array.isArray(userInfo.roles) ? userInfo.roles : [],
        accessToken: tokenSet.accessToken,
        tokenType: tokenSet.tokenType || "Bearer",
      },
    };
  },
  applySession(response, session) {
    sessionAdapter.apply(response, signSession(session));
  },
});

Do not depend on frontend state alone. Privileged API routes must read the trusted server session or refetch /oauth/userinfo server-side.

Service RBAC Helper

Define role-to-permission mapping in the service codebase.

export const ROLES = {
  ADMIN: "admin",
  TESTER: "tester",
} as const;

export const PERMISSIONS = {
  MANAGE_CAMPAIGNS: "manage_campaigns",
  REVIEW_PAYOUTS: "review_payouts",
  ACCESS_TEST_GAME: "access_test_game",
} as const;

export const ROLE_PERMISSIONS = {
  [ROLES.ADMIN]: [
    PERMISSIONS.MANAGE_CAMPAIGNS,
    PERMISSIONS.REVIEW_PAYOUTS,
    PERMISSIONS.ACCESS_TEST_GAME,
  ],
  [ROLES.TESTER]: [
    PERMISSIONS.ACCESS_TEST_GAME,
  ],
} as const;

Normalize roles before checking permissions:

function getEffectiveRoles(source: { roles?: unknown } = {}) {
  const roles = Array.isArray(source.roles) ? source.roles : [];
  const allowed = new Set(["admin", "tester"]);

  return Array.from(
    new Set(
      roles
        .map((role) => String(role || "").trim().toLowerCase())
        .filter((role) => allowed.has(role)),
    ),
  );
}

function hasPermission(source: { roles?: unknown }, permission: string) {
  return getEffectiveRoles(source).some((role) =>
    ROLE_PERMISSIONS[role as keyof typeof ROLE_PERMISSIONS]?.includes(permission as never),
  );
}

API Guard

Apply permission checks on every privileged API route.

export function requirePermission(request: Request, permission: string) {
  const session = readSignedSession(request);

  if (!session?.sub) {
    return {
      error: Response.json({ message: "Unauthorized" }, { status: 401 }),
    };
  }

  if (!hasPermission(session, permission)) {
    return {
      error: Response.json({ message: "Forbidden" }, { status: 403 }),
    };
  }

  return { session };
}

Use frontend checks only to improve UX:

  • hide admin links for non-admin users
  • show a signed-in but forbidden message for authenticated users without the role
  • never redirect an authenticated-but-forbidden user to login

Security Rules

  • Do not authorize platform admin routes from usernames, email allowlists, tenant roles, or community roles.
  • Do not mix tenant role-management documents into OAuth platform RBAC.
  • Do not persist permissions in Portal for this model; persist only platform roles in Portal and keep service permissions in code.
  • Do not grant access from roles unless the token was issued by TCM OAuth and the service requested the roles scope.
  • Re-check roles on server routes even when the UI already hides the action.

Related Docs