Skip to content

Conversation

@naveenpaul1
Copy link
Contributor

@naveenpaul1 naveenpaul1 commented Nov 19, 2025

Describe the Problem

Use the account server flow

Explain the Changes

  1. Core IAM API changes are moved to account_server from accountspace_nb, This will make sure all the DB actions are done from noobaa core
  2. iam_path_prefix changes added
  3. create_account added response with id and arn
  4. account_api apis are updated with IAM api methods
  5. RPC_ERRORS_TO_IAM updated to handle RPC error code

Issues: Fixed #xxx / Gap #xxx

Testing Instructions:

  1. Run all the IAM APIs
  • Doc added/updated
  • Tests added

Summary by CodeRabbit

  • New Features

    • Full IAM user management: create, retrieve, update, delete, and list users
    • Comprehensive access-key lifecycle: create, list, update, delete, and last-used info
    • User tagging and listing
    • User-level policy management (add/get/delete/list)
    • Account creation responses now include account id and ARN; account creation accepts owner and path
  • Bug Fixes

    • Broader IAM error coverage with improved error message propagation

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Walkthrough

Adds extensive IAM user/access-key/tag/policy APIs, extends create_account with owner/iam_path and returns id/arn, moves SDK IAM logic to RPC delegation, broadens IAM error mappings, and exposes new account utility helpers for IAM workflows.

Changes

Cohort / File(s) Change Summary
API Schema & Exports
src/api/account_api.js
Added owner and iam_path params to create_account; added id and arn to create_account response. Added IAM endpoints (user, access key, tags, policies) and new definitions: user_info, user_accesskey_info, tag. Wired into public definitions/exports.
IAM REST Error Mapping
src/endpoint/iam/iam_rest.js
Expanded RPC→IAM error mapping with many IAM error codes and changed error-message propagation to include original error message when applicable.
SDK — RPC Delegation
src/sdk/accountspace_nb.js
Replaced in-process IAM/account logic with RPC calls to account_sdk.rpc_client.account.*. Removed local IAM utilities/validation/mutations and simplified methods to RPC call + response shaping.
Server — IAM Implementations
src/server/system_services/account_server.js
Enhanced create_account to support owner/iam_path, return id, arn, token, access_keys. Implemented full IAM surface (create/get/update/delete/list users, access-keys, tags, policies, get_access_key_last_used) with validation, persistence, ARN computation, and exports.
Utilities / Helpers
src/util/account_util.js
Added validate_and_return_requested_account and return_list_member; changed _check_username_already_exists signature; added get_iam_username; swapped many IamError throws for RpcError; updated exports and list-member shaping (arn, create_date, password_last_used).
Surface Wiring
src/api/*, src/server/*, src/sdk/* (grouped)
Wired new endpoints, definitions, exported methods, and RPC delegation across API/SDK/server layers to expose the new IAM surface.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SDK as accountspace_nb
    participant RPC as RPC Client
    participant Server as account_server
    participant Util as account_util

    rect rgb(210,230,250)
    Note over SDK,Server: SDK delegates IAM ops via RPC (new flow)
    end

    Client->>SDK: get_user(username)
    SDK->>RPC: account.get_user(context, { username })
    RPC->>Server: get_user(req)
    Server->>Util: validate_and_return_requested_account(params, action, requesting_account)
    Util-->>Server: validated_account
    Server-->>RPC: user_info
    RPC-->>SDK: user_info
    SDK-->>Client: user_info
Loading
sequenceDiagram
    participant Client
    participant SDK as accountspace_nb
    participant RPC as RPC Client
    participant Server as account_server

    rect rgb(220,245,220)
    Note over Client,Server: create_account with owner → IAM user created and ARN returned
    end

    Client->>SDK: create_account({ owner, iam_path, ... })
    SDK->>RPC: account.create_account(context, params)
    RPC->>Server: create_account(req)
    Server->>Server: derive username, validate perms, create IAM user, generate keys
    Server-->>RPC: { id, arn, token, access_keys }
    RPC-->>SDK: { id, arn, token, access_keys }
    SDK-->>Client: { id, arn, token, access_keys }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus on:

  • Consistency of public signatures/exports across account_api.js, account_server.js, accountspace_nb.js, and account_util.js.
  • Correct propagation of requesting_account/context through SDK→RPC→server, and permission checks.
  • Error mapping changes in iam_rest.js and preservation/format of original messages.
  • Data-shape conversions for owner, iam_path, id, arn, access key creation/return semantics.
  • Concurrency, policy/tag limits, and delete-conflict handling in new IAM methods.

Possibly related PRs

Suggested reviewers

  • aayushchouhan09
  • liranmauda
  • jackyalbo

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title concisely describes the main objective—consolidating IAM API logic into the account server flow—which aligns with the primary architectural refactoring in the changeset.
✨ 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.

@naveenpaul1 naveenpaul1 force-pushed the iam_account_flow branch 3 times, most recently from 20fdd0b to b355dee Compare November 20, 2025 06:02
@naveenpaul1 naveenpaul1 marked this pull request as ready for review November 20, 2025 06:09
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: 2

🧹 Nitpick comments (10)
src/util/account_util.js (2)

107-117: IAM account creation and access-key branching look consistent, but rely on owner type conversion

  • Using req.rpc_params.owner as the is_iam flag in create_access_key_auth and in generate_account_keys cleanly separates IAM flows (append keys, no initial key on create) from legacy CLI flows (single key, replace).
  • Setting account.owner = req.rpc_params.owner and later reading iam_user.owner._id (e.g., in return_list_member and _get_account_owner_id_for_arn) assumes system_store converts the supplied value into an objectid reference on insert.

Please confirm that system_store.make_changes (for accounts from account_schema.js) normalizes owner strings into objectid refs. If not, consider parsing/normalizing here, e.g.:

-    if (req.rpc_params.owner) {
-        account.owner = req.rpc_params.owner;
-        account.iam_path = req.rpc_params.iam_path;
-    }
+    if (req.rpc_params.owner) {
+        account.owner = system_store.parse_system_store_id
+            ? system_store.parse_system_store_id(req.rpc_params.owner)
+            : req.rpc_params.owner;
+        account.iam_path = req.rpc_params.iam_path;
+    }

This keeps downstream assumptions (owner._id) safe.

Also applies to: 144-146, 231-237, 254-256


325-332: New IAM-oriented helpers and error mappings look correct and consistent

The refactoring to:

  • Map existence/ownership/limit errors via IamError.* into RpcError(code, message, http_code), and
  • Centralize account resolution/authorization in validate_and_return_requested_account plus return_list_member

is consistent with the new RPC_ERRORS_TO_IAM map and should produce stable IAM error codes and messages over RPC.

The logic in these helpers matches their usages in account_server.js (GET_USER, access key APIs, tagging, policies), and the new exports (validate_and_return_requested_account, get_iam_username, return_list_member) form a clean surface.

If you find _returned_username patterns confusing, you might document or rename on_itself in validate_and_return_requested_account call sites to make the “root-on-self vs root-on-IAM-user” semantics clearer, but it’s not blocking.

Also applies to: 363-371, 397-399, 422-424, 435-437, 442-444, 449-451, 474-476, 482-484, 493-495, 596-602, 715-731, 733-744, 769-771

src/api/account_api.js (2)

44-50: create_account schema changes line up with server behavior

  • Adding owner and iam_path params and id/arn fields in the reply matches the new IAM-aware account_server.create_account flow (root vs IAM user creation).
  • owner as a string and iam_path as a string are consistent with how the server currently consumes them.

Consider documenting in the API docstring that owner must be the root account ID and that IAM users created through this path will not get access keys automatically (CreateAccessKey must be used), to avoid misuse from callers outside the IAM REST flow.

Also applies to: 93-111, 1243-1245


888-978: Tag schema should match server-side expectations

Server-side tag_user/untag_user/list_user_tags code in account_server.js works with tag.key / tag.value on requested_account.tagging, and list_user_tags maps those to { member: { Key, Value } } in replies.

Here:

  • tag_user/untag_user use common_api#/definitions/tag for input.
  • list_user_tags replies reference #/definitions/tag, which you’ve defined with Key/Value.

Please double-check that common_api#/definitions/tag uses key/value (lowercase) to match the server-side implementation. If not, either:

  • Normalize case server-side (accept Key/Value and map to key/value), or
  • Point tag_user/untag_user at the new #/definitions/tag and adjust server code accordingly.

Aligning these avoids subtle JSON shape mismatches between client and server.

Also applies to: 1249-1331

src/endpoint/iam/iam_rest.js (1)

16-33: RPC-to-IAM error mapping is consistent; consider how much of err.message you want to expose

  • The expanded RPC_ERRORS_TO_IAM table correctly aligns common RPC codes (e.g., NoSuchEntity, LimitExceeded, DeleteConflict, AccessDeniedException) with their corresponding IamError.*, matching the new RpcError usage in account_util / account_server.
  • For non-IamError failures, handle_error now does:
const iam_err = ((err instanceof IamError) && err) ||
    new IamError(RPC_ERRORS_TO_IAM[err.rpc_code] || IamError.InternalFailure);
iam_err.message = err.message;

so the client always sees the underlying RPC/error message.

If there’s any risk of internal details leaking via err.message, you might want to only override iam_err.message for known, curated rpc_codes (e.g., those present in RPC_ERRORS_TO_IAM) and fall back to the default IAM message for unexpected/internal failures.

Also applies to: 232-237

src/sdk/accountspace_nb.js (2)

37-63: create_user assumes requesting account always has a default_resource

The IAM create_user flow builds:

default_resource: requesting_account.default_resource.name,

and sends it to account.create_account. If the requesting root account ever lacks default_resource (no S3 access configured), this will throw at .name.

If such accounts are possible, consider either:

  • Validating this precondition and returning a clear IAM error (e.g., “root account must have S3 access to create IAM users”), or
  • Falling back to a system default pool/namespace (similar to the root account create flow) when default_resource is missing.

If by design all IAM-capable roots always have default_resource, a short comment here stating that invariant would help future readers.


65-171: RPC delegation pattern for IAM operations looks clean

All IAM operations (get_user, update_user, delete_user, list_users, access key, tag, and policy methods) now:

  • Resolve requesting_account from system_store by email, and
  • Delegate directly to rpc_client.account.<op>(params, requesting_account).

This keeps AccountSpaceNB thin and centralizes logic in account_server.

If IAM traffic grows, consider caching requesting_account per-request in account_sdk to avoid repeated get_account_by_email lookups, but it’s not critical.

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

1213-1253: update_user: avoid username-existence check when not renaming

update_user unconditionally computes and checks new_username:

const new_username = account_util.get_account_name_from_username(req.rpc_params.new_username, requesting_account._id.toString());
account_util._check_username_already_exists(action, new_username, req.rpc_params.new_username);

even when req.rpc_params.new_username is not provided and only new_iam_path is being changed. This results in a pointless lookup for something like "undefined:<id>" and confusing log context, though it won’t currently throw.

Guard this block so it only runs when a new username is actually supplied:

-    const new_username = account_util.get_account_name_from_username(req.rpc_params.new_username, requesting_account._id.toString());
-    account_util._check_username_already_exists(action, new_username, req.rpc_params.new_username);
+    if (req.rpc_params.new_username) {
+        const new_username = account_util.get_account_name_from_username(
+            req.rpc_params.new_username,
+            requesting_account._id.toString()
+        );
+        account_util._check_username_already_exists(action, new_username, req.rpc_params.new_username);
+    }

This keeps logs and checks aligned with the actual intent of the call.


1301-1331: Prefer RpcError over IamError in RPC handlers to align with iam_rest mapping

In a few places, server-side IAM handlers throw IamError directly, for example:

  • create_access_key catch block:
const { code, http_code, type } = IamError.InternalFailure;
throw new IamError({ code, message: message_with_details, http_code, type });
  • tag_user when exceeding MAX_TAGS:
const { code, http_code, type } = IamError.LimitExceeded;
throw new IamError({ code, message: message_with_details, http_code, type });

Everywhere else in this module and in account_util, IAM conditions are surfaced as RpcError(code, message, http_code) and then translated to IAM errors in iam_rest.js using RPC_ERRORS_TO_IAM[err.rpc_code].

For consistency and clearer RPC error handling, consider throwing RpcError here as well:

-        const { code, http_code, type } = IamError.InternalFailure;
-        throw new IamError({ code, message: message_with_details, http_code, type });
+        const { code, http_code } = IamError.InternalFailure;
+        throw new RpcError(code, message_with_details, http_code);

and similarly for the LimitExceeded case. This keeps the error flow “RPC → iam_rest mapping → IAM XML” uniform.

Also applies to: 1514-1587


1333-1410: Access key IAM helpers match the new utility functions

The IAM access key handlers:

  • Use validate_and_return_requested_account for scoping/authorization,
  • Enforce the per-user key limit via _check_number_of_access_key_array,
  • Rely on generate_account_keys and get_non_updating_access_key for encryption-safe updates,
  • Sort list results by access_key, and
  • Correctly toggle deactivated using _check_access_key_is_deactivated and _get_access_key_status.

If you later implement pagination, you can reuse the existing sort logic and simply slice members according to max_items and marker; the current is_truncated = false placeholders are fine for now.

📜 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 418bb3b and 3456f89.

📒 Files selected for processing (5)
  • src/api/account_api.js (4 hunks)
  • src/endpoint/iam/iam_rest.js (2 hunks)
  • src/sdk/accountspace_nb.js (2 hunks)
  • src/server/system_services/account_server.js (5 hunks)
  • src/util/account_util.js (14 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-19T15:03:42.231Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.231Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/server/system_services/account_server.js
  • src/util/account_util.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/server/system_services/account_server.js
  • src/util/account_util.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/server/system_services/account_server.js
  • src/util/account_util.js
  • src/api/account_api.js
📚 Learning: 2025-06-17T12:59:51.543Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9042
File: src/util/cloud_utils.js:183-194
Timestamp: 2025-06-17T12:59:51.543Z
Learning: In the set_noobaa_s3_connection function in src/util/cloud_utils.js, internal NooBaa S3 endpoints (filtered by 'api': 's3', 'kind': 'INTERNAL') will always be non-secure, so tls: false is the correct setting despite the conditional https:// scheme logic.

Applied to files:

  • src/server/system_services/account_server.js
🧬 Code graph analysis (3)
src/endpoint/iam/iam_rest.js (2)
src/endpoint/iam/iam_utils.js (15)
  • IamError (82-82)
  • IamError (255-255)
  • IamError (481-481)
  • IamError (510-510)
  • IamError (538-538)
  • IamError (545-545)
  • IamError (573-573)
  • IamError (595-595)
  • IamError (624-624)
  • IamError (659-659)
  • IamError (676-676)
  • IamError (691-691)
  • IamError (698-698)
  • IamError (711-711)
  • IamError (715-715)
src/sdk/accountspace_fs.js (1)
  • IamError (14-14)
src/sdk/accountspace_nb.js (2)
src/server/system_services/account_server.js (19)
  • requesting_account (1196-1196)
  • requesting_account (1216-1216)
  • requesting_account (1258-1258)
  • requesting_account (1275-1275)
  • requesting_account (1303-1303)
  • requesting_account (1335-1335)
  • requesting_account (1348-1348)
  • requesting_account (1376-1376)
  • requesting_account (1393-1393)
  • requesting_account (1414-1414)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • req (136-136)
  • req (399-399)
  • system_store (17-17)
src/endpoint/iam/iam_constants.js (1)
  • IAM_DEFAULT_PATH (69-69)
src/util/account_util.js (2)
src/server/system_services/account_server.js (73)
  • req (136-136)
  • req (399-399)
  • account (71-71)
  • account (119-119)
  • account (137-139)
  • account (161-161)
  • account (209-209)
  • account (332-332)
  • account (695-695)
  • account (959-959)
  • message_with_details (1319-1319)
  • message_with_details (1436-1436)
  • action (41-41)
  • action (1195-1195)
  • action (1215-1215)
  • action (1257-1257)
  • action (1274-1274)
  • action (1302-1302)
  • action (1334-1334)
  • action (1346-1346)
  • action (1375-1375)
  • action (1391-1391)
  • action (1413-1413)
  • action (1456-1456)
  • action (1485-1485)
  • action (1515-1515)
  • action (1543-1543)
  • action (1557-1557)
  • email (117-117)
  • username (1198-1198)
  • username (1259-1259)
  • username (1381-1381)
  • username (1415-1415)
  • username (1458-1458)
  • username (1487-1487)
  • system_store (17-17)
  • dbg (12-12)
  • params (331-331)
  • params (694-694)
  • params (958-958)
  • requesting_account (1196-1196)
  • requesting_account (1216-1216)
  • requesting_account (1258-1258)
  • requesting_account (1275-1275)
  • requesting_account (1303-1303)
  • requesting_account (1335-1335)
  • requesting_account (1348-1348)
  • requesting_account (1376-1376)
  • requesting_account (1393-1393)
  • requesting_account (1414-1414)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • requested_account (1197-1197)
  • requested_account (1221-1221)
  • requested_account (1262-1262)
  • requested_account (1304-1304)
  • requested_account (1336-1336)
  • requested_account (1349-1349)
  • requested_account (1395-1395)
  • requested_account (1420-1420)
  • requested_account (1463-1463)
  • requested_account (1492-1492)
  • requested_account (1518-1518)
  • requested_account (1546-1546)
  • requested_account (1560-1560)
  • requested_account (1579-1579)
  • iam_path (1222-1222)
  • iam_path (1285-1285)
  • iam_username (1284-1284)
src/endpoint/s3/s3_bucket_policy_utils.js (3)
  • account (293-293)
  • RpcError (7-7)
  • dbg (5-5)
⏰ 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 (4)
src/api/account_api.js (1)

1266-1316: user_info and user_accesskey_info definitions align with server contracts

The shapes of user_info and user_accesskey_info (user_id, username, arn, iam_path, create_date, password_last_used, access_keys/status/access_key/secret_key) match what account_server.get_user, list_users, create_access_key, and list_access_keys return.

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

40-56: create_account IAM branch is reasonable; relies on owner normalization

  • For IAM-style account creation (rpc_params.owner present), the flow correctly:

    • Requires a root requester via _check_if_requesting_account_is_root_account,
    • Ensures uniqueness via _check_username_already_exists, and
    • Builds iam_arn with create_arn_for_user(req.account._id.toString(), user_name, iam_path || IAM_DEFAULT_PATH).
  • For root account creation (no owner), it falls back to the existing permission/param validation and returns { id, arn: create_arn_for_root(id), token, access_keys }.

As with account_util.create_account, this assumes that account.owner is stored as an objectid ref (so owner._id works everywhere). Please confirm that the value coming from rpc_params.owner is normalized by system_store to avoid surprises in helpers like _check_root_account_owns_user and return_list_member.

Also applies to: 998-1021


135-144: Guard on access_keys length in read_account_by_access_key is a good fix

The additional acc.access_keys && acc.access_keys.length > 0 guard before dereferencing acc.access_keys[0] prevents crashes for accounts without keys while preserving previous behavior.


1412-1512: Tagging logic looks sound; ensure it matches the tag schema

The IAM user tagging helpers:

  • Merge existing requested_account.tagging (array of { key, value }),
  • Enforce MAX_TAGS after merging,
  • Support deleting by tag_keys, and
  • Return a list_user_tags payload shaped as { tags: [{ member: { Key, Value } }], is_truncated: false }.

This is coherent with the IAM design.

Given the earlier schema note in account_api.js, please just confirm that req.rpc_params.tags indeed arrives as { key, value } (lowercase) to match this implementation, or adjust input normalization if it comes as Key/Value.

Comment on lines +1193 to +1212
async function get_user(req) {

const action = IAM_ACTIONS.GET_USER;
const requesting_account = req.account;
const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account);
const username = account_util.get_iam_username(req.rpc_params.username || requested_account.name.unwrap());
const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username,
requested_account.iam_path || IAM_DEFAULT_PATH);
return {
user_id: requested_account._id.toString(),
iam_path: requested_account.iam_path || IAM_DEFAULT_PATH,
username: username,
arn: iam_arn,
// TODO: GAP Need to save created date
create_date: Date.now(),
// TODO: Dates missing : GAP
password_last_used: Date.now(),
};
Copy link

@coderabbitai coderabbitai bot Nov 20, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

get_user builds ARNs based on caller ID, not the user’s owning account

get_user currently does:

const requesting_account = req.account;
const requested_account = account_util.validate_and_return_requested_account(...);
const username = account_util.get_iam_username(req.rpc_params.username || requested_account.name.unwrap());
const iam_arn = iam_utils.create_arn_for_user(
    requesting_account._id.toString(),
    username,
    requested_account.iam_path || IAM_DEFAULT_PATH
);

If an IAM user calls GetUser on itself (no username param), requesting_account === requested_account, so the ARN’s account ID comes from the IAM user’s _id, not from its root/owner. When a root account calls GetUser for the same IAM user, the ARN uses the root _id. This makes the ARN unstable and caller-dependent.

To keep ARNs consistent and rooted at the owning account, derive the account ID from the requested account’s owner when present, or from its own _id when it’s a root account. For example:

-    const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username,
-                                requested_account.iam_path || IAM_DEFAULT_PATH);
+    let owner_id_for_arn;
+    if (requested_account.owner !== undefined) {
+        owner_id_for_arn = typeof requested_account.owner === 'object'
+            ? requested_account.owner._id.toString()
+            : requested_account.owner.toString();
+    } else {
+        owner_id_for_arn = requested_account._id.toString();
+    }
+    const iam_arn = iam_utils.create_arn_for_user(
+        owner_id_for_arn,
+        username,
+        requested_account.iam_path || IAM_DEFAULT_PATH
+    );

This way, the same user always has the same ARN regardless of who calls GetUser.

🤖 Prompt for AI Agents
In src/server/system_services/account_server.js around lines 1193 to 1210,
get_user currently builds the ARN using the requesting account's _id which makes
ARNs caller-dependent; change it to derive the accountId for the ARN from the
requested account's owning account when present (e.g. requested_account.owner or
requested_account.owner_id) or fall back to requested_account._id for root
accounts, then pass that owner-derived id (toString()) into
iam_utils.create_arn_for_user along with the existing username and iam_path so
the ARN is stable and always rooted at the user’s owning account.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The caller should be the owner; otherwise, the validation will fail.

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine the validate_and_return_requested_account function implementation
echo "=== Searching for validate_and_return_requested_account implementation ==="
ast-grep --pattern 'function validate_and_return_requested_account($$$) { $$$ }'

echo -e "\n=== Also search with const/exports pattern ==="
rg -A 30 'validate_and_return_requested_account.*=.*function' src/util/account_util.js

Length of output: 1699


🏁 Script executed:

#!/bin/bash
# Check what GET_USER action validation does - look for permission checks
echo "=== Checking IAM_ACTIONS.GET_USER validation logic ==="
rg -B 5 -A 15 'IAM_ACTIONS\.GET_USER' src/

echo -e "\n=== Check if IAM users can call GetUser on themselves ==="
rg -B 3 -A 10 '_check_if_requesting_account_is_root_account' src/util/account_util.js

Length of output: 28742


🏁 Script executed:

#!/bin/bash
# Look at how requested_account is determined when username is not provided
echo "=== Check how requested_account is resolved in get_user ==="
rg -B 2 -A 5 'req\.rpc_params\.username \|\|' src/server/system_services/account_server.js

echo -e "\n=== Check the account_util validation function signature and exports ==="
rg 'exports\.validate_and_return_requested_account' src/util/account_util.js

Length of output: 808


@naveenpaul1, Thank you for the clarification! However, looking at the validate_and_return_requested_account implementation, I found that IAM users are actually allowed to call GetUser on themselves without providing a username parameter:

// From src/util/account_util.js lines 715-731
function validate_and_return_requested_account(params, action, requesting_account) {
    const on_itself = !params.username;
    let requested_account;
    if (on_itself) {
        // When accesskey API called without specific username, action on the same requesting account.
        // So in that case requesting account and requested account is same.
        requested_account = requesting_account;  // ← IAM user can act on itself!
    } else {
        _check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
        // ... validation for cross-account access
    }
    return requested_account;
}

This creates an ARN inconsistency issue:

Scenario 1: IAM user alice (owned by root account root123) calls GetUser without username:

  • requesting_account = alice (IAM user with _id: alice456)
  • requested_account = alice (same object, line 721)
  • Current ARN: arn:aws:iam::alice456:user/alice ❌ (uses IAM user's own ID)

Scenario 2: Root account root123 calls GetUser with username=alice:

  • requesting_account = root123 (root account with _id: root123)
  • requested_account = alice (IAM user owned by root123)
  • Current ARN: arn:aws:iam::root123:user/alice ✅ (uses owner's ID)

The same user gets different ARNs depending on who calls the API!

Fix: Use the owner's ID (when present) instead of the requesting account's ID:

const owner_id = requested_account.owner 
    ? (typeof requested_account.owner === 'object' 
        ? requested_account.owner._id.toString() 
        : requested_account.owner.toString())
    : requested_account._id.toString();
const iam_arn = iam_utils.create_arn_for_user(owner_id, username, requested_account.iam_path || IAM_DEFAULT_PATH);

This ensures the ARN is stable and always rooted at the owning root account. Would you like me to provide a more detailed code change suggestion?


🧬 Code Graph Analysis Results

[src/server/system_services/account_server.js] (relevant snippets summarized)

  1. is_support_or_admin_or_me(system, account, target_account)
  • Purpose: Determine whether the caller has permission to act on the target account based on role ownership or admin/operator privileges.
  • Parameters:
    • system: the current system object
    • account: the caller's account object
    • target_account: the account being targeted
  • Returns: boolean
  • Logic:
    • Returns true if caller is a support account, or
    • target_account exists and matches the caller (ownership), or
    • caller has admin or operator role for the current system
  • Notes: Used to gate updates/deletions on accounts. If false, operations should be rejected as unauthorized.
  1. _verify_can_delete_account(req, account_to_delete)
  • Purpose: Validate that a given account can be deleted in the context of the current request.
  • Parameters:
    • req: the RPC request context (includes system, account, etc.)
    • account_to_delete: the account object to be deleted
  • Returns: void (throws RpcError on failure)
  • Validation steps (high level):
    • Ensure account_to_delete exists; otherwise raise NO_SUCH_ACCOUNT
    • Disallow deletion of the special “support” account
    • Disallow deletion of the system owner account
    • Ensure the requester is allowed to delete the target (uses is_support_or_admin_or_me)
    • Ensure the account does not own any buckets (cannot delete an account that owns buckets)
  • Errors/Exceptions: Throws RpcError with appropriate codes/messages (e.g., UNAUTHORIZED, FORBIDDEN, NO_SUCH_ACCOUNT) when validations fail.
  1. read_account(req)
  • Purpose: Retrieve account information with access-aware visibility.
  • Parameters:
    • req: the RPC request context
  • Behavior:
    • Determines target email (from req.rpc_params.email or req.account.email)
    • Looks up the account by email; if not found, throws NO_SUCH_ACCOUNT
    • Computes is_self (caller is the same account) and is_operator (caller has operator role for the system)
    • Returns account info via get_account_info(account, is_self || is_operator)
  • Access semantics:
    • If caller is the owner (or an operator) certain sensitive fields may be included
    • Owner/operator status influences what get_account_info reveals
  1. read_account_by_access_key(req) (contextual relevance)
  • Purpose: Retrieve account info by matching an access key (for authentication-derived lookups).
  • Parameters:
    • req: the RPC request context
  • Returns: account info via get_account_info(account)
  • Notes: Used for credential-based lookups; access path should be restricted to authorized callers.
  1. verify_authorized_account(req)
  • Purpose: Quick authorization check for certain operations (used by operators and similar flows).
  • Parameters:
    • req: the RPC request context
  • Returns: boolean (true for operator paths, otherwise false)
  • Behavior: Allows operator calls to proceed without a password, otherwise requires standard authorization.

These snippets collectively establish owner-based validation and role-based access checks that relate directly to the stated user comment: “The caller should be the owner; otherwise, the validation will fail.”


🧠 Learnings used
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.231Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/endpoint/iam/iam_rest.js (1)

232-237: Overwriting all IAM error messages with raw err.message may leak internal details

handle_error now forces iam_err.message = err.message for every error, including InternalFailure. This can expose internal exception messages and stack-derived text to external IAM clients, which is generally undesirable for security and UX (clients see low‑level errors instead of stable, spec‑aligned messages).

Consider limiting message override to:

  • known, user‑facing validation / IAM errors (e.g., only when err instanceof IamError or when RPC_ERRORS_TO_IAM[err.rpc_code] is truthy), and
  • leaving the default canned message for InternalFailure / unexpected errors.

Alternatively, add a guard to avoid overriding when err is not a controlled RpcError/IamError instance.

src/util/account_util.js (2)

31-43: Persist account.owner as a system_store ID, not a bare string

In create_account you now:

const account = { _id: ..., name: ..., email: ..., ... };
...
const account_access_info = create_access_key_auth(req, account, req.rpc_params.owner);
...
if (req.rpc_params.owner) {
    account.owner = req.rpc_params.owner;
    account.iam_path = req.rpc_params.iam_path;
}

Given req.rpc_params.owner comes from the RPC layer as a string (e.g., requesting_account._id.toString()), this will store owner as a string on the account document. Elsewhere, the code assumes an ObjectId-like reference:

  • _check_root_account_owns_user compares requested_account.owner._id.
  • _get_account_owner_id_for_arn uses requesting_account.owner._id.
  • get_account_info and return_list_member do account.owner._id.toString().

With owner as a plain string, account.owner?._id.toString() in list_users will throw, and ARN helpers will see undefined IDs.

Please convert the incoming string to a proper system_store ID here, e.g.:

-    if (req.rpc_params.owner) {
-        account.owner = req.rpc_params.owner;
-        account.iam_path = req.rpc_params.iam_path;
-    }
+    if (req.rpc_params.owner) {
+        // owner is a string account id over RPC; normalize to a system_store ID reference
+        account.owner = system_store.parse_system_store_id(req.rpc_params.owner);
+        account.iam_path = req.rpc_params.iam_path;
+    }

This keeps wire types simple while preserving the in‑memory invariants expected by account_server and other utilities. Based on learnings

Also applies to: 107-117


335-351: Fix _check_root_account_owns_user when user_account.owner is not an object

The else-branch currently does:

if (typeof user_account.owner === 'object') {
    owner_account_id = String(user_account.owner._id);
} else {
    owner_account_id = user_account.owner._id;
}

If user_account.owner is a primitive (e.g., a string ID), user_account.owner._id is undefined, so owner_account_id becomes undefined. That breaks ownership checks and, combined with the new IAM flows, can cause _check_if_requested_is_owned_by_root_account to incorrectly fail with NoSuchEntity.

Safer logic:

if (typeof user_account.owner === 'object') {
    owner_account_id = String(user_account.owner._id);
-} else {
-    owner_account_id = user_account.owner._id;
-}
+} else {
+    owner_account_id = String(user_account.owner);
+}

That supports both ObjectId references and plain string IDs and avoids silently returning undefined.

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

40-64: Create-account IAM branch: overall flow is good, but relies on account.owner type being fixed upstream

The IAM-aware part of create_account:

  • Validates the caller is a root account (_check_if_requesting_account_is_root_account).
  • Prevents duplicate IAM usernames via _check_username_already_exists.
  • Builds an IAM user ARN with create_arn_for_user(req.account._id.toString(), ...).
  • Calls account_util.create_account and returns { id, arn, token, access_keys }, defaulting to root ARN when no owner is specified.

That high-level flow is correct. The main caveat is that downstream IAM helpers assume created_account.owner is an ObjectId-like reference; as noted in account_util.create_account, you currently assign owner from a string RPC param. Once you normalize account.owner there (e.g., via system_store.parse_system_store_id), the rest of this logic — including ARN creation and later use in list_users — will behave as intended.

Also applies to: 107-117, 141-146

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

1193-1210: get_user ARN still depends on caller account ID (duplicate of earlier concern)

get_user builds the ARN with:

const iam_arn = iam_utils.create_arn_for_user(
    requesting_account._id.toString(),
    username,
    requested_account.iam_path || IAM_DEFAULT_PATH
);

Combined with validate_and_return_requested_account, this means:

  • If a root account calls GetUser for an IAM user, the ARN uses the root account’s _id (good).
  • If an IAM user calls GetUser on itself (no username param), requesting_account === requested_account, so the ARN uses the IAM user’s _id (not the owner’s). At the same time, list_users builds ARNs using iam_user.owner._id.toString(), so the same user can have two different ARNs depending on who calls.

This mismatch makes ARNs caller-dependent and inconsistent with how list_users and return_list_member treat the owning account.

Even if you expect “the caller to always be the owner” for cross-user calls, the self-call case still produces an IAM-user-ID-based ARN. To stabilize ARNs, consider deriving the account ID from the user’s owning account (or root, when owner is undefined), e.g.:

-    const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username,
-                                requested_account.iam_path || IAM_DEFAULT_PATH);
+    const owner_id_for_arn = requested_account.owner
+        ? requested_account.owner._id.toString()
+        : requested_account._id.toString();
+    const iam_arn = iam_utils.create_arn_for_user(
+        owner_id_for_arn,
+        username,
+        requested_account.iam_path || IAM_DEFAULT_PATH
+    );

(or by reusing _get_account_owner_id_for_arn).

This aligns get_user with list_users and makes ARNs stable per user. Based on learnings

🧹 Nitpick comments (5)
src/api/account_api.js (2)

93-111: create_account reply shape: server returns extra fields beyond schema

The reply schema lists id, arn, token, and access_keys, but account_server.create_account also returns create_date (for IAM-created users). That’s fine if clients tolerate additional properties, but if you want full schema alignment you may want to:

  • either add create_date to the reply schema (with idate: true), or
  • drop it from the server response until you persist a real creation date.

Right now it’s a transient value (Date.now()) and not stored anywhere, so clients should not treat it as authoritative.


1285-1299: Consider reusing common_api#/definitions/tag in list_user_tags response to eliminate duplication

Verification confirms the concern: tag_user uses common_api#/definitions/tag (lowercase key/value), but list_user_tags response at line 941 uses a locally-defined #/definitions/tag (uppercase Key/Value). This creates schema drift between request and response formats for the same entity.

Recommend either:

  • Reuse the common definition by changing line 941 to $ref: 'common_api#/definitions/tag', or
  • If the uppercase casing is intentional for IAM wire format compatibility, rename the local definition to iam_tag and update line 941 for clarity.
src/util/account_util.js (2)

230-257: generate_account_keys IAM branch behavior is correct but slightly bypasses legacy permission logging

The new IAM-aware branch:

if (req.rpc_params.owner) {
    account_access_keys.push(access_key_obj);
} else {
    account_access_keys[0] = access_key_obj;
}
...
if (req.rpc_params.owner) {
    return decrypted_access_keys;
}

correctly:

  • accumulates multiple keys for IAM users, and
  • leaves root/CLI flows with a single rotated key.

Note that when called from account_server.create_access_key, you pass a minimal account_req without system; generate_account_keys therefore skips the is_support_or_admin_or_me check and logs system as system_store.data.systems[0]. Given IAM access-key RPCs are already guarded by auth: { system: 'admin' } and validate_and_return_requested_account, this is acceptable, but if you want consistent audit logs you may want to thread req.system through that call as well.


733-744: return_list_member uses placeholder dates; fine for now but should be revisited

return_list_member currently sets:

create_date: Date.now(),         // TODO: GAP Need to save created date
password_last_used: Date.now(),  // GAP

for every list entry, which doesn’t reflect the actual user creation or last-used times. Since this is explicitly marked as GAP and only affects metadata (not authorization), it’s acceptable for an initial cut, but it would be good to:

  • start persisting creation_date on IAM user accounts, and
  • either track or omit password_last_used until you can compute it correctly.

For now, consider omitting password_last_used entirely to avoid clients misinterpreting the placeholder.

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

1301-1331: IAM create_access_key flow matches IAM semantics and uses new IAM-aware key generation

This method:

  • Validates the target account via validate_and_return_requested_account.
  • Enforces the per-user key quota with _check_number_of_access_key_array.
  • Calls account_util.generate_account_keys with { email: requested_account.email, is_iam: true, owner: requesting_account._id.toString() }, which:
    • appends a new key to access_keys instead of replacing, and
    • returns decrypted keys only in the IAM branch.
  • Shapes the reply into { username, access_key, create_date, status: Active, secret_key }.

That aligns well with IAM behavior (multiple access keys per user, explicit create API). The only caveat is the same owner-type issue discussed earlier: passing owner as a string is fine as long as it’s converted back to an ID in generate_account_keys / create_account.

📜 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 3456f89 and ffe8131.

📒 Files selected for processing (5)
  • src/api/account_api.js (4 hunks)
  • src/endpoint/iam/iam_rest.js (2 hunks)
  • src/sdk/accountspace_nb.js (2 hunks)
  • src/server/system_services/account_server.js (5 hunks)
  • src/util/account_util.js (14 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-11-19T15:03:42.231Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.231Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/api/account_api.js
  • src/util/account_util.js
  • src/server/system_services/account_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/api/account_api.js
  • src/util/account_util.js
  • src/server/system_services/account_server.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/sdk/accountspace_nb.js
  • src/util/account_util.js
  • src/server/system_services/account_server.js
📚 Learning: 2025-11-18T07:00:17.636Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.636Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/server/system_services/account_server.js
📚 Learning: 2025-06-17T12:59:51.543Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9042
File: src/util/cloud_utils.js:183-194
Timestamp: 2025-06-17T12:59:51.543Z
Learning: In the set_noobaa_s3_connection function in src/util/cloud_utils.js, internal NooBaa S3 endpoints (filtered by 'api': 's3', 'kind': 'INTERNAL') will always be non-secure, so tls: false is the correct setting despite the conditional https:// scheme logic.

Applied to files:

  • src/server/system_services/account_server.js
🧬 Code graph analysis (4)
src/endpoint/iam/iam_rest.js (2)
src/endpoint/iam/iam_utils.js (15)
  • IamError (82-82)
  • IamError (255-255)
  • IamError (481-481)
  • IamError (510-510)
  • IamError (538-538)
  • IamError (545-545)
  • IamError (573-573)
  • IamError (595-595)
  • IamError (624-624)
  • IamError (659-659)
  • IamError (676-676)
  • IamError (691-691)
  • IamError (698-698)
  • IamError (711-711)
  • IamError (715-715)
src/sdk/accountspace_fs.js (1)
  • IamError (14-14)
src/sdk/accountspace_nb.js (1)
src/server/system_services/account_server.js (27)
  • require (13-13)
  • require (21-21)
  • require (22-22)
  • require (23-23)
  • require (28-29)
  • requesting_account (1196-1196)
  • requesting_account (1216-1216)
  • requesting_account (1258-1258)
  • requesting_account (1275-1275)
  • requesting_account (1303-1303)
  • requesting_account (1335-1335)
  • requesting_account (1348-1348)
  • requesting_account (1376-1376)
  • requesting_account (1393-1393)
  • requesting_account (1414-1414)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • params (331-331)
  • params (694-694)
  • params (958-958)
  • req (136-136)
  • req (399-399)
  • system_store (17-17)
src/util/account_util.js (3)
src/server/system_services/account_server.js (74)
  • req (136-136)
  • req (399-399)
  • account (71-71)
  • account (119-119)
  • account (137-139)
  • account (161-161)
  • account (209-209)
  • account (332-332)
  • account (695-695)
  • account (959-959)
  • IamError (27-27)
  • IamError (1320-1320)
  • IamError (1437-1437)
  • action (41-41)
  • action (1195-1195)
  • action (1215-1215)
  • action (1257-1257)
  • action (1274-1274)
  • action (1302-1302)
  • action (1334-1334)
  • action (1346-1346)
  • action (1375-1375)
  • action (1391-1391)
  • action (1413-1413)
  • action (1456-1456)
  • action (1485-1485)
  • action (1515-1515)
  • action (1543-1543)
  • action (1557-1557)
  • email (117-117)
  • username (1198-1198)
  • username (1259-1259)
  • username (1381-1381)
  • username (1415-1415)
  • username (1458-1458)
  • username (1487-1487)
  • system_store (17-17)
  • dbg (12-12)
  • params (331-331)
  • params (694-694)
  • params (958-958)
  • requesting_account (1196-1196)
  • requesting_account (1216-1216)
  • requesting_account (1258-1258)
  • requesting_account (1275-1275)
  • requesting_account (1303-1303)
  • requesting_account (1335-1335)
  • requesting_account (1348-1348)
  • requesting_account (1376-1376)
  • requesting_account (1393-1393)
  • requesting_account (1414-1414)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • requested_account (1197-1197)
  • requested_account (1221-1221)
  • requested_account (1262-1262)
  • requested_account (1304-1304)
  • requested_account (1336-1336)
  • requested_account (1349-1349)
  • requested_account (1395-1395)
  • requested_account (1420-1420)
  • requested_account (1463-1463)
  • requested_account (1492-1492)
  • requested_account (1518-1518)
  • requested_account (1546-1546)
  • requested_account (1560-1560)
  • requested_account (1579-1579)
  • iam_path (1222-1222)
  • iam_path (1285-1285)
  • iam_username (1284-1284)
src/endpoint/s3/s3_bucket_policy_utils.js (3)
  • account (293-293)
  • RpcError (7-7)
  • dbg (5-5)
src/endpoint/iam/iam_rest.js (2)
  • IamError (5-5)
  • dbg (4-4)
src/server/system_services/account_server.js (3)
src/util/account_util.js (33)
  • require (8-8)
  • require (14-14)
  • require (16-16)
  • require (17-18)
  • IamError (15-15)
  • IamError (330-330)
  • IamError (368-368)
  • IamError (397-397)
  • IamError (422-422)
  • IamError (435-435)
  • IamError (442-442)
  • IamError (449-449)
  • IamError (474-474)
  • IamError (482-482)
  • IamError (493-493)
  • IamError (600-600)
  • req (697-697)
  • access_key (507-508)
  • requested_account (717-717)
  • username (393-394)
  • username (428-429)
  • system_store (12-12)
  • _ (4-4)
  • account (31-43)
  • account (197-197)
  • account (305-305)
  • account (326-326)
  • account (364-364)
  • account (640-640)
  • member (569-576)
  • iam_user_policies (632-632)
  • iam_user_policy_index (583-583)
  • iam_user_policy_index (591-592)
src/endpoint/iam/iam_utils.js (3)
  • IamError (82-82)
  • IamError (255-255)
  • IamError (481-481)
src/endpoint/iam/iam_constants.js (4)
  • IAM_ACTIONS (5-23)
  • IAM_DEFAULT_PATH (69-69)
  • ACCESS_KEY_STATUS_ENUM (55-58)
  • MAX_TAGS (67-67)
⏰ 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-jest-unit-tests
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-package-lock-validation
🔇 Additional comments (11)
src/api/account_api.js (1)

627-1064: IAM RPC method shapes look consistent; system: 'admin' auth fixes prior exposure

The new IAM methods (get_user, update_user, delete_user, list_users, access-key/tag/policy ops) are well-shaped and match the corresponding server replies (e.g., user_info, user_accesskey_info). Setting auth: { system: 'admin' } for all of them closes the earlier gap where they were anonymously callable at the RPC layer.

No further schema issues spotted here.

src/util/account_util.js (3)

325-332: RpcError + IamError refactor for IAM helper errors looks consistent

The helpers now throw RpcError with IAM-style codes/http codes derived from IamError.*:

  • _check_if_account_existsNoSuchEntity
  • _check_username_already_existsEntityAlreadyExists
  • _check_if_requested_is_owned_by_root_account / _throw_error_perform_action_on_another_root_accountNoSuchEntity
  • _throw_access_denied_errorAccessDeniedException
  • _throw_error_delete_conflictDeleteConflict
  • _check_number_of_access_key_array / _check_total_policy_sizeLimitExceeded

These codes are all present in RPC_ERRORS_TO_IAM, so IAM REST will reconstruct the correct IamError flavor and preserve your detailed messages. The shapes (code, message_with_details, http_code) also align with RpcError expectations.

No issues here; this is a solid improvement over throwing raw IamError from deep inside util code.

Also applies to: 363-371, 389-399, 419-437, 439-451, 478-495, 596-602


715-731: validate_and_return_requested_account is the central IAM authorization gate; semantics look good

This helper:

  • Treats !params.username as “operate on self”, returning requesting_account.
  • Otherwise enforces:
    • caller is a root account (_check_if_requesting_account_is_root_account),
    • target IAM user exists (_check_if_account_exists),
    • root-vs-IAM constraints (_check_if_requested_account_is_root_account_or_IAM_user),
    • ownership (_check_if_requested_is_owned_by_root_account).

That matches the intended “only root (or its manager) can act on IAM users; IAM users can operate only on themselves” model and centralizes the checks nicely. Just make sure every IAM server method uses this (or an equivalent) when taking a username parameter, which is already true for the new IAM handlers.


769-771: New exports (validate_and_return_requested_account, get_iam_username, return_list_member) are coherent

Exporting these helpers makes sense given account_server’s new IAM methods:

  • validate_and_return_requested_account for central auth logic,
  • get_iam_username for normalizing names, and
  • return_list_member for list-users shaping.

API surface here looks reasonable; no concern.

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

135-143: read_account_by_access_key guard on empty access_keys is a solid safety fix

The new predicate:

acc.access_keys && acc.access_keys.length > 0 && acc.access_keys[0].access_key.unwrap() === access_key.unwrap()

avoids dereferencing access_keys[0] when the array is missing or empty. That prevents a potential runtime exception when scanning accounts that don’t yet have keys.

Looks good.


1374-1410: delete_access_key looks correct and reuses shared helpers

delete_access_key correctly:

  • Validates the caller and target via validate_and_return_requested_account.
  • Ensures the key belongs to the target account with _check_access_key_belongs_to_account.
  • Uses get_non_updating_access_key to produce an encrypted, filtered key list.
  • Persists via system_store.make_changes.

No obvious edge cases beyond the existing helper behavior; this is consistent and reuses shared logic appropriately.


1514-1587: User policy CRUD and listing leverage shared helpers correctly

For user policies:

  • put_user_policy:

    • Uses validate_and_return_requested_account.
    • Upserts into requested_account.iam_user_policies.
    • Enforces total inline policy size via _check_total_policy_size.
  • get_user_policy / delete_user_policy:

    • Reuse validate_and_return_requested_account and _check_user_policy_exists to locate the policy.
  • list_user_policies:

    • Validates and returns sorted policy names with is_truncated: false.

These are clean, consistent, and aligned with how IAM inline policies work (within the current GAPs like missing pagination). No changes needed beyond any future pagination or persisted metadata work.


1590-1627: New IAM exports are wired correctly

All newly added IAM handlers (get_user, update_user, delete_user, list_users, access-key/tag/policy methods, get_access_key_last_used) are exported and thus reachable via the expanded account_api surface.

Export list is consistent; nothing missing.


1273-1299: list_users will throw if account.owner is stored as a string

The filter:

const requesting_account_iam_users = _.filter(system_store.data.accounts, function(account) {
    // Check IAM user owner is same as requesting_account id
    return account.owner?._id.toString() === requesting_account._id.toString();
});

assumes account.owner is an object with an _id field. If account.owner is ever a plain string (e.g., from create_account storing req.rpc_params.owner directly), account.owner?._id evaluates to undefined, and undefined.toString() throws a TypeError, breaking list_users for those records.

Once account_util.create_account is fixed to normalize owner back to an ID object, this will be safe again. If you want extra robustness, you can also defensively handle both shapes:

-    return account.owner?._id.toString() === requesting_account._id.toString();
+    if (!account.owner) return false;
+    const owner_id = typeof account.owner === 'object'
+        ? account.owner._id.toString()
+        : account.owner.toString();
+    return owner_id === requesting_account._id.toString();

But the primary fix is to ensure the DB invariant that owner is always an ID reference, not a raw string. Based on learnings

⛔ Skipped due to learnings
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.231Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

1412-1512: Request tag shape matches server expectations; code is coherent

The tag definition in src/api/common_api.js (lines 76-86) specifies properties as lowercase key and value, which matches exactly how the server code reads them in tag_user. The three functions are internally consistent:

  • tag_user correctly reads tag.key and tag.value from req.rpc_params.tags
  • untag_user correctly filters by tag.key
  • list_user_tags correctly projects the internal schema to the API response schema

No input normalization is needed.

src/endpoint/iam/iam_rest.js (1)

16-33: Confirm all RpcError codes used by IAM flows are covered by RPC_ERRORS_TO_IAM

The new mapping is IAM-aligned, but any RpcError code not listed here will fall back to InternalFailure. Given the new helpers in account_util.js and IAM methods in account_server.js now throw RpcError with IAM-style codes (e.g., NoSuchEntity, EntityAlreadyExists, LimitExceeded, DeleteConflict), please double‑check that every IAM-related RpcError code you emit is either:

  • explicitly mapped here, or
  • intentionally treated as InternalFailure.

If new codes are added later (e.g., for additional IAM actions), they must be added to this map to preserve correct IAM error semantics.

Comment on lines +44 to +49
owner: {
type: 'string',
},
iam_path: {
type: 'string',
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align owner schema with server-side type conversion

create_account now exposes an owner: string param, which is then forwarded into account_util.create_account. On the server, account objects expect owner to be an ObjectId-like reference (with ._id) when stored in system_store.

Please ensure that account_util.create_account always converts this string to a proper system_store ID (e.g., system_store.parse_system_store_id(req.rpc_params.owner)) before persisting, so that downstream helpers that dereference owner._id continue to work without type errors. Based on learnings

🤖 Prompt for AI Agents
In src/api/account_api.js around lines 44 to 49, the owner field is declared as
a string but downstream code expects an ObjectId-like system_store ID; ensure
you convert the string owner to a system_store ID before persisting by calling
system_store.parse_system_store_id(req.rpc_params.owner) (either immediately
before passing params into account_util.create_account or inside
account_util.create_account), so the stored account.owner is an object with the
expected ._id shape and downstream dereferences do not break.

Comment on lines 37 to 63
async create_user(params, account_sdk) {

const action = IAM_ACTIONS.CREATE_USER;
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,
{ username: params.username, path: params.iam_path });
account_util._check_username_already_exists(action, params, requesting_account);
const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), params.username,
params.iam_path || IAM_DEFAULT_PATH);
const account_name = account_util.get_account_name_from_username(params.username, requesting_account._id.toString());
const req = {
rpc_params: {
name: account_name,
email: account_name,
has_login: false,
s3_access: true,
allow_bucket_creation: true,
owner: requesting_account._id,
is_iam: true,
owner: requesting_account._id.toString(),
iam_path: params.iam_path,
role: 'admin',

roles: ['admin'],
// TODO: default_resource remove
default_resource: 'noobaa-default-backing-store',
},
account: requesting_account,
};
const iam_account = await account_util.create_account(req);

default_resource: requesting_account.default_resource.name,
};
const iam_account = await account_sdk.rpc_client.account.create_account(req, requesting_account);
// TODO : Clean account cache
// TODO : Send Event
const requested_account = system_store.get_account_by_email(account_name);
return {
iam_path: requested_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,
arn: iam_account.arn,
create_date: iam_account.create_date,
};

}
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

Avoid repeated system_store.get_account_by_email lookups and clarify owner semantics

Every IAM method re‑resolves requesting_account from account_sdk.requesting_account.email, and create_user passes owner: requesting_account._id.toString() into the RPC layer. This works, but:

  • You’re doing the same system_store.get_account_by_email(...) in every method; a tiny helper (or caching on account_sdk) would reduce repetition and DB lookups.
  • On the server side, account_util.create_account currently assigns account.owner = req.rpc_params.owner directly. Since server‑side account objects expect owner to be an ObjectId-like reference (with ._id), keeping owner as a bare string in the DB will break places that do account.owner._id (for example, return_list_member() and get_account_info()), and will also crash list_users() when it does account.owner?._id.toString().

I’d recommend:

  • keep sending owner as a string ID over RPC (as you do now), and
  • in account_util.create_account, convert that to a system_store ID (e.g., via system_store.parse_system_store_id) before assigning to account.owner.

That keeps the wire format simple while preserving the in‑memory owner shape expected by account_server/account_util. Based on learnings

🤖 Prompt for AI Agents
In src/sdk/accountspace_nb.js around lines 37-63, avoid repeated
system_store.get_account_by_email lookups by caching the resolved
requesting_account on the account_sdk (e.g., populate
account_sdk.requesting_account once and reuse it) or add a small helper to
return the cached account; and ensure you continue to send owner as a string ID
over RPC but update the server-side account_util.create_account to convert that
string into a system_store ID/object (use system_store.parse_system_store_id or
equivalent) before assigning to account.owner so the in-memory account.owner
retains the expected ObjectId-like shape.

const SensitiveString = require('../util/sensitive_string');
const cloud_utils = require('../util/cloud_utils');
const auth_server = require('..//server/common_services/auth_server');
const system_store = require('..//server/system_services/system_store').get_instance();
Copy link
Contributor

Choose a reason for hiding this comment

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

If this file is planned to serve NC and Containerized - why do we have system_store in this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, planned to serve NC and Containerized, this is only for noobaa accounts and IAM accounts
This wont be share between NSFS NC

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will push it to next PR, Its not related to account flow

Signed-off-by: Naveen Paul <napaul@redhat.com>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/util/account_util.js (1)

106-145: Persisting owner as a raw string breaks server-side owner semantics

In create_account you now do:

const account_access_info = create_access_key_auth(req, account, req.rpc_params.owner);
...
if (req.rpc_params.owner) {
    account.owner = req.rpc_params.owner;
    account.iam_path = req.rpc_params.iam_path;
}
...
return {
    token: auth_server.make_auth_token(auth),
    access_keys: account_access_info.decrypted_access_keys,
    id: created_account._id.toString(),
    create_date: req.rpc_params.owner ? Date.now() : undefined, // GAP
};

On the server, account objects loaded from system_store expect owner to be an ObjectId-like reference (with ._id), not a bare string. This is relied on in several places:

  • get_account_info in account_server.js uses account.owner._id.toString() to populate info.owner.
  • _get_account_owner_id_for_arn uses requesting_account.owner._id to build ARNs.
  • _check_root_account_owns_user and return_list_member access user_account.owner._id.

If IAM users are created with account.owner set to a string here, those sites will either throw (owner._id is undefined) or silently produce wrong ARNs/ownership checks, and list_usersaccount.owner?._id.toString() === requesting_account._id.toString() will fail for such users. Based on learnings, account_server.js only processes accounts from account_schema where owner is an ObjectId-like reference, so this regression is specific to the new IAM flow.

You should convert the incoming owner string to a system_store ID before persisting, and keep the in‑memory shape consistent:

 async function create_account(req) {
-    const account_access_info = create_access_key_auth(req, account, req.rpc_params.owner);
+    const is_iam_user = Boolean(req.rpc_params.owner);
+    const account_access_info = create_access_key_auth(req, account, is_iam_user);
    ...
-    if (req.rpc_params.owner) {
-        account.owner = req.rpc_params.owner;
-        account.iam_path = req.rpc_params.iam_path;
-    }
+    if (req.rpc_params.owner) {
+        account.owner = system_store.parse_system_store_id(req.rpc_params.owner);
+        account.iam_path = req.rpc_params.iam_path || IAM_DEFAULT_PATH;
+    }
    ...
-    return {
-        token: auth_server.make_auth_token(auth),
-        access_keys: account_access_info.decrypted_access_keys,
-        id: created_account._id.toString(),
-        create_date: req.rpc_params.owner ? Date.now() : undefined,
-    };
+    return {
+        token: auth_server.make_auth_token(auth),
+        access_keys: account_access_info.decrypted_access_keys,
+        id: created_account._id.toString(),
+        create_date: is_iam_user ? Date.now() : undefined, // GAP: still not persisted
+    };
 }

This keeps the wire format (owner as string ID over RPC) while preserving the ObjectId‑like owner shape inside system_store, matching how other server paths behave.

♻️ Duplicate comments (3)
src/sdk/accountspace_nb.js (1)

39-170: Repeated system_store.get_account_by_email lookups in every IAM method

Each IAM wrapper (create_user, get_user, update_user, delete_user, list_users, access‑key/tag/policy methods) re‑resolves requesting_account from account_sdk.requesting_account.email via system_store.get_account_by_email(...) before making the RPC call.

Given account_sdk.requesting_account is already the authenticated account, you can:

  • either pass it directly to the RPC calls, or
  • add a small helper on AccountSpaceNB to resolve/cache once and reuse.

This will cut redundant DB reads and simplify the code, without changing behavior.

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

1195-1212: get_user builds ARNs from caller ID instead of the user’s owning account

get_user currently does:

const requesting_account = req.account;
const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account);
const username = account_util.get_iam_username(req.rpc_params.username || requested_account.name.unwrap());
const iam_arn = iam_utils.create_arn_for_user(
    requesting_account._id.toString(),
    username,
    requested_account.iam_path || IAM_DEFAULT_PATH
);

If an IAM user calls GetUser on itself (no username param), requesting_account === requested_account, so the ARN accountId comes from the IAM user’s own _id. When the root owner calls GetUser for the same user, the ARN accountId comes from the root account. The same logical user can thus have different ARNs depending on who calls the API.

To make ARNs stable and rooted at the owning root account, derive the accountId from the requested account’s owner when present, or from its own _id when it’s a root account. For example:

-    const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username,
-                                requested_account.iam_path || IAM_DEFAULT_PATH);
+    let owner_id_for_arn;
+    if (requested_account.owner !== undefined) {
+        owner_id_for_arn = typeof requested_account.owner === 'object'
+            ? requested_account.owner._id.toString()
+            : requested_account.owner.toString();
+    } else {
+        owner_id_for_arn = requested_account._id.toString();
+    }
+    const iam_arn = iam_utils.create_arn_for_user(
+        owner_id_for_arn,
+        username,
+        requested_account.iam_path || IAM_DEFAULT_PATH
+    );

This keeps the ARN consistent regardless of caller and correctly anchors it to the owning root account.

src/api/account_api.js (1)

645-705: create_user should require owner to avoid accidentally creating root accounts

The create_user RPC is intended for IAM users, but its params only require ['email', 'name'] while owner is optional:

required: ['email', 'name'],
properties: {
    ...
    owner: { type: 'string' },
    iam_path: { type: 'string' },
}

On the server, account_server.create_user just forwards into create_account(req), whose IAM branch is triggered solely by req.rpc_params.owner. If owner is omitted, the call will follow the non‑IAM branch and effectively create a regular/root account instead of an IAM user.

To keep the API unambiguous, you should either:

  • add owner to the required array for create_user, or
  • drop the explicit owner param and derive the owner from req.account in the server implementation.
🧹 Nitpick comments (2)
src/api/account_api.js (1)

916-1000: Tag input vs output schema use different tag definitions (intentional, but worth documenting)

For tag_user, the tags param uses common_api#/definitions/tag, while list_user_tags reply wraps member with the local #/definitions/tag (Key/Value). Server-side, account_server.tag_user stores tags as { key, value } and list_user_tags maps them to { Key, Value } in the response.

Functionally this works, but the mixed use of common_api vs local tag may be confusing for clients. Consider:

  • adding a brief comment near the methods stating that input uses lower-case key/value and output uses AWS-style Key/Value, or
  • standardizing both sides on the same schema if you don’t need the distinction.
src/util/account_util.js (1)

195-256: Using req.rpc_params.owner as the “is_iam” flag in generate_account_keys

In generate_account_keys you now distinguish IAM from non‑IAM flows by checking req.rpc_params.owner:

// IAM create accesskey will have multiple accesskeys.
if (req.rpc_params.owner) {
    account_access_keys.push(access_key_obj);
} else {
    // Noobaa CLI create and generate accesskey commands replace existing accesskey
    account_access_keys[0] = access_key_obj;
}
...
if (req.rpc_params.owner) {
    return decrypted_access_keys;
}

Functionally this is fine given the new IAM flows always pass owner for IAM users, and non‑IAM callers don’t. To make intent clearer and avoid accidental coupling to the exact type of owner (string vs falsy), consider normalizing at the top:

async function generate_account_keys(req) {
+    const is_iam_user = Boolean(req.rpc_params.owner);
    ...
-    if (req.rpc_params.owner) {
+    if (is_iam_user) {
        account_access_keys.push(access_key_obj);
    } else {
        account_access_keys[0] = access_key_obj;
    }
    ...
-    if (req.rpc_params.owner) {
+    if (is_iam_user) {
        return decrypted_access_keys;
    }

This avoids subtle bugs if owner is ever changed to a non-string truthy value or refactored.

📜 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 ffe8131 and 8598600.

📒 Files selected for processing (5)
  • src/api/account_api.js (4 hunks)
  • src/endpoint/iam/iam_rest.js (2 hunks)
  • src/sdk/accountspace_nb.js (2 hunks)
  • src/server/system_services/account_server.js (5 hunks)
  • src/util/account_util.js (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/endpoint/iam/iam_rest.js
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-11-19T15:03:42.231Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.231Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/server/system_services/account_server.js
  • src/sdk/accountspace_nb.js
  • src/util/account_util.js
  • src/api/account_api.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/server/system_services/account_server.js
  • src/sdk/accountspace_nb.js
  • src/util/account_util.js
  • src/api/account_api.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/server/system_services/account_server.js
  • src/sdk/accountspace_nb.js
  • src/util/account_util.js
  • src/api/account_api.js
📚 Learning: 2025-11-18T07:00:17.636Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.636Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/server/system_services/account_server.js
📚 Learning: 2025-06-17T12:59:51.543Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9042
File: src/util/cloud_utils.js:183-194
Timestamp: 2025-06-17T12:59:51.543Z
Learning: In the set_noobaa_s3_connection function in src/util/cloud_utils.js, internal NooBaa S3 endpoints (filtered by 'api': 's3', 'kind': 'INTERNAL') will always be non-secure, so tls: false is the correct setting despite the conditional https:// scheme logic.

Applied to files:

  • src/server/system_services/account_server.js
🧬 Code graph analysis (3)
src/server/system_services/account_server.js (2)
src/util/account_util.js (16)
  • req (685-685)
  • access_key (496-497)
  • requested_account (705-705)
  • username (390-391)
  • username (423-424)
  • system_store (12-12)
  • _ (4-4)
  • account (30-42)
  • account (196-196)
  • account (304-304)
  • account (325-325)
  • account (362-362)
  • account (628-628)
  • iam_user_policies (620-620)
  • iam_user_policy_index (572-572)
  • iam_user_policy_index (580-581)
src/endpoint/iam/iam_constants.js (4)
  • IAM_ACTIONS (5-23)
  • IAM_DEFAULT_PATH (69-69)
  • ACCESS_KEY_STATUS_ENUM (55-58)
  • MAX_TAGS (67-67)
src/sdk/accountspace_nb.js (3)
src/server/system_services/account_server.js (24)
  • require (13-13)
  • require (21-21)
  • require (22-22)
  • require (23-23)
  • require (27-28)
  • requesting_account (1198-1198)
  • requesting_account (1218-1218)
  • requesting_account (1260-1260)
  • requesting_account (1277-1277)
  • requesting_account (1305-1305)
  • requesting_account (1336-1336)
  • requesting_account (1349-1349)
  • requesting_account (1377-1377)
  • requesting_account (1394-1394)
  • requesting_account (1415-1415)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • req (134-134)
  • req (397-397)
  • system_store (17-17)
src/util/account_util.js (6)
  • require (8-8)
  • require (14-14)
  • require (15-15)
  • require (16-17)
  • req (685-685)
  • system_store (12-12)
src/endpoint/iam/iam_constants.js (1)
  • IAM_DEFAULT_PATH (69-69)
src/util/account_util.js (2)
src/server/system_services/account_server.js (68)
  • req (134-134)
  • req (397-397)
  • account (69-69)
  • account (117-117)
  • account (135-137)
  • account (159-159)
  • account (207-207)
  • account (330-330)
  • account (693-693)
  • account (957-957)
  • action (40-40)
  • action (1197-1197)
  • action (1217-1217)
  • action (1259-1259)
  • action (1276-1276)
  • action (1304-1304)
  • action (1335-1335)
  • action (1347-1347)
  • action (1376-1376)
  • action (1392-1392)
  • action (1414-1414)
  • action (1456-1456)
  • action (1485-1485)
  • action (1515-1515)
  • action (1543-1543)
  • action (1557-1557)
  • username (1200-1200)
  • username (1261-1261)
  • username (1382-1382)
  • username (1416-1416)
  • username (1458-1458)
  • username (1487-1487)
  • params (329-329)
  • params (692-692)
  • params (956-956)
  • requesting_account (1198-1198)
  • requesting_account (1218-1218)
  • requesting_account (1260-1260)
  • requesting_account (1277-1277)
  • requesting_account (1305-1305)
  • requesting_account (1336-1336)
  • requesting_account (1349-1349)
  • requesting_account (1377-1377)
  • requesting_account (1394-1394)
  • requesting_account (1415-1415)
  • requesting_account (1457-1457)
  • requesting_account (1486-1486)
  • requesting_account (1516-1516)
  • requesting_account (1545-1545)
  • requesting_account (1559-1559)
  • requesting_account (1578-1578)
  • requested_account (1199-1199)
  • requested_account (1223-1223)
  • requested_account (1264-1264)
  • requested_account (1306-1306)
  • requested_account (1337-1337)
  • requested_account (1350-1350)
  • requested_account (1396-1396)
  • requested_account (1421-1421)
  • requested_account (1463-1463)
  • requested_account (1492-1492)
  • requested_account (1518-1518)
  • requested_account (1546-1546)
  • requested_account (1560-1560)
  • requested_account (1579-1579)
  • iam_path (1224-1224)
  • iam_path (1287-1287)
  • iam_username (1286-1286)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (293-293)
  • RpcError (7-7)
⏰ 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 (2)
src/util/account_util.js (2)

703-719: validate_and_return_requested_account helper looks solid and centralizes IAM account resolution

The new validate_and_return_requested_account correctly captures the common pattern:

  • When params.username is missing, it treats the action as operating on the requesting account itself.
  • When params.username is provided, it enforces:
    • root‑only operation via _check_if_requesting_account_is_root_account,
    • existence of the derived account email,
    • that the requested account is a root or IAM user as appropriate, and
    • that it is owned by the requesting root via _check_if_requested_is_owned_by_root_account.

This nicely de-duplicates logic across get_user, access‑key, and policy methods. No changes needed here.


721-731: return_list_member assumes iam_user.owner is an ObjectId-like reference

return_list_member builds the ARN using:

arn: create_arn_for_user(iam_user.owner._id.toString(), iam_username, iam_path),

This relies on iam_user.owner being an ObjectId-like reference (with ._id), which is correct once create_account stores owner via system_store.parse_system_store_id as suggested above. Just be aware that if owner were ever accidentally left as a string again, this would throw. Fixing owner persistence in create_account should keep this helper safe.

Comment on lines 37 to 62
async create_user(params, account_sdk) {

const action = IAM_ACTIONS.CREATE_USER;
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,
{ username: params.username, path: params.iam_path });
account_util._check_username_already_exists(action, params, requesting_account);
const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), params.username,
params.iam_path || IAM_DEFAULT_PATH);
const account_name = account_util.get_account_name_from_username(params.username, requesting_account._id.toString());
const req = {
rpc_params: {
name: account_name,
email: account_name,
has_login: false,
s3_access: true,
allow_bucket_creation: true,
owner: requesting_account._id,
is_iam: true,
owner: requesting_account._id.toString(),
iam_path: params.iam_path,
role: 'admin',

roles: ['admin'],
// TODO: default_resource remove
default_resource: 'noobaa-default-backing-store',
},
account: requesting_account,
};
const iam_account = await account_util.create_account(req);

default_resource: requesting_account.default_resource.name,
};
const iam_account = await account_sdk.rpc_client.account.create_user(req, requesting_account);
// TODO : Clean account cache
// TODO : Send Event
const requested_account = system_store.get_account_by_email(account_name);
return {
iam_path: requested_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,
arn: iam_account.arn,
create_date: iam_account.create_date,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

create_user returns create_date that is never set by the RPC server

create_user forwards to account.create_user and then exposes create_date: iam_account.create_date, but account_server.create_account only returns { id, arn, token, access_keys } and drops the create_date coming from account_util.create_account. This means create_date here will always be undefined.

Either:

  • propagate create_date from account_server.create_account and include it in the RPC reply, or
  • drop create_date from this SDK response until the server actually sets it, to avoid a misleading field.
🤖 Prompt for AI Agents
In src/sdk/accountspace_nb.js around lines 37 to 62, the returned object
includes create_date which is never provided by the RPC response; remove the
create_date property from the returned object so the SDK does not expose an
undefined value. Update the return to omit create_date (leave iam_path,
username, user_id, arn) and add a TODO comment noting to reintroduce create_date
once account_server.create_account includes it in its RPC reply.

@naveenpaul1 naveenpaul1 requested a review from shirady November 20, 2025 13:06
@shirady
Copy link
Contributor

shirady commented Nov 20, 2025

@naveenpaul1 Approving as I understand that would be following PR

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