Initial commit: Luminary AI Gateway
CI / docker (push) Successful in 2m4s

OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress,
ingress key governance, monitoring, and security controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
renjue
2026-06-23 21:46:16 +08:00
commit 76ba500417
134 changed files with 18988 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
package auth
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
)
func deriveKey(secret string) []byte {
h := sha256.Sum256([]byte(secret))
return h[:]
}
func Encrypt(plaintext, secret string) (string, error) {
key := deriveKey(secret)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func Decrypt(encoded, secret string) (string, error) {
data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return "", err
}
key := deriveKey(secret)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return "", errors.New("ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plain), nil
}
func HashToken(token string) string {
h := sha256.Sum256([]byte(token))
return fmt.Sprintf("%x", h)
}
func GenerateToken(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b)[:n], nil
}
+93
View File
@@ -0,0 +1,93 @@
package loginlimit
import (
"sync"
"time"
)
// Limiter tracks failed admin login attempts per client IP.
type Limiter struct {
mu sync.Mutex
attempts map[string]*record
maxAttempts int
lockout time.Duration
}
type record struct {
failures int
lockedUntil time.Time
}
func New(maxAttempts int, lockout time.Duration) *Limiter {
if maxAttempts <= 0 {
maxAttempts = 5
}
if lockout <= 0 {
lockout = 15 * time.Minute
}
return &Limiter{
attempts: make(map[string]*record),
maxAttempts: maxAttempts,
lockout: lockout,
}
}
// Check reports whether the IP is locked and how long to wait.
func (l *Limiter) Check(ip string) (locked bool, retryAfter time.Duration) {
l.mu.Lock()
defer l.mu.Unlock()
r := l.attempts[ip]
if r == nil {
return false, 0
}
now := time.Now()
if now.Before(r.lockedUntil) {
return true, r.lockedUntil.Sub(now)
}
if r.failures >= l.maxAttempts {
delete(l.attempts, ip)
}
return false, 0
}
// RecordFailure increments the failure count and locks the IP when the limit is reached.
func (l *Limiter) RecordFailure(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
r := l.attempts[ip]
if r == nil {
r = &record{}
l.attempts[ip] = r
}
if now.Before(r.lockedUntil) {
return
}
if r.failures >= l.maxAttempts && !r.lockedUntil.IsZero() && now.After(r.lockedUntil) {
r.failures = 0
r.lockedUntil = time.Time{}
}
r.failures++
if r.failures >= l.maxAttempts {
r.lockedUntil = now.Add(l.lockout)
}
}
// Configure updates rate limit parameters at runtime.
func (l *Limiter) Configure(maxAttempts int, lockout time.Duration) {
l.mu.Lock()
defer l.mu.Unlock()
if maxAttempts > 0 {
l.maxAttempts = maxAttempts
}
if lockout > 0 {
l.lockout = lockout
}
}
// Reset clears failure state after a successful login.
func (l *Limiter) Reset(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
delete(l.attempts, ip)
}
+36
View File
@@ -0,0 +1,36 @@
package loginlimit
import (
"testing"
"time"
)
func TestLimiterLockout(t *testing.T) {
l := New(3, time.Minute)
ip := "192.168.1.1"
for i := 0; i < 2; i++ {
if locked, _ := l.Check(ip); locked {
t.Fatalf("unexpected lock at attempt %d", i)
}
l.RecordFailure(ip)
}
if locked, _ := l.Check(ip); locked {
t.Fatal("should not lock before max attempts")
}
l.RecordFailure(ip)
locked, retry := l.Check(ip)
if !locked {
t.Fatal("expected lock after max failures")
}
if retry <= 0 {
t.Fatal("expected positive retry duration")
}
l.Reset(ip)
if locked, _ := l.Check(ip); locked {
t.Fatal("expected unlock after reset")
}
}
+30
View File
@@ -0,0 +1,30 @@
package auth
import (
"strings"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(b), err
}
func CheckPassword(hash, password string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
// HashIngressKey stores a SHA-256 hex digest for O(1) cache lookup.
func HashIngressKey(key string) string {
return HashToken(key)
}
func IsLegacyIngressHash(hash string) bool {
return strings.HasPrefix(hash, "$2a$") || strings.HasPrefix(hash, "$2b$")
}
// CheckIngressKey verifies legacy bcrypt-hashed ingress keys.
func CheckIngressKey(hash, key string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(key)) == nil
}