-
Notifications
You must be signed in to change notification settings - Fork 0
Description
📌 Parent Issue
#182 (Phase 3: Secret Sharing & Access Control)
🎯 Goal
Implement CRUD API for Secrets with authorization policies, enabling users to create, read, update, and delete secrets with permission checks.
📋 Implementation Tasks
1. SecretController
Location: app/Http/Controllers/Api/V1/SecretController.php
Endpoints:
-
GET /api/v1/secrets- List user's secrets (owned + shared)- Query params:
filter(owned/shared/all),page,per_page - Response: Paginated list with minimal fields
- Authorization: Authenticated users
- Query params:
-
POST /api/v1/secrets- Create new secret- Validate: title required, tenant_id auto-set from user
- Use
title_plainfor encryption (auto-encrypts totitle_enc) - Set
owner_idto authenticated user - Response: Created secret with 201 status
-
GET /api/v1/secrets/{id}- View secret details- Check: owner OR has share with read+ permission
- Response: Full secret details (decrypted fields)
- Authorization: SecretPolicy@view
-
PATCH /api/v1/secrets/{id}- Update secret- Check: owner OR has share with write+ permission
- Validation: same as create
- Authorization: SecretPolicy@update
-
DELETE /api/v1/secrets/{id}- Soft delete secret- Check: owner OR has share with admin permission
- Cascade: Shares and attachments deleted automatically (FK constraint)
- Authorization: SecretPolicy@delete
2. SecretPolicy
Location: app/Policies/SecretPolicy.php
Methods:
-
viewAny(User $user)- Can list secrets (always true for authenticated) -
view(User $user, Secret $secret)- Owner OR shared with read+ -
create(User $user)- All authenticated users -
update(User $user, Secret $secret)- Owner OR shared with write+ -
delete(User $user, Secret $secret)- Owner OR shared with admin -
share(User $user, Secret $secret)- Owner OR shared with admin -
viewShares(User $user, Secret $secret)- Owner OR shared with admin
Implementation:
public function view(User $user, Secret $secret): bool
{
return $secret->userHasPermission($user, 'read');
}
public function update(User $user, Secret $secret): bool
{
return $secret->userHasPermission($user, 'write');
}
public function delete(User $user, Secret $secret): bool
{
return $secret->userHasPermission($user, 'admin');
}3. Form Requests
StoreSecretRequest:
-
title_plain- required, string, max:255 -
username_plain- nullable, string, max:255 -
password_plain- nullable, string -
url_plain- nullable, url, max:2048 -
notes_plain- nullable, string, max:10000 -
tags- nullable, array -
expires_at- nullable, date, after:now
UpdateSecretRequest:
- Same rules as StoreSecretRequest (all fields optional)
4. Routes
Location: routes/api.php
Route::middleware(['auth:sanctum', 'tenant.enforce'])->prefix('v1')->group(function () {
Route::apiResource('secrets', SecretController::class);
});5. Tests (Pest)
Feature Tests - SecretController:
- List secrets (owned only)
- List secrets (shared only)
- List secrets (owned + shared)
- List secrets with pagination
- Create secret (valid data)
- Create secret (validation errors)
- View secret (owner)
- View secret (shared with read permission)
- View secret (unauthorized user)
- Update secret (owner)
- Update secret (shared with write permission)
- Update secret (unauthorized user)
- Delete secret (owner)
- Delete secret (shared with admin permission)
- Delete secret (unauthorized user)
Feature Tests - SecretPolicy:
- Owner can do everything
- Read permission allows view, not update/delete
- Write permission allows view/update, not delete
- Admin permission allows everything
- No permission denies all actions
- Expired shares don't grant access
Coverage Target: ≥80% for new code
✅ Acceptance Criteria
- SecretController with 5 CRUD endpoints
- SecretPolicy with 7 authorization methods
- StoreSecretRequest + UpdateSecretRequest validation
- Routes registered in api.php
- All tests passing (≥16 tests)
- PHPStan level max passing
- Laravel Pint passing
- REUSE 3.3 compliant (SPDX headers)
- CHANGELOG.md updated (Added section)
- No breaking changes to existing endpoints
🔗 Dependencies
- Depends on:
- ✅ PR feat: add SecretShare model and migration (Phase 3 foundation) #183 (SecretShare model) - Merged
- ✅ Phase 1: Secret Model + CRUD API (Backend Foundation) #174 (Secret Model) - Merged
- ✅ 🔐 Implement RBAC System (Role-Based Access Control) #5 (RBAC System) - Implemented
- Blocks:
- Phase 3: Secret Sharing & Access Control (RBAC Integration) #185 (SecretShareController - needs SecretController)
- enhancement: Add file upload to backend API for Secret attachments frontend#141 (File Upload UI - needs List API)
- Part of: Issue Phase 3: Secret Sharing & Access Control (RBAC Integration) #182 (Phase 3: Secret Sharing)
📝 Technical Notes
Controller Response Format
// GET /v1/secrets
{
"data": [
{
"id": "uuid",
"title": "Decrypted Title",
"expires_at": "2025-12-31T23:59:59Z",
"created_at": "2025-11-17T10:00:00Z",
"is_owner": true
}
],
"meta": {
"current_page": 1,
"total": 42
}
}
// GET /v1/secrets/{id}
{
"data": {
"id": "uuid",
"title": "Decrypted",
"username": "Decrypted",
"password": "Decrypted",
"url": "Decrypted",
"notes": "Decrypted",
"tags": ["tag1", "tag2"],
"expires_at": null,
"attachments_count": 2,
"shares_count": 1,
"is_owner": true,
"created_at": "2025-11-17T10:00:00Z"
}
}Filter Logic
// Controller logic
$query = Secret::query();
match ($request->input('filter')) {
'owned' => $query->where('owner_id', $user->id),
'shared' => $query->whereHas('shares', fn($q) =>
$q->where('user_id', $user->id)
->orWhereIn('role_id', $user->roles->pluck('id'))
),
default => $query->where(fn($q) =>
$q->where('owner_id', $user->id)
->orWhereHas('shares', fn($sq) => ...)
),
};📝 PR Linking Instructions
When creating the PR:
Fixes #184
Part of: #182- Do NOT use
Fixes #182- this is not the last sub-issue - Estimated LOC: ~450-500
Type: Sub-Issue (Backend)
Priority: High
Estimated Effort: 3-4 days
Status: Ready to start 🚀
Metadata
Metadata
Assignees
Type
Projects
Status