Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
21 changes: 21 additions & 0 deletions server/resources/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ server.method('link.get', get, {})
server.method('link.list', list, {})
server.method('link.remove', remove, {})
server.method('link.checkCompany', checkCompany, {})
server.method('link.checkAttendeeAccess', checkAttendeeAccess, {})

async function create(authorId, link, author, editionId) {
let _link = {
Expand Down Expand Up @@ -180,3 +181,23 @@ async function checkCompany(userId, companyId, editionId) {
throw Boom.notFound('company not found')
}
}

// Restrict user-scoped calls to the authenticated attendee.
// Team/admin scopes keep their existing elevated access.
// this should not be even necessary if the call didnt need the attendeeId
// to be passed in the first place... but i won't refactor the frontend lol
async function checkAttendeeAccess(credentials, attendeeId) {
if (!credentials || !credentials.user || !credentials.user.id) {
log.error({ credentials: credentials, attendeeId: attendeeId }, 'missing credentials for attendee access check')
Comment thread
oPeras1 marked this conversation as resolved.
Outdated
throw Boom.unauthorized('missing credentials')
}

if (credentials.scope !== 'user') {
return
}

if (credentials.user.id !== attendeeId) {
log.error({ requester: credentials.user.id, attendeeId: attendeeId }, 'forbidden attendee link access')
throw Boom.forbidden('forbidden attendee link access')
}
}
5 changes: 5 additions & 0 deletions server/routes/link/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ exports.createAttendeeLink = {
handler: async function (request, h) {
try {
const edition = await request.server.methods.deck.getLatestEdition()
await request.server.methods.link.checkAttendeeAccess(request.auth.credentials, request.params.attendeeId)
await request.server.methods.link.checkCompany(request.payload.userId, request.payload.companyId, edition.id)
let link = await request.server.methods.link.create(request.params.attendeeId, request.payload, "attendee", edition.id)
Comment on lines 76 to 79

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces new authorization behavior for user-scoped attendee link endpoints, but there doesn’t appear to be test coverage for the /users/{attendeeId}/link* routes. Please add tests that assert a user cannot create/get/list/update/delete links for a different attendeeId (expect 403), while team/admin can still access as intended.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wont do, exercicio para o leitor

return h.response(render(link))
Expand Down Expand Up @@ -154,6 +155,7 @@ exports.updateAttendeeLink = {
handler: async function (request, h) {
try {
const edition = await request.server.methods.deck.getLatestEdition()
await request.server.methods.link.checkAttendeeAccess(request.auth.credentials, request.params.attendeeId)
let link = await request.server.methods.link.update(request.params, edition.id, request.payload, "attendee")
return h.response(render(link))
} catch (err) {
Expand Down Expand Up @@ -205,6 +207,7 @@ exports.getAttendeeLink = {
handler: async function (request, h) {
try {
const edition = await request.server.methods.deck.getLatestEdition()
await request.server.methods.link.checkAttendeeAccess(request.auth.credentials, request.params.attendeeId)
let link = await request.server.methods.link.get(request.params, edition.id, 'attendee')
return h.response(render(link))
} catch (err) {
Expand Down Expand Up @@ -264,6 +267,7 @@ exports.listAttendeeLinks = {
},
handler: async function (request, h) {
try {
await request.server.methods.link.checkAttendeeAccess(request.auth.credentials, request.params.attendeeId)
let user = await request.server.methods.user.get(request.auth.credentials.user.id)
if (!user) {
log.error('user not found')
Expand Down Expand Up @@ -384,6 +388,7 @@ exports.removeAttendeeLink = {
handler: async function (request, h) {
try {
const edition = await request.server.methods.deck.getLatestEdition()
await request.server.methods.link.checkAttendeeAccess(request.auth.credentials, request.params.attendeeId)
let link = await request.server.methods.link.remove(request.params, edition.id, "attendee")
if (!link) {
log.error({ err: 'not found', link: edition.id }, 'error deleting attendee link')
Expand Down
Loading