mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
154 lines
4.0 KiB
Go
154 lines
4.0 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/hanzoai/orm"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/luxfi/mpc/pkg/db"
|
|
)
|
|
|
|
type JWTClaims struct {
|
|
UserID string `json:"user_id"`
|
|
OrgID string `json:"org_id"`
|
|
Role string `json:"role"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
func (s *Server) generateJWT(userID, orgID, role string) (string, error) {
|
|
claims := JWTClaims{
|
|
UserID: userID,
|
|
OrgID: orgID,
|
|
Role: role,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
Issuer: "mpc.lux.network",
|
|
Audience: jwt.ClaimStrings{"mpc-api"},
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
},
|
|
}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return token.SignedString(s.jwtSecret)
|
|
}
|
|
|
|
func (s *Server) generateRefreshToken(userID, orgID, role string) (string, error) {
|
|
claims := JWTClaims{
|
|
UserID: userID,
|
|
OrgID: orgID,
|
|
Role: role,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
Issuer: "mpc.lux.network",
|
|
Audience: jwt.ClaimStrings{"mpc-refresh"},
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
},
|
|
}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return token.SignedString(s.jwtSecret)
|
|
}
|
|
|
|
func (s *Server) validateJWT(tokenStr string) (*JWTClaims, error) {
|
|
token, err := jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return s.jwtSecret, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
claims, ok := token.Claims.(*JWTClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, fmt.Errorf("invalid token")
|
|
}
|
|
if claims.Issuer != "mpc.lux.network" {
|
|
return nil, fmt.Errorf("invalid token issuer")
|
|
}
|
|
if !slices.Contains([]string(claims.Audience), "mpc-api") {
|
|
return nil, fmt.Errorf("invalid token audience")
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
func (s *Server) validateRefreshToken(tokenStr string) (*JWTClaims, error) {
|
|
token, err := jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return s.jwtSecret, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
claims, ok := token.Claims.(*JWTClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, fmt.Errorf("invalid token")
|
|
}
|
|
if claims.Issuer != "mpc.lux.network" {
|
|
return nil, fmt.Errorf("invalid token issuer")
|
|
}
|
|
if !slices.Contains([]string(claims.Audience), "mpc-refresh") {
|
|
return nil, fmt.Errorf("invalid token audience")
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
func hashPassword(password string) (string, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(hash), nil
|
|
}
|
|
|
|
func checkPassword(hash, password string) bool {
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
|
|
}
|
|
|
|
func generateAPIKeyToken() (string, string, error) {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", "", err
|
|
}
|
|
key := "lux_" + hex.EncodeToString(b)
|
|
hash := sha256.Sum256([]byte(key))
|
|
return key, hex.EncodeToString(hash[:]), nil
|
|
}
|
|
|
|
func (s *Server) validateAPIKey(ctx context.Context, key string) (*db.APIKey, error) {
|
|
hash := sha256.Sum256([]byte(key))
|
|
keyHash := hex.EncodeToString(hash[:])
|
|
prefix := key
|
|
if len(prefix) > 8 {
|
|
prefix = prefix[:8]
|
|
}
|
|
|
|
apiKey, err := orm.TypedQuery[db.APIKey](s.db.ORM).
|
|
Filter("keyHash=", keyHash).
|
|
Filter("keyPrefix=", prefix).
|
|
First()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid api key")
|
|
}
|
|
|
|
// Check expiry
|
|
if apiKey.ExpiresAt != nil && apiKey.ExpiresAt.Before(time.Now()) {
|
|
return nil, fmt.Errorf("api key expired")
|
|
}
|
|
|
|
// Update last used
|
|
now := time.Now()
|
|
apiKey.LastUsedAt = &now
|
|
apiKey.Update()
|
|
|
|
return apiKey, nil
|
|
}
|