Allow unauthenticated optional API key requests
This commit is contained in:
@@ -222,6 +222,8 @@ Stage 2 is complete when the built-in transport sends unauthenticated requests
|
|||||||
for optional missing sources, still enforces explicit requirements, and all
|
for optional missing sources, still enforces explicit requirements, and all
|
||||||
existing provider-response behavior remains green.
|
existing provider-response behavior remains green.
|
||||||
|
|
||||||
|
**Status:** Complete.
|
||||||
|
|
||||||
## Stage 3: Align the Public Contract, Durable Documentation, and Full Validation
|
## Stage 3: Align the Public Contract, Durable Documentation, and Full Validation
|
||||||
|
|
||||||
### Objective
|
### Objective
|
||||||
|
|||||||
@@ -133,13 +133,18 @@ func (c *OpenAICompatibleClient) Generate(ctx context.Context, req domain.Genera
|
|||||||
return nil, fmt.Errorf("%w: failed to create request: %v", ErrRequestFailed, err)
|
return nil, fmt.Errorf("%w: failed to create request: %v", ErrRequestFailed, err)
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
if apiKey := strings.TrimSpace(req.Target.APIKey); apiKey != "" {
|
apiKey := strings.TrimSpace(req.Target.APIKey)
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
envName := strings.TrimSpace(req.Target.APIKeyEnv)
|
||||||
} else if envName := strings.TrimSpace(req.Target.APIKeyEnv); envName != "" {
|
if apiKey == "" && envName != "" {
|
||||||
apiKey := strings.TrimSpace(os.Getenv(envName))
|
apiKey = strings.TrimSpace(os.Getenv(envName))
|
||||||
if apiKey == "" {
|
}
|
||||||
|
if apiKey == "" && req.Target.APIKeyRequired {
|
||||||
|
if envName != "" {
|
||||||
return nil, fmt.Errorf("%w: api key environment variable %q is not set", ErrInvalidRequest, envName)
|
return nil, fmt.Errorf("%w: api key environment variable %q is not set", ErrInvalidRequest, envName)
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("%w: api key is required", ErrInvalidRequest)
|
||||||
|
}
|
||||||
|
if apiKey != "" {
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -501,36 +501,61 @@ func checkCompleteRequestAndResponseMapping(t *testing.T) {
|
|||||||
|
|
||||||
func TestOpenAICompatibleClientAuthentication(t *testing.T) {
|
func TestOpenAICompatibleClientAuthentication(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
configureEnv func(*testing.T)
|
configureEnv func(*testing.T)
|
||||||
target domain.ExecutionTarget
|
target domain.ExecutionTarget
|
||||||
wantAuth string
|
wantAuthorization string
|
||||||
wantErr error
|
wantError error
|
||||||
wantCallCount int
|
wantCallCount int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "direct key takes precedence over environment",
|
name: "direct key takes precedence over environment",
|
||||||
configureEnv: func(t *testing.T) {
|
configureEnv: func(t *testing.T) {
|
||||||
t.Setenv("PROMPTKIT_TEST_API_KEY", "env-key")
|
t.Setenv("PROMPTKIT_TEST_API_KEY", " env-key ")
|
||||||
},
|
},
|
||||||
target: domain.ExecutionTarget{
|
target: domain.ExecutionTarget{
|
||||||
APIKeyEnv: "PROMPTKIT_TEST_API_KEY",
|
APIKeyEnv: "PROMPTKIT_TEST_API_KEY",
|
||||||
APIKey: "direct-llm-key",
|
APIKey: " direct-llm-key ",
|
||||||
},
|
},
|
||||||
wantAuth: "Bearer direct-llm-key",
|
wantAuthorization: "Bearer direct-llm-key",
|
||||||
wantCallCount: 1,
|
wantCallCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "environment key supplies authorization",
|
||||||
|
configureEnv: func(t *testing.T) {
|
||||||
|
t.Setenv("PROMPTKIT_TEST_API_KEY", " env-key ")
|
||||||
|
},
|
||||||
|
target: domain.ExecutionTarget{APIKeyEnv: "PROMPTKIT_TEST_API_KEY"},
|
||||||
|
wantAuthorization: "Bearer env-key",
|
||||||
|
wantCallCount: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no key omits authorization",
|
name: "no key omits authorization",
|
||||||
wantCallCount: 1,
|
wantCallCount: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing environment key fails before transport",
|
name: "optional missing environment omits authorization",
|
||||||
configureEnv: func(t *testing.T) {
|
configureEnv: func(t *testing.T) {
|
||||||
t.Setenv("PROMPTKIT_MISSING_KEY", "")
|
t.Setenv("PROMPTKIT_MISSING_KEY", "")
|
||||||
},
|
},
|
||||||
target: domain.ExecutionTarget{APIKeyEnv: "PROMPTKIT_MISSING_KEY"},
|
target: domain.ExecutionTarget{APIKeyEnv: "PROMPTKIT_MISSING_KEY"},
|
||||||
wantErr: ErrInvalidRequest,
|
wantCallCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "required missing environment fails before transport",
|
||||||
|
configureEnv: func(t *testing.T) {
|
||||||
|
t.Setenv("PROMPTKIT_MISSING_KEY", "")
|
||||||
|
},
|
||||||
|
target: domain.ExecutionTarget{
|
||||||
|
APIKeyEnv: "PROMPTKIT_MISSING_KEY",
|
||||||
|
APIKeyRequired: true,
|
||||||
|
},
|
||||||
|
wantError: ErrInvalidRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "required target without source fails before transport",
|
||||||
|
target: domain.ExecutionTarget{APIKeyRequired: true},
|
||||||
|
wantError: ErrInvalidRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,9 +570,9 @@ func TestOpenAICompatibleClientAuthentication(t *testing.T) {
|
|||||||
request.Target = tc.target
|
request.Target = tc.target
|
||||||
|
|
||||||
_, err := client.Generate(context.Background(), request)
|
_, err := client.Generate(context.Background(), request)
|
||||||
if tc.wantErr != nil {
|
if tc.wantError != nil {
|
||||||
if !errors.Is(err, tc.wantErr) {
|
if !errors.Is(err, tc.wantError) {
|
||||||
t.Fatalf("error = %v, want %v", err, tc.wantErr)
|
t.Fatalf("error = %v, want %v", err, tc.wantError)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Fatalf("generate: %v", err)
|
t.Fatalf("generate: %v", err)
|
||||||
@@ -556,8 +581,13 @@ func TestOpenAICompatibleClientAuthentication(t *testing.T) {
|
|||||||
t.Fatalf("provider calls = %d, want %d", got, tc.wantCallCount)
|
t.Fatalf("provider calls = %d, want %d", got, tc.wantCallCount)
|
||||||
}
|
}
|
||||||
if tc.wantCallCount == 1 {
|
if tc.wantCallCount == 1 {
|
||||||
if got := provider.lastRequest(t).header.Get("Authorization"); got != tc.wantAuth {
|
values := provider.lastRequest(t).header.Values("Authorization")
|
||||||
t.Fatalf("Authorization = %q, want %q", got, tc.wantAuth)
|
if tc.wantAuthorization == "" {
|
||||||
|
if len(values) != 0 {
|
||||||
|
t.Fatalf("Authorization values = %q, want absent", values)
|
||||||
|
}
|
||||||
|
} else if len(values) != 1 || values[0] != tc.wantAuthorization {
|
||||||
|
t.Fatalf("Authorization values = %q, want [%q]", values, tc.wantAuthorization)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user