From 1813c624b5970233abe25b8026c78ad07e48ba26 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 25 Feb 2026 16:56:35 -0800 Subject: [PATCH] Fix user id in api and add post to api_Keys --- api/auth_middleware.go | 5 +-- api/request_helpers.go | 1 - api/server.go | 2 + api/swagger/swagger-v1.yaml | 66 ++++++++++++++++++++++++++++ api/v1_users_developer_apps.go | 78 ++++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 5 deletions(-) diff --git a/api/auth_middleware.go b/api/auth_middleware.go index 8b3b3e49..90748081 100644 --- a/api/auth_middleware.go +++ b/api/auth_middleware.go @@ -232,16 +232,13 @@ func (app *ApiServer) validateOAuthJWTTokenToWalletAndUserId(ctx context.Context // - the user is not authorized to act on behalf of "myWallet" func (app *ApiServer) authMiddleware(c *fiber.Ctx) error { var wallet string - var myId int32 signer, _ := app.getApiSigner(c) + myId := app.getMyId(c) if signer != nil { wallet = strings.ToLower(signer.Address) - c.Locals("myId", signer.UserId) - myId = int32(signer.UserId) } else { wallet = app.recoverAuthorityFromSignatureHeaders(c) - myId = app.getMyId(c) // OAuth JWT fallback: when Bearer token is not api_access_key, try as OAuth JWT (Plans app) if wallet == "" && myId != 0 { if authHeader := c.Get("Authorization"); authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { diff --git a/api/request_helpers.go b/api/request_helpers.go index aca6266d..d448476c 100644 --- a/api/request_helpers.go +++ b/api/request_helpers.go @@ -22,7 +22,6 @@ type apiAccessKeySignerEntry struct { // Signer holds the address, public key, and private key for signing transactions type Signer struct { - UserId int Address string PrivateKey *ecdsa.PrivateKey } diff --git a/api/server.go b/api/server.go index 3ac78d24..1dfaa703 100644 --- a/api/server.go +++ b/api/server.go @@ -536,6 +536,8 @@ func NewApiServer(config config.Config) *ApiServer { g.Delete("/developer-apps/:address", app.deleteV1UsersDeveloperApp) g.Post("/developer_apps/:address/access-keys/deactivate", app.postV1UsersDeveloperAppAccessKeyDeactivate) g.Post("/developer-apps/:address/access-keys/deactivate", app.postV1UsersDeveloperAppAccessKeyDeactivate) + g.Post("/developer_apps/:address/register-api-key", app.requireAuthMiddleware, app.postV1UsersDeveloperAppRegisterApiKey) + g.Post("/developer-apps/:address/register-api-key", app.requireAuthMiddleware, app.postV1UsersDeveloperAppRegisterApiKey) g.Post("/developer_apps/:address/access-keys", app.postV1UsersDeveloperAppAccessKey) g.Post("/developer-apps/:address/access-keys", app.postV1UsersDeveloperAppAccessKey) diff --git a/api/swagger/swagger-v1.yaml b/api/swagger/swagger-v1.yaml index a4d0ac0f..394eefb8 100644 --- a/api/swagger/swagger-v1.yaml +++ b/api/swagger/swagger-v1.yaml @@ -690,6 +690,58 @@ paths: '500': description: Server error content: {} + /developer-apps/{address}/register-api-key: + post: + tags: + - developer_apps + description: Register api_key and api_secret in api_keys table for developer + apps created via entity manager transactions. Use when the client sends raw + ManageEntity tx instead of POST /developer-apps. Inserts with rps=10, + rpm=500000. Requires the app to exist in developer_apps and belong to the + authenticated user. + operationId: Register Developer App API Key + security: + - BasicAuth: [] + - BearerAuth: [] + parameters: + - name: user_id + in: query + description: The user ID of the user who owns the developer app + required: true + schema: + type: string + - name: address + in: path + description: Developer app address (API key) + required: true + schema: + type: string + requestBody: + x-codegen-request-body-name: metadata + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/register_api_key_request_body' + responses: + '200': + description: API key registered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/register_api_key_response' + '400': + description: Bad request (api_secret required) + content: {} + '401': + description: Unauthorized + content: {} + '404': + description: Developer app not found + content: {} + '500': + description: Server error + content: {} /developer-apps/{address}/access-keys/deactivate: post: tags: @@ -11989,6 +12041,20 @@ components: type: string description: The ID of the user with a non-zero balance example: 7eP5n + register_api_key_request_body: + type: object + required: + - api_secret + properties: + api_secret: + type: string + description: The API secret (private key hex) for the developer app + register_api_key_response: + type: object + properties: + success: + type: boolean + description: Whether the registration was successful deactivate_access_key_request_body: type: object required: diff --git a/api/v1_users_developer_apps.go b/api/v1_users_developer_apps.go index 80d917e3..6ebd4b9f 100644 --- a/api/v1_users_developer_apps.go +++ b/api/v1_users_developer_apps.go @@ -471,6 +471,84 @@ func (app *ApiServer) deleteV1UsersDeveloperApp(c *fiber.Ctx) error { }) } +type registerApiKeyBody struct { + ApiSecret string `json:"api_secret"` +} + +// postV1UsersDeveloperAppRegisterApiKey inserts api_key and api_secret into api_keys +// for developer apps created via entity manager transactions. Used when the client +// sends raw ManageEntity tx instead of POST /developer-apps. Inserts with rps=10, +// rpm=500000 (same as POST create). Requires the app to exist in developer_apps and +// belong to the authenticated user. +func (app *ApiServer) postV1UsersDeveloperAppRegisterApiKey(c *fiber.Ctx) error { + userID := app.getMyId(c) + if userID == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "user_id query parameter is required", + }) + } + + address := c.Params("address") + if address == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "address is required", + }) + } + if !strings.HasPrefix(address, "0x") { + address = "0x" + address + } + apiKey := strings.ToLower(address) + + var body registerApiKeyBody + if err := c.BodyParser(&body); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request body", + }) + } + apiSecret := strings.TrimSpace(body.ApiSecret) + if apiSecret == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "api_secret is required", + }) + } + + // Verify the app belongs to this user + var ownerUserID int32 + err := app.pool.QueryRow(c.Context(), ` + SELECT user_id FROM developer_apps + WHERE LOWER(address) = LOWER($1) + AND is_current = true + AND is_delete = false + ORDER BY created_at DESC + LIMIT 1 + `, apiKey).Scan(&ownerUserID) + if err != nil || ownerUserID != userID { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": "Developer app not found", + }) + } + + if app.writePool == nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Database write not available", + }) + } + + _, err = app.writePool.Exec(c.Context(), ` + INSERT INTO api_keys (api_key, api_secret, rps, rpm) + VALUES ($1, $2, 10, 500000) + ON CONFLICT (api_key) DO UPDATE SET api_secret = EXCLUDED.api_secret + `, apiKey, apiSecret) + if err != nil { + app.logger.Error("Failed to insert api_keys", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Failed to register API key", + }) + } + + return c.JSON(fiber.Map{"success": true}) +} + type deactivateAccessKeyBody struct { ApiAccessKey string `json:"api_access_key"` }