Skip to content

Conversation

naveenpaul1
Copy link
Contributor

@naveenpaul1 naveenpaul1 commented Oct 13, 2025

Describe the Problem

This PR contains implementation for the following IAM account APIs

IAM GetUser: UserName
IAM CreateUser: UserName
IAM UpdateUser: NewPath, NewUserName, UserName
IAM DeleteUser: UserName
IAM ListUsers: PathPrefix

NOTE: DB changes added in this PR could change after desig discusion.

Explain the Changes

  1. Extent AccountSDK
  2. Implement AccountSpace
  3. Update DB for for IAM

Issues: Fixed #xxx / Gap #xxx

Testing Instructions:

  1. Crate IAM client
  2. Execute IAM account CLI commands.
iam create-user --user-name iam-first1 --path /division_abc/subdivision_xyz/ 
iam update-user --user-name iam-first1 --new-user-name Bob --new-path /division_abc/newpath/
iam list-users
iam get-user --user-name Bob 
iam delete-user --user-name Bob

FYI : Noobaa operator changes are missing, Needs to create IAM service manually for testing

kind: Service
apiVersion: v1
metadata:
  name: iam
  namespace: noobaa
  labels:
    app: noobaa
spec:
  type: LoadBalancer
  ports:
    - name: iam-https
      protocol: TCP
      port: 443
      targetPort: 7444
  selector:
    noobaa-s3: noobaa
  • Doc added/updated
  • Tests added

Summary by CodeRabbit

  • New Features

    • IAM user management: create, retrieve, update, delete, and list IAM users.
    • Per-request account SDK available.
    • Account objects include owner, tagging, is_iam, iam_arn, and iam_path.
    • System recognizes the "iam" service and an "iam_user" role.
  • Changes

    • Default IAM SSL endpoint port set to 7444.
    • HTTP and HTTPS metrics collection enabled by default.
  • Refactor

    • Account creation, deletion, and key generation delegated to centralized account utilities.

Copy link

coderabbitai bot commented Oct 13, 2025

Walkthrough

Adds IAM-related handling and account lifecycle refactors: new AccountSpaceNB and NBAccountSDK wired per-request, centralized account logic in a new account_util, schema and config updates for IAM and metrics, and a new IAM constant.

Changes

Cohort / File(s) Summary of changes
Configuration
config.js
Changed default ENDPOINT_SSL_IAM_PORT from -1 to 7444; added ALLOW_HTTP_METRICS and ALLOW_HTTPS_METRICS (both true).
Endpoint wiring
src/endpoint/endpoint.js
Added imports for BucketSpaceNB, AccountSDK, AccountSpaceNB; introduced NBAccountSDK and attaches account_sdk (NBAccountSDK instance) to init requests.
IAM constants
src/endpoint/iam/iam_constants.js
Added and exported IAM_SPLIT_CHARACTERS = ':'.
Account SDK space
src/sdk/accountspace_nb.js
New AccountSpaceNB class implementing IAM user operations (create/get/update/delete/list); access-key methods stubbed as NotImplemented; exported as module.
Account server refactor
src/server/system_services/account_server.js
Delegated create_account, generate_account_keys, and delete_account flows to account_util (removed inline implementations).
Schemas (IAM additions)
src/server/system_services/schemas/account_schema.js, src/server/system_services/schemas/role_schema.js, src/server/system_services/schemas/system_schema.js
account_schema: added owner, tagging, is_iam, iam_arn, iam_path. role_schema: added iam_user enum value. system_schema: added iam to service and api enums.
Account utilities (new module)
src/util/account_util.js
New comprehensive account utility module with create_account, delete_user, generate_account_keys, policy/permission validators, many helper checks, ARN/error helpers, master-key/credential handling, audit logging, and exported helpers.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Endpoint
  participant NBAccountSDK
  participant AccountSpaceNB
  participant AccountUtil
  participant Store

  Note over Endpoint,NBAccountSDK: init attaches per-request account_sdk
  Client->>Endpoint: IAM user request
  Endpoint->>NBAccountSDK: forward request
  NBAccountSDK->>AccountSpaceNB: dispatch IAM operation
  AccountSpaceNB->>AccountUtil: validate & perform action
  AccountUtil->>Store: read/write accounts/roles/keys
  Store-->>AccountUtil: result
  AccountUtil-->>AccountSpaceNB: outcome
  AccountSpaceNB-->>NBAccountSDK: response
  NBAccountSDK-->>Endpoint: response
  Endpoint-->>Client: result
Loading
sequenceDiagram
  autonumber
  participant API as account_server
  participant AccountUtil
  participant Store

  rect rgb(245,250,255)
  Note right of API: Refactored account flows delegated to AccountUtil
  API->>AccountUtil: create_account(req)
  AccountUtil->>Store: persist account/roles/keys
  Store-->>AccountUtil: ok
  AccountUtil-->>API: token + access_keys
  end

  API->>AccountUtil: generate_account_keys(req)
  AccountUtil->>Store: update encrypted keys
  Store-->>AccountUtil: ok
  AccountUtil-->>API: decrypted keys

  API->>AccountUtil: delete_user(req, target)
  AccountUtil->>Store: remove account/roles
  Store-->>AccountUtil: ok
  AccountUtil-->>API: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • dannyzaken
  • liranmauda
  • jackyalbo

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title only references the CreateUser API and therefore does not encompass the broader set of IAM user management endpoints added (GetUser, UpdateUser, DeleteUser, and ListUsers), so it does not clearly communicate the primary objective of the PR. Update the title to reflect the full implementation of IAM user management APIs, for example “Implement IAM user management APIs (create, get, update, delete, list)”.
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (12)
config.js (2)

1025-1025: Default‑enabling IAM on 7444 may unintentionally expose a new port; mirror STS gating

Previously IAM was disabled by default (-1). Consider disabling unless explicitly enabled, and mirror the STS NC_NSFS_NO_DB_ENV guard.

Apply:

-config.ENDPOINT_SSL_IAM_PORT = Number(process.env.ENDPOINT_SSL_IAM_PORT) || 7444;
+config.ENDPOINT_SSL_IAM_PORT = Number(process.env.ENDPOINT_SSL_IAM_PORT)
+    || (process.env.NC_NSFS_NO_DB_ENV === 'true' ? -1 : 7444);

1029-1031: ALLOW_HTTP_METRICS/ALLOW_HTTPS_METRICS are not enforced

These flags are defined but not used when starting metrics servers. Gate the ports in endpoint.js.

Add in endpoint.js (inside main):

-const http_metrics_port = options.http_metrics_port || config.EP_METRICS_SERVER_PORT;
-const https_metrics_port = options.https_metrics_port || config.EP_METRICS_SERVER_SSL_PORT;
+const http_metrics_port = config.ALLOW_HTTP_METRICS
+  ? (options.http_metrics_port || config.EP_METRICS_SERVER_PORT)
+  : 0;
+const https_metrics_port = config.ALLOW_HTTPS_METRICS
+  ? (options.https_metrics_port || config.EP_METRICS_SERVER_SSL_PORT)
+  : 0;
src/endpoint/iam/iam_constants.js (1)

61-61: Naming nit: prefer singular or a clearer intent

IAM_SPLIT_CHARACTERS holds a single ':'; consider renaming to IAM_SPLIT_CHARACTER or IAM_ARN_DELIM for clarity.

Also applies to: 74-74

src/endpoint/endpoint.js (1)

97-105: Add account_sdk to EndpointRequest typedef

JSDoc does not include account_sdk.

Apply:

  * @typedef {import('http').IncomingMessage & {
   *  object_sdk?: ObjectSDK;
   *  sts_sdk?: StsSDK;
+  *  account_sdk?: AccountSDK;
   *  virtual_hosts?: readonly string[];
   *  bucket_logger?: PersistentLogger;
   *  notification_logger?: PersistentLogger;
   * }} EndpointRequest
src/sdk/accountspace_nb.js (5)

56-56: Use IAM_SPLIT_CHARACTERS for composing pseudo-email

Avoid inlining ':' to keep consistency with split logic.

-        const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
+        const account_name = new SensitiveString(
+            `${params.username}${IAM_SPLIT_CHARACTERS}${requesting_account.name.unwrap()}`
+        );

103-112: Use actual account timestamps; remove placeholders

Replace placeholders with real values if available; otherwise omit.

-            // TODO: Dates missing : GAP
-            create_date: new Date(),
-            password_last_used: new Date(),
+            create_date: requested_account.last_update ? new Date(requested_account.last_update) : undefined,
+            password_last_used: undefined, // until tracked

Please confirm last_update exists on accounts; if the schema stores a create_time, use that instead.


167-172: Minor: normalize details object to use path key

Align with _throw_access_denied_error:

-        account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
+        account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username, path: params.iam_path });

193-211: List users: prefer owner/is_iam filter over string parsing; fix date field

  • Filtering via name split is brittle. Use is_iam and owner === requesting_account._id.
  • Use a real timestamp from the account doc.

Apply:

-        const requesting_account_iam_users = _.filter(system_store.data.accounts, function(acc) {
-            if (!acc.name.unwrap().includes(IAM_SPLIT_CHARACTERS)) {
-                return false;
-            }
-            return acc.name.unwrap().split(IAM_SPLIT_CHARACTERS)[1] === root_name;
-        });
+        const root_id = String(requesting_account._id);
+        const requesting_account_iam_users = _.filter(system_store.data.accounts, acc =>
+            acc.is_iam === true && acc.owner === root_id
+        );
@@
-                create_date: iam_user.creation_date,
-                password_last_used: Date.now(), // GAP
+                create_date: iam_user.last_update ? new Date(iam_user.last_update) : undefined,
+                password_last_used: undefined,

Please confirm is_iam and owner are present on IAM users; otherwise we can fallback to name-split as a secondary condition.


220-249: Stubs: consider returning NotImplemented consistently with code/message

Current pattern is fine. Optionally, include the action name in the message to aid clients, e.g., message: 'CreateAccessKey NotImplemented'.

src/util/account_util.js (3)

59-70: Avoid hardcoded default_resource from callers

create_account already throws if a provided default_resource doesn't exist. Recommend callers omit default_resource unless validated. No code change here; documenting the expectation.


286-303: validate_assume_role_policy: minor robustness

If policy.statement or nested arrays contain non-SensitiveString principals, principal.unwrap() throws. Optionally normalize with principal instanceof SensitiveString ? principal.unwrap() : principal.


414-436: _throw_access_denied_error: expects details.path; align call sites

Callers pass iam_path. Either accept both keys or standardize on path. You fixed call sites above; alternatively make this tolerant:

-            user_message = create_arn_for_user(account_id_for_arn, details.username, details.path);
+            user_message = create_arn_for_user(
+                account_id_for_arn,
+                details.username,
+                details.path ?? details.iam_path
+            );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69d598a and 3836ab1.

📒 Files selected for processing (9)
  • config.js (1 hunks)
  • src/endpoint/endpoint.js (3 hunks)
  • src/endpoint/iam/iam_constants.js (2 hunks)
  • src/sdk/accountspace_nb.js (1 hunks)
  • src/server/system_services/account_server.js (4 hunks)
  • src/server/system_services/schemas/account_schema.js (1 hunks)
  • src/server/system_services/schemas/role_schema.js (1 hunks)
  • src/server/system_services/schemas/system_schema.js (2 hunks)
  • src/util/account_util.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/sdk/accountspace_nb.js (2)
src/util/account_util.js (21)
  • _ (4-4)
  • require (8-8)
  • require (14-14)
  • require (17-17)
  • require (18-18)
  • SensitiveString (9-9)
  • dbg (7-7)
  • system_store (12-12)
  • IamError (15-15)
  • IamError (312-312)
  • IamError (345-345)
  • IamError (373-373)
  • IamError (410-410)
  • IamError (434-434)
  • req (525-525)
  • account (31-43)
  • account (198-198)
  • account (291-291)
  • account (308-308)
  • account (341-341)
  • account (468-468)
src/endpoint/iam/iam_constants.js (3)
  • IAM_ACTIONS (5-16)
  • IAM_DEFAULT_PATH (47-47)
  • IAM_SPLIT_CHARACTERS (61-61)
src/endpoint/endpoint.js (1)
src/sdk/account_sdk.js (1)
  • BucketSpaceNB (9-9)
src/util/account_util.js (2)
src/server/system_services/account_server.js (31)
  • _ (5-5)
  • require (13-13)
  • require (21-21)
  • require (22-22)
  • require (23-23)
  • Dispatcher (14-14)
  • dbg (12-12)
  • SensitiveString (15-15)
  • cloud_utils (16-16)
  • system_store (17-17)
  • req (118-118)
  • req (382-382)
  • account (53-53)
  • account (101-101)
  • account (120-122)
  • account (144-144)
  • account (192-192)
  • account (315-315)
  • account (678-678)
  • account (942-942)
  • roles (448-448)
  • account_to_delete (402-402)
  • roles_to_delete (414-414)
  • access_keys (156-156)
  • system (197-197)
  • system (1016-1016)
  • email (99-99)
  • params (314-314)
  • params (677-677)
  • params (941-941)
  • status (892-892)
src/endpoint/iam/iam_constants.js (2)
  • IAM_ACTIONS (5-16)
  • ACCESS_KEY_STATUS_ENUM (34-37)
src/server/system_services/account_server.js (1)
src/sdk/accountspace_nb.js (1)
  • account_util (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run-package-lock-validation
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-jest-unit-tests
🔇 Additional comments (7)
src/server/system_services/schemas/role_schema.js (1)

28-29: Ensure auth/role checks recognize 'iam_user'

Adding 'iam_user' is fine. Verify any role-based checks, UI filters, and seeding/migrations include it (e.g., roles_by_system usage, admin/operator checks).

src/server/system_services/schemas/system_schema.js (1)

90-92: LGTM: schema now includes IAM service/api

Enums updated to include 'iam'. Make sure address discovery/registration emits entries for IAM accordingly.

Also applies to: 101-103

src/endpoint/endpoint.js (2)

461-465: Per-request NBAccountSDK wiring: LGTM; ensure consumers use req.account_sdk

Looks good. Verify iam_rest and any other handlers read req.account_sdk and not instantiate their own.


75-96: AccountSDK constructor signature matches injected spaces
AccountSDK’s constructor accepts {rpc_client, internal_rpc_client, bucketspace, accountspace}, matching NBAccountSDK’s super call.

src/server/system_services/account_server.js (2)

135-136: Delegating key generation: LGTM

Centralizing in account_util is cleaner.


38-45: Safe to return only { token, access_keys }: all existing callers only access those two fields.

src/util/account_util.js (1)

142-147: Return field names: create_date vs creation_date

Upstream consumers used creation_date. This function returns create_date. Keep it as create_date but ensure consumers reference it correctly (see fixes in AccountSpaceNB).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (7)
src/sdk/accountspace_nb.js (6)

52-54: Pass details.path (not details.iam_path) to access-denied builder

Wrong key breaks error messages/ARNs.

-        account_util._check_if_requesting_account_is_root_account(action, requesting_account,
-                { username: params.username, iam_path: params.iam_path });
+        account_util._check_if_requesting_account_is_root_account(
+            action,
+            requesting_account,
+            { username: params.username, path: params.iam_path }
+        );

58-70: Persist iam_path on created IAM user and remove hardcoded default_resource

Avoids stale path in later reads and brittle resource dependency.

             rpc_params: {
                 name: account_name,
                 email: account_name,
                 has_login: false,
                 s3_access: true,
                 allow_bucket_creation: true,
                 owner: requesting_account._id.toString(),
                 is_iam: true,
                 iam_arn: iam_arn,
+                iam_path: params.iam_path || IAM_DEFAULT_PATH,
                 role: 'iam_user',
-                // TODO: default_resource remove
-                default_resource: 'noobaa-default-backing-store',
             },

80-86: Fix returned iam_path and create_date

Use the created user’s path and the create_account reply field.

-            iam_path: requesting_account.iam_path || IAM_DEFAULT_PATH,
+            iam_path: params.iam_path || IAM_DEFAULT_PATH,
             username: params.username,
             user_id: iam_account.id,
             arn: iam_arn,
-            create_date: iam_account.creation_date,
+            create_date: iam_account.create_date,

95-96: Pass details.path (not details.iam_path) to access-denied builder

Same issue as in create_user.

-        account_util._check_if_requesting_account_is_root_account(action, requesting_account,
-                { username: params.username, iam_path: params.iam_path });
+        account_util._check_if_requesting_account_is_root_account(
+            action,
+            requesting_account,
+            { username: params.username, path: params.iam_path }
+        );

120-121: Pass details.path (not details.iam_path) to access-denied builder

Same fix as above.

-        account_util._check_if_requesting_account_is_root_account(action, requesting_account,
-                { username: params.username, iam_path: params.iam_path });
+        account_util._check_if_requesting_account_is_root_account(
+            action,
+            requesting_account,
+            { username: params.username, path: params.iam_path }
+        );

167-168: Pass details.path (not details.iam_path) to access-denied builder

Align with access-denied message builder expectations.

-        account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
+        account_util._check_if_requesting_account_is_root_account(
+            action,
+            requesting_account,
+            { username: params.username, path: params.iam_path }
+        );
src/util/account_util.js (1)

114-118: Persist iam_path for IAM users

Required for get/list/update correctness.

     if (req.rpc_params.is_iam) {
         account.owner = req.rpc_params.owner;
         account.is_iam = req.rpc_params.is_iam;
         account.iam_arn = req.rpc_params.iam_arn;
+        if (req.rpc_params.iam_path) {
+            account.iam_path = req.rpc_params.iam_path;
+        }
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3836ab1 and b6bee2c.

📒 Files selected for processing (2)
  • src/sdk/accountspace_nb.js (1 hunks)
  • src/util/account_util.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/sdk/accountspace_nb.js (3)
src/util/account_util.js (20)
  • _ (4-4)
  • require (8-8)
  • require (14-14)
  • require (17-17)
  • require (18-18)
  • SensitiveString (9-9)
  • system_store (12-12)
  • IamError (15-15)
  • IamError (312-312)
  • IamError (345-345)
  • IamError (373-373)
  • IamError (410-410)
  • IamError (434-434)
  • req (525-525)
  • account (31-43)
  • account (198-198)
  • account (291-291)
  • account (308-308)
  • account (341-341)
  • account (468-468)
src/server/system_services/account_server.js (22)
  • _ (5-5)
  • require (13-13)
  • require (21-21)
  • require (22-22)
  • require (23-23)
  • SensitiveString (15-15)
  • account_util (25-25)
  • account_util (40-40)
  • system_store (17-17)
  • params (314-314)
  • params (677-677)
  • params (941-941)
  • req (118-118)
  • req (382-382)
  • account (53-53)
  • account (101-101)
  • account (120-122)
  • account (144-144)
  • account (192-192)
  • account (315-315)
  • account (678-678)
  • account (942-942)
src/endpoint/iam/iam_constants.js (3)
  • IAM_ACTIONS (5-16)
  • IAM_DEFAULT_PATH (47-47)
  • IAM_SPLIT_CHARACTERS (61-61)
src/util/account_util.js (1)
src/endpoint/iam/iam_constants.js (2)
  • IAM_ACTIONS (5-16)
  • ACCESS_KEY_STATUS_ENUM (34-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-jest-unit-tests
  • GitHub Check: run-package-lock-validation
🔇 Additional comments (1)
src/util/account_util.js (1)

544-555: Export set looks good

Public helpers align with SDK usage.

Comment on lines 116 to 137
async update_user(params, account_sdk) {
const action = IAM_ACTIONS.UPDATE_USER;
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
{ username: params.username, iam_path: params.iam_path });
account_util._check_if_account_exists(action, params.username, requesting_account);
const requested_account = system_store.get_account_by_email(account_name);
let new_iam_path = requested_account.iam_path;
let new_user_name = requested_account.name.unwrap();
const root_account = _.find(system_store.data.accounts, account => account.name.unwrap() === requesting_account.name.unwrap());
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
account_util._check_if_requested_is_owned_by_root_account(action, root_account, requested_account);
if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
if (params.new_username !== undefined) new_user_name = params.new_username;
const iam_arn = iam_utils.create_arn_for_user(root_account._id.toString(), new_user_name, new_iam_path);
const new_account_name = new SensitiveString(`${params.new_username}:${requesting_account.name.unwrap()}`);
const updates = {
name: new_account_name,
email: new_account_name,
iam_arn: iam_arn,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

update_user: fix username derivation, prevent duplicates, persist path, await DB write, and return updated values

  • new_user_name currently equals "username:root", producing invalid ARN/name.
  • No check for duplicate new_username.
  • iam_path not persisted in updates.
  • Not awaiting make_changes (race/unhandled rejection).
  • Response returns stale iam_path/username.
-        let new_iam_path = requested_account.iam_path;
-        let new_user_name = requested_account.name.unwrap();
+        const current_username = requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0];
+        let new_iam_path = requested_account.iam_path;
+        let new_user_name = current_username;
@@
-        if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
-        if (params.new_username !== undefined) new_user_name = params.new_username;
+        if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
+        if (params.new_username !== undefined) new_user_name = params.new_username;
+
+        // Prevent rename to an existing username under the same root
+        if (params.new_username !== undefined && params.new_username !== current_username) {
+            account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account);
+        }
         const iam_arn = iam_utils.create_arn_for_user(root_account._id.toString(), new_user_name, new_iam_path);
-        const new_account_name = new SensitiveString(`${params.new_username}:${requesting_account.name.unwrap()}`);
+        const new_account_name = new SensitiveString(
+            `${new_user_name}${IAM_SPLIT_CHARACTERS}${requesting_account.name.unwrap()}`
+        );
         const updates = {
             name: new_account_name,
             email: new_account_name,
             iam_arn: iam_arn,
+            iam_path: new_iam_path,
         };
@@
-        system_store.make_changes({
+        await system_store.make_changes({
             update: {
                 accounts: [{
                     _id: requested_account._id,
                     $set: _.omitBy(updates, _.isUndefined),
                 }]
             }
         });
@@
-        return {
-            // TODO: IAM path needs to be saved
-            iam_path: requested_account.iam_path || IAM_DEFAULT_PATH,
-            username: requested_account.name.unwrap(),
-            user_id: requested_account._id.toString(),
-            arn: iam_arn
-        };
+        return {
+            iam_path: new_iam_path || IAM_DEFAULT_PATH,
+            username: new_user_name,
+            user_id: requested_account._id.toString(),
+            arn: iam_arn,
+        };

Also verify upstream param mapping (API → SDK): are NewUserName/NewPath mapped to new_username/new_iam_path?

Also applies to: 139-147, 150-156


🏁 Script executed:

#!/bin/bash
# 1. Locate update_user in file
rg -n "update_user" -C2 src/sdk/accountspace_nb.js

# 2. Check for existence of username-existence check
rg -n "check_username_already_exists" -C2 .

# 3. Find IAM_SPLIT_CHARACTERS definition
rg -n "IAM_SPLIT_CHARACTERS" -C2 .

# 4. Verify make_changes usage and async
rg -n "make_changes" -C2 src/sdk/accountspace_nb.js

# 5. Search for new_username param mapping
rg -n "new_username" -C2 .

# 6. Search for new_iam_path param mapping
rg -n "new_iam_path" -C2 .

Length of output: 26916


update_user: implement correct rename flow, persist iam_path, await write, and return updated values

  • Derive current_username via requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0] to avoid embedding root in username
  • Before applying params.new_username, call account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account) to block duplicates
  • Add iam_path: new_iam_path to the updates object
  • Prefix system_store.make_changes with await to ensure the DB write completes
  • Return { iam_path: new_iam_path, username: new_user_name, user_id, arn } instead of stale requested_account values

Verified that API → SDK mappings for NewUserName/NewPathnew_username/new_iam_path in src/endpoint/iam/ops/iam_update_user.js are correct.

🤖 Prompt for AI Agents
In src/sdk/accountspace_nb.js around lines 116 to 137, the update_user flow
needs to correctly derive the current username, validate and apply a rename,
persist iam_path, await the DB write, and return the updated values: derive
current_username as
requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0] (so the root
suffix isn't included), before applying params.new_username call
account_util._check_username_already_exists(action, { username:
params.new_username }, requesting_account) to block duplicates, include
iam_path: new_iam_path in the updates object, prefix
system_store.make_changes(...) with await so the write completes, and return the
fresh values { iam_path: new_iam_path, username: new_user_name, user_id, arn }
(ensure user_id and arn are computed/available from the updated account) instead
of returning stale requested_account fields.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/util/account_util.js (3)

18-19: Use IAM_DEFAULT_PATH and iam_path when constructing ARNs; add the missing import

Import IAM_DEFAULT_PATH and default to it when path is absent. Also prefer iam_path over requesting_account.path to avoid undefined in ARNs. This fixes incorrect/noisy error messages.

-const { IAM_ACTIONS, ACCESS_KEY_STATUS_ENUM } = require('../endpoint/iam/iam_constants');
+const { IAM_ACTIONS, ACCESS_KEY_STATUS_ENUM, IAM_DEFAULT_PATH } = require('../endpoint/iam/iam_constants');
 function _throw_access_denied_error(action, requesting_account, details, entity) {
   const full_action_name = get_action_message_title(action);
   const account_id_for_arn = _get_account_owner_id_for_arn(requesting_account);
-  const arn_for_requesting_account = create_arn_for_user(account_id_for_arn,
-      requesting_account.name.unwrap(), requesting_account.path);
+  const arn_for_requesting_account = create_arn_for_user(
+      account_id_for_arn,
+      requesting_account.name.unwrap(),
+      requesting_account.iam_path || IAM_DEFAULT_PATH
+  );
   const basic_message = `User: ${arn_for_requesting_account} is not authorized to perform:` +
   `${full_action_name} on resource: `;
   let message_with_details;
   if (entity === 'USER') {
       let user_message;
       if (action === IAM_ACTIONS.LIST_ACCESS_KEYS) {
           user_message = `user ${details.username}`;
       } else {
-          user_message = create_arn_for_user(account_id_for_arn, details.username, details.path);
+          user_message = create_arn_for_user(
+              account_id_for_arn,
+              details.username instanceof SensitiveString ? details.username.unwrap() : details.username,
+              (details.path && details.path.unwrap ? details.path.unwrap() : details.path) || IAM_DEFAULT_PATH
+          );
       }
       message_with_details = basic_message +
       `${user_message} because no identity-based policy allows the ${full_action_name} action`;
   } else {
       message_with_details = basic_message + `access key ${details.access_key}`;
   }
   const { code, http_code, type } = IamError.AccessDeniedException;
   throw new IamError({ code, message: message_with_details, http_code, type });
 }

Also applies to: 414-433


114-118: Persist iam_path for IAM users during create_account

Currently iam_path is never stored; later reads expect requested_account.iam_path.

   if (req.rpc_params.is_iam) {
       account.owner = req.rpc_params.owner;
       account.is_iam = req.rpc_params.is_iam;
       account.iam_arn = req.rpc_params.iam_arn;
+      if (req.rpc_params.iam_path) {
+          account.iam_path = req.rpc_params.iam_path;
+      }
   }

448-456: Avoid undefined account id in ARN owner resolution

Provide a fallback when requested_account is not supplied.

 function _get_account_owner_id_for_arn(requesting_account, requested_account) {
   if (!requesting_account.iam_operate_on_root_account) {
       if (requesting_account.owner !== undefined) {
           return requesting_account.owner;
       }
       return requesting_account._id;
   }
-  return requested_account?._id;
+  return requested_account ? requested_account._id : requesting_account._id;
 }
🧹 Nitpick comments (2)
src/util/account_util.js (2)

11-12: Normalize require paths (double slash)

Minor cleanup; prevents path inconsistencies.

-const auth_server = require('..//server/common_services/auth_server');
-const system_store = require('..//server/system_services/system_store').get_instance();
+const auth_server = require('../server/common_services/auth_server');
+const system_store = require('../server/system_services/system_store').get_instance();

98-106: PII in logs (emails)

Emails are PII; prefer masking or structured logging with redaction.

  • Mask emails (e.g., a***@example.com) or omit them at info/alert levels.
  • Centralize masking in Dispatcher or a log helper to avoid leaks.

Also applies to: 168-176, 179-187, 232-239

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed9db3 and 7137dbf.

📒 Files selected for processing (1)
  • src/util/account_util.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/util/account_util.js (2)
src/sdk/accountspace_nb.js (5)
  • _ (4-4)
  • require (12-12)
  • SensitiveString (5-5)
  • system_store (9-9)
  • IamError (11-11)
src/endpoint/iam/iam_constants.js (2)
  • IAM_ACTIONS (5-16)
  • ACCESS_KEY_STATUS_ENUM (34-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-package-lock-validation
  • GitHub Check: run-jest-unit-tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (9)
src/server/system_services/account_server.js (1)

405-405: Verify delete_user handles full account cleanup for non-IAM accounts.

The function name delete_user suggests IAM-specific logic. Ensure it performs complete cleanup (buckets, policies, access keys, roles, etc.) for both IAM and classic accounts, not just role removal.

Based on past review feedback.

Run this script to verify the delete_user implementation:

#!/bin/bash
# Verify delete_user handles full account cleanup
rg -n -A 20 'function delete_user|exports\.delete_user' src/util/account_util.js
src/sdk/accountspace_nb.js (8)

128-128: Only check for duplicate username if renaming.

The duplicate check runs even when params.new_username is undefined, and doesn't verify the new name differs from the current one.

Apply this diff:

         let new_user_name = requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0];
-        account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account);
         const root_account = _.find(system_store.data.accounts, account => account.name.unwrap() === requesting_account.name.unwrap());
         account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
         account_util._check_if_requested_is_owned_by_root_account(action, root_account, requested_account);
         if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
-        if (params.new_username !== undefined) new_user_name = params.new_username;
+        if (params.new_username !== undefined && params.new_username !== new_user_name) {
+            new_user_name = params.new_username;
+            account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account);
+        }

Based on past review feedback.


153-159: Return updated values, not stale or undefined ones.

The response uses stale or potentially undefined values. After the fix to line 127, new_user_name will be correct. However, ensure new_iam_path is defined (it might be undefined if requested_account.iam_path is undefined).

Apply this diff to ensure defaults:

         return {
-            // TODO: IAM path needs to be saved
             iam_path: new_iam_path || IAM_DEFAULT_PATH,
             username: new_user_name,
             user_id: requested_account._id.toString(),
             arn: iam_arn
         };

Based on past review feedback.


70-71: Remove hardcoded default_resource.

Hardcoding 'noobaa-default-backing-store' will fail if that resource doesn't exist. Let account_util.create_account resolve the default resource automatically.

Apply this diff:

                 iam_path: params.iam_path,
                 role: 'iam_user',
-
-                // TODO: default_resource remove
-                default_resource: 'noobaa-default-backing-store',
             },

Based on past review feedback.


81-88: Return the created user's iam_path, not the requesting account's.

Line 83 returns requesting_account.iam_path, but the response should reflect the created user's IAM path from params.iam_path.

Apply this diff:

         return {
-            // TODO : PATH Missing
-            iam_path: requesting_account.iam_path || IAM_DEFAULT_PATH,
+            iam_path: params.iam_path || IAM_DEFAULT_PATH,
             username: params.username,
             user_id: iam_account.id,
             arn: iam_arn,
             create_date: iam_account.create_date,
         };

Based on past review feedback.


111-113: Use persisted creation timestamp and drop placeholder password_last_used.

  • Line 111: requested_account.last_update is the last modification time, not creation time. Accounts should track creation timestamp separately.
  • Line 113: Returning new Date() for password_last_used is misleading—it returns "now" instead of the actual last password use time.

Apply this diff:

             arn: requested_account.iam_arn,
-            create_date: new Date(requested_account.last_update),
-            // TODO: Dates missing : GAP
-            password_last_used: new Date(),
+            create_date: requested_account.create_date || new Date(requested_account.last_update),
+            password_last_used: undefined, // Unknown; avoid misleading timestamps
         };

Based on past review feedback.


127-127: Extract username without root suffix.

requested_account.name.unwrap() includes the root account suffix (e.g., username:root). Extract just the username portion.

Apply this diff:

         const requested_account = system_store.get_account_by_email(account_name);
         let new_iam_path = requested_account.iam_path;
-        let new_user_name = requested_account.name.unwrap();
+        let new_user_name = requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0];
         account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account);

Based on past review feedback.


136-140: Persist iam_path in updates.

The updates object doesn't include iam_path, so path changes won't be saved to the database.

Apply this diff:

         const updates = {
             name: new_account_name,
             email: new_account_name,
             iam_arn: iam_arn,
+            iam_path: new_iam_path,
         };

Based on past review feedback.


188-218: Implement PathPrefix filtering and fix timestamp issues.

  • Missing PathPrefix filtering: The method ignores params.path_prefix (or params.PathPrefix), so it returns all users regardless of their IAM path.
  • Line 209: Uses last_update instead of creation timestamp.
  • Line 211: Returns Date.now() instead of actual password last-used time.

Apply this diff:

     async list_users(params, account_sdk) {
         const action = IAM_ACTIONS.LIST_USERS;
-        //const requesting_account = account_sdk.requesting_account;
         const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
         account_util._check_if_requesting_account_is_root_account(action, requesting_account, { });
         const is_truncated = false; // GAP - no pagination at this point
+        const path_prefix = params?.path_prefix || params?.PathPrefix;
 
         const root_name = requesting_account.name.unwrap();
-        // CORE CHANGES PENDING - START
         const requesting_account_iam_users = _.filter(system_store.data.accounts, function(acc) {
             if (!acc.name.unwrap().includes(IAM_SPLIT_CHARACTERS)) {
                 return false;
             }
             return acc.name.unwrap().split(IAM_SPLIT_CHARACTERS)[1] === root_name;
         });
         let members = _.map(requesting_account_iam_users, function(iam_user) {
+            if (path_prefix && !(iam_user.iam_path || IAM_DEFAULT_PATH).startsWith(path_prefix)) {
+                return undefined;
+            }
             const member = {
                 user_id: iam_user._id.toString(),
                 iam_path: iam_user.iam_path || IAM_DEFAULT_PATH,
                 username: iam_user.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0],
                 arn: iam_user.iam_arn,
-                create_date: new Date(iam_user.last_update),
-                // TODO: Miising password_last_used
-                password_last_used: Date.now(), // GAP
+                create_date: iam_user.create_date || new Date(iam_user.last_update),
+                password_last_used: undefined, // Unknown; avoid misleading timestamps
             };
             return member;
         });
-        // CORE CHANGES PENDING - END
+        members = members.filter(Boolean);
         members = members.sort((a, b) => a.username.localeCompare(b.username));
         return { members, is_truncated };
     }

Based on past review feedback.

🧹 Nitpick comments (2)
src/server/system_services/schemas/account_schema.js (2)

31-31: Consider renaming owner to owner_account for clarity.

The field name owner is ambiguous. Using owner_account makes it explicit that this references the owning account record.

Based on past review feedback.


36-36: Add validation pattern for iam_arn.

IAM ARNs follow AWS format (arn:partition:iam:region:account-id:resource-type/resource-id). Adding a pattern constraint prevents invalid ARN values.

Consider adding:

-iam_arn: { type: 'string' },
+iam_arn: { 
+    type: 'string',
+    pattern: '^arn:[^:]*:iam:[^:]*:[^:]*:(user|role)/.+$'
+},

Based on past review feedback.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7137dbf and 5fef7ca.

📒 Files selected for processing (4)
  • src/sdk/accountspace_nb.js (1 hunks)
  • src/server/system_services/account_server.js (4 hunks)
  • src/server/system_services/schemas/account_schema.js (1 hunks)
  • src/util/account_util.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/util/account_util.js
🧰 Additional context used
🧬 Code graph analysis (2)
src/sdk/accountspace_nb.js (2)
src/util/account_util.js (21)
  • _ (4-4)
  • require (8-8)
  • require (14-14)
  • require (17-17)
  • require (18-18)
  • SensitiveString (9-9)
  • dbg (7-7)
  • system_store (12-12)
  • IamError (15-15)
  • IamError (312-312)
  • IamError (345-345)
  • IamError (373-373)
  • IamError (410-410)
  • IamError (434-434)
  • req (522-522)
  • account (31-43)
  • account (199-199)
  • account (291-291)
  • account (308-308)
  • account (341-341)
  • account (465-465)
src/endpoint/iam/iam_constants.js (3)
  • IAM_ACTIONS (5-16)
  • IAM_DEFAULT_PATH (47-47)
  • IAM_SPLIT_CHARACTERS (61-61)
src/server/system_services/account_server.js (2)
src/sdk/accountspace_nb.js (2)
  • account_util (6-6)
  • require (12-12)
src/util/account_util.js (5)
  • require (8-8)
  • require (14-14)
  • require (17-17)
  • require (18-18)
  • req (522-522)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run-package-lock-validation
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-jest-unit-tests
🔇 Additional comments (3)
src/server/system_services/account_server.js (2)

39-46: LGTM: Delegation to account_util centralizes account creation logic.

The refactor to delegate account creation to account_util improves code organization and reusability.


136-136: LGTM: Delegation to account_util for key generation.

Consistent with the overall refactoring pattern to centralize account operations.

src/sdk/accountspace_nb.js (1)

163-186: LGTM: IAM user deletion flow is correct.

The method properly validates ownership and delegates to account_util.delete_user.

@nimrod-becker
Copy link
Contributor

@dannyzaken @jackyalbo @shirady PTAL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants