Identity & Members

Users Service

The Users service manages user accounts in the biztechUsers DynamoDB table. Handlers are in services/users/handler.js.


Endpoints

MethodPathAuthHandlerDescription
POST/usersPubliccreateCreate a user
GET/users/check/{email}PubliccheckUserCheck whether a user record exists
GET/users/checkMembership/{email}PubliccheckUserMembershipCheck for a current-year membership row
GET/users/{email}CognitogetGet user record (see self pattern)
GET/usersCognitogetAllList all users (no handler-level admin check)
PATCH/users/{email}CognitoupdateUpdate a user
PATCH/users/favEvent/{email}CognitofavouriteEventToggle a favorite event
DELETE/users/{email}CognitodelDelete a user

GET /users/{email} Self vs. Admin Lookup

This endpoint has a special behavior: non-admin callers always get their own record, regardless of the {email} path param.

The handler reads the caller's email from the Cognito JWT claims:

let email = event.requestContext.authorizer.claims.email.toLowerCase()
// Only admins can override the email from path params:
if (
  email.endsWith('@ubcbiztech.com') &&
  isValidEmail(event.pathParameters.email)
) {
  email = event.pathParameters.email
}

This is why the frontend and middleware call GET /users/self. The string "self" is not a real path segment - it's a placeholder that gets ignored. The handler resolves identity from the Cognito token, so any value in {email} works for non-admins. Admins can pass a real email to fetch any user.


User Record Shape

Table: biztechUsers

Purpose: stable account identity. This table is now the source of truth for profileID.

DynamoDB enforces only the id key. Other fields describe the effective write-path shape and may be absent unless noted otherwise.

FieldTypeKey / IndexDescription
idStringPKLowercase email.
profileIDStringGSI profileID-indexStable profile ID. Used to find the user's profile.
fnameString-First name.
lnameString-Last name.
educationString-School/education status.
studentIdString/Number-Student number.
facultyString-Faculty.
majorString-Major/program.
yearString/Number-Year of study.
genderString-Existing user write paths store pronouns/gender under gender; profiles use pronouns.
dietString-Dietary restrictions.
adminBoolean-Auto-set for ubcbiztech.com; immutable via user API.
favedEventsID;yearStringSet-Favorite events in "eventId;year" format.
createdAtNumber-Epoch ms.
updatedAtNumber-Epoch ms.

Indexes

IndexKeyPurpose
table primary keyidEmail to user.
profileID-indexprofileIDKEYS_ONLY projection; profile ID to user/email.

Immutable Fields

The admin field cannot be changed via PATCH /users/{email}. The handler also rejects the legacy isMember property, which is no longer part of the user contract. Older DynamoDB rows may still physically contain isMember, but current membership logic does not use it. See constants/tables.js -> IMMUTABLE_USER_PROPS.


POST /users Create User

Auth: Public (no Cognito required)

Creates a user record. The email is validated and lowercased.

  • If the email ends with @ubcbiztech.com, admin is set to true automatically
  • Returns 409 if a user with this email already exists (enforced by DynamoDB ConditionExpression: attribute_not_exists(id))
  • Returns 201 with the created user params on success
  • Optional favedEventsArray (array of strings, validated for uniqueness, stored as DynamoDB StringSet)

GET /users/check/{email}

Auth: Public

Returns true (200) if a user record exists, false (200) if not. Used before registration to check whether an account needs to be created.


GET /users/checkMembership/{email}

Auth: Public

Returns true when a row exists for the lowercase email in the current biztechMembers2027 table, otherwise false.


PATCH /users/{email} Update User

Auth: Cognito

Updates user fields. The handler validates the user exists first. If the request body contains any field in IMMUTABLE_USER_PROPS (currently ["admin"]), the handler throws an error - it does not silently strip the fields.


PATCH /users/favEvent/{email} Toggle Favorite Event

Auth: Cognito

Toggles a favorite event on or off.

Request body:

FieldTypeRequiredDescription
eventIDStringYesEvent ID
yearNumberYesEvent year
isFavouriteBooleanYestrue to add, false to remove

The handler validates that the event exists in biztechEvents and the user exists in biztechUsers. It uses DynamoDB ADD/DELETE operations on the favedEventsID;year StringSet.


DELETE /users/{email}

Auth: Cognito

Deletes the user record from biztechUsers. Validates the user exists first.


IAM Permissions

The users service has IAM access to:

  • biztechUsers* - Scan, GetItem, PutItem, UpdateItem, DeleteItem
  • biztechEvents* - GetItem (for favourite event validation)
  • inviteCodes* - GetItem (legacy invite code system)

Previous
User, Member & Profile Relationships