Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

User representations don't match #6299

@ocket8888

Description

@ocket8888

This Improvement request (usability, performance, tech debt, etc.) affects these Traffic Control components:

  • Traffic Ops
  • Documentation

Current behavior:

The representations of Users are different depending on the API endpoint used to view them, e.g. /user/current uses the field roleName to hold the name of the User's Role, while /users shows the same information in the rolename property.

The current model for users (API v3.0, 4.0 makes some structural changes in some contexts) is such that requests and responses have different "shapes" depending on the request method - and some fields are even renamed between requests and responses.

A TypeScript model of Users in APIv3
export interface PostRequestUser {
	addressLine1?: string | null;
	addressLine2?: string | null;
	city?: string | null;
	company?: string | null;
	confirmLocalPasswd: string;
	country?: string | null;
	email: string;
	fullName: string;
	gid?: number | null;
	id?: never;
	lastUpdated?: never;
	localPasswd: string;
	newUser?: boolean | null;
	phoneNumber?: string | null;
	postalCode?: string | null;
	publicSshKey?: string | null;
	registrationSent?: never;
	role: number;
	roleName?: never;
	rolename?: never;
	stateOrProvince?: string | null;
	tenant?: never;
	tenantId?: never;
	tenantID: number;
	uid?: number | null;
	username: string;
}

interface PutRequestNotChangingPasswordUser {
	addressLine1?: string | null;
	addressLine2?: string | null;
	city?: string | null;
	company?: string | null;
	confirmLocalPasswd?: never;
	country?: string | null;
	email: string;
	fullName: string;
	gid?: number | null;
	id?: never;
	lastUpdated?: never;
	localPasswd?: never;
	newUser?: boolean | null;
	phoneNumber?: string | null;
	postalCode?: string | null;
	publicSshKey?: string | null;
	registrationSent?: never;
	role: number;
	roleName?: never;
	rolename?: never;
	stateOrProvince?: string | null;
	tenant?: never;
	tenantId?: never;
	tenantID: number;
	uid?: number | null;
	username: string;
}

export type PutRequestUser = PostRequestUser | PutRequestNotChangingPasswordUser;

interface ResponseUser {
	addressLine1: string | null;
	addressLine2: string | null;
	city: string | null;
	company: string | null;
	country: string | null;
	email: string;
	fullName: string;
	gid: number | null;
	id: number;
	lastUpdated: Date;
	newUser: boolean | null;
	phoneNumber: string | null;
	postalCode: string | null;
	publicSshKey: string | null;
	registrationSent?: null | Date;
	role: number;
	stateOrProvince: null;
	tenant: string;
	tenantID?: never;
	tenantId: number;
	uid: number | null;
	username: string;
}

export interface PutOrPostResponseUser extends ResponseUser {
	/**
	 * This appears only in response to POST requests, or to PUT requests where
	 * the user's password was changed.
	 */
	confirmLocalPasswd?: string;
	rolename?: never;
	roleName: string;
}

export interface GetResponseUser extends ResponseUser {
	confirmLocalPasswd?: never;
	rolename: string;
	roleName?: never;
}

export type User = PutRequestUser | PostRequestUser | PutOrPostResponseUser | GetResponseUser;

export interface ResponseCurrentUser {
	addressLine1: string | null;
	addressLine2: string | null;
	city: string | null;
	company: string | null;
	country: string | null;
	email: string;
	fullName: string;
	gid: number | null;
	id: number;
	lastUpdated: Date;
	localUser: boolean;
	newUser: boolean;
	phoneNumber: string | null;
	postalCode: string | null;
	publicSshKey: string | null;
	role: number;
	roleName: string;
	stateOrProvince: string | null;
	tenant: string;
	tenantId: number;
	uid: number | null;
	username: string;
}

export interface RequestCurrentUser {
	addressLine1?: never;
	addressLine2?: never;
	city?: never;
	company?: never;
	country?: never;
	email?: never;
	fullName?: never;
	gid?: never;
	id?: never;
	lastUpdated?: never;
	localUser?: never;
	newUser?: never;
	phoneNumber?: never;
	postalCode?: never;
	publicSshKey?: never;
	role?: never;
	roleName?: never;
	stateOrProvince?: never;
	tenant?: never;
	tenantId?: never;
	uid?: never;
	username?: never;
}

export type CurrentUser = ResponseCurrentUser | RequestCurrentUser;
  • requests use tenantID, responses have tenantId
  • responses from POST requests and PUT requests where the password was changed include confirmLocalPasswd but GET representations never do
  • POST and PUT responses don't include registrationSent but GET requests always do
  • /users never shows localUser, but /user/current always does
  • POST and PUT requests allow newUser to be null, but that's actually not allowed in the database, and GET requests will always show an actual boolean value. Plus, if newUser was null in a POST or PUT to /users, it will be null in the response, but if it was null in a PUT to /user/current the response will properly show false like a subsequent GET
  • PUT to /user/current doesn't require any fields, not even those required by POST and PUT requests to /users, making it non-idempotent in violation of the HTTP spec
  • PUT to /user/current literally doesn't work at all, see PUT /user/current doesn't work #6367

and then of course we have the pervasive issue where things are allowed to be missing from the request as a synonym for "null", but that's more an issue just because in TS/JS undefined and null are different, but Go makes no such distinction without jumping through some encoding/json.RawMessage hoops.

New behavior:

Users should be represented consistently in a single way throughout the API.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Traffic Opsrelated to Traffic OpsauthenticationRelating to login, registration, passwords, tokens, etc.improvementThe functionality exists but it could be improved in some way.medium difficultythe estimated level of effort to resolve this issue is mediummedium impactimpacts a significant portion of a CDN, or has the potential to do so

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions