Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions api/auth_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ") {
Expand Down
1 change: 0 additions & 1 deletion api/request_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
66 changes: 66 additions & 0 deletions api/swagger/swagger-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
78 changes: 78 additions & 0 deletions api/v1_users_developer_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down