Skip to content

Commit

Permalink
wip rework
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed Nov 13, 2023
1 parent 5b5c3c0 commit 83b963f
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 33 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
go.opentelemetry.io/contrib/propagators/aws v1.21.0
go.opentelemetry.io/otel v1.20.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0
go.opentelemetry.io/otel/sdk v1.20.0
go.uber.org/zap v1.26.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravY
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0 h1:4s9HxB4azeeQkhY0GE5wZlMj4/pz8tE5gx2OQpGUw58=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0/go.mod h1:djVA3TUJ2fSdMX0JE5XxFBOaZzprElJoP7fD4vnV2SU=
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA=
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM=
go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM=
Expand Down
23 changes: 13 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"fmt"
"github.com/labstack/echo/v4"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"io"
"os"
"os/signal"
Expand All @@ -15,17 +16,19 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

err := WithEchoServer(ctx, func(ctx context.Context, app *echo.Echo) error {
go func() {
<-ctx.Done()
if err := app.Shutdown(context.Background()); err != nil {
fmt.Printf("error shutting down echo server: %v\n", err)
} else {
fmt.Println("shutdown complete")
}
}()
err := WithLocalTracing(ctx, func(ctx context.Context, tp *sdktrace.TracerProvider) error {
return WithEchoServer(ctx, func(ctx context.Context, app *echo.Echo) error {
go func() {
<-ctx.Done()
if err := app.Shutdown(context.Background()); err != nil {
fmt.Printf("error shutting down echo server: %v\n", err)
} else {
fmt.Println("shutdown complete")
}
}()

return app.Start(":8090")
return app.Start(":8090")
})
})

if err != nil {
Expand Down
19 changes: 19 additions & 0 deletions otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"go.opentelemetry.io/contrib/propagators/aws/xray"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
sdkresource "go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
Expand Down Expand Up @@ -65,3 +67,20 @@ func newFunctionURLTracerProvider(ctx context.Context, serviceName string) (*sdk
sdktrace.WithResource(resource),
), nil
}

func WithLocalTracing(ctx context.Context, fn func(ctx context.Context, tp *sdktrace.TracerProvider) error) error {
exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
return err
}

tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exp),
)

otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

return fn(ctx, tp)
}
2 changes: 2 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func newEchoServer(log *otelzap.Logger, pool *pgxpool.Pool, conv *service.Sessio
app := echo.New()
app.Use(
otelecho.Middleware("api.gw2auth.com"),
web.CSRFMiddleware(),
web.Middleware(log, pool),
)

Expand All @@ -97,6 +98,7 @@ func newEchoServer(log *otelzap.Logger, pool *pgxpool.Pool, conv *service.Sessio
app.GET("/api-v2/application/summary", web.AppSummaryEndpoint())
app.GET("/api-v2/authinfo", web.AuthInfoEndpoint(), authMw)
app.GET("/api-v2/gw2account", web.Gw2AccountsEndpoint(), authMw)
app.GET("/api-v2/gw2account/:id", web.Gw2AccountEndpoint(), authMw)
app.PATCH("/api-v2/gw2account/:id", web.UpdateGw2AccountEndpoint(), authMw)

return app
Expand Down
114 changes: 106 additions & 8 deletions web/gw2_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const (
verificationStatusVerified verificationStatus = "VERIFIED"
)

type gw2Account struct {
type gw2AccountForList struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
Expand All @@ -29,11 +29,31 @@ type gw2Account struct {
AuthorizedApps uint32 `json:"authorizedApps"`
}

type authorizedApp struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
LastUsed time.Time `json:"lastUsed"`
}

type gw2Account struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
CreationTime time.Time `json:"creationTime"`
VerificationStatus verificationStatus `json:"verificationStatus"`
ApiToken string `json:"apiToken,omitempty"`
AuthorizedApps []authorizedApp `json:"authorizedApps"`
}

type gw2AccountUpdate struct {
DisplayName string `json:"displayName,omitempty"`
ApiToken string `json:"apiToken,omitempty"`
}

func Gw2AccountsEndpoint() echo.HandlerFunc {
return wrapAuthenticatedHandlerFunc(func(c echo.Context, rctx RequestContext, session service.Session) error {
ctx := c.Request().Context()

var results []gw2Account
var results []gw2AccountForList
err := rctx.ExecuteTx(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}, func(tx pgx.Tx) error {
sql := `
SELECT
Expand Down Expand Up @@ -66,8 +86,8 @@ GROUP BY gw2_acc.gw2_account_id
return err
}

results, err = pgx.CollectRows(rows, func(row pgx.CollectableRow) (gw2Account, error) {
var acc gw2Account
results, err = pgx.CollectRows(rows, func(row pgx.CollectableRow) (gw2AccountForList, error) {
var acc gw2AccountForList
var verified bool
var pendingVer bool

Expand Down Expand Up @@ -106,9 +126,83 @@ GROUP BY gw2_acc.gw2_account_id
})
}

type gw2AccountUpdate struct {
DisplayName string `json:"displayName,omitempty"`
ApiToken string `json:"apiToken,omitempty"`
func Gw2AccountEndpoint() echo.HandlerFunc {
return wrapAuthenticatedHandlerFunc(func(c echo.Context, rctx RequestContext, session service.Session) error {
gw2AccountId, err := uuid.FromString(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}

ctx := c.Request().Context()

var acc gw2Account
var verified bool
var pendingVer bool
err = rctx.ExecuteTx(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}, func(tx pgx.Tx) error {
sql := `
SELECT
MAX(gw2_acc.gw2_account_name),
MAX(gw2_acc.display_name),
MAX(gw2_acc.creation_time),
COUNT(gw2_acc_ver.account_id) > 0,
COUNT(gw2_acc_ver_pend.account_id) > 0,
MAX(gw2_acc_tk.gw2_api_token),
ARRAY_AGG(JSONB_BUILD_OBJECT(
'id', authorized_apps.app_id,
'name', authorized_apps.display_name,
'lastUsed', authorized_apps.last_used
))
FROM gw2_accounts gw2_acc
LEFT JOIN gw2_account_verifications gw2_acc_ver
USING (account_id, gw2_account_id)
LEFT JOIN gw2_account_verification_pending_challenges gw2_acc_ver_pend
USING (account_id, gw2_account_id)
LEFT JOIN gw2_account_api_tokens gw2_acc_tk
USING (account_id, gw2_account_id)
LEFT JOIN (
SELECT
app_client_auth_gw2_acc.account_id AS account_id,
app_client_auth_gw2_acc.gw2_account_id AS gw2_account_id,
app.id AS app_id,
MAX(app.display_name) AS display_name,
MAX(app_client_auth.last_update_time) AS last_used
FROM application_client_authorization_gw2_accounts app_client_auth_gw2_acc
INNER JOIN application_client_authorizations app_client_auth
ON app_client_auth_gw2_acc.application_client_authorization_id = app_client_auth.id
INNER JOIN application_clients app_client
ON app_client_auth.application_client_id = app_client.id
INNER JOIN applications app
ON app_client.application_id = app.id
GROUP BY app_client_auth_gw2_acc.account_id, app_client_auth_gw2_acc.gw2_account_id, app.id
) AS authorized_apps
USING (account_id, gw2_account_id)
WHERE gw2_acc.account_id = $1 AND gw2_acc.gw2_account_id = $2
GROUP BY gw2_acc.gw2_account_id
`
return tx.QueryRow(ctx, sql, session.AccountId, gw2AccountId).Scan(
&acc.Name,
&acc.DisplayName,
&acc.CreationTime,
&verified,
&pendingVer,
&acc.ApiToken,
&acc.AuthorizedApps,
)
})
if err != nil {
return err
}

if verified {
acc.VerificationStatus = verificationStatusVerified
} else if pendingVer {
acc.VerificationStatus = verificationStatusPending
} else {
acc.VerificationStatus = verificationStatusNone
}

return c.JSON(http.StatusOK, acc)
})
}

func UpdateGw2AccountEndpoint() echo.HandlerFunc {
Expand All @@ -127,6 +221,10 @@ func UpdateGw2AccountEndpoint() echo.HandlerFunc {
return echo.NewHTTPError(http.StatusBadRequest, errors.New("nothing to update"))
}

if update.DisplayName != "" && len(update.DisplayName) > 100 {
return echo.NewHTTPError(http.StatusBadRequest, errors.New("displayname length must be < 100"))
}

rctx.Log().Info(
"updating gw2account",
zap.String("accountId", session.AccountId.String()),
Expand Down Expand Up @@ -154,7 +252,7 @@ WHERE account_id = $1 AND gw2_account_id = $2
})

if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
return err
}

if rowsAffected < 1 {
Expand Down
82 changes: 67 additions & 15 deletions web/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package web

import (
"context"
"crypto/rand"
"encoding/base64"
crdbpgx "github.com/cockroachdb/cockroach-go/v2/crdb/crdbpgxv5"
"github.com/gw2auth/gw2auth.com-api/service"
"github.com/jackc/pgx/v5"
Expand Down Expand Up @@ -60,6 +62,21 @@ func contextManipulatingMiddleware(fn func(c echo.Context) (context.Context, err
}
}

func isSecure(c echo.Context) bool {
if c.IsTLS() || c.Scheme() == "https" {
return true
}

h := c.Request().Header
if h.Get("X-Forwarded-Proto") == "https" {
return true
} else if strings.Contains(h.Get("Forwarded"), "proto=https") {
return true
}

return false
}

func Middleware(log *otelzap.Logger, pool *pgxpool.Pool) echo.MiddlewareFunc {
return contextManipulatingMiddleware(func(c echo.Context) (context.Context, error) {
ctx := c.Request().Context()
Expand All @@ -71,21 +88,6 @@ func Middleware(log *otelzap.Logger, pool *pgxpool.Pool) echo.MiddlewareFunc {
}

func AuthenticatedMiddleware(conv *service.SessionJwtConverter) echo.MiddlewareFunc {
isSecure := func(c echo.Context) bool {
if c.IsTLS() || c.Scheme() == "https" {
return true
}

h := c.Request().Header
if h.Get("X-Forwarded-Proto") == "https" {
return true
} else if strings.Contains(h.Get("Forwarded"), "proto=https") {
return true
}

return false
}

updateCookie := func(c echo.Context, cookie *http.Cookie, newValue string, exp time.Time) {
cookie = &http.Cookie{
Name: cookie.Name,
Expand Down Expand Up @@ -181,6 +183,56 @@ func AuthenticatedMiddleware(conv *service.SessionJwtConverter) echo.MiddlewareF
})
}

func CSRFMiddleware() echo.MiddlewareFunc {
ignoreMethods := map[string]bool{
http.MethodGet: true,
http.MethodHead: true,
http.MethodOptions: true,
http.MethodTrace: true,
}

return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
req := c.Request()
token := ""

if cookie, err := c.Cookie("XSRF-TOKEN"); err == nil {
token = cookie.Value
} else {
b := make([]byte, 32)
_, err = rand.Read(b)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "could not generate csrf token")
}

token = base64.RawURLEncoding.EncodeToString(b)
c.SetCookie(&http.Cookie{
Name: cookie.Name,
Value: token,
Path: "/",
MaxAge: 0,
Secure: cookie.Secure || isSecure(c),
HttpOnly: false,
SameSite: http.SameSiteStrictMode,
})
}

if v, _ := ignoreMethods[req.Method]; v {
return next(c)
}

clientToken := req.Header.Get("X-Xsrf-Token")
if clientToken == "" {
return echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header")
} else if clientToken != token {
return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
}

return next(c)
}
}
}

type HandlerFunc func(c echo.Context, rctx RequestContext) error
type AuthenticatedHandlerFunc func(c echo.Context, rctx RequestContext, session service.Session) error

Expand Down

0 comments on commit 83b963f

Please sign in to comment.