Skip to content

Commit

Permalink
add noscript handler for outbound link
Browse files Browse the repository at this point in the history
  • Loading branch information
negrel committed Oct 31, 2024
1 parent 7c0c369 commit bd4019b
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 3 deletions.
2 changes: 2 additions & 0 deletions cmd/server/full/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func Initialize(logger wired.BootstrapLogger) wired.App {
grafana.ProvideService,
grafanaCli.ProvideClient,
handlers.ProvideGetNoscriptEventsCustom,
handlers.ProvideGetNoscriptEventsOutboundLink,
handlers.ProvideGetNoscriptEventsPageviews,
handlers.ProvideHealthCheck,
handlers.ProvidePostEventsFileDownload,
Expand All @@ -45,6 +46,7 @@ func Initialize(logger wired.BootstrapLogger) wired.App {
middlewares.ProvideMetrics,
middlewares.ProvideNonRegisteredOriginFilter,
middlewares.ProvideNoscriptHandlersCache,
middlewares.ProvideReferrerAsDefaultOrigin,
middlewares.ProvideRequestId,
middlewares.ProvideStatic,
originregistry.ProvideEnvVarService,
Expand Down
4 changes: 3 additions & 1 deletion cmd/server/full/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cmd/server/ingestion/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func Initialize(logger wired.BootstrapLogger) wired.App {
eventstore.ProvideConfig,
eventstore.ProvideService,
handlers.ProvideGetNoscriptEventsCustom,
handlers.ProvideGetNoscriptEventsOutboundLink,
handlers.ProvideGetNoscriptEventsPageviews,
handlers.ProvideHealthCheck,
handlers.ProvidePostEventsCustom,
Expand All @@ -40,6 +41,7 @@ func Initialize(logger wired.BootstrapLogger) wired.App {
middlewares.ProvideMetrics,
middlewares.ProvideNonRegisteredOriginFilter,
middlewares.ProvideNoscriptHandlersCache,
middlewares.ProvideReferrerAsDefaultOrigin,
middlewares.ProvideRequestId,
middlewares.ProvideStatic,
originregistry.ProvideEnvVarService,
Expand Down
4 changes: 3 additions & 1 deletion cmd/server/ingestion/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion mocks/static/noscript/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ <h1>Index</h1>
<a href="/pets/index.html">Pets</a>
<a href="/noscript/index.html">No script</a>
<a href="/noscript/index2.html">No script 2</a>
<a href="http://prisme.localhost:8000/api/v1/noscript/events/outbound-link?url=http://www.example.com">External</a>
<a href="http://prisme.localhost:8000/api/v1/noscript/events/outbound-link?url=http://www.example.com"
referrerpolicy="unsafe-url">
External
</a>
<a href="http://www.example.com" ping="http://prisme.localhost:8000/api/v1/events/outbound-link">
External via ping
</a>
Expand Down
70 changes: 70 additions & 0 deletions pkg/handlers/noscript_outbound_link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package handlers

import (
"fmt"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/prismelabs/analytics/pkg/event"
hutils "github.com/prismelabs/analytics/pkg/handlers/utils"
"github.com/prismelabs/analytics/pkg/services/eventstore"
"github.com/prismelabs/analytics/pkg/services/saltmanager"
"github.com/prismelabs/analytics/pkg/services/sessionstorage"
"github.com/prismelabs/analytics/pkg/uri"
)

type GetNoscriptEventsOutboundLink fiber.Handler

// ProvideGetNoscriptEventsOutboundLink is a wire provider for
// GET /api/v1/noscript/events/outbound-link handler.
func ProvideGetNoscriptEventsOutboundLink(
eventStore eventstore.Service,
sessionStorage sessionstorage.Service,
saltManagerService saltmanager.Service,
) GetNoscriptEventsOutboundLink {
return func(c *fiber.Ctx) error {
var err error
outboundLinkClickEv := event.OutboundLinkClick{}

outboundLinkClickEv.PageUri, err = hutils.PeekAndParseReferrerQueryHeader(c)
if err != nil {
return err
}

outboundUri, err := uri.Parse(c.Query("url"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("invalid outbound link: %v", err.Error()))
}

// Check that link is external.
if outboundUri.Host() == outboundLinkClickEv.PageUri.Host() {
return fiber.NewError(fiber.StatusBadRequest, "internal link")
}

// Compute device id.
deviceId := hutils.ComputeDeviceId(
saltManagerService.StaticSalt().Bytes(), c.Request().Header.UserAgent(),
utils.UnsafeBytes(c.IP()), utils.UnsafeBytes(outboundLinkClickEv.PageUri.Host()),
)

ctx := c.UserContext()
var ok bool
outboundLinkClickEv.Session, ok = sessionStorage.WaitSession(deviceId, outboundLinkClickEv.PageUri, hutils.ContextTimeout(ctx))
if !ok {
// Fallback to root of referrer. This is needed if referrer query or header contained entire url
// while referrer pageview event contains only origin because of different referrer policy.
outboundLinkClickEv.Session, ok = sessionStorage.WaitSession(deviceId, outboundLinkClickEv.PageUri.RootUri(), hutils.ContextTimeout(ctx))
}
if !ok {
return errSessionNotFound
}

// Store event.
err = eventStore.StoreOutboundLinkClick(ctx, &outboundLinkClickEv)
if err != nil {
return fmt.Errorf("failed to store custom event: %w", err)
}

return c.Redirect(outboundUri.String(), fiber.StatusFound)
}
}
29 changes: 29 additions & 0 deletions pkg/middlewares/referrer_as_default_origin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package middlewares

import (
"github.com/gofiber/fiber/v2"
"github.com/prismelabs/analytics/pkg/uri"
)

type ReferrerAsDefaultOrigin fiber.Handler

// ProvideReferrerAsDefaultOrigin is a wire provider for a middleware that sets
// request origin to referrer header if undefined.
func ProvideReferrerAsDefaultOrigin() ReferrerAsDefaultOrigin {
return func(c *fiber.Ctx) error {
println(c.Request().String())
headers := &c.Request().Header

// Origin is missing. This can happen on cross origin GET requests.
if len(headers.Peek(fiber.HeaderOrigin)) == 0 {
referrer, err := uri.ParseBytes(headers.Peek(fiber.HeaderReferer))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, `invalid "Referer"`)
}

headers.Set(fiber.HeaderOrigin, referrer.Origin())
}

return c.Next()
}
}
4 changes: 4 additions & 0 deletions pkg/wired/fiber.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func ProvideFiber(
eventsCorsMiddleware middlewares.EventsCors,
eventsRateLimiterMiddleware middlewares.EventsRateLimiter,
getNoscriptCustomEventHandler handlers.GetNoscriptEventsCustom,
getNoscriptOutboundLinkEventHandler handlers.GetNoscriptEventsOutboundLink,
getNoscriptPageViewEventHandler handlers.GetNoscriptEventsPageviews,
minimalFiber MinimalFiber,
nonRegisteredOriginFilterMiddleware middlewares.NonRegisteredOriginFilter,
Expand All @@ -20,6 +21,7 @@ func ProvideFiber(
postFileDownloadEventHandler handlers.PostEventsFileDownload,
postOutboundLinkEventHandler handlers.PostEventsOutboundLink,
postPageViewEventHandler handlers.PostEventsPageviews,
referrerAsDefaultOriginMiddleware middlewares.ReferrerAsDefaultOrigin,
) *fiber.App {
app := (*fiber.App)(minimalFiber)

Expand All @@ -34,6 +36,7 @@ func ProvideFiber(
app.Use("/api/v1/noscript/events/*",
fiber.Handler(eventsCorsMiddleware),
fiber.Handler(eventsRateLimiterMiddleware),
fiber.Handler(referrerAsDefaultOriginMiddleware),
fiber.Handler(nonRegisteredOriginFilterMiddleware),
fiber.Handler(apiEventsTimeoutMiddleware),
// Prevent caching of GET responses.
Expand All @@ -47,6 +50,7 @@ func ProvideFiber(
app.Get("/api/v1/noscript/events/custom/:name", fiber.Handler(getNoscriptCustomEventHandler))

app.Post("/api/v1/events/outbound-link", fiber.Handler(postOutboundLinkEventHandler))
app.Get("/api/v1/noscript/events/outbound-link", fiber.Handler(getNoscriptOutboundLinkEventHandler))

app.Post("/api/v1/events/file-download", fiber.Handler(postFileDownloadEventHandler))

Expand Down

0 comments on commit bd4019b

Please sign in to comment.