Skip to content

Commit

Permalink
Restrict CORS for Builder Secret Keys (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
VojtechVitek authored Dec 11, 2024
1 parent d072300 commit addf813
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 31 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ test-coverage-inspect: test-coverage
generate:
go generate -x ./...

.PHONY: proto
proto:
go generate -x ./proto/...

lint:
golangci-lint run ./... --fix -c .golangci.yml

45 changes: 36 additions & 9 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package authcontrol

import (
"cmp"
"context"
"errors"
"log/slog"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -143,10 +143,8 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
return
}

var (
accessKey string
sessionType proto.SessionType
)
sessionType := proto.SessionType_Public
var accessKey string

for _, f := range cfg.AccessKeyFuncs {
if accessKey = f(r); accessKey != "" {
Expand All @@ -163,15 +161,18 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
serviceClaim, _ := claims["service"].(string)
accountClaim, _ := claims["account"].(string)
adminClaim, _ := claims["admin"].(bool)
// We support both claims for now, we'll deprecate one if we can.
// - `project` is used by the builder to generate Project JWT tokens.
// - `project_id` is used by API for WaaS related authentication.
projectClaim, _ := cmp.Or(claims["project"], claims["project_id"]).(float64)

// - `project` claim is used in Builder Admin API Secret Keys (JWT used by third-party customers).
projectClaim, _ := claims["project"].(float64)

// - `project_id` claim is used by API->WaaS related authentication.
projectIDClaim, _ := claims["project_id"].(float64)

switch {
case serviceClaim != "":
ctx = WithService(ctx, serviceClaim)
sessionType = proto.SessionType_InternalService

case accountClaim != "":
ctx = WithAccount(ctx, accountClaim)
sessionType = proto.SessionType_Wallet
Expand Down Expand Up @@ -200,6 +201,32 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
if projectClaim > 0 {
ctx = WithProjectID(ctx, uint64(projectClaim))
sessionType = max(sessionType, proto.SessionType_Project)
} else if projectIDClaim > 0 {
ctx = WithProjectID(ctx, uint64(projectIDClaim))
sessionType = max(sessionType, proto.SessionType_Project)
}

// Restrict CORS for Builder Admin API Secret Keys.
// These keys are designed for backend service use by third-party customers, not for web apps.
if accountClaim != "" && projectClaim > 0 {
// Secret Keys are distinguished from Wallet JWTs or Builder session JWTs
// by the presence of both `project` and `account` claims. (As of Dec '24)
// Related discussion: https://github.com/0xsequence/issue-tracker/issues/3802.

origin := r.Header.Get("Origin")
if origin != "" {
err := proto.ErrSecretKeyCorsDisallowed.WithCausef("project_id: %v", projectClaim)

slog.ErrorContext(ctx, "CORS disallowed for Secret Key",
slog.Any("error", err),
slog.String("origin", origin),
slog.Uint64("project_id", uint64(projectClaim)),
)

// TODO: Uncomment once we're confident it won't disrupt major customers.
// cfg.ErrHandler(r, w, err)
// return
}
}
}

Expand Down
23 changes: 12 additions & 11 deletions proto/authcontrol.gen.go

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

20 changes: 18 additions & 2 deletions proto/authcontrol.gen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable */
// authcontrol v0.9.1 809804f85757ee407e93c191d6d5bfb75b82cb56
// authcontrol v0.9.1 6d8f688a98165b12e0ddfa4ecbaeb8bd7d7f92ac
// --
// Code generated by [email protected] with typescript generator. DO NOT EDIT.
//
Expand All @@ -16,7 +16,7 @@ export const WebRPCVersion = "v1"
export const WebRPCSchemaVersion = "v0.9.1"

// Schema hash generated from your RIDL schema
export const WebRPCSchemaHash = "809804f85757ee407e93c191d6d5bfb75b82cb56"
export const WebRPCSchemaHash = "6d8f688a98165b12e0ddfa4ecbaeb8bd7d7f92ac"

type WebrpcGenVersions = {
webrpcGenVersion: string;
Expand Down Expand Up @@ -413,6 +413,19 @@ export class ProjectNotFoundError extends WebrpcError {
}
}

export class SecretKeyCorsDisallowedError extends WebrpcError {
constructor(
name: string = 'SecretKeyCorsDisallowed',
code: number = 1009,
message: string = `CORS disallowed. Admin API Secret Key can't be used from a web app.`,
status: number = 0,
cause?: string
) {
super(name, code, message, status, cause)
Object.setPrototypeOf(this, SecretKeyCorsDisallowedError.prototype)
}
}


export enum errors {
WebrpcEndpoint = 'WebrpcEndpoint',
Expand All @@ -435,6 +448,7 @@ export enum errors {
Geoblocked = 'Geoblocked',
RateLimited = 'RateLimited',
ProjectNotFound = 'ProjectNotFound',
SecretKeyCorsDisallowed = 'SecretKeyCorsDisallowed',
}

export enum WebrpcErrorCodes {
Expand All @@ -458,6 +472,7 @@ export enum WebrpcErrorCodes {
Geoblocked = 1006,
RateLimited = 1007,
ProjectNotFound = 1008,
SecretKeyCorsDisallowed = 1009,
}

export const webrpcErrorByCode: { [code: number]: any } = {
Expand All @@ -481,6 +496,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {
[1006]: GeoblockedError,
[1007]: RateLimitedError,
[1008]: ProjectNotFoundError,
[1009]: SecretKeyCorsDisallowedError,
}

export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>
Expand Down
20 changes: 11 additions & 9 deletions proto/errors.ridl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ webrpc = v1
name = authcontrol
version = v0.9.1

error 1000 Unauthorized "Unauthorized access" HTTP 401
error 1001 PermissionDenied "Permission denied" HTTP 403
error 1002 SessionExpired "Session expired" HTTP 403
error 1003 MethodNotFound "Method not found" HTTP 404
error 1004 RequestConflict "Conflict with target resource" HTTP 409
error 1005 Aborted "Request aborted" HTTP 400
error 1006 Geoblocked "Geoblocked region" HTTP 451
error 1007 RateLimited "Rate-limited. Please slow down." HTTP 429
error 1008 ProjectNotFound "Project not found" HTTP 401
error 1000 Unauthorized "Unauthorized access" HTTP 401
error 1001 PermissionDenied "Permission denied" HTTP 403
error 1002 SessionExpired "Session expired" HTTP 403
error 1003 MethodNotFound "Method not found" HTTP 404
error 1004 RequestConflict "Conflict with target resource" HTTP 409
error 1005 Aborted "Request aborted" HTTP 400
error 1006 Geoblocked "Geoblocked region" HTTP 451
error 1007 RateLimited "Rate-limited. Please slow down." HTTP 429
error 1008 ProjectNotFound "Project not found" HTTP 401
error 1009 SecretKeyCorsDisallowed "CORS disallowed. Admin API Secret Key can't be used from a web app." HTTP 403

0 comments on commit addf813

Please sign in to comment.