Add owner context and harden ownership boundaries

This commit is contained in:
diyaa
2026-06-04 12:47:55 +02:00
parent d392e532e0
commit 958ebb71f5
23 changed files with 938 additions and 96 deletions
+117 -1
View File
@@ -313,6 +313,7 @@ function createPrismaMock() {
describe('Velody API wiring (e2e)', () => {
let app: NestExpressApplication;
let prismaMock: ReturnType<typeof createPrismaMock>['prismaMock'];
let assetsController: AssetsController;
let artworkController: ArtworkController;
let healthController: HealthController;
@@ -325,7 +326,9 @@ describe('Velody API wiring (e2e)', () => {
let storageRoot: string;
beforeEach(async () => {
const { prismaMock, state } = createPrismaMock();
const prismaSetup = createPrismaMock();
prismaMock = prismaSetup.prismaMock;
const { state } = prismaSetup;
prismaState = state;
storageRoot = await mkdtemp(join(tmpdir(), 'velody-e2e-'));
@@ -385,6 +388,23 @@ describe('Velody API wiring (e2e)', () => {
expect(response.version).toBe('0.1.0');
});
it('creates the bootstrap default owner during application startup', () => {
expect(prismaMock.user.upsert).toHaveBeenCalledWith({
where: {
slug: 'default-owner',
},
update: {
displayName: 'Default Owner',
isDefault: true,
},
create: {
slug: 'default-owner',
displayName: 'Default Owner',
isDefault: true,
},
});
});
it('registers a device and accepts heartbeat', async () => {
const registerResponse = await devicesController.register({
platform: 'MACOS',
@@ -394,6 +414,9 @@ describe('Velody API wiring (e2e)', () => {
expect(registerResponse.deviceId).toBeDefined();
expect(registerResponse.bootstrapToken).toBeDefined();
expect(prismaState.devices.get(registerResponse.deviceId)?.userId).toBe(
prismaState.defaultUser.id,
);
const heartbeatResponse = await devicesController.heartbeat({
deviceId: registerResponse.deviceId,
@@ -403,6 +426,28 @@ describe('Velody API wiring (e2e)', () => {
expect(heartbeatResponse.ok).toBe(true);
});
it('rejects heartbeat updates for a foreign-owner device', async () => {
const foreignDeviceId = randomUUID();
prismaState.devices.set(foreignDeviceId, {
id: foreignDeviceId,
userId: randomUUID(),
platform: 'MACOS',
deviceName: 'Foreign Mac',
appVersion: '0.1.0',
installTokenHash: 'foreign-device-hash',
lastSeenAt: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
});
await expect(
devicesController.heartbeat({
deviceId: foreignDeviceId,
appVersion: '0.1.1',
}),
).rejects.toBeInstanceOf(NotFoundException);
});
it('returns sync bootstrap and changes payloads', async () => {
const bootstrapResponse = await syncController.bootstrap();
const changesResponse = await syncController.changes({ after: '0' });
@@ -412,6 +457,47 @@ describe('Velody API wiring (e2e)', () => {
expect(changesResponse.nextCursor).toBe('0');
});
it('sync bootstrap and changes do not expose foreign-owner data', async () => {
const foreignUserId = randomUUID();
const foreignTrackId = randomUUID();
prismaState.tracks.set(foreignTrackId, {
id: foreignTrackId,
userId: foreignUserId,
primaryAudioAssetId: null,
artworkAssetId: null,
title: 'Foreign Bootstrap Track',
artist: 'Elsewhere',
album: null,
albumArtist: null,
genre: null,
discNumber: null,
trackNumber: null,
year: null,
durationMs: 180000,
status: 'ACTIVE',
deletedAt: null,
createdAt: new Date('2026-05-29T08:00:00.000Z'),
updatedAt: new Date('2026-05-29T08:01:00.000Z'),
});
prismaState.libraryEvents.set(1n, {
id: 1n,
userId: foreignUserId,
entityType: 'TRACK',
entityId: foreignTrackId,
action: 'CREATED',
payloadVersion: 1,
createdAt: new Date('2026-05-29T08:02:00.000Z'),
});
const bootstrapResponse = await syncController.bootstrap();
const changesResponse = await syncController.changes({ after: '0' });
expect(bootstrapResponse.tracks).toEqual([]);
expect(changesResponse.events).toEqual([]);
expect(changesResponse.nextCursor).toBe('0');
});
it('downloads audio asset bytes for the owning device user', async () => {
const registerResponse = await devicesController.register({
platform: 'IPHONE',
@@ -610,6 +696,36 @@ describe('Velody API wiring (e2e)', () => {
expect(headers.get('content-length')).toBe(String(bytes.length));
});
it('rejects artwork download requests for another user artwork', async () => {
const registerResponse = await devicesController.register({
platform: 'IPHONE',
deviceName: 'Artwork iPhone',
appVersion: '0.1.0',
});
const artworkId = randomUUID();
const otherUserId = randomUUID();
prismaState.artworkAssets.set(artworkId, {
id: artworkId,
userId: otherUserId,
sha256: 'sha-other-artwork',
storageKey: join('users', otherUserId, 'artwork', 'sha-other-artwork.png'),
mimeType: 'image/png',
width: 1,
height: 1,
fileSizeBytes: BigInt(10),
createdAt: new Date('2026-05-29T08:00:00.000Z'),
});
await expect(
artworkController.download(
artworkId,
{ deviceId: registerResponse.deviceId },
{ setHeader() {} } as any,
),
).rejects.toBeInstanceOf(ForbiddenException);
});
it('returns not found when the requested artwork file is missing', async () => {
const registerResponse = await devicesController.register({
platform: 'IPHONE',