feat(ui): 1 modal to manage shares (users & public share)

fix(share): ensure Authz parent is created/updated on publicShare create/update

fix(ShareModal): do not show Token (public) grants in People section
This commit is contained in:
Edouard Vanbelle
2026-05-25 16:54:50 +02:00
parent a88e4c2733
commit 79c1a37931
15 changed files with 1897 additions and 367 deletions
+96 -7
View File
@@ -2,6 +2,8 @@
* @import {Grant, ResourceTypeEnum, SharedWithMeResponse} from '../core/types.js'
*/
import { getCsrfHeaders } from '../core/csrf.js';
const grants = {
/** @type {Record<String, Record<String, Grant[]>>} */
outgoingGrants: {},
@@ -13,14 +15,15 @@ const grants = {
const response = await fetch('/api/grants/outgoing');
if (!response.ok) {
console.log(`error ${response.status} while fetching /api/grants/outgoing:`, await response.json());
console.error(`error ${response.status} while fetching /api/grants/outgoing`);
return;
}
/** @type {Grant[]} */
const outgoingGrants = await response.json();
console.log(outgoingGrants);
// Reset and rebuild cache
this.outgoingGrants = {};
// store grants by type, then by id
outgoingGrants.forEach((grant) => {
@@ -28,8 +31,6 @@ const grants = {
this.outgoingGrants[grant.resource.type][grant.resource.id] ??= [];
this.outgoingGrants[grant.resource.type][grant.resource.id].push(grant);
});
console.log(`outgoing grants: `, this.outgoingGrants);
},
/**
@@ -50,7 +51,7 @@ const grants = {
const response = await fetch('/api/grants/incoming');
if (!response.ok) {
console.log(`error ${response.status} while fetching /api/grants/incoming:`, await response.json);
console.error(`error ${response.status} while fetching /api/grants/incoming`);
return;
}
@@ -63,8 +64,6 @@ const grants = {
this.incomingGrants[grant.resource.type][grant.resource.id] ??= [];
this.incomingGrants[grant.resource.type][grant.resource.id].push(grant);
});
console.log(`incoming grants: `, this.incomingGrants);
},
/**
@@ -105,6 +104,96 @@ const grants = {
}
return response.json();
},
/**
* Fetch all grants on a specific resource (for the "Manage sharing" panel).
* Refreshes the outgoingGrants cache for this resource.
*
* @param {ResourceTypeEnum} resourceType
* @param {string} resourceId
* @returns {Promise<Grant[]>}
*/
async fetchGrantsForResource(resourceType, resourceId) {
const params = new URLSearchParams({ resource_type: resourceType, resource_id: resourceId });
const response = await fetch(`/api/grants?${params}`, { credentials: 'same-origin' });
if (!response.ok) {
throw new Error(`fetchGrantsForResource: HTTP ${response.status}`);
}
/** @type {Grant[]} */
const result = await response.json();
// Refresh the outgoing cache for this resource
this.outgoingGrants[resourceType] ??= {};
this.outgoingGrants[resourceType][resourceId] = result;
return result;
},
/**
* Create a new grant.
* Body mirrors `CreateGrantDto`: `{ subject, resource, role }` OR `{ subject, resource, permissions }`.
*
* @param {Object} dto - CreateGrantDto shape
* @returns {Promise<Grant[]>}
*/
async createGrant(dto) {
const response = await fetch('/api/grants', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', ...getCsrfHeaders() },
body: JSON.stringify(dto)
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(body.error || `createGrant: HTTP ${response.status}`);
}
return response.json();
},
/**
* Reconcile a subject's role on a resource (replaces all their permissions).
* Body mirrors `UpdateRoleDto`: `{ subject, resource, role }`.
*
* @param {Object} dto - UpdateRoleDto shape
* @returns {Promise<Grant[]>}
*/
async updateRole(dto) {
const response = await fetch('/api/grants/role', {
method: 'PUT',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', ...getCsrfHeaders() },
body: JSON.stringify(dto)
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(body.error || `updateRole: HTTP ${response.status}`);
}
return response.json();
},
/**
* Revoke a single grant by its UUID.
*
* @param {string} grantId
* @returns {Promise<void>}
*/
async revokeGrant(grantId) {
const response = await fetch(`/api/grants/${encodeURIComponent(grantId)}`, {
method: 'DELETE',
credentials: 'same-origin',
headers: getCsrfHeaders()
});
if (!response.ok) {
throw new Error(`revokeGrant: HTTP ${response.status}`);
}
}
};