Skip to content

feat(cloud): implement Secure Send sharing flow (create, list, revoke)#287

Merged
DenizAltunkapan merged 1 commit into
Vault-Web:mainfrom
priyanshuvishwakarma273403:feat/cloud-secure-send
Jul 22, 2026
Merged

feat(cloud): implement Secure Send sharing flow (create, list, revoke)#287
DenizAltunkapan merged 1 commit into
Vault-Web:mainfrom
priyanshuvishwakarma273403:feat/cloud-secure-send

Conversation

@priyanshuvishwakarma273403

Copy link
Copy Markdown
Contributor

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.

@GabrielBBaldez GabrielBBaldez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 DenizAltunkapan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread frontend/src/app/services/cloud.service.ts Outdated
Comment thread frontend/src/app/services/cloud.service.ts Outdated
Comment thread frontend/src/app/services/cloud.service.ts Outdated
Comment thread frontend/src/app/services/cloud.service.ts Outdated
Comment thread frontend/src/app/services/cloud.service.spec.ts Outdated

@GabrielBBaldez GabrielBBaldez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@DenizAltunkapan
DenizAltunkapan merged commit ba3e973 into Vault-Web:main Jul 22, 2026
2 checks passed
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.

[Feature]: Secure Send sharing flow in the Cloud UI (create, list, revoke)

3 participants