Fetch Feishu tenant identity from app credentials

This commit is contained in:
opsx committed 2026-10-22 14:31:39 +08:00
1 parent 0528bc4b9a
commit eb3861bbe8
7 files changed
+316 -11

No files matched your search

@@ -3,6 +3,7 @@ package identityprovider
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
@@ -12,6 +13,116 @@ import (
"vpn-platform/backend/internal/store"
)
func TestQueryTenantUsesAppCredentialsWithoutPersistingOrLeaking(t *testing.T) {
dir := t.TempDir()
t.Setenv("VPN_DATA_DIR", dir)
if err := store.Init(dir); err != nil {
t.Fatal(err)
}
previous := upstreamClient
defer func() { upstreamClient = previous }()
upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
response := `{"code":0,"tenant_access_token":"temporary-token"}`
switch req.URL.String() {
case feishuTenantToken:
if req.Method != http.MethodPost || req.Header.Get("Content-Type") != "application/json" {
t.Fatal("invalid token request")
}
var creds tenantCredentials
if json.NewDecoder(req.Body).Decode(&creds) != nil || creds.AppID != "cli_test" || creds.AppSecret != "private-value" {
t.Fatal("invalid credentials")
}
case feishuTenantQuery:
if req.Method != http.MethodGet || req.Header.Get("Authorization") != "Bearer temporary-token" {
t.Fatal("invalid tenant request")
}
response = `{"code":0,"data":{"tenant":{"tenant_key":"tenant_123","name":"Example"}}}`
default:
t.Fatalf("unexpected endpoint: %s", req.URL)
}
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(response)), Header: make(http.Header)}, nil
})}
gin.SetMode(gin.TestMode)
server := gin.New()
p := New()
server.POST("/tenant", p.QueryTenant)
server.PUT("/config", p.Configure)
query := []byte(`{"app_id":"cli_test","app_secret":"private-value"}`)
request := httptest.NewRequest(http.MethodPost, "/tenant", bytes.NewReader(query))
request.RemoteAddr = "127.0.0.1:12345"
request.Header.Set("X-Forwarded-Proto", "https")
resp := httptest.NewRecorder()
server.ServeHTTP(resp, request)
if resp.Code != 200 || !strings.Contains(resp.Body.String(), `"tenant_key":"tenant_123"`) || strings.Contains(resp.Body.String(), "temporary-token") || strings.Contains(resp.Body.String(), "private-value") {
t.Fatalf("unexpected query response: %d %s", resp.Code, resp.Body.String())
}
if _, err := current(); err == nil {
t.Fatal("query unexpectedly persisted configuration")
}
request = httptest.NewRequest(http.MethodPut, "/config", strings.NewReader(`{"app_id":"cli_test","app_secret":"private-value","enabled":false}`))
request.RemoteAddr = "127.0.0.1:12345"
request.Header.Set("X-Forwarded-Proto", "https")
resp = httptest.NewRecorder()
server.ServeHTTP(resp, request)
if resp.Code != 200 {
t.Fatalf("save failed: %d %s", resp.Code, resp.Body.String())
}
request = httptest.NewRequest(http.MethodPost, "/tenant", strings.NewReader(`{"app_id":"cli_test"}`))
request.RemoteAddr = "127.0.0.1:12345"
request.Header.Set("X-Forwarded-Proto", "https")
resp = httptest.NewRecorder()
server.ServeHTTP(resp, request)
if resp.Code != 200 {
t.Fatalf("saved credentials not reused: %d %s", resp.Code, resp.Body.String())
}
request = httptest.NewRequest(http.MethodPut, "/config", strings.NewReader(`{"app_id":"cli_other","tenant_key":"tenant_123","enabled":false}`))
request.RemoteAddr = "127.0.0.1:12345"
request.Header.Set("X-Forwarded-Proto", "https")
resp = httptest.NewRecorder()
server.ServeHTTP(resp, request)
if resp.Code != 400 {
t.Fatalf("new app reused old secret: %d %s", resp.Code, resp.Body.String())
}
cfg, err := current()
if err != nil || cfg.AppID != "cli_test" {
t.Fatal("rejected app change modified saved configuration")
}
request = httptest.NewRequest(http.MethodPost, "/tenant", bytes.NewReader(query))
resp = httptest.NewRecorder()
server.ServeHTTP(resp, request)
if resp.Code != 403 {
t.Fatalf("plaintext request accepted: %d", resp.Code)
}
}
func TestQueryTenantRejectsWrongAppAndUpstreamFailure(t *testing.T) {
dir := t.TempDir()
t.Setenv("VPN_DATA_DIR", dir)
if err := store.Init(dir); err != nil {
t.Fatal(err)
}
secret, err := store.EncryptSecret("private-value")
if err != nil {
t.Fatal(err)
}
if _, err := resolveTenantCredentials(tenantCredentials{AppID: "cli_other"}, "cli_test", secret); err == nil {
t.Fatal("old secret reused for another app")
}
creds, err := resolveTenantCredentials(tenantCredentials{AppID: "cli_test"}, "cli_test", secret)
if err != nil || creds.AppSecret != "private-value" {
t.Fatal("saved secret not reused for same app")
}
previous := upstreamClient
defer func() { upstreamClient = previous }()
upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"code":999,"msg":"private-value"}`)), Header: make(http.Header)}, nil
})}
_, _, err = queryFeishuTenant(httptest.NewRequest(http.MethodGet, "/", nil).Context(), creds)
if err == nil || strings.Contains(err.Error(), "private-value") {
t.Fatalf("unsafe upstream error: %v", err)
}
}
func TestAdminNeverEchoesFeishuSecretAndKeepsEncryptedValue(t *testing.T) {
dir := t.TempDir()
t.Setenv("VPN_DATA_DIR", dir)