Skip to content

Conversation

@necrodome
Copy link
Collaborator

@necrodome necrodome commented Oct 10, 2025

Summary by CodeRabbit

  • New Features

    • Users can share ledgers with others via email.
    • Configurable roles and permissions when granting access.
    • Prevents sharing a ledger with oneself.
  • Security

    • Improved token verification and session/token refresh handling for share actions.
    • Clearer authorization and not-found error messages during sharing flows.

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

@necrodome necrodome requested a review from mabels October 10, 2025 18:06
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds ledger-sharing-by-email: new request/response message types and a type guard; FP API method to share a ledger with a user (token verification, auth checks, DB updates); request handler routing for the new message; minor import reorganizations.

Changes

Cohort / File(s) Summary
Message types & guards
core/protocols/dashboard/msg-types.ts, core/protocols/dashboard/msg-is.ts
Add ReqShareWithUser and ResShareWithUser interfaces; add/import ReqShareWithUser and implement isReqShareWithUser type guard in FAPIMsgImpl.
Backend API logic
dashboard/backend/api.ts
Add shareWithUser(req: ReqShareWithUser): Promise<Result<ResShareWithUser>> to FPApiInterface/FPApiSQL; add private verifyFPToken method; update token verification/extension flow and related imports/types.
Request routing / handler
dashboard/backend/create-handler.ts
Add case for FPAPIMsg.isReqShareWithUser(jso) to route to fpApi.shareWithUser; reorder imports.
HTTP server imports
dashboard/backend/cf-serve.ts
Reorganize imports from Cloudflare types/modules (import ordering/explicit CFRequest/CFResponse; no functional change).
Repository metadata
manifest_file, package.json
Metadata / dependency-related files touched per diff (updates not detailed in diff summary).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant Handler as Request Handler
    participant API as FPApiSQL
    participant DB as Database
    participant JWT as Token Verifier

    Client->>Handler: POST ReqShareWithUser
    activate Handler
    Handler->>Handler: isReqShareWithUser(jso)
    Handler->>API: shareWithUser(req)
    deactivate Handler

    activate API
    API->>JWT: verifyFPToken(auth token)
    alt token invalid
        JWT-->>API: Error
        API-->>Client: Error (Invalid token)
    else token valid
        API->>DB: Lookup ledger & verify admin
        alt ledger not found
            API-->>Client: Error (Not found)
        else not admin
            API-->>Client: Error (Not authorized)
        else admin
            API->>DB: Lookup user by email
            API->>DB: Add user to ledger (role/right)
            API-->>Client: ResShareWithUser (success)
        end
    end
    deactivate API
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review verifyFPToken for correct JWT validation, error handling, and integration with extendToken/createFPToken.
  • Inspect shareWithUser for authorization checks (ledger admin verification), self-share prevention, and correct DB updates (user lookup, tenant/ledger updates).
  • Confirm ReqShareWithUser / ResShareWithUser shapes align with handler and clients and that isReqShareWithUser correctly narrows input.
  • Check import reorganizations in create-handler.ts and cf-serve.ts for unused or missing imports.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 clearly and specifically describes the primary change: adding a new endpoint for sharing users. It accurately reflects the main objective of the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch selem/sharing

📜 Recent 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 269c3a3 and 7916aaa.

📒 Files selected for processing (5)
  • core/protocols/dashboard/msg-is.ts (3 hunks)
  • core/protocols/dashboard/msg-types.ts (1 hunks)
  • dashboard/backend/api.ts (8 hunks)
  • dashboard/backend/cf-serve.ts (1 hunks)
  • dashboard/backend/create-handler.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/protocols/dashboard/msg-types.ts
  • core/protocols/dashboard/msg-is.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-09T19:56:31.545Z
Learnt from: mabels
Repo: fireproof-storage/fireproof PR: 1130
File: core/device-id/device-id-protocol.ts:47-60
Timestamp: 2025-09-09T19:56:31.545Z
Learning: In the Result type from adviser/cement, Result.Err can accept Result objects directly without needing to unwrap them with .Err(). The codebase consistently uses Result.Err(resultObject) pattern, as seen in core/keybag/key-bag.ts lines 94 and 102.

Applied to files:

  • dashboard/backend/api.ts
🧬 Code graph analysis (2)
dashboard/backend/api.ts (5)
core/protocols/dashboard/msg-types.ts (4)
  • ReqShareWithUser (407-413)
  • ResShareWithUser (415-424)
  • ReqExtendToken (518-521)
  • ResExtendToken (523-526)
dashboard/backend/create-fp-token.ts (3)
  • FPTokenContext (7-14)
  • getFPTokenContext (31-53)
  • createFPToken (16-29)
dashboard/backend/ledgers.ts (1)
  • sqlLedgers (7-25)
dashboard/backend/users.ts (1)
  • queryUser (56-62)
core/types/protocols/cloud/msg-types.zod.ts (1)
  • FPCloudClaim (43-43)
dashboard/backend/create-handler.ts (1)
dashboard/backend/api.ts (1)
  • FPAPIMsg (127-127)
🔇 Additional comments (3)
dashboard/backend/cf-serve.ts (1)

1-2: Typed Cloudflare Request/Response wiring looks good

Aliasing Request/Response to CFRequest/CFResponse from @cloudflare/workers-types and using them only for env.ASSETS.fetch keeps the worker-global Request/Response free and avoids type confusion. No behavior change here.

dashboard/backend/create-handler.ts (1)

7-11: FP API wiring and Env typing are consistent

The new imports (FPAPIMsg, FPApiSQL, FPApiToken, DashSqlite, Env) and the isReqShareWithUser switch case follow the existing handler pattern: the API surface is extended cleanly and the new request type is routed to fpApi.shareWithUser without altering error handling or CORS behavior.

Also applies to: 304-306

dashboard/backend/api.ts (1)

2070-2119: FP token verification and extension flow look consistent

The new verifyFPToken helper and extendToken implementation correctly:

  • Derive a JWK from ctx.publicToken and call jwtVerify with issuer/audience constraints.
  • Reject expired tokens (redundant with jwtVerify, but safe).
  • Re-issue a token using createFPToken with validFor overridden to ctx.extendValidFor, preserving the original claim payload.

This is a reasonable and self-contained JWT flow; no behavioral issues spotted in these additions.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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
Contributor

@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

🧹 Nitpick comments (1)
dashboard/backend/api.ts (1)

2008-2027: Manual expiry check is redundant.

The manual expiry check at line 2019 is redundant because jwtVerify already validates token expiry by default. While this doesn't cause issues, removing it would simplify the code.

Consider applying this diff to remove the redundant check:

       const payload = verifyResult.payload as FPCloudClaim;
 
-      // Check if token is expired
-      const now = Date.now();
-      if (!payload.exp || payload.exp * 1000 <= now) {
-        return Result.Err("Token is expired");
-      }
-
       return Result.Ok(payload);
📜 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 b957cc6 and 803b88b.

📒 Files selected for processing (4)
  • core/protocols/dashboard/msg-is.ts (3 hunks)
  • core/protocols/dashboard/msg-types.ts (1 hunks)
  • dashboard/backend/api.ts (8 hunks)
  • dashboard/backend/create-handler.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
core/protocols/dashboard/msg-types.ts (1)
core/types/protocols/cloud/msg-types.zod.ts (2)
  • Role (38-38)
  • ReadWrite (39-39)
core/protocols/dashboard/msg-is.ts (1)
core/protocols/dashboard/msg-types.ts (1)
  • ReqShareWithUser (408-414)
dashboard/backend/api.ts (5)
core/protocols/dashboard/msg-types.ts (4)
  • ReqShareWithUser (408-414)
  • ResShareWithUser (416-425)
  • ReqExtendToken (519-522)
  • ResExtendToken (524-527)
dashboard/backend/create-fp-token.ts (3)
  • FPTokenContext (7-14)
  • getFPTokenContext (31-53)
  • createFPToken (16-29)
dashboard/backend/ledgers.ts (1)
  • sqlLedgers (7-25)
dashboard/backend/users.ts (1)
  • queryUser (56-62)
core/types/protocols/cloud/msg-types.zod.ts (1)
  • FPCloudClaim (43-43)
🔇 Additional comments (6)
dashboard/backend/create-handler.ts (1)

275-277: LGTM!

The new route handler for shareWithUser follows the established pattern and correctly routes the request to fpApi.shareWithUser(jso).

core/protocols/dashboard/msg-types.ts (1)

408-425: LGTM!

The new request and response interfaces for sharing ledgers with users are well-structured and follow the established patterns in this file.

core/protocols/dashboard/msg-is.ts (1)

108-110: LGTM!

The type guard implementation for ReqShareWithUser follows the established pattern and correctly uses hasType to check for the "reqShareWithUser" type string.

dashboard/backend/api.ts (3)

1716-1746: LGTM!

The token verification and authorization flow is well-implemented:

  • Properly retrieves and validates the JWT context
  • Extracts user and ledger IDs from the verified token payload
  • Enforces admin-level access before allowing ledger sharing

1748-1778: LGTM!

The user lookup and validation logic is robust:

  • Properly validates ledger existence and active status
  • Handles edge cases: user not found, multiple users, self-sharing
  • Provides clear error messages guiding users to sign up first

2032-2057: LGTM!

The token extension implementation is well-structured:

  • Properly verifies the existing token before extending
  • Creates a new token with the extended validity period from configuration
  • Maintains the original payload claims


// 6. Add user to tenant first
const rAddUserToTenant = await this.addUserToTenant(this.db, {
userName: req.email,
Copy link
Contributor

Choose a reason for hiding this comment

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

you want only share a ledger not a hole tenant --- that basically shares all ledger of the user to the other.

Copy link
Contributor

@mabels mabels left a comment

Choose a reason for hiding this comment

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

Where you do an invite? --- it does not help

Copy link
Contributor

@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 (1)
dashboard/backend/api.ts (1)

1814-1814: Fix inconsistent default for right field.

The response returns right: req.right || "write" but line 1799 adds the user to the ledger with right: req.right || "read". This mismatch means the response claims "write" access when "read" access was actually granted.

Apply this diff:

       role: req.role || "member",
-      right: req.right || "write",
+      right: req.right || "read",
     });
🧹 Nitpick comments (1)
dashboard/backend/api.ts (1)

2017-2021: Redundant expiration check.

The jwtVerify function from the jose library automatically validates token expiration. The manual check at lines 2019-2021 is redundant and can be removed.

Apply this diff to remove the redundant check:

       const payload = verifyResult.payload as FPCloudClaim;
 
-      // Check if token is expired
-      const now = Date.now();
-      if (!payload.exp || payload.exp * 1000 <= now) {
-        return Result.Err("Token is expired");
-      }
-
       return Result.Ok(payload);
📜 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 803b88b and 36d7bda.

📒 Files selected for processing (4)
  • core/protocols/dashboard/msg-is.ts (3 hunks)
  • core/protocols/dashboard/msg-types.ts (1 hunks)
  • dashboard/backend/api.ts (8 hunks)
  • dashboard/backend/create-handler.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/protocols/dashboard/msg-is.ts
  • dashboard/backend/create-handler.ts
🧰 Additional context used
🧬 Code graph analysis (2)
core/protocols/dashboard/msg-types.ts (1)
core/types/protocols/cloud/msg-types.zod.ts (2)
  • Role (38-38)
  • ReadWrite (39-39)
dashboard/backend/api.ts (5)
core/protocols/dashboard/msg-types.ts (4)
  • ReqShareWithUser (408-414)
  • ResShareWithUser (416-425)
  • ReqExtendToken (519-522)
  • ResExtendToken (524-527)
dashboard/backend/create-fp-token.ts (3)
  • FPTokenContext (7-14)
  • getFPTokenContext (31-53)
  • createFPToken (16-29)
dashboard/backend/ledgers.ts (1)
  • sqlLedgers (7-25)
dashboard/backend/users.ts (1)
  • queryUser (56-62)
core/types/protocols/cloud/msg-types.zod.ts (1)
  • FPCloudClaim (43-43)
🔇 Additional comments (3)
core/protocols/dashboard/msg-types.ts (1)

408-425: LGTM! Well-structured interfaces for the share-with-user feature.

The request interface appropriately uses optional role and right fields, while the response ensures these fields are always populated with the actual values used. The structure aligns well with other request/response pairs in this file.

dashboard/backend/api.ts (2)

2032-2057: LGTM! Clean refactor of token extension logic.

The refactored extendToken method correctly leverages the new verifyFPToken helper and createFPToken utility. The flow is clear: verify the existing token, then generate a new one with extended validity using ctx.extendValidFor.


1780-1790: Tenant membership alone does not grant ledger access
isAdminOfLedger only checks sqlLedgerUsers entries (and falls back to tenant-admin for existing ledgerUsers members); tenant “member” roles have no ledger permissions.

Copy link
Contributor

@mabels mabels left a comment

Choose a reason for hiding this comment

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

From where the double CORS is coming from?
--- this is not correct, this will break the local version.

@necrodome
Copy link
Collaborator Author

From where the double CORS is coming from? --- this is not correct, this will break the local version.

@mabels
Copy link
Contributor

mabels commented Oct 14, 2025

that had been there for ever, the question where is the second coming from

@mabels
Copy link
Contributor

mabels commented Oct 15, 2025

pls use u github handle in the branch name @necrodome

@mabels
Copy link
Contributor

mabels commented Oct 15, 2025

move the CORS fix in mabels/fix-double-cors or #1257

Copy link
Contributor

@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

🧹 Nitpick comments (2)
dashboard/src/pages/cloud/api/token-auto.tsx (1)

54-59: Good addition for shared ledger support.

The new getListLedgersByUser() query correctly retrieves all ledgers including shared ones. The dual-source approach (tenantsData + allUserLedgers) ensures shared ledgers are accessible even when not in the user's tenant list.

Minor: The comment at line 54 suggests tenantsData includes shared ledgers, but the need for a separate query implies it may not—consider clarifying that comment.

dashboard/backend/api.ts (1)

2007-2011: Remove redundant expiration check.

The manual expiration check on lines 2007-2011 is unnecessary because jwtVerify already validates the exp claim as part of the standard JWT verification process. This check will never be reached for an expired token since jwtVerify would throw first.

Apply this diff to remove the redundant check:

       const payload = verifyResult.payload as FPCloudClaim;
-
-      // Check if token is expired
-      const now = Date.now();
-      if (!payload.exp || payload.exp * 1000 <= now) {
-        return Result.Err("Token is expired");
-      }
 
       return Result.Ok(payload);
📜 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 a7a362d and 1a58862.

📒 Files selected for processing (5)
  • core/protocols/dashboard/msg-is.ts (3 hunks)
  • core/protocols/dashboard/msg-types.ts (1 hunks)
  • dashboard/backend/api.ts (8 hunks)
  • dashboard/backend/create-handler.ts (2 hunks)
  • dashboard/src/pages/cloud/api/token-auto.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/protocols/dashboard/msg-types.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-09T19:56:31.545Z
Learnt from: mabels
Repo: fireproof-storage/fireproof PR: 1130
File: core/device-id/device-id-protocol.ts:47-60
Timestamp: 2025-09-09T19:56:31.545Z
Learning: In the Result type from adviser/cement, Result.Err can accept Result objects directly without needing to unwrap them with .Err(). The codebase consistently uses Result.Err(resultObject) pattern, as seen in core/keybag/key-bag.ts lines 94 and 102.

Applied to files:

  • dashboard/backend/api.ts
🧬 Code graph analysis (4)
dashboard/backend/create-handler.ts (1)
dashboard/backend/api.ts (1)
  • FPAPIMsg (125-125)
dashboard/src/pages/cloud/api/token-auto.tsx (1)
dashboard/src/cloud-context.ts (1)
  • createLedgerMutation (290-312)
dashboard/backend/api.ts (5)
core/protocols/dashboard/msg-types.ts (2)
  • ReqShareWithUser (408-414)
  • ResShareWithUser (416-425)
dashboard/backend/create-fp-token.ts (3)
  • FPTokenContext (7-14)
  • getFPTokenContext (31-53)
  • createFPToken (16-29)
dashboard/backend/ledgers.ts (1)
  • sqlLedgers (7-25)
dashboard/backend/users.ts (1)
  • queryUser (56-62)
core/types/protocols/cloud/msg-types.zod.ts (1)
  • FPCloudClaim (43-43)
core/protocols/dashboard/msg-is.ts (1)
core/protocols/dashboard/msg-types.ts (1)
  • ReqShareWithUser (408-414)
🪛 Biome (2.1.2)
dashboard/src/pages/cloud/api/token-auto.tsx

[error] 77-77: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (15)
dashboard/src/pages/cloud/api/token-auto.tsx (7)

2-2: LGTM!

The added imports support the new shared ledger functionality.

Also applies to: 5-5


77-80: Correct guard refinement for dual data source.

The guard change from checking both tenantsData && ledgerInfo to just ledgerInfo is correct—it allows the effect to process when either tenantsData or allUserLedgers loads, while preventing reprocessing once ledgerInfo is set.


92-125: Well-structured two-stage ledger resolution.

The new lookup strategy correctly prioritizes allUserLedgers (which includes shared ledgers) before falling back to tenantsData. This ensures shared ledgers are discovered even when not in the user's own tenants.


129-141: Excellent safeguard for shared-only users.

The added condition prevents ledger creation attempts for users who only have access to shared ledgers but don't own any tenants. This is a thoughtful addition that prevents unnecessary API calls and potential errors.


142-142: Correct dependency array.

The addition of allUserLedgers to the effect dependencies is necessary and properly maintains effect correctness.


206-206: Error handling properly extended.

The inclusion of allUserLedgersError ensures all data-fetching errors are captured and displayed to the user.


77-77: Static analysis false positive: hooks are correctly positioned.

The Biome error claiming conditional hook usage is incorrect. All hooks (including this useEffect) are called at the component's top level in a consistent order. The early return at line 33 is a conditional render, occurring after all hooks—this is a standard and correct React pattern.

core/protocols/dashboard/msg-is.ts (2)

1-22: LGTM! Import organization is clean.

The addition of ReqShareWithUser and the reordering of imports follows a consistent alphabetical pattern, making the import block more maintainable.


44-44: LGTM! Type guard implementation follows established patterns.

The new type guard for ReqShareWithUser correctly uses the hasType helper and matches the pattern of all other type guards in this file.

Also applies to: 108-110

dashboard/backend/create-handler.ts (2)

7-7: LGTM! Import improvements enhance code organization.

The type-only import for Env on line 11 is a good TypeScript practice that can help with tree-shaking and makes the intent clearer.

Also applies to: 9-11


280-282: LGTM! Handler integration follows established patterns.

The new case for shareWithUser is correctly integrated into the request handler switch statement and follows the exact pattern of all other request handlers.

dashboard/backend/api.ts (4)

3-3: LGTM! Import additions support the new sharing functionality.

All new imports are necessary for the shareWithUser feature and token verification logic. The organization is clean and logical.

Also applies to: 9-9, 29-29, 48-48, 58-62


115-115: LGTM! Interface method declaration is properly typed.

The shareWithUser method signature follows the established pattern and uses appropriate Result-wrapped types.


1781-1792: Graceful handling of already-shared ledgers.

The addUserToLedger helper (lines 456-541) returns early if the user is already attached to the ledger (line 470-497), which means shareWithUser will succeed idempotently when called multiple times for the same user. This is good behavior, though the success message "Successfully shared ledger" might be slightly misleading on repeated calls. Consider whether the response should indicate if this was a new share or an existing one.


2022-2047: LGTM! Clean refactoring using the new verifyFPToken helper.

The refactored extendToken method is more maintainable by leveraging the shared verifyFPToken helper. The logic correctly extends the token expiry using ctx.extendValidFor and follows the established Result pattern.

Copy link
Contributor

@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)
dashboard/backend/cf-serve.ts (1)

1-7: Remove duplicate imports.

Lines 1-3 duplicate lines 5-7, causing redeclaration errors that will prevent the code from compiling or running. Additionally, CORS is imported but never used, and DefaultHttpHeaders is missing from the new imports but is used on line 39.

Apply this diff to fix the duplicate imports:

-import { drizzle } from "drizzle-orm/d1";
-import { D1Database, Fetcher, Request as CFRequest, Response as CFResponse } from "@cloudflare/workers-types";
-import { DefaultHttpHeaders, createHandler } from "./create-handler.js";
-import { URI } from "@adviser/cement";
 import { Request as CFRequest, Response as CFResponse, D1Database, Fetcher } from "@cloudflare/workers-types";
 import { drizzle } from "drizzle-orm/d1";
-import { CORS, createHandler } from "./create-handler.js";
+import { DefaultHttpHeaders, createHandler } from "./create-handler.js";
+import { URI } from "@adviser/cement";
 import { resWellKnownJwks } from "./well-known-jwks.js";
♻️ Duplicate comments (1)
dashboard/backend/api.ts (1)

1814-1814: Fix inconsistent default for right field (regression).

The default value for the right field in the response (line 1814) is still inconsistent with the database operation (line 1799). This issue was previously marked as addressed, but the fix appears to have been reverted or not applied:

  • Line 1799: adds user with right: req.right || "read"
  • Line 1814: returns right: req.right || "write"

This means the response claims "write" access when only "read" access was actually granted.

Apply this diff to fix the inconsistency:

     return Result.Ok({
       type: "resShareWithUser",
       success: true,
       message: `Successfully shared ledger with ${req.email}`,
       ledgerId: ledgerId,
       userId: targetUser.userId,
       email: req.email,
       role: req.role || "member",
-      right: req.right || "write",
+      right: req.right || "read",
     });
🧹 Nitpick comments (1)
dashboard/backend/api.ts (1)

1780-1786: Consider aligning tenant role with ledger role.

The user is always added to the tenant with role "member" (line 1785), regardless of the ledger role being assigned. This could create a permissions mismatch where a user is a ledger admin but only a tenant member.

Consider whether the tenant role should match or be influenced by the ledger role, especially when req.role is "admin".

Based on learnings

Apply this diff if tenant role should match ledger role:

     // 6. Add user to tenant first
     const rAddUserToTenant = await this.addUserToTenant(this.db, {
       userName: req.email,
       tenantId: ledger.tenantId,
       userId: targetUser.userId,
-      role: "member",
+      role: req.role || "member",
     });
📜 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 1a58862 and 8e88832.

📒 Files selected for processing (2)
  • dashboard/backend/api.ts (8 hunks)
  • dashboard/backend/cf-serve.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-09T19:56:31.545Z
Learnt from: mabels
Repo: fireproof-storage/fireproof PR: 1130
File: core/device-id/device-id-protocol.ts:47-60
Timestamp: 2025-09-09T19:56:31.545Z
Learning: In the Result type from adviser/cement, Result.Err can accept Result objects directly without needing to unwrap them with .Err(). The codebase consistently uses Result.Err(resultObject) pattern, as seen in core/keybag/key-bag.ts lines 94 and 102.

Applied to files:

  • dashboard/backend/api.ts
🧬 Code graph analysis (1)
dashboard/backend/api.ts (5)
core/protocols/dashboard/msg-types.ts (4)
  • ReqShareWithUser (408-414)
  • ResShareWithUser (416-425)
  • ReqExtendToken (519-522)
  • ResExtendToken (524-527)
dashboard/backend/create-fp-token.ts (3)
  • FPTokenContext (7-14)
  • getFPTokenContext (31-53)
  • createFPToken (16-29)
dashboard/backend/ledgers.ts (1)
  • sqlLedgers (7-25)
dashboard/backend/users.ts (1)
  • queryUser (56-62)
core/types/protocols/cloud/msg-types.zod.ts (1)
  • FPCloudClaim (43-43)
🪛 Biome (2.1.2)
dashboard/backend/cf-serve.ts

[error] 5-5: Shouldn't redeclare 'CFRequest'. Consider to delete it or rename it.

'CFRequest' is defined here:

(lint/suspicious/noRedeclare)


[error] 5-5: Shouldn't redeclare 'CFResponse'. Consider to delete it or rename it.

'CFResponse' is defined here:

(lint/suspicious/noRedeclare)


[error] 5-5: Shouldn't redeclare 'D1Database'. Consider to delete it or rename it.

'D1Database' is defined here:

(lint/suspicious/noRedeclare)


[error] 5-5: Shouldn't redeclare 'Fetcher'. Consider to delete it or rename it.

'Fetcher' is defined here:

(lint/suspicious/noRedeclare)


[error] 6-6: Shouldn't redeclare 'drizzle'. Consider to delete it or rename it.

'drizzle' is defined here:

(lint/suspicious/noRedeclare)


[error] 7-7: Shouldn't redeclare 'createHandler'. Consider to delete it or rename it.

'createHandler' is defined here:

(lint/suspicious/noRedeclare)

🪛 ESLint
dashboard/backend/cf-serve.ts

[error] 5-5: '@cloudflare/workers-types' imported multiple times.

(import/no-duplicates)


[error] 6-6: 'drizzle-orm/d1' imported multiple times.

(import/no-duplicates)


[error] 7-7: 'CORS' is defined but never used. Allowed unused vars must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 7-7: './create-handler.js' imported multiple times.

(import/no-duplicates)

🔇 Additional comments (2)
dashboard/backend/api.ts (2)

2008-2027: LGTM!

The verifyFPToken helper correctly verifies JWT tokens with proper signature validation, issuer/audience checks, and expiration handling. The explicit expiration check (lines 2018-2021) provides defense-in-depth even though jwtVerify performs similar validation.


2029-2057: LGTM!

The refactored extendToken method properly leverages the new verifyFPToken helper and correctly extends token validity using ctx.extendValidFor. The implementation is clean and maintains proper separation of concerns.

Comment on lines +1716 to +1885
async shareWithUser(req: ReqShareWithUser, ictx: Partial<FPTokenContext> = {}): Promise<Result<ResShareWithUser>> {
// 1. Get JWT context and verify token
const rCtx = await getFPTokenContext(this.sthis, ictx);
if (rCtx.isErr()) {
return Result.Err(rCtx.Err());
}
const ctx = rCtx.Ok();

const rPayload = await this.verifyFPToken(req.auth.token, ctx);
if (rPayload.isErr()) {
return Result.Err(rPayload.Err());
}
const payload = rPayload.Ok();

// 2. Extract user ID and ledger ID from token
if (!payload.userId) {
return Result.Err("No user ID in token");
}

if (!payload.selected?.ledger) {
return Result.Err("No ledger selected in token");
}

const userId = payload.userId;
const ledgerId = payload.selected.ledger;

// 3. Check if user is admin of the ledger
if (!(await this.isAdminOfLedger(userId, ledgerId))) {
return Result.Err("Not authorized to share this ledger. Admin access required.");
}

// 4. Get ledger details
const ledger = await this.db
.select()
.from(sqlLedgers)
.where(and(eq(sqlLedgers.ledgerId, ledgerId), eq(sqlLedgers.status, "active")))
.get();

if (!ledger) {
return Result.Err("Ledger not found or inactive");
}

// 5. Find user by email
const rUser = await queryUser(this.db, { byString: req.email });
if (rUser.isErr()) {
return Result.Err(rUser.Err());
}

const users = rUser.Ok();
if (users.length === 0) {
return Result.Err(`User with email ${req.email} not found. User must sign up first.`);
}

if (users.length > 1) {
return Result.Err(`Multiple users found for email ${req.email}`);
}

const targetUser = users[0];

// Prevent self-sharing
if (targetUser.userId === userId) {
return Result.Err("Cannot share ledger with yourself");
}

// 6. Add user to tenant first
const rAddUserToTenant = await this.addUserToTenant(this.db, {
userName: req.email,
tenantId: ledger.tenantId,
userId: targetUser.userId,
role: "member",
});

if (rAddUserToTenant.isErr()) {
return Result.Err(rAddUserToTenant.Err());
}

// 7. Add user to ledger
const rAddUser = await this.addUserToLedger(this.db, {
userName: req.email,
ledgerId: ledgerId,
tenantId: ledger.tenantId,
userId: targetUser.userId,
role: req.role || "member",
right: req.right || "read",
});

if (rAddUser.isErr()) {
return Result.Err(rAddUser.Err());
}

return Result.Ok({
type: "resShareWithUser",
success: true,
message: `Successfully shared ledger with ${req.email}`,
ledgerId: ledgerId,
userId: targetUser.userId,
email: req.email,
role: req.role || "member",
right: req.right || "write",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add maxShares validation before sharing.

The shareWithUser method doesn't check the ledger's maxShares limit (defined in the sqlLedgers schema) before adding a new user. While addUserToLedger checks for duplicate users (lines 470-493), it doesn't validate against maxShares. The checkMaxRoles call validates tenant-level role limits, not ledger-level share limits.

Add a check after line 1756 to count existing active users on the ledger and reject if it would exceed maxShares:

    // Check maxShares limit
    const activeUsers = await this.db
      .select()
      .from(sqlLedgerUsers)
      .where(
        and(
          eq(sqlLedgerUsers.ledgerId, ledgerId),
          eq(sqlLedgerUsers.status, "active")
        )
      )
      .all();
    
    if (ledger.maxShares && activeUsers.length >= ledger.maxShares) {
      return Result.Err("Ledger share limit reached. Maximum shares exceeded.");
    }
🤖 Prompt for AI Agents
In dashboard/backend/api.ts around lines 1716 to 1816 (add the new check after
line 1756), add a pre-share validation that queries sqlLedgerUsers for active
users on the ledger and compares the count against ledger.maxShares; if
ledger.maxShares is set and the active count is already >= maxShares, return a
Result.Err indicating the ledger share limit has been reached. Ensure the DB
query filters by ledgerId and status="active" and use the existing Result.Err
format/message consistent with other errors.

@jchris jchris marked this pull request as draft November 21, 2025 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants