From d25fbf97c14fd8be8a12e4d857a205d22aeb6cc0 Mon Sep 17 00:00:00 2001 From: ByteBaker <42913098+ByteBaker@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:59:42 +0530 Subject: [PATCH] fix: do not show service account token post creation (#11110) So far, service account token could be viewed and copied even after an SA was created. This was bad design and posed risk of impersonation. The changes prevent this from happening by returning only the redacted token in the API response. Full token with create API itself, and removing GET API to get the service account details (which returned raw token). The same affects token rotation as well. --- src/common/meta/service_account.rs | 36 +++++ src/common/meta/user.rs | 4 + .../http/request/service_accounts/mod.rs | 58 ------- src/handler/http/router/mod.rs | 2 +- src/handler/http/router/openapi.rs | 1 - src/service/users.rs | 52 ++++++- .../tests/pages/serviceaccount_page.py | 14 +- .../api-testing/tests/test_serviceaccounts.py | 27 +--- .../ServiceAccountsList.spec.js | 146 ++---------------- .../serviceAccounts/ServiceAccountsList.vue | 117 ++------------ web/src/services/service_accounts.spec.ts | 43 ------ web/src/services/service_accounts.ts | 8 - 12 files changed, 129 insertions(+), 379 deletions(-) diff --git a/src/common/meta/service_account.rs b/src/common/meta/service_account.rs index b3dba781a2..542551eb84 100644 --- a/src/common/meta/service_account.rs +++ b/src/common/meta/service_account.rs @@ -31,6 +31,14 @@ pub struct APIToken { pub user: String, } +#[derive(Serialize, Deserialize, ToSchema)] +pub struct ServiceAccountCreateResponse { + pub code: u16, + pub message: String, + pub token: String, + pub user: String, +} + #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Eq, PartialEq, Default)] pub struct UpdateServiceAccountRequest { #[serde(default)] @@ -92,6 +100,34 @@ mod tests { assert_eq!(request.last_name, "Name"); } + #[test] + fn test_service_account_create_response() { + let resp = ServiceAccountCreateResponse { + code: 200, + message: "User saved successfully".to_string(), + token: "abcd1234567890ef".to_string(), + user: "sa@example.com".to_string(), + }; + assert_eq!(resp.code, 200); + assert_eq!(resp.token, "abcd1234567890ef"); + assert_eq!(resp.user, "sa@example.com"); + } + + #[test] + fn test_service_account_create_response_serialization() { + let resp = ServiceAccountCreateResponse { + code: 200, + message: "User saved successfully".to_string(), + token: "tok123".to_string(), + user: "sa@example.com".to_string(), + }; + let serialized = serde_json::to_string(&resp).unwrap(); + let deserialized: ServiceAccountCreateResponse = serde_json::from_str(&serialized).unwrap(); + assert_eq!(resp.token, deserialized.token); + assert_eq!(resp.user, deserialized.user); + assert_eq!(resp.code, deserialized.code); + } + #[test] fn test_update_service_account_request_default() { let request = UpdateServiceAccountRequest::default(); diff --git a/src/common/meta/user.rs b/src/common/meta/user.rs index 9c3115dbc0..d2b4d45ab7 100644 --- a/src/common/meta/user.rs +++ b/src/common/meta/user.rs @@ -192,6 +192,8 @@ pub struct UserResponse { #[serde(default, skip_serializing_if = "Option::is_none")] pub orgs: Option>, pub created_at: i64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub token: Option, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] @@ -629,6 +631,7 @@ mod tests { role: UserRole::Admin, }]), created_at: 1234567890, + token: None, }; assert_eq!(response.email, "test@example.com"); @@ -651,6 +654,7 @@ mod tests { is_external: false, orgs: None, created_at: 1234567890, + token: None, }], }; diff --git a/src/handler/http/request/service_accounts/mod.rs b/src/handler/http/request/service_accounts/mod.rs index 3e52d548b3..81ba240660 100644 --- a/src/handler/http/request/service_accounts/mod.rs +++ b/src/handler/http/request/service_accounts/mod.rs @@ -394,61 +394,3 @@ pub async fn delete_bulk( err, }) } - -/// GetAPIToken - -#[utoipa::path( - get, - path = "/{org_id}/service_accounts/{email_id}", - context_path = "/api", - tag = "ServiceAccounts", - operation_id = "GetServiceAccountToken", - summary = "Get service account API token", - description = "Retrieves the current API token for a specific service account. The API token is used for authenticating automated systems and applications when making API requests. \ - \ - **Security Note:** Service accounts with `allow_static_token=false` will return a masked token (***MASKED***) instead of the actual token. These accounts must use the `assume_service_account` API to obtain temporary session tokens. \ - \ - Keep tokens secure and rotate them regularly for security best practices. If the token is compromised, use the update endpoint with rotateToken=true to generate a new one.", - security( - ("Authorization"= []) - ), - params( - ("org_id" = String, Path, description = "Organization name"), - ("email_id" = String, Path, description = "Service Account email id"), - ), - responses( - (status = 200, description = "Success", content_type = "application/json", body = Object), - (status = 404, description = "NotFound", content_type = "application/json", body = ()), - ), - extensions( - ("x-o2-ratelimit" = json!({"module": "Service Accounts", "operation": "get"})), - ("x-o2-mcp" = json!({"description": "Get service account token", "category": "users"})) - ) -)] -pub async fn get_api_token( - Path((org, user_id)): Path<(String, String)>, - Headers(_user_email): Headers, -) -> Response { - let config = config::get_config(); - if !config.auth.service_account_enabled { - return MetaHttpResponse::forbidden("Service Accounts Not Enabled"); - } - - // Always return single token for the requested org - let org_id = Some(org.as_str()); - match crate::service::organization::get_passcode(org_id, &user_id).await { - Ok(passcode) => ( - StatusCode::OK, - Json(APIToken { - token: passcode.passcode, - user: passcode.user, - }), - ) - .into_response(), - Err(e) => ( - StatusCode::NOT_FOUND, - Json(MetaHttpResponse::error(StatusCode::NOT_FOUND, e)), - ) - .into_response(), - } -} diff --git a/src/handler/http/router/mod.rs b/src/handler/http/router/mod.rs index 6483b4eb72..33804a16a2 100644 --- a/src/handler/http/router/mod.rs +++ b/src/handler/http/router/mod.rs @@ -744,7 +744,7 @@ pub fn service_routes() -> Router { // Service accounts .route("/{org_id}/service_accounts", get(service_accounts::list).post(service_accounts::save)) .route("/{org_id}/service_accounts/bulk", delete(service_accounts::delete_bulk)) - .route("/{org_id}/service_accounts/{email_id}", get(service_accounts::get_api_token).put(service_accounts::update).delete(service_accounts::delete)) + .route("/{org_id}/service_accounts/{email_id}", put(service_accounts::update).delete(service_accounts::delete)) // MCP .route("/{org_id}/mcp", get(mcp::handle_mcp_get).post(mcp::handle_mcp_post)) diff --git a/src/handler/http/router/openapi.rs b/src/handler/http/router/openapi.rs index fa3693f00a..5d8a6fd02b 100644 --- a/src/handler/http/router/openapi.rs +++ b/src/handler/http/router/openapi.rs @@ -167,7 +167,6 @@ use crate::{common::meta, handler::http::request}; request::service_accounts::save, request::service_accounts::update, request::service_accounts::delete, - request::service_accounts::get_api_token, request::mcp::handle_mcp_post, request::mcp::handle_mcp_get, request::mcp::oauth_authorization_server_metadata, diff --git a/src/service/users.rs b/src/service/users.rs index 964e397c9b..ac8a3c1d67 100644 --- a/src/service/users.rs +++ b/src/service/users.rs @@ -46,6 +46,7 @@ use crate::{ meta::{ http::HttpResponse as MetaHttpResponse, organization::{DEFAULT_ORG, OrgRoleMapping}, + service_account::ServiceAccountCreateResponse, user::{ UpdateUser, UserList, UserOrgRole, UserRequest, UserResponse, UserUpdateMode, get_default_user_org, @@ -56,6 +57,14 @@ use crate::{ service::{db, organization}, }; +fn redact_token(token: &str) -> String { + if token.len() <= 4 { + "*".repeat(token.len()) + } else { + format!("{}{}", &token[..4], "*".repeat(token.len() - 4)) + } +} + pub async fn post_user( org_id: &str, mut usr_req: UserRequest, @@ -140,6 +149,7 @@ pub async fn post_user( let password = get_hash(&usr_req.password, &salt); let password_ext = get_hash(&usr_req.password, &cfg.auth.ext_auth_salt); let token = generate_random_string(16); + let token_for_response = token.clone(); let rum_token = format!("rum{}", generate_random_string(16)); let org_id = org_id.replace(' ', "_"); let user = usr_req.to_new_dbuser( @@ -191,7 +201,16 @@ pub async fn post_user( } } } - Ok(MetaHttpResponse::ok("User saved successfully")) + if usr_req.role.base_role == UserRole::ServiceAccount { + Ok(MetaHttpResponse::json(ServiceAccountCreateResponse { + code: 200, + message: "User saved successfully".to_string(), + token: token_for_response, + user: usr_req.email.clone(), + })) + } else { + Ok(MetaHttpResponse::ok("User saved successfully")) + } } else { Ok(MetaHttpResponse::bad_request("User already exists")) } @@ -751,6 +770,7 @@ pub async fn list_users( is_external: user.is_external, orgs: None, created_at: 0, // Not used + token: None, }); } return Ok(MetaHttpResponse::json(UserList { data: user_list })); @@ -780,6 +800,11 @@ pub async fn list_users( user.role.ne(&UserRole::ServiceAccount) }; if should_include { + let token = if user.role.eq(&UserRole::ServiceAccount) { + Some(redact_token(&org_user.value().token)) + } else { + None + }; user_list.push(UserResponse { email: user.email.clone(), role: user.role.to_string(), @@ -788,6 +813,7 @@ pub async fn list_users( is_external: user.is_external, orgs: None, created_at: org_user.value().created_at, + token, }); } } @@ -814,6 +840,7 @@ pub async fn list_users( is_external: user.user_type.is_external(), orgs: user_orgs.get(user.email.as_str()).cloned(), created_at, + token: None, }); } } @@ -842,6 +869,7 @@ pub async fn list_users( is_external: root_user.is_external, orgs: None, created_at: 0, + token: None, }); } } @@ -1220,6 +1248,28 @@ mod tests { // Mutex to ensure test setup is serialized to prevent race conditions static TEST_SETUP_LOCK: tokio::sync::OnceCell> = tokio::sync::OnceCell::const_new(); + #[test] + fn test_redact_token_normal() { + assert_eq!(redact_token("abcd1234567890ef"), "abcd************"); + } + + #[test] + fn test_redact_token_exactly_four_chars() { + // tokens of 4 chars or fewer are fully masked (nothing revealed) + assert_eq!(redact_token("abcd"), "****"); + } + + #[test] + fn test_redact_token_shorter_than_four() { + assert_eq!(redact_token("ab"), "**"); + assert_eq!(redact_token(""), ""); + } + + #[test] + fn test_redact_token_five_chars() { + assert_eq!(redact_token("abcde"), "abcd*"); + } + #[test] fn test_is_user_from_org() { let target_org = "org1".to_string(); diff --git a/tests/api-testing/tests/pages/serviceaccount_page.py b/tests/api-testing/tests/pages/serviceaccount_page.py index fe4c0e72a6..0cf416f062 100644 --- a/tests/api-testing/tests/pages/serviceaccount_page.py +++ b/tests/api-testing/tests/pages/serviceaccount_page.py @@ -33,17 +33,11 @@ class ServiceAccountPage: ) assert response.status_code == 200, f"Failed to create service account: {response.content.decode()}" - print(f"Service account created successfully: {response.json()}") + response_data = response.json() + print(f"Service account created successfully: {response_data}") - response_get = session.get( - f"{base_url}api/{org_id}/service_accounts/{email_address}", - headers=headers - ) - assert response_get.status_code == 200, f"Failed to get service account: {response_get.content.decode()}" - - print(f"Service account created successfully: {response_get.json()}") - - token = response_get.json()['token'] + # Token is returned in the create response + token = response_data['token'] print(f"Token: {token}") diff --git a/tests/api-testing/tests/test_serviceaccounts.py b/tests/api-testing/tests/test_serviceaccounts.py index 6a19f7daad..973f223331 100644 --- a/tests/api-testing/tests/test_serviceaccounts.py +++ b/tests/api-testing/tests/test_serviceaccounts.py @@ -93,14 +93,11 @@ def test_e2e_creategetdeleteserviceaccounts(create_session, base_url): assert ( resp_create_serviceaccount.status_code == 200 ), f"Expected 200, but got {resp_create_serviceaccount.status_code} {resp_create_serviceaccount.content}" - resp_get_serviceaccount = session.get( - - f"{base_url}api/{org_id}/service_accounts/{email_id}" - ) - assert ( - resp_get_serviceaccount.status_code == 200 - ), f"Getting this service account, but got {resp_get_serviceaccount.status_code} {resp_get_serviceaccount.content}" + # Token is returned in the create response; verify it is present + create_data = resp_create_serviceaccount.json() + assert "token" in create_data and create_data["token"], \ + f"Expected token in create response, but got: {create_data}" resp_delete_serviceaccount = session.delete( f"{base_url}api/{org_id}/service_accounts/{email_id}" @@ -134,14 +131,6 @@ def test_e2e_creategetupdatedeleteserviceaccounts(create_session, base_url): assert ( resp_create_serviceaccount.status_code == 200 ), f"Expected 200, but got {resp_create_serviceaccount.status_code} {resp_create_serviceaccount.content}" - resp_get_serviceaccount = session.get( - - f"{base_url}api/{org_id}/service_accounts/{email_id}" - ) - - assert ( - resp_get_serviceaccount.status_code == 200 - ), f"Getting this service account, but got {resp_get_serviceaccount.status_code} {resp_get_serviceaccount.content}" payload = { "email":email_id, @@ -190,14 +179,6 @@ def test_e2e_creategetrefreshdeleteserviceaccounts(create_session, base_url): assert ( resp_create_serviceaccount.status_code == 200 ), f"Expected 200, but got {resp_create_serviceaccount.status_code} {resp_create_serviceaccount.content}" - resp_get_serviceaccount = session.get( - - f"{base_url}api/{org_id}/service_accounts/{email_id}" - ) - - assert ( - resp_get_serviceaccount.status_code == 200 - ), f"Getting this service account, but got {resp_get_serviceaccount.status_code} {resp_get_serviceaccount.content}" resp_refresh_serviceaccount = session.put( f"{base_url}api/{org_id}/service_accounts/{email_id}?rotateToken=true", json=payload diff --git a/web/src/components/iam/serviceAccounts/ServiceAccountsList.spec.js b/web/src/components/iam/serviceAccounts/ServiceAccountsList.spec.js index eb3bb6e0ca..ac75faf6d4 100644 --- a/web/src/components/iam/serviceAccounts/ServiceAccountsList.spec.js +++ b/web/src/components/iam/serviceAccounts/ServiceAccountsList.spec.js @@ -24,7 +24,6 @@ vi.mock("@/services/service_accounts", () => ({ default: { list: vi.fn(), delete: vi.fn(), - get_service_token: vi.fn(), refresh_token: vi.fn() } })); @@ -88,7 +87,6 @@ describe("ServiceAccountsList Component", () => { // Reset mock implementations vi.mocked(service_accounts.list).mockReset(); vi.mocked(service_accounts.delete).mockReset(); - vi.mocked(service_accounts.get_service_token).mockReset(); vi.mocked(service_accounts.refresh_token).mockReset(); // Setup default successful response for list @@ -258,103 +256,6 @@ describe("ServiceAccountsList Component", () => { }); describe("Token Management", () => { - it("gets service token successfully", async () => { - const mockToken = "test-token-123"; - vi.mocked(service_accounts.get_service_token).mockResolvedValue({ - data: { token: mockToken } - }); - - const row = { - email: "service1@example.com", - isLoading: false, - isTokenVisible: false - }; - - await wrapper.vm.getServiceToken(row); - - expect(service_accounts.get_service_token).toHaveBeenCalledWith("test-org", "service1@example.com"); - expect(row.token).toBe(mockToken); - expect(row.isTokenVisible).toBe(true); - }); - - it("toggles token visibility when token exists", () => { - const row = { - token: "existing-token", - isTokenVisible: false - }; - - wrapper.vm.getServiceToken(row); - - expect(row.isTokenVisible).toBe(true); - }); - - it("hides token when already visible", () => { - const row = { - token: "existing-token", - isTokenVisible: true - }; - - wrapper.vm.getServiceToken(row); - - expect(row.isTokenVisible).toBe(false); - }); - - it("gets service token for service token variable", async () => { - const mockToken = "service-token-456"; - vi.mocked(service_accounts.get_service_token).mockResolvedValue({ - data: { token: mockToken } - }); - - const row = { email: "service@example.com" }; - - await wrapper.vm.getServiceToken(row, false); - - expect(wrapper.vm.serviceToken).toBe(mockToken); - }); - - it("handles token fetch error", async () => { - const mockError = { - response: { - status: 500, - data: { message: "Token fetch failed" } - } - }; - - vi.mocked(service_accounts.get_service_token).mockRejectedValue(mockError); - - const row = { - email: "service1@example.com", - isLoading: true, - isTokenVisible: false - }; - - await wrapper.vm.getServiceToken(row, true); - await flushPromises(); - - expect(row.isLoading).toBe(false); - }); - - it("does not show error notification for 403 token fetch", async () => { - const mockError = { - response: { - status: 403, - data: { message: "Forbidden" } - } - }; - - vi.mocked(service_accounts.get_service_token).mockRejectedValue(mockError); - - const row = { - email: "service1@example.com", - isLoading: false, - isTokenVisible: false - }; - - await wrapper.vm.getServiceToken(row, true); - - expect(notifyMock).not.toHaveBeenCalled(); - }); - it("refreshes service token successfully", async () => { const mockToken = "refreshed-token-789"; vi.mocked(service_accounts.refresh_token).mockResolvedValue({ @@ -366,10 +267,10 @@ describe("ServiceAccountsList Component", () => { isLoading: false }; - await wrapper.vm.refreshServiceToken(row, true); + await wrapper.vm.refreshServiceToken(row); expect(service_accounts.refresh_token).toHaveBeenCalledWith("test-org", "service1@example.com"); - expect(row.token).toBe(mockToken); + expect(wrapper.vm.serviceToken).toBe(mockToken); }); it("confirms refresh action", () => { @@ -552,45 +453,22 @@ describe("ServiceAccountsList Component", () => { }); describe("Token Display", () => { - it("masks token correctly", () => { - const token = "abcd1234efgh5678"; - const maskedToken = wrapper.vm.maskToken(token); - expect(maskedToken).toBe("abcd **** 5678"); + it("redactToken masks all but first 4 chars", () => { + expect(wrapper.vm.redactToken("abcd1234567890ef")).toBe("abcd************"); }); - it("handles short tokens in maskToken", () => { - const shortToken = "abc123"; - const maskedToken = wrapper.vm.maskToken(shortToken); - expect(maskedToken).toBe("abc123"); + it("redactToken with exactly 4 chars masks all", () => { + // tokens of 4 chars or fewer are fully masked (nothing revealed) + expect(wrapper.vm.redactToken("abcd")).toBe("****"); }); - it("displays token correctly when visible", () => { - const row = { - token: "test-token-123", - isTokenVisible: true - }; - - const displayToken = wrapper.vm.getDisplayToken(row); - expect(displayToken).toBe("test **** -123"); + it("redactToken with fewer than 4 chars masks all", () => { + expect(wrapper.vm.redactToken("ab")).toBe("**"); + expect(wrapper.vm.redactToken("")).toBe(""); }); - it("displays stars when token not visible", () => { - const row = { - token: "test-token-123", - isTokenVisible: false - }; - - const displayToken = wrapper.vm.getDisplayToken(row); - expect(displayToken).toBe("* * * * * * * * * * * * * * * *"); - }); - - it("displays stars when no token", () => { - const row = { - isTokenVisible: true - }; - - const displayToken = wrapper.vm.getDisplayToken(row); - expect(displayToken).toBe("* * * * * * * * * * * * * * * *"); + it("redactToken with 5 chars masks last char only", () => { + expect(wrapper.vm.redactToken("abcde")).toBe("abcd*"); }); }); diff --git a/web/src/components/iam/serviceAccounts/ServiceAccountsList.vue b/web/src/components/iam/serviceAccounts/ServiceAccountsList.vue index 16c652ce09..1b8303fa91 100644 --- a/web/src/components/iam/serviceAccounts/ServiceAccountsList.vue +++ b/web/src/components/iam/serviceAccounts/ServiceAccountsList.vue @@ -78,48 +78,8 @@ along with this program. If not, see . @@ -268,7 +228,7 @@ along with this program. If not, see . no-caps class="no-border" color="primary" - @click="refreshServiceToken(toBeRefreshed,false)" + @click="refreshServiceToken(toBeRefreshed)" > {{ t("user.ok") }} @@ -522,39 +482,6 @@ export default defineComponent({ confirmDelete.value = true; deleteUserEmail = props.row.email; }; - const getServiceToken = async (row:any, fromColum = true) =>{ - if(fromColum){ row.isLoading = true; - if(!row.isTokenVisible){ - row.isTokenVisible = true; - setTimeout(() => { - row.isTokenVisible = false; - }, 5 * 60 * 1000); - } - else{ - row.isTokenVisible = false; - } - if(row.token){ - row.isLoading = false; - return; - } - } - service_accounts.get_service_token(store.state.selectedOrganization.identifier,row.email).then((res)=>{ - if(fromColum) row.token = res.data.token; - else serviceToken.value = res.data.token; - }).catch((err)=>{ - if(err.response?.status != 403){ - $q.notify({ - color: "negative", - message: `Error fetching token: ${err.response?.data?.message || 'Unknown error'}`, - }); - } - - }).finally(()=>{ - row.isLoading = false; - }); - } - - const getServiceAccountsUsers = async () =>{ const dismiss = $q.notify({ spinner: true, @@ -576,7 +503,7 @@ export default defineComponent({ email: maskText(data.email), first_name: data.first_name, last_name: data.last_name, - isTokenVisible: false, + token: data.token || '', }; }); @@ -645,6 +572,11 @@ export default defineComponent({ }); }; + const redactToken = (token: string): string => { + if (token.length <= 4) return '*'.repeat(token.length); + return token.slice(0, 4) + '*'.repeat(token.length - 4); + }; + const addMember = async (res: any, data: any, operationType: string) => { showAddUserDialog.value = false; if (res.code == 200 ) { @@ -654,7 +586,7 @@ export default defineComponent({ message: "Service Account created successfully.", }); - await getServiceToken(data, false); + serviceToken.value = res.token; isShowToken.value = true; if ( store.state.selectedOrganization.identifier == data.organization @@ -667,6 +599,7 @@ export default defineComponent({ email: data.email, first_name: data.first_name, last_name: data.last_name, + token: res.token ? redactToken(res.token) : '', }; serviceAccountsState.service_accounts_users.push(user); @@ -768,18 +701,18 @@ export default defineComponent({ } }; - const refreshServiceToken = async (row:any,fromColum = true) =>{ - if(fromColum) row.isLoading = true; + const refreshServiceToken = async (row:any) =>{ + row.isLoading = true; await service_accounts.refresh_token(store.state.selectedOrganization.identifier,row.email).then((res)=>{ - row.token = res.data.token; serviceToken.value = res.data.token; - row.isTokenVisible = true; isShowToken.value = true; $q.notify({ color: "positive", message: "Service token refreshed successfully.", }); + + getServiceAccountsUsers(); }).catch((err)=>{ if(err.response?.status != 403){ $q.notify({ @@ -787,7 +720,7 @@ export default defineComponent({ message: err.response?.data?.message || "Error while refreshing token.", }); } - + }).finally(()=>{ row.isLoading = false; }); @@ -823,17 +756,6 @@ export default defineComponent({ toBeRefreshed.value = row; }; - const getDisplayToken = (row: any): string => { - if (!row.token) return '* * * * * * * * * * * * * * * *'; - if (!row.isTokenVisible) return '* * * * * * * * * * * * * * * *'; - return maskToken(row.token); - }; - - const maskToken = (token: string): string => { - if (token.length <= 8) return token; - return `${token.slice(0, 4)} **** ${token.slice(-4)}`; - }; - const filterData = (rows: any, terms: any) => { var filtered = []; terms = terms.toLowerCase(); @@ -880,7 +802,6 @@ export default defineComponent({ addUser, confirmDeleteAction, outlinedVisibility, - getServiceToken, deleteUser, getServiceAccountsUsers, pagination, @@ -898,8 +819,6 @@ export default defineComponent({ isShowToken, serviceToken, confirmRefreshAction, - getDisplayToken, - maskToken, filterQuery, filterData, userEmail, @@ -918,6 +837,7 @@ export default defineComponent({ confirmBulkDelete, openBulkDeleteDialog, bulkDeleteServiceAccounts, + redactToken, }; }, }); @@ -925,9 +845,6 @@ export default defineComponent({