

# **WORKFORCEOS**

**FRONTEND GUIDE FOR AI CODING AGENTS - PART 4 - Profile Management**

This document is a part of a REST API guide for the workforceos project.
It is designed for AI agents that will generate frontend code to consume the project's backend.

This document includes information and api descriptions about building a **profile page** in the frontend using the auth service profile api calls. Avatar images are stored in the auth service's database buckets — no external bucket service is needed for avatars.

The project has 1 auth service, 1 notification service, 1 BFF service, and 10 business services, plus other helper services such as bucket and realtime. In this document you will use the auth service (including its database bucket endpoints for avatar uploads).

Each service is a separate microservice application and listens for HTTP requests at different service URLs.

Services may be deployed to the preview server, staging server, or production server. Therefore, each service has 3 access URLs.
The frontend application must support all deployment environments during development, and the user should be able to select the target API server on the home page.


## Accessing the backend

Each backend service has its own URL for each deployment environment. Users may want to test the frontend in one of the three deployments—preview, staging, or production. Please ensure that the register and login pages include a deployment server selection option so that, as the frontend coding agent, you can set the base URL for all services.

The base URL of the application in each environment is as follows:

* **Preview:** `https://workforceos.prw.mindbricks.com`
* **Staging:** `https://workforceos-stage.mindbricks.co`
* **Production:** `https://workforceos.mindbricks.co`

For the auth service, service urls are as follows:

* **Preview:** `https://workforceos.prw.mindbricks.com/auth-api`
* **Staging:** `https://workforceos-stage.mindbricks.co/auth-api`
* **Production:** `https://workforceos.mindbricks.co/auth-api`

For each other service, the service URL will be given in the service sections.

Any request that requires login must include a valid token in the Bearer authorization header.


## Multi-Tenancy Management

**THIS APPLICATION IS MULTI-TENANT**

This application is mult-tanant, it means; as a SaaS application, it isolates each tenant-data from each other. The tenants are called `company` and a data object with the same name exist to store and manage the tenant information. The `company` data instances are referenced from other data objects or entities as `companyId`. Any data object which is in tenant level (some data objects may still stay in SaaS level) has a `companyId` property which attaches it to a `company` tenant. But this value is added to the data object instance in the time of creation by the system according to the current `company` user is navigating. 

For human readability each `company` has also got a `companyCodename` which is a unique form of its name.
Frontend must forward tenant targeting with the tenant header:

```js
headers["mbx-company-codename"] = "babil";
```

When no `companyCodename` is given in the related parameters, application will assume that the access targets the root target, the Saas level. The `companyCodename` of SaaS level is always `root`. When no `companyCodename` is specified, `root` is assumed as the current `companyCodename`.

Note that, logins and registrations are all tenant scoped. If any a tenant-lvel api is login-required then it will check for a `company` specific token according to the target `company` defined in a related parameter with its codename. The application creates the cookies with `companyCodename` prefixes but it is recommended for the frontend application to populate the bearer header with the user's `company` related token.

Not that all `company` tenants are managed in the same backend services, however a `company` tanant frontend should be specific to one `company`. Frontend should have a technical way to understand which `company` is targeted by the user, for production frontend application this should be managed by the subdomains that represents the codename of the company like,

````
https://babil.storeCreator.com
````

Using the subdomain, frontend will distinguish that the codename is `babil` and will atatch it to all tenant-level api calls.

The url may also be used for the SaaS directly with a more general wording like,

````
https://www.storeCreator.com
````

However, this subdomain management may not be handled easily in the AI frontend builders that they use their own preview urls, an other url structure should be handled to simulate the subdomain behaviour for tenant forwarding.

It may be like,

````
https://{somePreviewUrlOfABuilderPlatform}/babil/login
````

In some way, frontend should provide a simple and practical way to the user to target a specific tenant.

### Sample Company Tenant

The applcation backend also includes a sample tenant created with a sample tenant owner, who has the same email and password of the superadmin. This sample is created in the backend to be able to test the multi-tenant behaviour of the frontend at the beginning.

The sample company has a codename `babil` and can be targetd by attaching this codename to the API calls. So whwn the front end home page is built, homepages both for the SaaS and tenant shoul be created and ready to be tested.



## Avatar Storage (Database Buckets)

User avatars and tenant avatars are stored directly in the auth service database using **database buckets** (dbBuckets). This means avatar files are uploaded to and downloaded from the **auth service itself** — no external bucket service is needed.

The auth service provides these avatar buckets:

### User Avatar Bucket

**Upload:** `POST {authBaseUrl}/bucket/userAvatars/upload`
**Download by ID:** `GET {authBaseUrl}/bucket/userAvatars/download/{fileId}`
**Download by Key:** `GET {authBaseUrl}/bucket/userAvatars/download/key/{accessKey}`

- **Read access:** Public (anyone can view avatars, no auth needed for download)
- **Write access:** Authenticated (any logged-in user can upload their own avatar)
- **Allowed types:** image/png, image/jpeg, image/webp, image/gif
- **Max size:** 5 MB
- **Access key:** Each uploaded file gets a 12-character random key for shareable links

**Upload example (multipart/form-data):**

```js
const formData = new FormData();
formData.append('file', croppedImageBlob, 'avatar.png');

const response = await fetch(`${authBaseUrl}/bucket/userAvatars/upload`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
  },
  body: formData,
});

const result = await response.json();
// result.file.id — the file ID (use for download URL)
// result.file.accessKey — 12-char key for public sharing
// result.file.fileName, result.file.mimeType, result.file.fileSize
```

**After uploading, update the user's avatar field** with the download URL:

```js
const avatarUrl = `${authBaseUrl}/bucket/userAvatars/download/${result.file.id}`;
// OR use the access key for a shorter, shareable URL:
const avatarUrl = `${authBaseUrl}/bucket/userAvatars/download/key/${result.file.accessKey}`;

await updateProfile({ avatar: avatarUrl });
```

**Displaying avatars:** Since read access is public, avatar URLs can be used directly in `<img>` tags without any authentication token:

```jsx
<img src={user.avatar} alt="Avatar" />
```


### Company Avatar Bucket

**Upload:** `POST {authBaseUrl}/bucket/companyAvatars/upload`
**Download by ID:** `GET {authBaseUrl}/bucket/companyAvatars/download/{fileId}`
**Download by Key:** `GET {authBaseUrl}/bucket/companyAvatars/download/key/{accessKey}`

Same configuration as user avatars: public read, authenticated write, images only, 5 MB max.

Upload and display work the same way — upload the image, store the download URL in the company's avatar field.


### Listing and Deleting Avatars

The auth service also provides metadata APIs for each bucket (auto-generated):

| API | Method | Path | Description |
|-----|--------|------|-------------|
| `getUserAvatarsFile` | GET | `/v1/userAvatarsFiles/:id` | Get file metadata (no binary) |
| `listUserAvatarsFiles` | GET | `/v1/userAvatarsFiles` | List files with filtering |
| `deleteUserAvatarsFile` | DELETE | `/v1/userAvatarsFiles/:id` | Delete file and its data |

| `getCompanyAvatarsFile` | GET | `/v1/companyAvatarsFiles/:id` | Get tenant avatar metadata |
| `listCompanyAvatarsFiles` | GET | `/v1/companyAvatarsFiles` | List tenant avatars |
| `deleteCompanyAvatarsFile` | DELETE | `/v1/companyAvatarsFiles/:id` | Delete tenant avatar |


## Profile Page

Design a profile page to manage (view and edit) user information. The profile page should include an avatar upload component that uploads to the database bucket.

On the profile page, you will need 4 business APIs: `getUser` , `updateProfile`, `updateUserPassword` and `archiveProfile`. Do not rely on the `/currentuser` response for profile data, because it contains session information. The most recent user data is in the user database and should be accessed via the `getUser` business API.

The `updateProfile`, `updateUserPassword` and `archiveProfile` can only be called by the users themselves. They are designed specific to the profile page.

**Avatar upload workflow:**
1. User selects an image → crop with `react-easy-crop` (install it, do not implement your own)
2. Convert cropped area to a Blob
3. Upload to `POST {authBaseUrl}/bucket/userAvatars/upload` with the access token
4. Get back the file metadata (id, accessKey)
5. Construct the download URL: `{authBaseUrl}/bucket/userAvatars/download/key/{accessKey}`
6. Call `PATCH {authBaseUrl}/v1/profile` with body `{ avatar: downloadUrl }` — **no userId in the URL**. The route is session-based; the BE resolves the target user from the access token.

**Note that the user cannot change/update their `email` or `roleId`.**

For password update you should make a separate block in the UI, so that user can enter old password, new password and confirm new password before calling the `updateUserPassword`.

Here are the 3 auth APIs—`getUser` , `updateProfile` and `updateUserPassword`— as follows:
You can access these APIs through the auth service base URL, `{appUrl}/auth-api`.


### `Get User` API
This api is used by admin roles or the users themselves to get the user profile information.


**Rest Route**

The `getUser` API REST controller can be triggered via the following route:

`/v1/users/:userId`


**Rest Request Parameters**


The `getUser` api has got 1 regular request parameter  

| Parameter              | Type                   | Required | Population                   |
| ---------------------- | ---------------------- | -------- | ---------------------------- |
| userId  | ID  | true | request.params?.["userId"] |
**userId** : This id paremeter is used to query the required data object.



**REST Request**
To access the api you can use the **REST** controller with the path **GET  /v1/users/:userId**
```js
  axios({
    method: 'GET',
    url: `/v1/users/${userId}`,
    data: {
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```





### `Update Profile` API
This route is used by users to update their own profiles. The target user is always the session user — no userId is needed in the URL.


**Rest Route**

The `updateProfile` API REST controller can be triggered via the following route:

`/v1/profile`


**Rest Request Parameters**


The `updateProfile` api has got 2 regular request parameters  

| Parameter              | Type                   | Required | Population                   |
| ---------------------- | ---------------------- | -------- | ---------------------------- |
| fullname  | String  | false | request.body?.["fullname"] |
| avatar  | String  | false | request.body?.["avatar"] |
**fullname** : A string value to represent the fullname of the user
**avatar** : The avatar url of the user. A random avatar will be generated if not provided



**REST Request**
To access the api you can use the **REST** controller with the path **PATCH  /v1/profile**
```js
  axios({
    method: 'PATCH',
    url: '/v1/profile',
    data: {
            fullname:"String",  
            avatar:"String",  
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```





### `Update Userpassword` API
This route is used to update the password of users in the profile page by users themselves. The target user is always the session user — no userId is needed in the URL.


**Rest Route**

The `updateUserPassword` API REST controller can be triggered via the following route:

`/v1/profile/password`


**Rest Request Parameters**


The `updateUserPassword` api has got 2 regular request parameters  

| Parameter              | Type                   | Required | Population                   |
| ---------------------- | ---------------------- | -------- | ---------------------------- |
| oldPassword  | String  | true | request.body?.["oldPassword"] |
| newPassword  | String  | true | request.body?.["newPassword"] |
**oldPassword** : The old password of the user that will be overridden bu the new one. Send for double check.
**newPassword** : The new password of the user to be updated



**REST Request**
To access the api you can use the **REST** controller with the path **PATCH  /v1/profile/password**
```js
  axios({
    method: 'PATCH',
    url: '/v1/profile/password',
    data: {
            oldPassword:"String",  
            newPassword:"String",  
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```




### Archiving A Profile

A user may want to archive their profile. So the profile page should include an archive section for the users to archive their accounts.
When an account is archived, it is marked as archived and an aarchiveDate is atteched to the profile. All user data is kept in the database for 1 month after user archived.
If user tries to login or register with the same email, the account will be activated again. But if no login or register occures in 1 month after archiving, the profile and its related data will be deleted permanenetly.
So in the profile page,

1. The arcihve options should be accepted after user writes a text like ("ARCHİVE MY ACCOUNT") to a confirmation dialog, so that frontend UX can ensure this is not an unconscious request.
2. The user should be warned about the process, that his account will be available for a restore for 1 month.

The archive api, can only be called by the users themselves and its used as follows.

### `Archive Profile` API
This api is used by users to archive their own profiles. The target user is always the session user — no userId is needed in the URL.


**Rest Route**

The `archiveProfile` API REST controller can be triggered via the following route:

`/v1/profile`


**Rest Request Parameters**
The `archiveProfile` api has got no request parameters.    




**REST Request**
To access the api you can use the **REST** controller with the path **DELETE  /v1/profile**
```js
  axios({
    method: 'DELETE',
    url: '/v1/profile',
    data: {
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "DELETE",
	"action": "delete",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": false,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```






## Tenant Profile Page (Tenant Manager)

In multi-tenant projects, also build a tenant profile page for `tenantOwner` and `tenantAdmin` roles.
This page allows these roles to view/update tenant standard and custom properties.

Use tenant-scoped profile APIs:

- Read current tenant profile: `getCompanyProfile` (`/companyprofile`)
- Update current tenant profile: `updateCompany` (send tenant header and use current tenant id as route param)

For SaaS-level tenant management pages (`superAdmin`, `saasAdmin`, `saasUser`), use:

- `getCompanyAccount`
- `listCompaniesAccounts`

Do not use deprecated tenant APIs such as `getCompanyByCodename` or `listRegisteredCompanies`.


### `Get Companyprofile` API
Get tenant profile information in tenant level. A private route for tenantOwner and tenantAdmin.


**Rest Route**

The `getCompanyProfile` API REST controller can be triggered via the following route:

`/v1/companyprofile`


**Rest Request Parameters**
The `getCompanyProfile` api has got no request parameters.    




**REST Request**
To access the api you can use the **REST** controller with the path **GET  /v1/companyprofile**
```js
  axios({
    method: 'GET',
    url: '/v1/companyprofile',
    data: {
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```




### `Update Company` API
Update a company by id. An admin route which can be called by admins or tenant owners.


**Rest Route**

The `updateCompany` API REST controller can be triggered via the following route:

`/v1/companies/:companyId`


**Rest Request Parameters**


The `updateCompany` api has got 6 regular request parameters  

| Parameter              | Type                   | Required | Population                   |
| ---------------------- | ---------------------- | -------- | ---------------------------- |
| companyId  | ID  | true | request.params?.["companyId"] |
| name  | String  | false | request.body?.["name"] |
| fullname  | String  | false | request.body?.["fullname"] |
| avatar  | String  | false | request.body?.["avatar"] |
| industry  | String  | false | request.body?.["industry"] |
| companySize  | String  | false | request.body?.["companySize"] |
**companyId** : This id paremeter is used to select the required data object that will be updated
**name** : A string value to represent one word name of the company
**fullname** : A string value to represent the fullname of the company
**avatar** : A string value represent the url of the company avatar. Keep null for random avatar.
**industry** : The industry the company operates in (e.g., Technology, Healthcare, Finance, etc.)
**companySize** : The size of the company (e.g., 1-10, 11-50, 51-200, etc.)



**REST Request**
To access the api you can use the **REST** controller with the path **PATCH  /v1/companies/:companyId**
```js
  axios({
    method: 'PATCH',
    url: `/v1/companies/${companyId}`,
    data: {
            name:"String",  
            fullname:"String",  
            avatar:"String",  
            industry:"String",  
            companySize:"String",  
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```




### `Get Companyaccount` API
Get tenant account information by id. A private SaaS-level route for superAdmin, saasAdmin and saasUser.


**Rest Route**

The `getCompanyAccount` API REST controller can be triggered via the following route:

`/v1/companyaccounts/:companyId`


**Rest Request Parameters**


The `getCompanyAccount` api has got 1 regular request parameter  

| Parameter              | Type                   | Required | Population                   |
| ---------------------- | ---------------------- | -------- | ---------------------------- |
| companyId  | ID  | true | request.params?.["companyId"] |
**companyId** : The id of the company account to fetch



**REST Request**
To access the api you can use the **REST** controller with the path **GET  /v1/companyaccounts/:companyId**
```js
  axios({
    method: 'GET',
    url: `/v1/companyaccounts/${companyId}`,
    data: {
    
    },
    params: {
    
        }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}
```




### `List Companiesaccounts` API
List tenant accounts in SaaS level for superAdmin, saasAdmin and saasUser.


**Rest Route**

The `listCompaniesAccounts` API REST controller can be triggered via the following route:

`/v1/companyaccounts`


**Rest Request Parameters**



**Filter Parameters**

The `listCompaniesAccounts` api supports 5 optional filter parameters for filtering list results:

**name** (`String`): A string value to represent one word name of the company

- Single (partial match, case-insensitive): `?name=<value>`
- Multiple: `?name=<value1>&name=<value2>`
- Null: `?name=null`


**codename** (`String`): A string value to represent a unique code name for the company which is generated automatically using name

- Single (partial match, case-insensitive): `?codename=<value>`
- Multiple: `?codename=<value1>&codename=<value2>`
- Null: `?codename=null`


**fullname** (`String`): A string value to represent the fullname of the company

- Single (partial match, case-insensitive): `?fullname=<value>`
- Multiple: `?fullname=<value1>&fullname=<value2>`
- Null: `?fullname=null`


**avatar** (`String`): A string value represent the url of the company avatar. Keep null for random avatar.

- Single (partial match, case-insensitive): `?avatar=<value>`
- Multiple: `?avatar=<value1>&avatar=<value2>`
- Null: `?avatar=null`


**ownerId** (`ID`): An ID value to represent the user id of company owner who created the tenant

- Single: `?ownerId=<value>`
- Multiple: `?ownerId=<value1>&ownerId=<value2>`
- Null: `?ownerId=null`



**REST Request**
To access the api you can use the **REST** controller with the path **GET  /v1/companyaccounts**
```js
  axios({
    method: 'GET',
    url: '/v1/companyaccounts',
    data: {
    
    },
    params: {
    
        // Filter parameters (see Filter Parameters section above)
        // name: '<value>' // Filter by name
        // codename: '<value>' // Filter by codename
        // fullname: '<value>' // Filter by fullname
        // avatar: '<value>' // Filter by avatar
        // ownerId: '<value>' // Filter by ownerId
            }
  });
```   
**REST Response**


```json
{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "companies",
	"method": "GET",
	"action": "list",
	"appVersion": "Version",
	"rowCount": "\"Number\"",
	"companies": [
		{
			"id": "ID",
			"name": "String",
			"codename": "String",
			"fullname": "String",
			"avatar": "String",
			"ownerId": "ID",
			"industry": "String",
			"companySize": "String",
			"isActive": true,
			"recordVersion": "Integer",
			"createdAt": "Date",
			"updatedAt": "Date",
			"_owner": "ID"
		},
		{},
		{}
	],
	"paging": {
		"pageNumber": "Number",
		"pageRowCount": "NUmber",
		"totalRowCount": "Number",
		"pageCount": "Number"
	},
	"filters": [],
	"uiPermissions": []
}
```





---

After you complete this step, please ensure you have not made the following common mistakes:

1. Avatar uploads go to the **auth service's database bucket** endpoints (`/bucket/userAvatars/upload`), not to an external bucket service. Use the same `accessToken` (Bearer header) for both auth APIs and avatar bucket uploads — no bucket-specific tokens are needed.
1. Note that any api call to the application backend is based on a service base url, in this prompt all auth apis (including avatar bucket endpoints) should be called by the auth service base url.
1. On the profile page, fetch the latest user data from the service using `getUser`. The `/currentuser` API is session-stored data; the latest data is in the database.
1. When you upload the avatar image on the profile page, use the returned download URL as the user's `avatar` property and update the user record when the Save button is clicked.

**After this prompt, the user may give you new instructions to update your first output or provide subsequent prompts about the project.**