Skip to content

Commit

Permalink
feat(locksmith): Event protected attributes (#13677)
Browse files Browse the repository at this point in the history
* checking for protected attributes on events

* feat(locksmith): adding protected attributes for events

* getLock now can include protected keys

* making sure we protect notifyCheckInUrls
  • Loading branch information
julien51 committed Apr 26, 2024
1 parent 7b2a666 commit 1ccde1a
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 36 deletions.
7 changes: 5 additions & 2 deletions locksmith/__tests__/operations/eventOperations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('eventOperations', () => {
checkoutConfig: { config: { locks: {} } },
}
const [{ slug }] = await saveEvent(eventParams, '0x123')
const savedEvent = await getEventBySlug(slug)
const savedEvent = await getEventBySlug(slug, false)
expect(savedEvent.slug).toEqual('my-rsvp-party')
expect(savedEvent?.data.requiresApproval).toEqual(true)
})
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('eventOperations', () => {
},
}
const [{ slug }] = await saveEvent(eventParams, '0x123')
const savedEvent = await getEventBySlug(slug)
const savedEvent = await getEventBySlug(slug, true)
expect(savedEvent.slug).toEqual('an-event-with-all-the-data')
expect(savedEvent?.data).toEqual({
name: 'An Event with all the data',
Expand Down Expand Up @@ -166,6 +166,9 @@ describe('eventOperations', () => {
emailSender: 'Julien Genestoux',
requiresApproval: false,
})

const savedEventWithoutProtectedData = await getEventBySlug(slug, false)
expect(savedEventWithoutProtectedData?.data.replyTo).toEqual(undefined)
})
})
})
2 changes: 1 addition & 1 deletion locksmith/src/controllers/v2/customEmailController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const EventInviteBody = z.object({

export const sendEventInvite: RequestHandler = async (request, response) => {
const slug = request.params.slug.toLowerCase().trim()
const event = await getEventBySlug(slug)
const event = await getEventBySlug(slug, true)

const { recipients } = await EventInviteBody.parseAsync(request.body)
const results = await Promise.all(
Expand Down
36 changes: 35 additions & 1 deletion locksmith/src/controllers/v2/eventsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { getLockSettingsBySlug } from '../../operations/lockSettingOperations'
import { getLockMetadata } from '../../operations/metadataOperations'
import { PaywallConfig, PaywallConfigType } from '@unlock-protocol/core'
import listManagers from '../../utils/lockManagers'
import { removeProtectedAttributesFromObject } from '../../utils/protectedAttributes'
import { isVerifierOrManagerForLock } from '../../utils/middlewares/isVerifierMiddleware'

// DEPRECATED!
export const getEventDetailsByLock: RequestHandler = async (
Expand Down Expand Up @@ -65,6 +67,9 @@ export const getAllEvents: RequestHandler = async (request, response) => {
offset: (page - 1) * 10,
include: [{ model: CheckoutConfig, as: 'checkoutConfig' }],
})
events.forEach((event) => {
event.data = removeProtectedAttributesFromObject(event.data)
})
return response.status(200).send({
data: events,
page,
Expand All @@ -76,7 +81,10 @@ export const getAllEvents: RequestHandler = async (request, response) => {
// whose slug matches and get the event data from that lock.
export const getEvent: RequestHandler = async (request, response) => {
const slug = request.params.slug.toLowerCase().trim()
const event = await getEventBySlug(slug)
const event = await getEventBySlug(
slug,
true /** includeProtected and we will cleanup later */
)

if (event) {
const eventResponse = event.toJSON() as any // TODO: type!
Expand All @@ -88,6 +96,32 @@ export const getEvent: RequestHandler = async (request, response) => {
},
})
}

// Check if the caller is a verifier or manager and remove protected attributes if not
let isManagerOrVerifier = false

if (request.user) {
const locks = Object.keys(eventResponse.checkoutConfig.config.locks)
for (let i = 0; i < locks.length; i++) {
if (!isManagerOrVerifier) {
const lock = locks[i]
const network =
eventResponse.checkoutConfig.config.locks[lock].network ||
eventResponse.checkoutConfig.config.network
isManagerOrVerifier = await isVerifierOrManagerForLock(
lock,
request.user.walletAddress,
network
)
}
}
}
if (!isManagerOrVerifier) {
eventResponse.data = removeProtectedAttributesFromObject(
eventResponse.data
)
}

return response.status(200).send(eventResponse)
}

Expand Down
6 changes: 5 additions & 1 deletion locksmith/src/controllers/v2/ticketsController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ export class TicketsController {
}
)

const event = await getEventForLock(lockAddress, network)
const event = await getEventForLock(
lockAddress,
network,
true /** includeProtected */
)

const web3Service = new Web3Service(networks)
const tokenOwner = await web3Service.ownerOf(lockAddress, id, network)
Expand Down
20 changes: 16 additions & 4 deletions locksmith/src/operations/eventOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CheckoutConfig, EventData } from '../models'
import { saveCheckoutConfig } from './checkoutConfigOperations'
import { EventBodyType } from '../controllers/v2/eventsController'
import { Op } from 'sequelize'
import { removeProtectedAttributesFromObject } from '../utils/protectedAttributes'

interface AttributeProps {
value: string
Expand Down Expand Up @@ -39,7 +40,8 @@ const getEventDate = (

export const getEventForLock = async (
lockAddress: string,
network?: number
network: number,
includeProtected: boolean
) => {
const checkoutConfigs = await CheckoutConfig.findAll({
where: {
Expand All @@ -58,6 +60,9 @@ export const getEventForLock = async (
checkoutConfigId: checkoutConfigs.map((record) => record.id),
},
})
if (event && !includeProtected) {
event.data = removeProtectedAttributesFromObject(event.data)
}
return event
}

Expand Down Expand Up @@ -155,12 +160,19 @@ export const getEventMetadataForLock = async (
return eventDetail
}

export const getEventBySlug = async (slug: string) => {
return await EventData.findOne({
export const getEventBySlug = async (
slug: string,
includeProtected: boolean
) => {
const event = await EventData.findOne({
where: {
slug,
},
})
if (event && !includeProtected) {
event.data = removeProtectedAttributesFromObject(event.data)
}
return event
}

export const createEventSlug = async (
Expand All @@ -171,7 +183,7 @@ export const createEventSlug = async (
const slug = index
? kebabCase([cleanName, index].join('-'))
: kebabCase(cleanName)
const event = await getEventBySlug(slug)
const event = await getEventBySlug(slug, false /** includeProtected */)
if (event) {
return createEventSlug(name, index ? index + 1 : 1)
}
Expand Down
11 changes: 7 additions & 4 deletions locksmith/src/operations/lockSettingOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from '../controllers/v2/lockSettingController'
import { LockSetting } from '../models/lockSetting'
import * as Normalizer from '../utils/normalizer'
import { protectedAttributes } from '../utils/protectedAttributes'
import { getEventForLock } from './eventOperations'

interface SendEmailProps {
Expand Down Expand Up @@ -39,9 +40,7 @@ export async function getSettings({
includeProtected?: boolean
}): Promise<LockSetting | LockSettingProps> {
// list of array of keys to exclude
const attributesExcludes = includeProtected
? []
: ['replyTo', 'promoCodes', 'passwords']
const attributesExcludes = includeProtected ? [] : protectedAttributes

const settings = await LockSetting.findOne({
where: {
Expand All @@ -55,7 +54,11 @@ export async function getSettings({

const lockSettings = settings || { ...DEFAULT_LOCK_SETTINGS }

const eventDetails = await getEventForLock(lockAddress, network)
const eventDetails = await getEventForLock(
lockAddress,
network,
includeProtected
)
if (eventDetails?.data) {
if (eventDetails?.data.replyTo) {
lockSettings.replyTo = eventDetails.data.replyTo
Expand Down
6 changes: 5 additions & 1 deletion locksmith/src/operations/metadataOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,11 @@ export const getLockMetadata = async ({
}

// Now let's see if there is an event data that needs to be attached to this lock!
const event = await getEventForLock(lockAddress, network)
const event = await getEventForLock(
lockAddress,
network,
false /** includeProtected, metadata is always public */
)

// Add the event data!
if (event) {
Expand Down
5 changes: 4 additions & 1 deletion locksmith/src/utils/middlewares/eventOrganizerMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ export const eventOrganizerMiddleware: RequestHandler = async (

// If this is an existing event!
if (slug) {
const existingEvent = await getEventBySlug(slug)
const existingEvent = await getEventBySlug(
slug,
false /** includeProtected */
)
if (existingEvent?.checkoutConfigId) {
const checkoutConfig = await getCheckoutConfigById(
existingEvent.checkoutConfigId
Expand Down
48 changes: 27 additions & 21 deletions locksmith/src/utils/middlewares/isVerifierMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,39 @@ import Normalizer from '../normalizer'
import { logger } from '@sentry/utils'
import { Verifier } from '../../models/verifier'

export const isVerifierOrManagerForLock = async (
lockAddress: string,
address: string,
network: number
) => {
let isLockManager = false

const verifier = await Verifier.findOne({
where: {
lockAddress,
address,
network,
},
})

if (!verifier) {
const web3Service = new Web3Service(networks)
isLockManager = await web3Service.isLockManager(
lockAddress,
address,
network
)
}
return verifier?.id !== undefined || isLockManager
}

export const isVerifierMiddleware: RequestHandler = async (req, res, next) => {
try {
const network = Number(req.params.network)
const lockAddress = Normalizer.ethereumAddress(req.params.lockAddress)
const address = Normalizer.ethereumAddress(req.user!.walletAddress!)

let isLockManager = false

const isVerifier = await Verifier.findOne({
where: {
lockAddress,
address,
network,
},
})

if (!isVerifier) {
const web3Service = new Web3Service(networks)
isLockManager = await web3Service.isLockManager(
lockAddress,
address,
network
)
}
const isVerifierOrManager = isVerifier?.id !== undefined || isLockManager

if (isVerifierOrManager) {
if (await isVerifierOrManagerForLock(lockAddress, address, network)) {
return next()
} else {
return res.status(403).send({
Expand Down
16 changes: 16 additions & 0 deletions locksmith/src/utils/protectedAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const protectedAttributes = [
'replyTo',
'promoCodes',
'passwords',
'notifyCheckInUrls',
]

export const removeProtectedAttributesFromObject = (obj: any) => {
const newObject = {
...obj,
}
protectedAttributes.forEach((attr) => {
delete newObject[attr]
})
return newObject
}

0 comments on commit 1ccde1a

Please sign in to comment.