mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 22:58:49 +00:00
feat: enhance API key management and ownership recording in verifyHandler
- Implemented logic to ensure an API key is created or retrieved for each wallet during the verification process. - Added best-effort recording of ownership for both API keys and wallets in the namespace ownership database. - Improved error handling and logging for better traceability of ownership checks and API key operations. - Cleaned up unnecessary comments and whitespace in the auth_handlers.go file for better code readability.
This commit is contained in:
parent
c208ff3288
commit
ad088bd476
@ -192,7 +192,6 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch chainType {
|
switch chainType {
|
||||||
case "ETH":
|
case "ETH":
|
||||||
// EVM personal_sign verification of the nonce
|
// EVM personal_sign verification of the nonce
|
||||||
// Hash: keccak256("\x19Ethereum Signed Message:\n" + len(nonce) + nonce)
|
|
||||||
msg := []byte(req.Nonce)
|
msg := []byte(req.Nonce)
|
||||||
prefix := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(msg)))
|
prefix := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(msg)))
|
||||||
hash := ethcrypto.Keccak256(prefix, msg)
|
hash := ethcrypto.Keccak256(prefix, msg)
|
||||||
@ -228,7 +227,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "SOL":
|
case "SOL":
|
||||||
// Solana uses Ed25519 signatures
|
// Solana uses Ed25519 signatures
|
||||||
// Signature is base64-encoded, public key is the wallet address (base58)
|
// Signature is base64-encoded, public key is the wallet address (base58)
|
||||||
|
|
||||||
// Decode base64 signature (Solana signatures are 64 bytes)
|
// Decode base64 signature (Solana signatures are 64 bytes)
|
||||||
sig, err := base64.StdEncoding.DecodeString(req.Signature)
|
sig, err := base64.StdEncoding.DecodeString(req.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -241,7 +240,6 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode base58 public key (Solana wallet address)
|
// Decode base58 public key (Solana wallet address)
|
||||||
// Using a simple base58 decoder
|
|
||||||
pubKeyBytes, err := base58Decode(req.Wallet)
|
pubKeyBytes, err := base58Decode(req.Wallet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid wallet address: %v", err))
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid wallet address: %v", err))
|
||||||
@ -296,6 +294,45 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusInternalServerError, err.Error())
|
writeError(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure API key exists for this (namespace, wallet) and record ownerships
|
||||||
|
// This is done automatically after successful verification; no second nonce needed
|
||||||
|
var apiKey string
|
||||||
|
|
||||||
|
// Try existing linkage
|
||||||
|
r1, err := db.Query(internalCtx,
|
||||||
|
"SELECT api_keys.key FROM wallet_api_keys JOIN api_keys ON wallet_api_keys.api_key_id = api_keys.id WHERE wallet_api_keys.namespace_id = ? AND LOWER(wallet_api_keys.wallet) = LOWER(?) LIMIT 1",
|
||||||
|
nsID, req.Wallet,
|
||||||
|
)
|
||||||
|
if err == nil && r1 != nil && r1.Count > 0 && len(r1.Rows) > 0 && len(r1.Rows[0]) > 0 {
|
||||||
|
if s, ok := r1.Rows[0][0].(string); ok {
|
||||||
|
apiKey = s
|
||||||
|
} else {
|
||||||
|
b, _ := json.Marshal(r1.Rows[0][0])
|
||||||
|
_ = json.Unmarshal(b, &apiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(apiKey) == "" {
|
||||||
|
// Create new API key with format ak_<random>:<namespace>
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
if _, err := rand.Read(buf); err == nil {
|
||||||
|
apiKey = "ak_" + base64.RawURLEncoding.EncodeToString(buf) + ":" + ns
|
||||||
|
if _, err := db.Query(internalCtx, "INSERT INTO api_keys(key, name, namespace_id) VALUES (?, ?, ?)", apiKey, "", nsID); err == nil {
|
||||||
|
// Link wallet -> api_key
|
||||||
|
rid, err := db.Query(internalCtx, "SELECT id FROM api_keys WHERE key = ? LIMIT 1", apiKey)
|
||||||
|
if err == nil && rid != nil && rid.Count > 0 && len(rid.Rows) > 0 && len(rid.Rows[0]) > 0 {
|
||||||
|
apiKeyID := rid.Rows[0][0]
|
||||||
|
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO wallet_api_keys(namespace_id, wallet, api_key_id) VALUES (?, ?, ?)", nsID, strings.ToLower(req.Wallet), apiKeyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record ownerships (best-effort)
|
||||||
|
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'api_key', ?)", nsID, apiKey)
|
||||||
|
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'wallet', ?)", nsID, req.Wallet)
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"access_token": token,
|
"access_token": token,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
@ -303,6 +340,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
"refresh_token": refresh,
|
"refresh_token": refresh,
|
||||||
"subject": req.Wallet,
|
"subject": req.Wallet,
|
||||||
"namespace": ns,
|
"namespace": ns,
|
||||||
|
"api_key": apiKey,
|
||||||
"nonce": req.Nonce,
|
"nonce": req.Nonce,
|
||||||
"signature_verified": true,
|
"signature_verified": true,
|
||||||
})
|
})
|
||||||
@ -1091,17 +1129,17 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Used for decoding Solana public keys (base58-encoded 32-byte ed25519 public keys)
|
// Used for decoding Solana public keys (base58-encoded 32-byte ed25519 public keys)
|
||||||
func base58Decode(encoded string) ([]byte, error) {
|
func base58Decode(encoded string) ([]byte, error) {
|
||||||
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||||
|
|
||||||
// Build reverse lookup map
|
// Build reverse lookup map
|
||||||
lookup := make(map[rune]int)
|
lookup := make(map[rune]int)
|
||||||
for i, c := range alphabet {
|
for i, c := range alphabet {
|
||||||
lookup[c] = i
|
lookup[c] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to big integer
|
// Convert to big integer
|
||||||
num := big.NewInt(0)
|
num := big.NewInt(0)
|
||||||
base := big.NewInt(58)
|
base := big.NewInt(58)
|
||||||
|
|
||||||
for _, c := range encoded {
|
for _, c := range encoded {
|
||||||
val, ok := lookup[c]
|
val, ok := lookup[c]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -1110,10 +1148,10 @@ func base58Decode(encoded string) ([]byte, error) {
|
|||||||
num.Mul(num, base)
|
num.Mul(num, base)
|
||||||
num.Add(num, big.NewInt(int64(val)))
|
num.Add(num, big.NewInt(int64(val)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to bytes
|
// Convert to bytes
|
||||||
decoded := num.Bytes()
|
decoded := num.Bytes()
|
||||||
|
|
||||||
// Add leading zeros for each leading '1' in the input
|
// Add leading zeros for each leading '1' in the input
|
||||||
for _, c := range encoded {
|
for _, c := range encoded {
|
||||||
if c != '1' {
|
if c != '1' {
|
||||||
@ -1121,6 +1159,6 @@ func base58Decode(encoded string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
decoded = append([]byte{0}, decoded...)
|
decoded = append([]byte{0}, decoded...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -214,6 +214,8 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
|
|||||||
// Identify actor from context
|
// Identify actor from context
|
||||||
ownerType := ""
|
ownerType := ""
|
||||||
ownerID := ""
|
ownerID := ""
|
||||||
|
apiKeyFallback := ""
|
||||||
|
|
||||||
if v := ctx.Value(ctxKeyJWT); v != nil {
|
if v := ctx.Value(ctxKeyJWT); v != nil {
|
||||||
if claims, ok := v.(*jwtClaims); ok && claims != nil && strings.TrimSpace(claims.Sub) != "" {
|
if claims, ok := v.(*jwtClaims); ok && claims != nil && strings.TrimSpace(claims.Sub) != "" {
|
||||||
// Determine subject type.
|
// Determine subject type.
|
||||||
@ -237,6 +239,13 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
|
|||||||
ownerID = strings.TrimSpace(s)
|
ownerID = strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if ownerType == "wallet" {
|
||||||
|
// If we have a JWT wallet, also capture the API key as fallback
|
||||||
|
if v := ctx.Value(ctxKeyAPIKey); v != nil {
|
||||||
|
if s, ok := v.(string); ok && strings.TrimSpace(s) != "" {
|
||||||
|
apiKeyFallback = strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ownerType == "" || ownerID == "" {
|
if ownerType == "" || ownerID == "" {
|
||||||
@ -244,6 +253,12 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.logger.ComponentInfo("gateway", "namespace auth check",
|
||||||
|
zap.String("namespace", ns),
|
||||||
|
zap.String("owner_type", ownerType),
|
||||||
|
zap.String("owner_id", ownerID),
|
||||||
|
)
|
||||||
|
|
||||||
// Check ownership in DB using internal auth context
|
// Check ownership in DB using internal auth context
|
||||||
db := g.client.Database()
|
db := g.client.Database()
|
||||||
internalCtx := client.WithInternalAuth(ctx)
|
internalCtx := client.WithInternalAuth(ctx)
|
||||||
@ -261,6 +276,12 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
q := "SELECT 1 FROM namespace_ownership WHERE namespace_id = ? AND owner_type = ? AND owner_id = ? LIMIT 1"
|
q := "SELECT 1 FROM namespace_ownership WHERE namespace_id = ? AND owner_type = ? AND owner_id = ? LIMIT 1"
|
||||||
res, err := db.Query(internalCtx, q, nsID, ownerType, ownerID)
|
res, err := db.Query(internalCtx, q, nsID, ownerType, ownerID)
|
||||||
|
|
||||||
|
// If primary owner check fails and we have a JWT wallet with API key fallback, try the API key
|
||||||
|
if (err != nil || res == nil || res.Count == 0) && ownerType == "wallet" && apiKeyFallback != "" {
|
||||||
|
res, err = db.Query(internalCtx, q, nsID, "api_key", apiKeyFallback)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil || res == nil || res.Count == 0 {
|
if err != nil || res == nil || res.Count == 0 {
|
||||||
writeError(w, http.StatusForbidden, "forbidden: not an owner of namespace")
|
writeError(w, http.StatusForbidden, "forbidden: not an owner of namespace")
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user