mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-24 23:17:23 +00:00
fix(share): only a confirmed email designates a recipient
Recipient resolution by email accepted unconfirmed accounts, so pre-registering someone else's address (unconfirmed) was enough to receive shares meant for them once no confirmed account held it. An email now only resolves to an account that has confirmed it; username shares are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
18943d8c7c
commit
b76ca7a13f
@@ -79,7 +79,10 @@ describe('share consistency across KV, SQL and Redis', () => {
|
||||
const found = await server.stores.user.getByUsername(username);
|
||||
if (!found) throw new Error('test user missing');
|
||||
const email = `${username}@test.local`;
|
||||
await server.stores.user.update(found.id, { email });
|
||||
await server.stores.user.update(found.id, {
|
||||
email,
|
||||
email_confirmed: true,
|
||||
});
|
||||
const user = await server.stores.user.getById(found.id, {
|
||||
force: true,
|
||||
});
|
||||
|
||||
@@ -41,7 +41,10 @@ describe('ShareService', () => {
|
||||
const user = await server.stores.user.getByUsername(username);
|
||||
if (!user) throw new Error('test user missing');
|
||||
const email = `${username}@test.local`;
|
||||
await server.stores.user.update(user.id, { email });
|
||||
await server.stores.user.update(user.id, {
|
||||
email,
|
||||
email_confirmed: true,
|
||||
});
|
||||
const fresh = await server.stores.user.getById(user.id, {
|
||||
force: true,
|
||||
});
|
||||
@@ -202,6 +205,33 @@ describe('ShareService', () => {
|
||||
).rejects.toMatchObject({ statusCode: 400 });
|
||||
});
|
||||
|
||||
it('does not resolve an email its account has not confirmed', async () => {
|
||||
const owner = await makeUser();
|
||||
const squatter = await makeUser();
|
||||
await server.stores.user.update(squatter.user.id, {
|
||||
email_confirmed: false,
|
||||
});
|
||||
const file = await makeFile(owner.user);
|
||||
|
||||
await expect(
|
||||
share(owner.actor, {
|
||||
uid: file.uuid,
|
||||
recipient: { email: squatter.email },
|
||||
mode: 'read',
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
statusCode: 404,
|
||||
legacyCode: 'user_does_not_exist',
|
||||
});
|
||||
|
||||
// A username names exactly one account, confirmed or not.
|
||||
await share(owner.actor, {
|
||||
uid: file.uuid,
|
||||
recipient: { username: squatter.user.username },
|
||||
mode: 'read',
|
||||
});
|
||||
});
|
||||
|
||||
it('hides a file from a stranger trying to share it', async () => {
|
||||
const owner = await makeUser();
|
||||
const stranger = await makeUser();
|
||||
|
||||
@@ -661,7 +661,10 @@ export class ShareService extends PuterService {
|
||||
: username
|
||||
? await this.stores.user.getByUsername(username)
|
||||
: null;
|
||||
if (!user?.username) {
|
||||
// An unconfirmed email is a claim, not an identity: resolving it would
|
||||
// hand the share to whoever registered the address first.
|
||||
const unconfirmedEmailMatch = Boolean(email) && !user?.email_confirmed;
|
||||
if (!user?.username || unconfirmedEmailMatch) {
|
||||
throw new HttpError(404, 'Recipient does not exist', {
|
||||
legacyCode: 'user_does_not_exist',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user