File size: 6,810 Bytes
bf9e111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# @sdk/access SDK Reference

The `github.com/router-for-me/CLIProxyAPI/v6/sdk/access` package centralizes inbound request authentication for the proxy. It offers a lightweight manager that chains credential providers, so servers can reuse the same access control logic inside or outside the CLI runtime.

## Importing

```go
import (
    sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
    "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
)
```

Add the module with `go get github.com/router-for-me/CLIProxyAPI/v6/sdk/access`.

## Manager Lifecycle

```go
manager := sdkaccess.NewManager()
providers, err := sdkaccess.BuildProviders(cfg)
if err != nil {
    return err
}
manager.SetProviders(providers)
```

* `NewManager` constructs an empty manager.
* `SetProviders` replaces the provider slice using a defensive copy.
* `Providers` retrieves a snapshot that can be iterated safely from other goroutines.
* `BuildProviders` translates `config.Config` access declarations into runnable providers. When the config omits explicit providers but defines inline API keys, the helper auto-installs the built-in `config-api-key` provider.

## Authenticating Requests

```go
result, err := manager.Authenticate(ctx, req)
switch {
case err == nil:
    // Authentication succeeded; result describes the provider and principal.
case errors.Is(err, sdkaccess.ErrNoCredentials):
    // No recognizable credentials were supplied.
case errors.Is(err, sdkaccess.ErrInvalidCredential):
    // Supplied credentials were present but rejected.
default:
    // Transport-level failure was returned by a provider.
}
```

`Manager.Authenticate` walks the configured providers in order. It returns on the first success, skips providers that surface `ErrNotHandled`, and tracks whether any provider reported `ErrNoCredentials` or `ErrInvalidCredential` for downstream error reporting.

If the manager itself is `nil` or no providers are registered, the call returns `nil, nil`, allowing callers to treat access control as disabled without branching on errors.

Each `Result` includes the provider identifier, the resolved principal, and optional metadata (for example, which header carried the credential).

## Configuration Layout

The manager expects access providers under the `auth.providers` key inside `config.yaml`:

```yaml
auth:
  providers:
    - name: inline-api
      type: config-api-key
      api-keys:
        - sk-test-123
        - sk-prod-456
```

Fields map directly to `config.AccessProvider`: `name` labels the provider, `type` selects the registered factory, `sdk` can name an external module, `api-keys` seeds inline credentials, and `config` passes provider-specific options.

### Loading providers from external SDK modules

To consume a provider shipped in another Go module, point the `sdk` field at the module path and import it for its registration side effect:

```yaml
auth:
  providers:
    - name: partner-auth
      type: partner-token
      sdk: github.com/acme/xplatform/sdk/access/providers/partner
      config:
        region: us-west-2
        audience: cli-proxy
```

```go
import (
    _ "github.com/acme/xplatform/sdk/access/providers/partner" // registers partner-token
    sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
)
```

The blank identifier import ensures `init` runs so `sdkaccess.RegisterProvider` executes before `BuildProviders` is called.

## Built-in Providers

The SDK ships with one provider out of the box:

- `config-api-key`: Validates API keys declared inline or under top-level `api-keys`. It accepts the key from `Authorization: Bearer`, `X-Goog-Api-Key`, `X-Api-Key`, or the `?key=` query string and reports `ErrInvalidCredential` when no match is found.

Additional providers can be delivered by third-party packages. When a provider package is imported, it registers itself with `sdkaccess.RegisterProvider`.

### Metadata and auditing

`Result.Metadata` carries provider-specific context. The built-in `config-api-key` provider, for example, stores the credential source (`authorization`, `x-goog-api-key`, `x-api-key`, or `query-key`). Populate this map in custom providers to enrich logs and downstream auditing.

## Writing Custom Providers

```go
type customProvider struct{}

func (p *customProvider) Identifier() string { return "my-provider" }

func (p *customProvider) Authenticate(ctx context.Context, r *http.Request) (*sdkaccess.Result, error) {
    token := r.Header.Get("X-Custom")
    if token == "" {
        return nil, sdkaccess.ErrNoCredentials
    }
    if token != "expected" {
        return nil, sdkaccess.ErrInvalidCredential
    }
    return &sdkaccess.Result{
        Provider:  p.Identifier(),
        Principal: "service-user",
        Metadata:  map[string]string{"source": "x-custom"},
    }, nil
}

func init() {
    sdkaccess.RegisterProvider("custom", func(cfg *config.AccessProvider, root *config.Config) (sdkaccess.Provider, error) {
        return &customProvider{}, nil
    })
}
```

A provider must implement `Identifier()` and `Authenticate()`. To expose it to configuration, call `RegisterProvider` inside `init`. Provider factories receive the specific `AccessProvider` block plus the full root configuration for contextual needs.

## Error Semantics

- `ErrNoCredentials`: no credentials were present or recognized by any provider.
- `ErrInvalidCredential`: at least one provider processed the credentials but rejected them.
- `ErrNotHandled`: instructs the manager to fall through to the next provider without affecting aggregate error reporting.

Return custom errors to surface transport failures; they propagate immediately to the caller instead of being masked.

## Integration with cliproxy Service

`sdk/cliproxy` wires `@sdk/access` automatically when you build a CLI service via `cliproxy.NewBuilder`. Supplying a preconfigured manager allows you to extend or override the default providers:

```go
coreCfg, _ := config.LoadConfig("config.yaml")
providers, _ := sdkaccess.BuildProviders(coreCfg)
manager := sdkaccess.NewManager()
manager.SetProviders(providers)

svc, _ := cliproxy.NewBuilder().
  WithConfig(coreCfg).
  WithAccessManager(manager).
  Build()
```

The service reuses the manager for every inbound request, ensuring consistent authentication across embedded deployments and the canonical CLI binary.

### Hot reloading providers

When configuration changes, rebuild providers and swap them into the manager:

```go
providers, err := sdkaccess.BuildProviders(newCfg)
if err != nil {
    log.Errorf("reload auth providers failed: %v", err)
    return
}
accessManager.SetProviders(providers)
```

This mirrors the behaviour in `cliproxy.Service.refreshAccessProviders` and `api.Server.applyAccessConfig`, enabling runtime updates without restarting the process.