From 2df051e21607ccc2e4fe404a4f5234ceff599682 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 8 Jun 2026 19:16:01 -0700 Subject: [PATCH] fix sharetoken permissions for pixels,links,boards, and failed routes --- pnpm-lock.yaml | 61 -------- src/app/api/reports/route.ts | 34 +++- .../event-data-pivot/array-series/route.ts | 4 +- .../event-data-pivot/date-series/route.ts | 4 +- .../event-data-pivot/numeric-series/route.ts | 4 +- .../event-data-pivot/numeric-stats/route.ts | 4 +- .../event-data-pivot/property-series/route.ts | 4 +- .../[websiteId]/event-data-pivot/route.ts | 4 +- .../[websiteId]/event-data/[eventId]/route.ts | 4 +- .../[websiteId]/event-data/events/route.ts | 4 +- .../[websiteId]/event-data/fields/route.ts | 4 +- .../event-data/properties/route.ts | 4 +- .../websites/[websiteId]/event-data/route.ts | 4 +- .../[websiteId]/event-data/stats/route.ts | 4 +- .../[websiteId]/event-data/values/route.ts | 4 +- .../[websiteId]/segments/[segmentId]/route.ts | 4 +- .../websites/[websiteId]/segments/route.ts | 4 +- .../[websiteId]/session-data-pivot/route.ts | 4 +- .../session-data/array-series/route.ts | 4 +- .../session-data/date-series/route.ts | 4 +- .../session-data/numeric-series/route.ts | 4 +- .../session-data/numeric-stats/route.ts | 4 +- .../session-data/properties/route.ts | 4 +- .../session-data/property-series/route.ts | 4 +- .../[websiteId]/session-data/stats/route.ts | 4 +- .../[websiteId]/session-data/values/route.ts | 4 +- .../sessions/[sessionId]/activity/route.ts | 11 +- .../sessions/[sessionId]/properties/route.ts | 11 +- .../[websiteId]/sessions/[sessionId]/route.ts | 11 +- .../[websiteId]/sessions/weekly/route.ts | 2 +- src/app/share/ShareProvider.tsx | 15 +- .../hooks/queries/useShareTokenQuery.ts | 22 ++- src/components/hooks/useApi.ts | 12 +- src/permissions/share.test.ts | 148 ++++++++++++++++++ src/permissions/share.ts | 30 ++-- 35 files changed, 308 insertions(+), 145 deletions(-) create mode 100644 src/permissions/share.test.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b440a0f19..600758496 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -331,8 +331,6 @@ importers: specifier: ^4.1.6 version: 4.1.6(@types/node@25.9.0)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.0)(typescript@6.0.3))(vite@8.0.11(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.1)(tsx@4.22.2)) - dist: {} - packages: '@adobe/css-tools@4.4.4': @@ -624,28 +622,24 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [musl] '@biomejs/cli-linux-arm64@2.4.15': resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.15': resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [musl] '@biomejs/cli-linux-x64@2.4.15': resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [glibc] '@biomejs/cli-win32-arm64@2.4.15': resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==} @@ -1626,105 +1620,89 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1866,28 +1844,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@16.2.6': resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@16.2.6': resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@16.2.6': resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@16.2.6': resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} @@ -1960,42 +1934,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -2310,42 +2278,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18': resolution: {integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==} @@ -2492,79 +2454,66 @@ packages: resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.4': resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.4': resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.4': resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.4': resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.4': resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.4': resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.4': resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.4': resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.4': resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.4': resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.4': resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.4': resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.4': resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} @@ -2753,42 +2702,36 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.15.33': resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-ppc64-gnu@1.15.33': resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] - libc: [glibc] '@swc/core-linux-s390x-gnu@1.15.33': resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==} engines: {node: '>=10'} cpu: [s390x] os: [linux] - libc: [glibc] '@swc/core-linux-x64-gnu@1.15.33': resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.15.33': resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.15.33': resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==} @@ -4327,28 +4270,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} diff --git a/src/app/api/reports/route.ts b/src/app/api/reports/route.ts index 1742c93dc..353f329e0 100644 --- a/src/app/api/reports/route.ts +++ b/src/app/api/reports/route.ts @@ -3,9 +3,34 @@ import { uuid } from '@/lib/crypto'; import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { pagingParams, reportSchema, reportTypeParam } from '@/lib/schema'; -import { canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions'; +import { + canUpdateWebsite, + canViewAuthenticatedWebsite, + canViewWebsiteSection, +} from '@/permissions'; +import type { ShareSection } from '@/permissions'; import { createReport, getReports } from '@/queries/prisma'; +function getReportSection(type?: z.infer): ShareSection | null { + switch (type) { + case 'attribution': + case 'breakdown': + case 'performance': + case 'retention': + case 'revenue': + case 'utm': + return type; + case 'funnel': + return 'funnels'; + case 'goal': + return 'goals'; + case 'journey': + return 'journeys'; + default: + return null; + } +} + export async function GET(request: Request) { const schema = z.object({ websiteId: z.uuid(), @@ -26,7 +51,12 @@ export async function GET(request: Request) { search, }; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + const section = getReportSection(type); + const canView = section + ? await canViewWebsiteSection(auth, websiteId, section) + : await canViewAuthenticatedWebsite(auth, websiteId); + + if (!canView) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/array-series/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/array-series/route.ts index 1451f62e6..36dccc86a 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/array-series/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/array-series/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataArraySeries } from '@/queries/sql/events/getEventDataArraySeries'; export async function GET( @@ -28,7 +28,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/date-series/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/date-series/route.ts index f3f659678..9b2b4ab11 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/date-series/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/date-series/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataDateSeries } from '@/queries/sql/events/getEventDataDateSeries'; export async function GET( @@ -27,7 +27,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/numeric-series/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/numeric-series/route.ts index 9b85f1b5e..85cfe8da0 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/numeric-series/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/numeric-series/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataNumericSeries } from '@/queries/sql/events/getEventDataNumericSeries'; export async function GET( @@ -29,7 +29,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/numeric-stats/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/numeric-stats/route.ts index 5c8360462..fc580f774 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/numeric-stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/numeric-stats/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataNumericStats } from '@/queries/sql/events/getEventDataNumericStats'; export async function GET( @@ -26,7 +26,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/property-series/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/property-series/route.ts index 3fa27b58b..d92ec455f 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/property-series/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/property-series/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataPropertySeries } from '@/queries/sql/events/getEventDataPropertySeries'; export async function GET( @@ -28,7 +28,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data-pivot/route.ts b/src/app/api/websites/[websiteId]/event-data-pivot/route.ts index c2a533e93..1fc0947c2 100644 --- a/src/app/api/websites/[websiteId]/event-data-pivot/route.ts +++ b/src/app/api/websites/[websiteId]/event-data-pivot/route.ts @@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, pagingParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataPivot } from '@/queries/sql/events/getEventDataPivot'; export async function GET( @@ -28,7 +28,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts index b5d65868e..256df74dd 100644 --- a/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts @@ -1,6 +1,6 @@ import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataById } from '@/queries/sql/events/getEventDataById'; export async function GET( @@ -15,7 +15,7 @@ export async function GET( const { websiteId, eventId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts index 70ed3750b..b6ceec70f 100644 --- a/src/app/api/websites/[websiteId]/event-data/events/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataEvents } from '@/queries/sql/events/getEventDataEvents'; export async function GET( @@ -23,7 +23,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts index 76257a3cc..a0734f1aa 100644 --- a/src/app/api/websites/[websiteId]/event-data/fields/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataFields } from '@/queries/sql'; export async function GET( @@ -23,7 +23,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts index 0afc90c0f..a10fde2d6 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataProperties } from '@/queries/sql'; export async function GET( @@ -23,7 +23,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/route.ts b/src/app/api/websites/[websiteId]/event-data/route.ts index b3eae5313..5b4f31226 100644 --- a/src/app/api/websites/[websiteId]/event-data/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, pagingParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventData } from '@/queries/sql/events/getEventData'; export async function GET( @@ -24,7 +24,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts index 1a8b54bd5..1230cef22 100644 --- a/src/app/api/websites/[websiteId]/event-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataStats } from '@/queries/sql'; export async function GET( @@ -23,7 +23,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts index 6734e5344..4d6679080 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getEventDataValues } from '@/queries/sql'; export async function GET( @@ -26,7 +26,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/segments/[segmentId]/route.ts b/src/app/api/websites/[websiteId]/segments/[segmentId]/route.ts index 1bd3b22ea..1ed6d7955 100644 --- a/src/app/api/websites/[websiteId]/segments/[segmentId]/route.ts +++ b/src/app/api/websites/[websiteId]/segments/[segmentId]/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { parseRequest } from '@/lib/request'; import { json, notFound, ok, unauthorized } from '@/lib/response'; import { anyObjectParam, segmentTypeParam } from '@/lib/schema'; -import { canDeleteWebsite, canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions'; +import { canDeleteWebsite, canUpdateWebsite, canViewSharedWebsiteFilters } from '@/permissions'; import { deleteSegment, getWebsiteSegment, updateSegment } from '@/queries/prisma'; export async function GET( @@ -17,7 +17,7 @@ export async function GET( const { websiteId, segmentId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewSharedWebsiteFilters(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/segments/route.ts b/src/app/api/websites/[websiteId]/segments/route.ts index 3b094dc34..39f326582 100644 --- a/src/app/api/websites/[websiteId]/segments/route.ts +++ b/src/app/api/websites/[websiteId]/segments/route.ts @@ -3,7 +3,7 @@ import { uuid } from '@/lib/crypto'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { searchParams, segmentParamSchema, segmentTypeParam } from '@/lib/schema'; -import { canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions'; +import { canUpdateWebsite, canViewSharedWebsiteFilters } from '@/permissions'; import { createSegment, getWebsiteSegments } from '@/queries/prisma'; export async function GET( @@ -24,7 +24,7 @@ export async function GET( const { websiteId } = await params; const { type } = query; - if (websiteId && !(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (websiteId && !(await canViewSharedWebsiteFilters(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data-pivot/route.ts b/src/app/api/websites/[websiteId]/session-data-pivot/route.ts index 01677d305..221431222 100644 --- a/src/app/api/websites/[websiteId]/session-data-pivot/route.ts +++ b/src/app/api/websites/[websiteId]/session-data-pivot/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, pagingParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataPivot } from '@/queries/sql/sessions/getSessionDataPivot'; export async function GET( @@ -28,7 +28,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/array-series/route.ts b/src/app/api/websites/[websiteId]/session-data/array-series/route.ts index 05c7619f8..229289207 100644 --- a/src/app/api/websites/[websiteId]/session-data/array-series/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/array-series/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataArraySeries } from '@/queries/sql'; export async function GET( @@ -27,7 +27,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/date-series/route.ts b/src/app/api/websites/[websiteId]/session-data/date-series/route.ts index 4b4da4ddc..5e6b5480d 100644 --- a/src/app/api/websites/[websiteId]/session-data/date-series/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/date-series/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataDateSeries } from '@/queries/sql'; export async function GET( @@ -26,7 +26,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/numeric-series/route.ts b/src/app/api/websites/[websiteId]/session-data/numeric-series/route.ts index b1db37c23..2c6935e70 100644 --- a/src/app/api/websites/[websiteId]/session-data/numeric-series/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/numeric-series/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataNumericSeries } from '@/queries/sql'; export async function GET( @@ -28,7 +28,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/numeric-stats/route.ts b/src/app/api/websites/[websiteId]/session-data/numeric-stats/route.ts index 2b6b8892f..b7983116a 100644 --- a/src/app/api/websites/[websiteId]/session-data/numeric-stats/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/numeric-stats/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataNumericStats } from '@/queries/sql'; export async function GET( @@ -26,7 +26,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts index a15df8e98..cfea38b8f 100644 --- a/src/app/api/websites/[websiteId]/session-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataProperties } from '@/queries/sql'; export async function GET( @@ -25,7 +25,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/property-series/route.ts b/src/app/api/websites/[websiteId]/session-data/property-series/route.ts index 79672479b..b0082555a 100644 --- a/src/app/api/websites/[websiteId]/session-data/property-series/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/property-series/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataPropertySeries } from '@/queries/sql'; export async function GET( @@ -27,7 +27,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/stats/route.ts b/src/app/api/websites/[websiteId]/session-data/stats/route.ts index 65b7d060d..8331d40fe 100644 --- a/src/app/api/websites/[websiteId]/session-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/stats/route.ts @@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataActivityStats } from '@/queries/sql/sessions/getSessionDataActivityStats'; export async function GET( @@ -27,7 +27,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts index f7af66134..31f95410c 100644 --- a/src/app/api/websites/[websiteId]/session-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { filterParams } from '@/lib/schema'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionDataValues } from '@/queries/sql'; export async function GET( @@ -25,7 +25,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts index d6681072f..23eb78ab8 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionActivity } from '@/queries/sql'; export async function GET( @@ -21,7 +21,14 @@ export async function GET( const { websiteId, sessionId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if ( + !(await canViewWebsiteSection(auth, websiteId, [ + 'sessions', + 'events', + 'realtime', + 'revenue', + ])) + ) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts index 1ba60e4c1..cfbc56c78 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts @@ -1,6 +1,6 @@ import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getSessionData } from '@/queries/sql'; export async function GET( @@ -15,7 +15,14 @@ export async function GET( const { websiteId, sessionId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if ( + !(await canViewWebsiteSection(auth, websiteId, [ + 'sessions', + 'events', + 'realtime', + 'revenue', + ])) + ) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts index 21ac130b4..91d412351 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts @@ -1,6 +1,6 @@ import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { canViewAuthenticatedWebsite } from '@/permissions'; +import { canViewWebsiteSection } from '@/permissions'; import { getWebsiteSession } from '@/queries/sql'; export async function GET( @@ -15,7 +15,14 @@ export async function GET( const { websiteId, sessionId } = await params; - if (!(await canViewAuthenticatedWebsite(auth, websiteId))) { + if ( + !(await canViewWebsiteSection(auth, websiteId, [ + 'sessions', + 'events', + 'realtime', + 'revenue', + ])) + ) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts index 910f83333..b7ecef33d 100644 --- a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -24,7 +24,7 @@ export async function GET( const { websiteId } = await params; - if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) { + if (!(await canViewWebsiteSection(auth, websiteId, ['overview', 'sessions']))) { return unauthorized(); } diff --git a/src/app/share/ShareProvider.tsx b/src/app/share/ShareProvider.tsx index 790298bc6..9f5b526aa 100644 --- a/src/app/share/ShareProvider.tsx +++ b/src/app/share/ShareProvider.tsx @@ -5,6 +5,7 @@ import { createContext, type ReactNode, useEffect } from 'react'; import { useShareTokenQuery } from '@/components/hooks'; import { ENTITY_TYPE } from '@/lib/constants'; import type { ShareParameters, WhiteLabel } from '@/lib/types'; +import { setShareData, useApp } from '@/store/app'; export interface ShareData { shareId: string; @@ -22,6 +23,8 @@ export interface ShareData { export const ShareContext = createContext(null); +const selector = (state: { shareToken: { token?: string } | null }) => state.shareToken; + const ALL_SECTION_IDS = [ 'overview', 'events', @@ -55,8 +58,10 @@ export function ShareProvider({ slug, children }: { slug: string; children: Reac const { share, isLoading, isFetching } = useShareTokenQuery(slug); const router = useRouter(); const pathname = usePathname(); + const shareToken = useApp(selector); const path = getSharePath(pathname); const isWebsiteShare = share?.shareType === ENTITY_TYPE.website; + const isShareReady = !!share?.token && shareToken?.token === share.token; const allowedSections = isWebsiteShare && share?.parameters @@ -75,11 +80,17 @@ export function ShareProvider({ slug, children }: { slug: string; children: Reac } }, [shouldRedirect, slug, allowedSections, router]); - if (isFetching && isLoading) { + useEffect(() => { + return () => { + setShareData(null, null); + }; + }, [slug]); + + if ((isFetching && isLoading) || (share && !isShareReady)) { return ; } - if (!share || shouldRedirect) { + if (!share || !isShareReady || shouldRedirect) { return null; } diff --git a/src/components/hooks/queries/useShareTokenQuery.ts b/src/components/hooks/queries/useShareTokenQuery.ts index 362fe8ee3..c0031ce72 100644 --- a/src/components/hooks/queries/useShareTokenQuery.ts +++ b/src/components/hooks/queries/useShareTokenQuery.ts @@ -1,18 +1,24 @@ -import { setShareData } from '@/store/app'; +import { useEffect } from 'react'; +import { setShareData, useApp } from '@/store/app'; import { useApi } from '../useApi'; export function useShareTokenQuery(slug: string) { const { get, useQuery } = useApi(); + const shareId = useApp(state => state.share?.shareId); + const shareToken = useApp(state => state.shareToken?.token); const query = useQuery({ queryKey: ['share', slug], - queryFn: async () => { - const data = await get(`/share/${slug}`); - - setShareData(data, { token: data?.token }); - - return data; - }, + queryFn: async () => get(`/share/${slug}`), }); + useEffect(() => { + if ( + query.data?.token && + (shareId !== query.data.shareId || shareToken !== query.data.token) + ) { + setShareData(query.data, { token: query.data.token }); + } + }, [query.data, shareId, shareToken]); + return { share: query.data, ...query }; } diff --git a/src/components/hooks/useApi.ts b/src/components/hooks/useApi.ts index aaef8fced..0250fce8b 100644 --- a/src/components/hooks/useApi.ts +++ b/src/components/hooks/useApi.ts @@ -1,5 +1,4 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { usePathname } from 'next/navigation'; import { useCallback } from 'react'; import { getApiUrl } from '@/lib/api-url'; import { getClientAuthToken } from '@/lib/client'; @@ -7,8 +6,6 @@ import { SHARE_CONTEXT_HEADER, SHARE_TOKEN_HEADER } from '@/lib/constants'; import { type FetchResponse, httpDelete, httpGet, httpPost, httpPut } from '@/lib/fetch'; import { useApp } from '@/store/app'; -const selector = (state: { shareToken: { token?: string } }) => state.shareToken; - async function handleResponse(res: FetchResponse): Promise { if (!res.ok) { const { message, code, status } = res?.data?.error || {}; @@ -19,13 +16,12 @@ async function handleResponse(res: FetchResponse): Promise { } export function useApi() { - const shareToken = useApp(selector); - const pathname = usePathname(); - const isSharePath = pathname?.startsWith('/share'); + const shareId = useApp(state => state.share?.shareId); + const shareToken = useApp(state => state.shareToken?.token); const shareHeaders = - isSharePath && shareToken?.token - ? { [SHARE_TOKEN_HEADER]: shareToken.token, [SHARE_CONTEXT_HEADER]: '1' } + shareId && shareToken + ? { [SHARE_TOKEN_HEADER]: shareToken, [SHARE_CONTEXT_HEADER]: '1' } : {}; const defaultHeaders = { diff --git a/src/permissions/share.test.ts b/src/permissions/share.test.ts new file mode 100644 index 000000000..693ed9af6 --- /dev/null +++ b/src/permissions/share.test.ts @@ -0,0 +1,148 @@ +import { expect, test, vi } from 'vitest'; +import { ENTITY_TYPE } from '@/lib/constants'; +import { + canViewSharedWebsite, + canViewSharedWebsiteFilters, + canViewWebsiteSection, +} from './share'; + +vi.mock('./website', () => ({ + canViewWebsite: vi.fn(), +})); + +test('canViewWebsiteSection allows board shares for included websites', async () => { + await expect( + canViewWebsiteSection( + { + shareToken: { + shareType: ENTITY_TYPE.board, + websiteIds: ['website-1'], + parameters: {}, + }, + }, + 'website-1', + 'goals', + ), + ).resolves.toBe(true); +}); + +test('canViewWebsiteSection respects section flags on website shares', async () => { + await expect( + canViewWebsiteSection( + { + shareToken: { + shareType: ENTITY_TYPE.website, + websiteId: 'website-1', + parameters: { + overview: true, + goals: false, + }, + }, + }, + 'website-1', + 'goals', + ), + ).resolves.toBe(false); +}); + +test('canViewWebsiteSection allows any requested enabled section', async () => { + await expect( + canViewWebsiteSection( + { + shareToken: { + shareType: ENTITY_TYPE.website, + websiteId: 'website-1', + parameters: { + overview: true, + compare: false, + }, + }, + }, + 'website-1', + ['overview', 'compare'], + ), + ).resolves.toBe(true); +}); + +test('canViewSharedWebsite allows board shares for included websites', async () => { + await expect( + canViewSharedWebsite( + { + shareToken: { + shareType: ENTITY_TYPE.board, + websiteIds: ['website-1'], + parameters: {}, + }, + }, + 'website-1', + ), + ).resolves.toBe(true); +}); + +test('canViewSharedWebsiteFilters requires allowFilter for share tokens', async () => { + await expect( + canViewSharedWebsiteFilters( + { + shareToken: { + shareType: ENTITY_TYPE.website, + websiteId: 'website-1', + parameters: { + allowFilter: false, + }, + }, + }, + 'website-1', + ), + ).resolves.toBe(false); + + await expect( + canViewSharedWebsiteFilters( + { + shareToken: { + shareType: ENTITY_TYPE.website, + websiteId: 'website-1', + parameters: { + allowFilter: true, + }, + }, + }, + 'website-1', + ), + ).resolves.toBe(true); +}); + +test('canViewWebsiteSection allows pixel shares for the shared entity id', async () => { + await expect( + canViewWebsiteSection( + { + shareToken: { + shareType: ENTITY_TYPE.pixel, + pixelId: 'pixel-1', + parameters: { + overview: true, + }, + }, + }, + 'pixel-1', + 'overview', + ), + ).resolves.toBe(true); +}); + +test('canViewWebsiteSection allows link shares for the shared entity id', async () => { + await expect( + canViewWebsiteSection( + { + shareToken: { + shareType: ENTITY_TYPE.link, + linkId: 'link-1', + parameters: { + overview: true, + }, + }, + }, + 'link-1', + 'overview', + ), + ).resolves.toBe(true); +}); diff --git a/src/permissions/share.ts b/src/permissions/share.ts index 1d22e8dda..f041b74f1 100644 --- a/src/permissions/share.ts +++ b/src/permissions/share.ts @@ -40,7 +40,14 @@ type ShareSectionInput = ShareSection | ShareSection[]; function shareTokenIncludesWebsite(auth: Auth | null | undefined, websiteId: string) { const { shareToken } = auth || {}; - return shareToken?.websiteId === websiteId || shareToken?.websiteIds?.includes(websiteId); + return ( + shareToken?.websiteId === websiteId || + shareToken?.pixelId === websiteId || + shareToken?.linkId === websiteId || + shareToken?.websiteIds?.includes(websiteId) || + shareToken?.pixelIds?.includes(websiteId) || + shareToken?.linkIds?.includes(websiteId) + ); } export async function canViewWebsiteSection( @@ -54,11 +61,7 @@ export async function canViewWebsiteSection( const { shareToken } = auth || {}; - if ( - !shareToken || - shareToken.shareType !== ENTITY_TYPE.website || - !shareTokenIncludesWebsite(auth, websiteId) - ) { + if (!shareToken || !shareTokenIncludesWebsite(auth, websiteId)) { return false; } @@ -79,11 +82,20 @@ export async function canViewSharedWebsite(auth: Auth | null | undefined, websit return canViewWebsite(auth, websiteId); } + return shareTokenIncludesWebsite(auth, websiteId); +} + +export async function canViewSharedWebsiteFilters( + auth: Auth | null | undefined, + websiteId: string, +) { + if (auth?.user) { + return canViewWebsite(auth, websiteId); + } + const { shareToken } = auth || {}; - return ( - shareToken?.shareType === ENTITY_TYPE.website && shareTokenIncludesWebsite(auth, websiteId) - ); + return shareTokenIncludesWebsite(auth, websiteId) && shareToken?.parameters?.allowFilter !== false; } export async function canViewAuthenticatedWebsite(