Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
150
backend/auth/auth.go
Normal file
150
backend/auth/auth.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Auth handles user authentication
|
||||
type Auth struct {
|
||||
db *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewAuth creates a new auth handler
|
||||
func NewAuth(db *pgxpool.Pool) *Auth {
|
||||
return &Auth{db: db}
|
||||
}
|
||||
|
||||
// User represents a user
|
||||
type User struct {
|
||||
ID string
|
||||
Email string
|
||||
Username string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// RegisterUser registers a new user
|
||||
func (a *Auth) RegisterUser(ctx context.Context, email, username, password string) (*User, error) {
|
||||
// Hash password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
// Insert user
|
||||
query := `
|
||||
INSERT INTO users (email, username, password_hash)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, email, username, created_at
|
||||
`
|
||||
|
||||
var user User
|
||||
err = a.db.QueryRow(ctx, query, email, username, hashedPassword).Scan(
|
||||
&user.ID, &user.Email, &user.Username, &user.CreatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// AuthenticateUser authenticates a user
|
||||
func (a *Auth) AuthenticateUser(ctx context.Context, email, password string) (*User, error) {
|
||||
var user User
|
||||
var passwordHash string
|
||||
|
||||
query := `SELECT id, email, username, password_hash, created_at FROM users WHERE email = $1`
|
||||
err := a.db.QueryRow(ctx, query, email).Scan(
|
||||
&user.ID, &user.Email, &user.Username, &passwordHash, &user.CreatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)); err != nil {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GenerateAPIKey generates a new API key for a user
|
||||
func (a *Auth) GenerateAPIKey(ctx context.Context, userID, name string, tier string) (string, error) {
|
||||
// Generate random key
|
||||
keyBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(keyBytes); err != nil {
|
||||
return "", fmt.Errorf("failed to generate key: %w", err)
|
||||
}
|
||||
|
||||
apiKey := "ek_" + hex.EncodeToString(keyBytes)
|
||||
|
||||
// Hash key for storage
|
||||
hashedKey := sha256.Sum256([]byte(apiKey))
|
||||
hashedKeyHex := hex.EncodeToString(hashedKey[:])
|
||||
|
||||
// Determine rate limits based on tier
|
||||
var rateLimitPerSecond, rateLimitPerMinute int
|
||||
switch tier {
|
||||
case "free":
|
||||
rateLimitPerSecond = 5
|
||||
rateLimitPerMinute = 100
|
||||
case "pro":
|
||||
rateLimitPerSecond = 20
|
||||
rateLimitPerMinute = 1000
|
||||
case "enterprise":
|
||||
rateLimitPerSecond = 100
|
||||
rateLimitPerMinute = 10000
|
||||
default:
|
||||
rateLimitPerSecond = 5
|
||||
rateLimitPerMinute = 100
|
||||
}
|
||||
|
||||
// Store API key
|
||||
query := `
|
||||
INSERT INTO api_keys (user_id, key_hash, name, tier, rate_limit_per_second, rate_limit_per_minute)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`
|
||||
|
||||
_, err := a.db.Exec(ctx, query, userID, hashedKeyHex, name, tier, rateLimitPerSecond, rateLimitPerMinute)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to store API key: %w", err)
|
||||
}
|
||||
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
// ValidateAPIKey validates an API key
|
||||
func (a *Auth) ValidateAPIKey(ctx context.Context, apiKey string) (string, error) {
|
||||
hashedKey := sha256.Sum256([]byte(apiKey))
|
||||
hashedKeyHex := hex.EncodeToString(hashedKey[:])
|
||||
|
||||
var userID string
|
||||
var revoked bool
|
||||
query := `SELECT user_id, revoked FROM api_keys WHERE key_hash = $1`
|
||||
err := a.db.QueryRow(ctx, query, hashedKeyHex).Scan(&userID, &revoked)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid API key")
|
||||
}
|
||||
|
||||
if revoked {
|
||||
return "", fmt.Errorf("API key revoked")
|
||||
}
|
||||
|
||||
// Update last used
|
||||
a.db.Exec(ctx, `UPDATE api_keys SET last_used_at = NOW() WHERE key_hash = $1`, hashedKeyHex)
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user