-
Notifications
You must be signed in to change notification settings - Fork 1
[API-75] Implement full requireAuthMiddleware #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,35 @@ func (app *ApiServer) recoverAuthorityFromSignatureHeaders(c *fiber.Ctx) (int32, | |
| return userId, walletLower | ||
| } | ||
|
|
||
| // Checks if authedWallet is authorized to act on behalf of userId | ||
| func (app *ApiServer) isAuthorizedRequest(c *fiber.Ctx, userId int32, authedWallet string) bool { | ||
| cacheKey := fmt.Sprintf("%d:%s", userId, authedWallet) | ||
| if hit, ok := app.resolveGrantCache.Get(cacheKey); ok { | ||
| return hit | ||
| } | ||
|
|
||
| var isAuthorized bool | ||
| err := app.pool.QueryRow(c.Context(), ` | ||
| SELECT EXISTS ( | ||
| SELECT 1 | ||
| FROM grants | ||
| WHERE | ||
| is_current = true | ||
| AND user_id = $1 | ||
| AND grantee_address = $2 | ||
| AND is_approved = true | ||
| AND is_revoked = false | ||
| ) | ||
| `, userId, authedWallet).Scan(&isAuthorized) | ||
|
|
||
| if err != nil { | ||
| return false | ||
| } | ||
|
|
||
| app.resolveGrantCache.Set(cacheKey, isAuthorized) | ||
| return isAuthorized | ||
| } | ||
|
|
||
| func (app *ApiServer) getAuthedUserId(c *fiber.Ctx) int32 { | ||
| return int32(c.Locals("authedUserId").(int32)) | ||
| } | ||
|
|
@@ -78,11 +107,28 @@ func (app *ApiServer) authMiddleware(c *fiber.Ctx) error { | |
| return c.Next() | ||
| } | ||
|
|
||
| // Middleware that asserts authedUserId is valid | ||
| func (app *ApiServer) requiresAuthMiddleware(c *fiber.Ctx) error { | ||
| // Middleware that asserts the authedUserId is valid and is the same as the userId in | ||
| // the request path or a managed user of the authedUserId | ||
| func (app *ApiServer) requireAuthMiddleware(c *fiber.Ctx) error { | ||
| authedUserId := app.getAuthedUserId(c) | ||
| authedWallet := app.getAuthedWallet(c) | ||
| myId := app.getMyId(c) | ||
| wallet := c.Params("wallet") | ||
| if authedUserId == 0 { | ||
| return fiber.NewError(fiber.StatusUnauthorized, "You must be logged in to make this request") | ||
| } | ||
| return c.Next() | ||
|
|
||
| if myId != 0 && myId == authedUserId { | ||
| return c.Next() | ||
| } | ||
|
|
||
| if wallet != "" && wallet == authedWallet { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this meant as a catch-all for endpoints that use a |
||
| return c.Next() | ||
| } | ||
|
|
||
| if app.isAuthorizedRequest(c, myId, authedWallet) { | ||
| return c.Next() | ||
| } | ||
|
|
||
| return fiber.NewError(fiber.StatusForbidden, "You are not authorized to make this request") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,9 @@ func NewApiServer(config config.Config) *ApiServer { | |
| } | ||
|
|
||
| pool, err := pgxpool.NewWithConfig(context.Background(), connConfig) | ||
| // To turn off pgx logging, use this: | ||
| // pool, err := pgxpool.New(context.Background(), config.DbUrl) | ||
|
|
||
| if err != nil { | ||
| logger.Fatal("db connect failed", zap.Error(err)) | ||
| } | ||
|
|
@@ -100,6 +103,14 @@ func NewApiServer(config config.Config) *ApiServer { | |
| panic(err) | ||
| } | ||
|
|
||
| resolveGrantCache, err := otter.MustBuilder[string, bool](50_000). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am I wrong or does this mean when you delete a grant we will take 10 minutes to stop honoring it for the manager/app in question? |
||
| WithTTL(10 * time.Minute). | ||
| CollectStats(). | ||
| Build() | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| privateKey, err := crypto.HexToECDSA(config.DelegatePrivateKey) | ||
| if err != nil { | ||
| panic(err) | ||
|
|
@@ -117,6 +128,7 @@ func NewApiServer(config config.Config) *ApiServer { | |
| started: time.Now(), | ||
| resolveHandleCache: resolveHandleCache, | ||
| resolveWalletCache: resolveWalletCache, | ||
| resolveGrantCache: resolveGrantCache, | ||
| rewardAttester: *rewards.NewRewardAttester(privateKey, []rewards.Reward{}), | ||
| solanaConfig: config.SolanaConfig, | ||
| antiAbuseOracles: config.AntiAbuseOracles, | ||
|
|
@@ -183,7 +195,7 @@ func NewApiServer(config config.Config) *ApiServer { | |
| // Users | ||
| g.Get("/users", app.v1Users) | ||
|
|
||
| g.Get("/users/account/:wallet", app.requiresAuthMiddleware, app.v1UsersAccount) | ||
| g.Get("/users/account/:wallet", app.requireAuthMiddleware, app.v1UsersAccount) | ||
|
|
||
| g.Use("/users/handle/:handle", app.requireHandleMiddleware) | ||
| g.Get("/users/handle/:handle", app.v1User) | ||
|
|
@@ -255,6 +267,7 @@ type ApiServer struct { | |
| started time.Time | ||
| resolveHandleCache otter.Cache[string, int32] | ||
| resolveWalletCache otter.Cache[string, int32] | ||
| resolveGrantCache otter.Cache[string, bool] | ||
| rewardAttester rewards.RewardAttester | ||
| solanaConfig config.SolanaConfig | ||
| antiAbuseOracles []string | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| user_id,grantee_address,is_approved,is_revoked | ||
| 1,0x5f1a372b28956c8363f8bc3a231a6e9e1186ead8,true,false | ||
| 1,0xc451c1f8943b575158310552b41230c61844a1c1,false,true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this prone to any casing issues or did we fix that already?