Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
3eb9d64
refactor(permissions): drop hardcoded group permission map for a flat…
jfcastro92 Aug 11, 2026
26841a9
test(drivers): assert credential-gate intent instead of a 403 proxy
jfcastro92 Aug 11, 2026
8a2bd94
Merge remote-tracking branch 'upstream/main' into juancastro/put-1072…
jfcastro92 Aug 12, 2026
1f40fbc
fix(permissions): report whether a revoke removed anything and persis…
jfcastro92 Aug 12, 2026
d8380f4
feat(permissions): replicate permission invalidations across regions
jfcastro92 Aug 12, 2026
eda7a0e
feat(share): extend the share table into an index of active shares
jfcastro92 Aug 12, 2026
ec0a196
feat(share): query and maintain active shares in ShareStore
jfcastro92 Aug 12, 2026
c39d48a
feat(users): add a batched lookup by email
jfcastro92 Aug 12, 2026
abce494
fix(cache): apply cache updates broadcast from peer regions
jfcastro92 Aug 12, 2026
ba48efd
fix(permissions): scope a revoke to the issuer that granted it
jfcastro92 Aug 12, 2026
33fb7c4
feat(share): add ShareService with a per-day share limit
jfcastro92 Aug 12, 2026
f9c9dae
feat(share): expose sharing over HTTP
jfcastro92 Aug 12, 2026
6b5d6f8
feat(share): keep recipients consistent when a shared item changes
jfcastro92 Aug 12, 2026
e7c856d
fix(fs): stop listing issuer homes at the filesystem root
jfcastro92 Aug 12, 2026
e399843
fix(acl): serialize concurrent mode changes on one node and pin app c…
jfcastro92 Aug 12, 2026
a2e4b1c
fix(fs): expire signed URLs over entries the signer doesn't own
jfcastro92 Aug 12, 2026
1f0c009
refactor(permissions): drop the unused permission-issuer lookup
jfcastro92 Aug 12, 2026
4fe0120
fix(share): return the created share, not just an acknowledgement
jfcastro92 Aug 12, 2026
6e6fddf
feat(puter.js): add file sharing to puter.fs
jfcastro92 Aug 12, 2026
4abb1c3
feat(gui): add a Shared folder for items others shared with you
jfcastro92 Aug 12, 2026
c551b91
feat(gui): share items from the context menu
jfcastro92 Aug 12, 2026
06248b6
fix(share): withdraw what a removed recipient re-shared
jfcastro92 Aug 13, 2026
d66c30b
feat(share): report access inherited from a parent folder
jfcastro92 Aug 13, 2026
6b5be7b
fix(gui): load the puter.js bundle the server configured
jfcastro92 Aug 13, 2026
fd12a0f
refactor(gui): extract the action icon set into a helper
jfcastro92 Aug 13, 2026
1f8807f
feat(gui): surface Shared in the file browser
jfcastro92 Aug 13, 2026
9e9a210
feat(gui): manage access from the share dialog
jfcastro92 Aug 13, 2026
57716d4
test(share): cover access inherited from a parent folder
jfcastro92 Aug 13, 2026
5e697cb
Merge remote-tracking branch 'upstream/main' into juancastro/put-1412…
jfcastro92 Aug 13, 2026
c497e6c
fix(share): keep downstream access from surviving a delegate who leaves
jfcastro92 Aug 13, 2026
132803d
fix(gui): name the real owner in the share dialog
jfcastro92 Aug 13, 2026
c1680bd
fix(gui): page through every shared item instead of the first 50
jfcastro92 Aug 13, 2026
35832d3
feat(share): return item metadata with a share
jfcastro92 Aug 13, 2026
5bff43e
fix(share): invalidate a holder's cache when the entry is deleted
jfcastro92 Aug 13, 2026
4c6cca2
fix(gui): treat items inside a shared folder as someone else's
jfcastro92 Aug 13, 2026
e7e3256
feat(permissions): let manage inherit down the filesystem tree
jfcastro92 Aug 13, 2026
9713e1a
fix(gui): offer sharing inside a folder you manage
jfcastro92 Aug 13, 2026
3527efe
fix(share): make the daily share limit hold under concurrency
jfcastro92 Aug 14, 2026
22dcc72
test(share): cover concurrency, measure cost, and name cases for what…
jfcastro92 Aug 14, 2026
5f44033
fix(gui): import the ownership helpers the item menu calls
jfcastro92 Aug 14, 2026
e47ec44
fix(share): authorize before resolving the recipient
jfcastro92 Aug 14, 2026
d384a41
fix(permissions): broadcast permission row-cache invalidations to pee…
jfcastro92 Aug 14, 2026
3eb6827
fix(fs): refuse to rename an entry owned by another user
jfcastro92 Aug 14, 2026
aa98344
fix(permissions): decide a flat delete from the primary, not a laggin…
jfcastro92 Aug 14, 2026
982830a
fix(permissions): keep a failed remote flat-invalidation from crashin…
jfcastro92 Aug 14, 2026
18943d8
fix(share): revoke every requested item and recipient, not just the f…
jfcastro92 Aug 14, 2026
b76ca7a
fix(share): only a confirmed email designates a recipient
jfcastro92 Aug 14, 2026
68f34f0
fix(share): accept tilde-rooted paths like the FS routes do
jfcastro92 Aug 14, 2026
058f411
fix(share): walk a directory revoke by parent linkage, not path prefix
jfcastro92 Aug 14, 2026
ed2b956
fix(gui): give each item its own share dialog
jfcastro92 Aug 14, 2026
9e50da9
feat(permissions): let manage answer a write check
jfcastro92 Aug 14, 2026
389f562
fix(fs): authorize restructuring by write on the parent
jfcastro92 Aug 14, 2026
b680a4f
fix(fs): let a share recipient work inside a shared folder
jfcastro92 Aug 14, 2026
2371b51
fix(fs): give a new entry to the owner of the folder it lands in
jfcastro92 Aug 14, 2026
fe535d5
feat(fs): address shared entries as ~/share/<uid>
jfcastro92 Aug 14, 2026
40b82c6
revert(fs): mask only the directory bar, not the addressing
jfcastro92 Aug 14, 2026
086f78b
Merge remote-tracking branch 'origin/main' into juancastro/put-1412-f…
Salazareo Aug 15, 2026
9195896
fix: path obfuscation, webdav + small ui stuff
Salazareo Aug 15, 2026
17d7a9d
test(share): assert the masked share path by its exact shape
Salazareo Aug 15, 2026
be848fa
fix(share): rename policy, shared-view guards, webdav share parent, q…
Salazareo Aug 16, 2026
b99fece
Merge remote-tracking branch 'origin/main' into juancastro/put-1412-f…
Salazareo Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/backend/clients/database/SqliteDatabaseClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { DatabaseClientFactory } from './index.js';
import { SqliteDatabaseClient } from './SqliteDatabaseClient.js';

/** Highest schema version the migration table can reach. */
const CURRENT_SCHEMA_VERSION = 62;
const CURRENT_SCHEMA_VERSION = 63;
const SYSTEM_USER_UUID = '5d4adce0-a381-4982-9c02-6e2540026238';

const sqliteConfig = (
Expand Down
1 change: 1 addition & 0 deletions src/backend/clients/database/SqliteDatabaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const AVAILABLE_MIGRATIONS: [number, string[]][] = [
[59, ['0064_abuse-moderation-events.sql']],
[60, ['0065_app-feedback.sql']],
[61, ['0066_owned-email-unique.sql']],
[62, ['0067_share_entries.sql']],
];

export class SqliteDatabaseClient extends AbstractDatabaseClient {
Expand Down
89 changes: 89 additions & 0 deletions src/backend/clients/database/migrations/mysql/mysql_mig_22.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
-- Copyright (C) 2024-present Puter Technologies Inc.
--
-- This file is part of Puter.
--
-- Puter is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.

-- Grow `share` from a pending-email-invite table into the index of active
-- shares. See sqlite/0067_share_entries.sql for the column rationale.
--
-- Idempotent: columns go through _puter_add_col (from mig_1), indexes and
-- foreign keys through the guarded procedure below. There is no per-file
-- applied-state tracking, so every statement must tolerate a re-run.

CALL _puter_add_col('share', 'holder_user_id', '`holder_user_id` int unsigned DEFAULT NULL');
CALL _puter_add_col('share', 'fsentry_id', '`fsentry_id` int unsigned DEFAULT NULL');
CALL _puter_add_col('share', 'mode', '`mode` varchar(20) DEFAULT NULL');
CALL _puter_add_col('share', 'applied_at', '`applied_at` timestamp NULL DEFAULT NULL');

DROP PROCEDURE IF EXISTS _puter_add_share_index_constraints;
DELIMITER //
CREATE PROCEDURE _puter_add_share_index_constraints()
BEGIN
IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'share'
AND INDEX_NAME = 'idx_share_holder'
) THEN
ALTER TABLE `share` ADD INDEX `idx_share_holder` (`holder_user_id`, `id`);
END IF;

IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'share'
AND INDEX_NAME = 'idx_share_fsentry'
) THEN
ALTER TABLE `share` ADD INDEX `idx_share_fsentry` (`fsentry_id`);
END IF;

IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'share'
AND INDEX_NAME = 'idx_share_holder_entry_issuer'
) THEN
ALTER TABLE `share` ADD UNIQUE INDEX `idx_share_holder_entry_issuer`
(`holder_user_id`, `fsentry_id`, `issuer_user_id`);
END IF;

IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'share'
AND CONSTRAINT_NAME = 'share_holder_user_fk'
) THEN
ALTER TABLE `share` ADD CONSTRAINT `share_holder_user_fk`
FOREIGN KEY (`holder_user_id`) REFERENCES `user` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
END IF;

-- The cascade that retires a share with its file.
IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'share'
AND CONSTRAINT_NAME = 'share_fsentry_fk'
) THEN
ALTER TABLE `share` ADD CONSTRAINT `share_fsentry_fk`
FOREIGN KEY (`fsentry_id`) REFERENCES `fsentries` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END//
DELIMITER ;

CALL _puter_add_share_index_constraints();

DROP PROCEDURE IF EXISTS _puter_add_share_index_constraints;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- Copyright (C) 2024-present Puter Technologies Inc.
--
-- This file is part of Puter.
--
-- Puter is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.

-- Grow share from a pending-email-invite table into the index of active
-- shares. See sqlite/0067_share_entries.sql for the column rationale.

ALTER TABLE share
ADD COLUMN IF NOT EXISTS holder_user_id integer
REFERENCES "user" (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD COLUMN IF NOT EXISTS fsentry_id integer
REFERENCES fsentries (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD COLUMN IF NOT EXISTS mode varchar(20),
ADD COLUMN IF NOT EXISTS applied_at timestamp;

CREATE INDEX IF NOT EXISTS idx_share_holder
ON share (holder_user_id, id);
CREATE INDEX IF NOT EXISTS idx_share_fsentry
ON share (fsentry_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_share_holder_entry_issuer
ON share (holder_user_id, fsentry_id, issuer_user_id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-- Copyright (C) 2024-present Puter Technologies Inc.
--
-- This file is part of Puter.
--
-- Puter is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.

-- Grow `share` from a pending-email-invite table into the index of active
-- shares. Permissions stay the source of truth for access; this is what makes
-- shares listable and gives them a lifecycle.
--
-- - `holder_user_id` : NULL while an invite awaits signup.
-- - `fsentry_id` : the shared node. ON DELETE CASCADE retires the share
-- with the file, which permissions alone don't do.
-- - `mode` : see|list|read|write|manage. Unconstrained on purpose —
-- ACLService owns the mode set and adding one shouldn't
-- need a three-dialect migration.
-- - `applied_at` : set when an invite is claimed. Claiming updates the
-- row rather than deleting it, so the share stays
-- queryable.

ALTER TABLE `share` ADD COLUMN `holder_user_id` INTEGER DEFAULT NULL
REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `share` ADD COLUMN `fsentry_id` INTEGER DEFAULT NULL
REFERENCES `fsentries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `share` ADD COLUMN `mode` TEXT DEFAULT NULL;
ALTER TABLE `share` ADD COLUMN `applied_at` TIMESTAMP DEFAULT NULL;

-- "Shared with me", keyset-paginated: ORDER BY ends in `id` as the tiebreaker.
CREATE INDEX IF NOT EXISTS `idx_share_holder`
ON `share` (`holder_user_id`, `id`);

-- Who has access to one node — also how an owner sees a manage-delegate's
-- re-grants, which the issuer/holder permission tables can't answer.
CREATE INDEX IF NOT EXISTS `idx_share_fsentry` ON `share` (`fsentry_id`);

-- One row per (holder, node, issuer). Pending invites have a NULL
-- holder_user_id and so aren't covered here; dedup for those belongs with the
-- invite flow.
CREATE UNIQUE INDEX IF NOT EXISTS `idx_share_holder_entry_issuer`
ON `share` (`holder_user_id`, `fsentry_id`, `issuer_user_id`);
15 changes: 15 additions & 0 deletions src/backend/clients/event/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,21 @@ export type EventMap = {
data?: unknown;
ttlSeconds?: number;
};
/**
* Permission cache generations were bumped, so peer regions must bump their
* own — the counter is per-cluster, so a local bump says nothing to them.
* Carries the actors, not the values: the numbers only have to change.
*/
'outer.permission.generationBumped': { actorUids: string[] };
/**
* Flat permission entries were deleted. Grant-path flat entries carry no
* expiry, so without this a revoke never lands in a peer region whose KV
* table isn't replicated. Revoke-only: a grant that fails to replicate just
* denies there, which is the safe direction.
*/
'outer.permission.flatInvalidated': {
entries: Array<{ holderUserId: number; permission: string }>;
};
'outer.fs.write-hash': { hash: string; uuid: string };
/**
* Cache keys the KV read cache must stop serving, because the entries
Expand Down
Loading
Loading