mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-19 02:18:25 +08:00
Five additive SDK changes that PR-3 (CLI Factory adapter) and PR-4 (auth login / status) consume. WithToken split (Breaking + alias): - WithAPIKey(key) sets X-API-Key header (long-lived API keys) - WithBearerToken(token) sets Authorization: Bearer header (JWTs) - WithToken kept as deprecated alias for WithAPIKey for two minor versions - doRequest now sets X-API-Key and Authorization headers independently; callers can carry both kinds of credentials simultaneously applyAuthHeaders extraction: - Header injection (X-API-Key, Authorization, X-Request-ID, X-Tenant-ID) is now a Client method shared between doRequest and the multipart path in CreateKnowledgeFromFile. Eliminates ~30 lines of drift risk and fixes the latent missing X-Tenant-ID on file uploads. - Drops the dead second tenant-header switch in doRequest. Raw escape hatch: - Client.Raw(ctx, method, path, body) is the public passthrough used by the future \`weknora api\` command. godoc marks it experimental. Auth methods (auth.go, new): - Login(ctx, LoginRequest) -> POST /auth/login (existing server endpoint) - GetCurrentUser(ctx) -> GET /auth/me, returns *CurrentUserResponse - RefreshToken(ctx, refreshToken) -> POST /auth/refresh - Types: LoginRequest, LoginResponse, AuthUser, AuthTenant, CurrentUserResponse, RefreshTokenResponse No server changes. The auth methods consume endpoints that already exist; PR-3/PR-4 wire them through the CLI.
118 lines
3.8 KiB
Go
118 lines
3.8 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// LoginRequest is the body for POST /api/v1/auth/login.
|
|
type LoginRequest struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// LoginResponse is the body returned by POST /api/v1/auth/login.
|
|
//
|
|
// Token is the JWT access token; RefreshToken renews it via Auth.Refresh.
|
|
type LoginResponse struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message,omitempty"`
|
|
User *AuthUser `json:"user,omitempty"`
|
|
Tenant *AuthTenant `json:"tenant,omitempty"`
|
|
Token string `json:"token,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
}
|
|
|
|
// AuthUser is the principal returned by /auth/login and /auth/me.
|
|
//
|
|
// Fields mirror the server's UserInfo projection (no PasswordHash).
|
|
type AuthUser struct {
|
|
ID string `json:"id"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Avatar string `json:"avatar,omitempty"`
|
|
TenantID uint64 `json:"tenant_id"`
|
|
IsActive bool `json:"is_active"`
|
|
CanAccessAllTenants bool `json:"can_access_all_tenants,omitempty"`
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
}
|
|
|
|
// AuthTenant is the tenant projection returned alongside AuthUser.
|
|
type AuthTenant struct {
|
|
ID uint64 `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// CurrentUserResponse is the body of GET /api/v1/auth/me.
|
|
type CurrentUserResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
User *AuthUser `json:"user,omitempty"`
|
|
Tenant *AuthTenant `json:"tenant,omitempty"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// RefreshTokenResponse is the body of POST /api/v1/auth/refresh.
|
|
type RefreshTokenResponse struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message,omitempty"`
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
|
|
// Login authenticates with email + password and returns the JWT access token,
|
|
// refresh token, and principal info. Maps to POST /api/v1/auth/login.
|
|
//
|
|
// Used by `weknora auth login`.
|
|
func (c *Client) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
|
|
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/auth/login", req, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("login request: %w", err)
|
|
}
|
|
var out LoginResponse
|
|
if err := parseResponse(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// GetCurrentUser returns the currently authenticated principal and the
|
|
// tenant projection. Maps to GET /api/v1/auth/me; the bearer token must
|
|
// already be set on the client (use WithBearerToken).
|
|
//
|
|
// Used by `weknora auth status` and `weknora whoami`.
|
|
func (c *Client) GetCurrentUser(ctx context.Context) (*CurrentUserResponse, error) {
|
|
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/auth/me", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get current user: %w", err)
|
|
}
|
|
var out CurrentUserResponse
|
|
if err := parseResponse(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// RefreshToken renews the JWT access token using a refresh token.
|
|
// Maps to POST /api/v1/auth/refresh.
|
|
//
|
|
// Callers (`weknora auth refresh`) read the refresh token from secrets,
|
|
// invoke this method, and persist both new tokens. The SDK does not touch
|
|
// the secrets store directly — it stays a transport-only layer.
|
|
func (c *Client) RefreshToken(ctx context.Context, refreshToken string) (*RefreshTokenResponse, error) {
|
|
body := struct {
|
|
RefreshToken string `json:"refreshToken"`
|
|
}{RefreshToken: refreshToken}
|
|
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/auth/refresh", body, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("refresh token: %w", err)
|
|
}
|
|
var out RefreshTokenResponse
|
|
if err := parseResponse(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|