Skip to content

Commit addf813

Browse files
authored
Restrict CORS for Builder Secret Keys (#31)
1 parent d072300 commit addf813

File tree

5 files changed

+81
-31
lines changed

5 files changed

+81
-31
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ test-coverage-inspect: test-coverage
2424
generate:
2525
go generate -x ./...
2626

27+
.PHONY: proto
28+
proto:
29+
go generate -x ./proto/...
30+
2731
lint:
2832
golangci-lint run ./... --fix -c .golangci.yml
2933

middleware.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package authcontrol
22

33
import (
4-
"cmp"
54
"context"
65
"errors"
6+
"log/slog"
77
"net/http"
88
"strings"
99
"time"
@@ -143,10 +143,8 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
143143
return
144144
}
145145

146-
var (
147-
accessKey string
148-
sessionType proto.SessionType
149-
)
146+
sessionType := proto.SessionType_Public
147+
var accessKey string
150148

151149
for _, f := range cfg.AccessKeyFuncs {
152150
if accessKey = f(r); accessKey != "" {
@@ -163,15 +161,18 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
163161
serviceClaim, _ := claims["service"].(string)
164162
accountClaim, _ := claims["account"].(string)
165163
adminClaim, _ := claims["admin"].(bool)
166-
// We support both claims for now, we'll deprecate one if we can.
167-
// - `project` is used by the builder to generate Project JWT tokens.
168-
// - `project_id` is used by API for WaaS related authentication.
169-
projectClaim, _ := cmp.Or(claims["project"], claims["project_id"]).(float64)
164+
165+
// - `project` claim is used in Builder Admin API Secret Keys (JWT used by third-party customers).
166+
projectClaim, _ := claims["project"].(float64)
167+
168+
// - `project_id` claim is used by API->WaaS related authentication.
169+
projectIDClaim, _ := claims["project_id"].(float64)
170170

171171
switch {
172172
case serviceClaim != "":
173173
ctx = WithService(ctx, serviceClaim)
174174
sessionType = proto.SessionType_InternalService
175+
175176
case accountClaim != "":
176177
ctx = WithAccount(ctx, accountClaim)
177178
sessionType = proto.SessionType_Wallet
@@ -200,6 +201,32 @@ func Session(cfg Options) func(next http.Handler) http.Handler {
200201
if projectClaim > 0 {
201202
ctx = WithProjectID(ctx, uint64(projectClaim))
202203
sessionType = max(sessionType, proto.SessionType_Project)
204+
} else if projectIDClaim > 0 {
205+
ctx = WithProjectID(ctx, uint64(projectIDClaim))
206+
sessionType = max(sessionType, proto.SessionType_Project)
207+
}
208+
209+
// Restrict CORS for Builder Admin API Secret Keys.
210+
// These keys are designed for backend service use by third-party customers, not for web apps.
211+
if accountClaim != "" && projectClaim > 0 {
212+
// Secret Keys are distinguished from Wallet JWTs or Builder session JWTs
213+
// by the presence of both `project` and `account` claims. (As of Dec '24)
214+
// Related discussion: https://github.com/0xsequence/issue-tracker/issues/3802.
215+
216+
origin := r.Header.Get("Origin")
217+
if origin != "" {
218+
err := proto.ErrSecretKeyCorsDisallowed.WithCausef("project_id: %v", projectClaim)
219+
220+
slog.ErrorContext(ctx, "CORS disallowed for Secret Key",
221+
slog.Any("error", err),
222+
slog.String("origin", origin),
223+
slog.Uint64("project_id", uint64(projectClaim)),
224+
)
225+
226+
// TODO: Uncomment once we're confident it won't disrupt major customers.
227+
// cfg.ErrHandler(r, w, err)
228+
// return
229+
}
203230
}
204231
}
205232

proto/authcontrol.gen.go

Lines changed: 12 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/authcontrol.gen.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable */
2-
// authcontrol v0.9.1 809804f85757ee407e93c191d6d5bfb75b82cb56
2+
// authcontrol v0.9.1 6d8f688a98165b12e0ddfa4ecbaeb8bd7d7f92ac
33
// --
44
// Code generated by [email protected] with typescript generator. DO NOT EDIT.
55
//
@@ -16,7 +16,7 @@ export const WebRPCVersion = "v1"
1616
export const WebRPCSchemaVersion = "v0.9.1"
1717

1818
// Schema hash generated from your RIDL schema
19-
export const WebRPCSchemaHash = "809804f85757ee407e93c191d6d5bfb75b82cb56"
19+
export const WebRPCSchemaHash = "6d8f688a98165b12e0ddfa4ecbaeb8bd7d7f92ac"
2020

2121
type WebrpcGenVersions = {
2222
webrpcGenVersion: string;
@@ -413,6 +413,19 @@ export class ProjectNotFoundError extends WebrpcError {
413413
}
414414
}
415415

416+
export class SecretKeyCorsDisallowedError extends WebrpcError {
417+
constructor(
418+
name: string = 'SecretKeyCorsDisallowed',
419+
code: number = 1009,
420+
message: string = `CORS disallowed. Admin API Secret Key can't be used from a web app.`,
421+
status: number = 0,
422+
cause?: string
423+
) {
424+
super(name, code, message, status, cause)
425+
Object.setPrototypeOf(this, SecretKeyCorsDisallowedError.prototype)
426+
}
427+
}
428+
416429

417430
export enum errors {
418431
WebrpcEndpoint = 'WebrpcEndpoint',
@@ -435,6 +448,7 @@ export enum errors {
435448
Geoblocked = 'Geoblocked',
436449
RateLimited = 'RateLimited',
437450
ProjectNotFound = 'ProjectNotFound',
451+
SecretKeyCorsDisallowed = 'SecretKeyCorsDisallowed',
438452
}
439453

440454
export enum WebrpcErrorCodes {
@@ -458,6 +472,7 @@ export enum WebrpcErrorCodes {
458472
Geoblocked = 1006,
459473
RateLimited = 1007,
460474
ProjectNotFound = 1008,
475+
SecretKeyCorsDisallowed = 1009,
461476
}
462477

463478
export const webrpcErrorByCode: { [code: number]: any } = {
@@ -481,6 +496,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {
481496
[1006]: GeoblockedError,
482497
[1007]: RateLimitedError,
483498
[1008]: ProjectNotFoundError,
499+
[1009]: SecretKeyCorsDisallowedError,
484500
}
485501

486502
export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>

proto/errors.ridl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ webrpc = v1
33
name = authcontrol
44
version = v0.9.1
55

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

0 commit comments

Comments
 (0)