Remove the Go reference server; keep only Rust
CI / Rust (fmt / clippy / test / build) (push) Successful in 1m41s
CI / HTTP/JSON reference + SQLite persistence (push) Successful in 1m16s
CI / Rust (fmt / clippy / test / build) (pull_request) Successful in 47s
CI / HTTP/JSON reference + SQLite persistence (pull_request) Successful in 1m34s

The Go implementation is preserved in the v1.0.0 tag. Its observable
behaviour stays the API reference: responses recorded from the Go server
live in tools/compatibility_expected.txt, and tools/compatibility.py now
compares the Rust server against them (--update re-records after an
intentional change). The Go SQLite schema is frozen in
tests/fixtures/go_schema_v1.sql for the existing-database test.

Move the web client from internal/httpapi/web to web/, drop the Go CI job
and Go version pin, and update README, AGENTS.md and docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wtandClaude Opus 5.5 committed 2026-10-30 16:27:33 +07:00
1 parent 16dd2a3b3c
commit 10272f3851
33 files changed
+890 -1438

No files matched your search

-90
View File
@@ -1,90 +0,0 @@
package auth
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"strings"
"time"
)
type Claims struct {
Subject string `json:"sub"`
Email string `json:"email"`
Issuer string `json:"iss"`
Issued int64 `json:"iat"`
Expires int64 `json:"exp"`
ID string `json:"jti"`
}
type TokenManager struct {
secret []byte
accessTTL time.Duration
}
func NewTokenManager(secret string, accessTTL time.Duration) TokenManager {
return TokenManager{secret: []byte(secret), accessTTL: accessTTL}
}
func (manager TokenManager) AccessToken(userID, email string) (string, time.Time, error) {
now := time.Now().UTC()
expires := now.Add(manager.accessTTL)
id, err := RandomToken(16)
if err != nil {
return "", time.Time{}, err
}
header, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
claims, _ := json.Marshal(Claims{Subject: userID, Email: email, Issuer: "balance-server", Issued: now.Unix(), Expires: expires.Unix(), ID: id})
unsigned := encode(header) + "." + encode(claims)
signature := manager.sign(unsigned)
return unsigned + "." + signature, expires, nil
}
func (manager TokenManager) Verify(token string) (Claims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return Claims{}, errors.New("invalid token")
}
unsigned := parts[0] + "." + parts[1]
expected := manager.sign(unsigned)
if !hmac.Equal([]byte(parts[2]), []byte(expected)) {
return Claims{}, errors.New("invalid token signature")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return Claims{}, errors.New("invalid token payload")
}
var claims Claims
if err := json.Unmarshal(payload, &claims); err != nil {
return Claims{}, errors.New("invalid token claims")
}
if claims.Issuer != "balance-server" || claims.Subject == "" || time.Now().UTC().Unix() >= claims.Expires {
return Claims{}, errors.New("expired or invalid token")
}
return claims, nil
}
func RandomToken(bytes int) (string, error) {
value := make([]byte, bytes)
if _, err := rand.Read(value); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(value), nil
}
func TokenHash(token string) string {
hash := sha256.Sum256([]byte(token))
return hex.EncodeToString(hash[:])
}
func (manager TokenManager) sign(value string) string {
mac := hmac.New(sha256.New, manager.secret)
_, _ = mac.Write([]byte(value))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
func encode(value []byte) string { return base64.RawURLEncoding.EncodeToString(value) }