92 lines
3.2 KiB
Go
92 lines
3.2 KiB
Go
package licenseserver
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/b1tsblog/ai-disclosure-standard/pkg/licensekit"
|
|
)
|
|
|
|
func keyPair(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
|
t.Helper()
|
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return pub, priv
|
|
}
|
|
|
|
func TestRegisterIntrospectAndRevoke(t *testing.T) {
|
|
issuerPub, issuerPriv := keyPair(t)
|
|
leasePub, leasePriv := keyPair(t)
|
|
storeKeys := licensekit.NewTrustStore()
|
|
storeKeys.LicenseKeys["issuer"] = licensekit.EncodeKey(issuerPub)
|
|
storeKeys.LeaseKeys["lease"] = licensekit.EncodeKey(leasePub)
|
|
registry, err := OpenStore("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server, err := New(Config{TrustStore: storeKeys, LeasePrivateKey: leasePriv, LeaseKeyID: "lease", AdminToken: "secret", DefaultLeaseTTL: time.Hour}, registry, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
httpServer := httptest.NewServer(server.Handler())
|
|
defer httpServer.Close()
|
|
now := time.Now().UTC()
|
|
claims := licensekit.Claims{Version: 1, LicenseID: "lic_1", Issuer: "vendor", Customer: "customer", Product: "product", Edition: "pro", Features: []string{"feature"}, Domains: []string{"*"}, IssuedAt: now.Unix(), ExpiresAt: now.Add(time.Hour).Unix(), Verification: licensekit.VerificationPolicy{Mode: licensekit.ModeHybrid, LeaseTTLSeconds: 300}}
|
|
token, err := licensekit.SignLicense(issuerPriv, "issuer", claims)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
registerBody, _ := json.Marshal(map[string]string{"token": token})
|
|
req, _ := http.NewRequest(http.MethodPost, httpServer.URL+"/v1/admin/licenses", bytes.NewReader(registerBody))
|
|
req.Header.Set("Authorization", "Bearer secret")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("register status %d", resp.StatusCode)
|
|
}
|
|
introBody, _ := json.Marshal(map[string]string{"token": token, "product": "product", "baseUrl": "https://example.org"})
|
|
resp, err = http.Post(httpServer.URL+"/v1/introspect", "application/json", bytes.NewReader(introBody))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var intro map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&intro)
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK || intro["valid"] != true {
|
|
t.Fatalf("introspection failed: %d %#v", resp.StatusCode, intro)
|
|
}
|
|
req, _ = http.NewRequest(http.MethodPost, httpServer.URL+"/v1/admin/licenses/lic_1/revoke", bytes.NewBufferString(`{"reason":"payment"}`))
|
|
req.Header.Set("Authorization", "Bearer secret")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err = http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("revoke status %d", resp.StatusCode)
|
|
}
|
|
resp, err = http.Post(httpServer.URL+"/v1/introspect", "application/json", bytes.NewReader(introBody))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("expected revoked status, got %d", resp.StatusCode)
|
|
}
|
|
}
|