feat(cloud): implement Secure Send sharing flow (create, list, revoke)#287
Conversation
GabrielBBaldez
left a comment
There was a problem hiding this comment.
The UI work here is solid — dialogs, revoke state, toasts and the responsive styling all look good. But the service layer never reaches the backend, so the flow can't work as it stands.
The endpoints don't exist
SecureSendController from cloud-page#104 (merged) maps:
@RequestMapping("/api")
@PostMapping("/secure-sends") // plural
@GetMapping("/secure-sends")
@DeleteMapping("/secure-sends/{id}")This PR calls /secure-send (singular), then falls back to /shares/secure-send, then /shares. All three 404 — there is no path where a link actually gets created.
The tests don't catch it because cloud.service.spec.ts asserts the same wrong URL (expectOne(\${service.apiUrl}/secure-send`)) and cloud.component.spec.tsmocksCloudService` wholesale, so the real contract is never exercised.
The request body doesn't match either
The backend takes an absolute instant, validated:
@NotBlank private String filePath;
@NotNull @Future private Instant expiresAt;
private String password;The PR sends expiryMinutes: number. Even at the correct URL this is a 400, since expiresAt would be null. The expiry dropdown can stay exactly as it is — just convert on the way out:
expiresAt: new Date(Date.now() + expiryMinutes * 60_000).toISOString()Response fields
SecureSendDto returns url, fileName, createdAt, expiresAt, passwordProtected, revoked.
revoked is read correctly. passwordProtected is not — the mapper looks for hasPassword ?? protected, so in the links table the password badge will read false for every link, including protected ones.
Please drop the catchError fallback chains
Beyond the wrong URLs, chaining alternates on a POST is worth avoiding on principle: if the first call fails after the server already created the link, the retry creates a second one. It also defeats the rate limiter that cloud-page#104 added — a 429 on the first URL just fires two more POSTs — and the error the component finally sees comes from the last attempt, so a real 401 or 500 surfaces as a 404 from /shares. The contract is merged and known, so a single call per operation is both correct and easier to debug.
Same for the mapper's invented defaults: id: ... || String(Date.now()) and expiresAt: ... || now + 24h mean a malformed response yields a link with a fabricated id (which revoke will then send to DELETE) and an expiry that doesn't match the server's. Better to let it fail loudly.
getStorageQuota looks unrelated
It isn't in the PR description and doesn't seem connected to #283 — and it defaults totalBytes to a hardcoded 10 * 1024 * 1024 * 1024 when the backend doesn't return one, which shows the user an invented quota. Worth splitting into its own PR against whatever the real quota endpoint turns out to be.
Worth knowing: #274 touches cloud.component.{ts,html,scss,spec.ts} too, so whichever lands second will need a rebase — no action needed now, just a heads-up.
Happy to look again once the calls line up with the controller.
DenizAltunkapan
left a comment
There was a problem hiding this comment.
The UI side of this is genuinely nice: the create dialog, the one-time URL display, the shared-links table, the revoke state and the responsive styling all look good. GabrielBBaldez already gave a thorough review and I agree with all of it, so I am not going to repeat every point. I verified the backend contract against SecureSendController and the DTOs from cloud-page#104 and confirmed the mismatches are real, so I am marking this as changes requested to make the blocking items explicit. Once the service calls line up with the merged controller this should be close to mergeable. Concrete items inline.
46c7fd7 to
b886ccd
Compare
GabrielBBaldez
left a comment
There was a problem hiding this comment.
Re-checked the service against SecureSendController — all four blocking items are resolved: it hits /secure-sends (plural) on create/list/revoke with no fallback chain, sends expiresAt as an instant derived from the expiry dropdown, reads passwordProtected first in the mapper, and getStorageQuota is gone. The spec now asserts the real endpoint, and CI is green.
Nice work turning it around. Approving — @DenizAltunkapan this is unblocked from my side whenever you want to re-review.
Summary
This PR implements the end-to-end Secure Send file sharing flow in the Vault Web Cloud UI, allowing users to create expiring share links with optional password protection, view generated URLs, list active links, and revoke access on demand.
Closes #283 (Consumes backend endpoints from Vault-Web/cloud-page#104).
Problem
The Cloud backend supported Secure Send links for sharing single files with external recipients, but the Vault Web frontend lacked UI controls to trigger creation, set expiration or password protection, or list and revoke existing share links.
Proposed Solution & Key Implementation Details
DTO Models (SecureSendLinkDto.ts):
Standardized SecureSendLinkDto and CreateSecureSendRequestDto for secure data exchange.
Backend API Integration (cloud.service.ts):
Added createSecureSendLink(), listSecureSendLinks(), and revokeSecureSendLink() to connect directly with backend endpoints.
Cloud UI Integration (cloud.component.html, cloud.component.ts, cloud.component.scss):
File Action: Added a Share button (pi pi-share-alt) to file rows in the Cloud table view.
Header Action: Added a Shared Links button (pi pi-link) in the Cloud header actions.
Create Dialog: Configurable modal allowing selection of expiration periods (1 Hour, 1 Day, 7 Days, 30 Days) and optional password protection.
Generated URL Display: Modal displaying the generated share link once with a Copy to Clipboard action.
Shared Links Management View: Table modal displaying active links, creation date, expiry, password protection status, and a Revoke button that dynamically marks links as Revoked.
Security & Error Handling:
Raw passwords and tokens are never logged or exposed in query parameters.
Surface error feedback and HTTP 429 rate limits via UiToastService notifications.
Accessibility & Styling:
Uniform single ariaLabel attributes across action controls.
Styled using custom CSS badges and responsive glassmorphism themes.
Unit Tests & Quality Checks:
Unit tests added in cloud.service.spec.ts and cloud.component.spec.ts.
Verified npm run prettier:check (100% compliant) and npm run build (Exit code: 0).
Acceptance Criteria Verification
A user can create a Secure Send link for a single file with an expiry and optional password.
The generated URL is shown once after creation and can be copied to the clipboard.
A user can list their active links with expiry, password state, and revocation state.
A user can revoke a link and see its state update.
Passwords and raw tokens are never exposed in query strings or logs.
Loading and error states (including HTTP 429 rate limits) are handled with toast notifications.
The flow works on desktop and mobile widths.