Skip to content

Commit

Permalink
feat: add webhook forward explorer events (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleortega authored Sep 4, 2024
1 parent 984ec5f commit b0409da
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 1 deletion.
33 changes: 33 additions & 0 deletions src/adapters/event-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Event, Events, MoveToParcelEvent } from '@dcl/schemas'

enum ExplorerEventIds {
MOVE_TO_PARCEL = 'move_to_parcel'
}

function parseExplorerClientEvent(event: any): Event | undefined {
if (event && (event.event as string).toLocaleLowerCase() === ExplorerEventIds.MOVE_TO_PARCEL) {
return {
type: Events.Type.CLIENT,
subType: Events.SubType.Client.MOVE_TO_PARCEL,
timestamp: event.timestamp,
key: event.messageId,
metadata: {
authChain: JSON.parse(event.context.auth_chain),
userAddress: event.context.dcl_eth_address,
sessionId: event.context.session_id,
timestamp: event.sentAt,
realm: event.context.realm,
parcel: {
isEmptyParcel: event.properties.is_empty_parcel,
newParcel: event.properties.new_parcel,
oldParcel: event.properties.old_parcel,
sceneHash: event.properties.scene_hash
}
}
} as MoveToParcelEvent
}

return undefined
}

export { parseExplorerClientEvent }
76 changes: 76 additions & 0 deletions src/controllers/handlers/forward-explorer-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { parseExplorerClientEvent } from '../../adapters/event-parser'
import { HandlerContextWithPath } from '../../types'
import crypto from 'crypto'

function validateIfSegmentIsTheSourceOfTheEvent(
body: any,
signatureHeader: string | null,
segmentSigningKey: string
): boolean {
if (!signatureHeader) {
return false
}

const digest = crypto
.createHmac('sha1', segmentSigningKey)
.update(Buffer.from(JSON.stringify(body), 'utf-8'))
.digest('hex')

return digest === signatureHeader
}

export async function setForwardExplorerEventsHandler(
context: Pick<
HandlerContextWithPath<'eventPublisher' | 'config' | 'logs', '/forward'>,
'params' | 'request' | 'components'
>
) {
const logger = context.components.logs.getLogger('forward-explorer-events')
const segmentSignigKey = await context.components.config.requireString('SEGMENT_SIGNING_KEY')

const body = await context.request.json()
const signatureHeader = context.request.headers.get('x-signature')
const result = validateIfSegmentIsTheSourceOfTheEvent(body, signatureHeader, segmentSignigKey)

if (!result) {
logger.warn('Invalid signature', {
signatureHeader: signatureHeader ? signatureHeader : 'empty-signature',
body: JSON.stringify(body)
})
return {
status: 401,
body: {
error: 'Invalid signature',
ok: false
}
}
}

const parsedEvent = parseExplorerClientEvent(body)

if (!parsedEvent) {
logger.warn('Invalid event', {
body: JSON.stringify(body)
})
return {
status: 400,
body: {
error: 'Invalid event',
ok: false
}
}
}

await context.components.eventPublisher.publishMessage(parsedEvent)

logger.info('Event parsed and forwarded', {
parsedEvent: JSON.stringify(parsedEvent)
})

return {
status: 200,
body: {
ok: true
}
}
}
40 changes: 40 additions & 0 deletions src/controllers/middlewares/explorer-authentication-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NotAuthorizedError } from '@dcl/platform-server-commons'
import { IHttpServerComponent, ILoggerComponent } from '@well-known-components/interfaces'
import crypto from 'crypto'

export function authMiddleware(authSecret: string, logs: ILoggerComponent) {
const logger = logs.getLogger('auth-middleware')
if (!authSecret) {
throw new Error('Bearer token middleware requires a secret')
}

return async function (
ctx: IHttpServerComponent.DefaultContext<any>,
next: () => Promise<IHttpServerComponent.IResponse>
): Promise<IHttpServerComponent.IResponse> {
const signature = ctx.request.headers.get('x-signature')
logger.info('Received request', { signature })
if (!signature) {
throw new NotAuthorizedError('Authorization header is missing')
}

const body = await ctx.request.json()

const digest = crypto
.createHmac('sha1', authSecret)
.update(Buffer.from(JSON.stringify(body), 'utf-8'))
.digest('hex')

logger.info('Received request', { body, result: signature !== digest ? 'invalid' : 'valid' })
if (signature !== digest) {
throw new NotAuthorizedError('Invalid signature')
}

ctx.state = {
...ctx.state,
consumedBody: body
}

return await next()
}
}
3 changes: 3 additions & 0 deletions src/controllers/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Router } from '@well-known-components/http-server'
import { bearerTokenMiddleware, errorHandler } from '@dcl/platform-server-commons'
import { GlobalContext } from '../types'
import { setCursorHandler } from './handlers/set-cursor'
import { setForwardExplorerEventsHandler } from './handlers/forward-explorer-events'

// We return the entire router because it will be easier to test than a whole server
export async function setupRouter({ components }: GlobalContext): Promise<Router<GlobalContext>> {
Expand All @@ -14,6 +15,8 @@ export async function setupRouter({ components }: GlobalContext): Promise<Router
router.post('/producers/:producer/set-since', bearerTokenMiddleware(signingKey), setCursorHandler)
}

router.post('/forward', setForwardExplorerEventsHandler)

router.use(errorHandler)

return router
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export type BaseComponents = {
metrics: IMetricsComponent<keyof typeof metricDeclarations>
fetch: IFetchComponent
producerRegistry: IProducerRegistry
eventPublisher: IEventPublisher
}

// components used in runtime
export type AppComponents = BaseComponents & {
database: DatabaseComponent
eventPublisher: IEventPublisher
l2CollectionsSubGraph: ISubgraphComponent
landManagerSubGraph: ISubgraphComponent
marketplaceSubGraph: ISubgraphComponent
Expand Down

0 comments on commit b0409da

Please sign in to comment.