Skip to content

Commit 2beda44

Browse files
Merge pull request #3 from BlackyDrum/main
Add static api authentication with bearer token
2 parents 8d8691f + 8cebafd commit 2beda44

File tree

8 files changed

+132
-19
lines changed

8 files changed

+132
-19
lines changed

.github/workflows/test.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ jobs:
1515
name: Tests on PHP ${{ matrix.php }} - ${{ matrix.dependency-version }}
1616

1717
services:
18-
chroma:
18+
chroma-wo-auth:
1919
image: chromadb/chroma
2020
ports:
2121
- 8000:8000
2222

23+
chroma-w-auth:
24+
image: chromadb/chroma
25+
ports:
26+
- 8001:8000
27+
env:
28+
CHROMA_SERVER_AUTH_CREDENTIALS: 'test-token'
29+
CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER: 'chromadb.auth.token.TokenConfigServerAuthCredentialsProvider'
30+
CHROMA_SERVER_AUTH_PROVIDER: 'chromadb.auth.token.TokenAuthServerProvider'
31+
2332
steps:
2433
- name: Checkout
2534
uses: actions/checkout@v3

README.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ composer require codewithkyrian/chromadb-php
136136
```php
137137
use Codewithkyrian\ChromaDB\ChromaDB;
138138

139-
$chromaDB = ChromaDB::client();
139+
$chroma = ChromaDB::client();
140140

141141
```
142142

@@ -147,7 +147,7 @@ factory method:
147147
```php
148148
use Codewithkyrian\ChromaDB\ChromaDB;
149149

150-
$chromaDB = ChromaDB::factory()
150+
$chroma = ChromaDB::factory()
151151
->withHost('http://localhost')
152152
->withPort(8000)
153153
->withDatabase('new_database')
@@ -157,6 +157,47 @@ $chromaDB = ChromaDB::factory()
157157

158158
If the tenant or database doesn't exist, the package will automatically create them for you.
159159

160+
### Authentication
161+
162+
ChromaDB supports static token-based authentication. To use it, you need to start the Chroma server passing the required
163+
environment variables as stated in the documentation. If you're using the docker image, you can pass in the environment
164+
variables using the `--env` flag or by using a `.env` file and for the docker-compose file, you can use the `env_file`
165+
option, or pass in the environment variables directly like so:
166+
167+
```yaml
168+
version: '3.9'
169+
170+
services:
171+
chroma:
172+
image: 'chromadb/chroma'
173+
ports:
174+
- '8000:8000'
175+
environment:
176+
- CHROMA_SERVER_AUTH_CREDENTIALS=test-token
177+
- CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.token.TokenConfigServerAuthCredentialsProvider
178+
- CHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.token.TokenAuthServerProvider
179+
180+
...
181+
```
182+
183+
You can then connect to ChromaDB using the factory method:
184+
185+
```php
186+
use Codewithkyrian\ChromaDB\ChromaDB;
187+
188+
$chroma = ChromaDB::factory()
189+
->withAuthToken('test-token')
190+
->connect();
191+
```
192+
193+
### Getting the version
194+
195+
```php
196+
197+
echo $chroma->version(); // 0.4.0
198+
199+
```
200+
160201
### Creating a Collection
161202

162203
Creating a collection is as simple as calling the `createCollection` method on the client and passing in the name of

docker-compose.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
version: '3.9'
22

33
services:
4-
server:
4+
chroma_wo_auth:
55
image: 'chromadb/chroma'
66
ports:
77
- '8000:8000'
8-
volumes:
9-
- chroma-data:/chroma/chroma
108

11-
volumes:
12-
chroma-data:
13-
driver: local
9+
chroma_w_auth:
10+
image: 'chromadb/chroma'
11+
ports:
12+
- '8001:8000'
13+
environment:
14+
CHROMA_SERVER_AUTH_CREDENTIALS: 'test-token'
15+
CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER: 'chromadb.auth.token.TokenConfigServerAuthCredentialsProvider'
16+
CHROMA_SERVER_AUTH_PROVIDER: 'chromadb.auth.token.TokenAuthServerProvider'

src/Factory.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ class Factory
3333
*/
3434
protected string $tenant = 'default_tenant';
3535

36+
/**
37+
* The bearer token used for authentication.
38+
*/
39+
protected string $authToken;
40+
3641
/**
3742
* The http client to use for the requests.
3843
*/
3944
protected \GuzzleHttp\Client $httpClient;
4045

41-
4246
/**
4347
* The ChromaDB api provider for the instance.
4448
*/
@@ -80,6 +84,15 @@ public function withTenant(string $tenant): self
8084
return $this;
8185
}
8286

87+
/**
88+
* The bearer token used to authenticate requests.
89+
*/
90+
public function withAuthToken(string $authToken): self
91+
{
92+
$this->authToken = $authToken;
93+
return $this;
94+
}
95+
8396
/**
8497
* The http client to use for the requests.
8598
*/
@@ -100,14 +113,20 @@ public function createApiClient() : ChromaApiClient
100113
{
101114
$this->baseUrl = $this->host . ':' . $this->port;
102115

103-
$this->httpClient ??= new \GuzzleHttp\Client([
116+
$headers = [
117+
'Content-Type' => 'application/json',
118+
'Accept' => 'application/json',
119+
];
120+
121+
if (!empty($this->authToken)) {
122+
$headers['Authorization'] = 'Bearer ' . $this->authToken;
123+
}
124+
125+
$this->httpClient ??= new \GuzzleHttp\Client([
104126
'base_uri' => $this->baseUrl,
105-
'headers' => [
106-
'Content-Type' => 'application/json',
107-
'Accept' => 'application/json'
108-
],
127+
'headers' => $headers,
109128
]);
110129

111130
return new ChromaApiClient($this->httpClient);
112131
}
113-
}
132+
}

src/Generated/ChromaApiClient.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public function reset(): bool
318318

319319
private function handleChromaApiException(\Exception|ClientExceptionInterface $e): void
320320
{
321-
if ($e instanceof ServerException) {
321+
if ($e instanceof ClientExceptionInterface) {
322322
$errorString = $e->getResponse()->getBody()->getContents();
323323

324324
if (preg_match('/(?<={"\"error\"\:\")([^"]*)/', $errorString, $matches)) {
@@ -357,6 +357,11 @@ private function handleChromaApiException(\Exception|ClientExceptionInterface $e
357357
ChromaException::throwSpecific($message, $error_type, $e->getCode());
358358
}
359359

360+
// If the structure is {'error': 'Error Type', 'message' : 'Error message'}
361+
if (isset($error['error']) && isset($error['message'])) {
362+
ChromaException::throwSpecific($error['message'], $error['error'], $e->getCode());
363+
}
364+
360365
// If the structure is 'error' => 'Collection not found'
361366
if (isset($error['error'])) {
362367
$message = $error['error'];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
namespace Codewithkyrian\ChromaDB\Generated\Exceptions;
7+
8+
class ChromaAuthorizationException extends ChromaException
9+
{
10+
11+
}

src/Generated/Exceptions/ChromaException.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static function throwSpecific(string $message, string $type, int $code)
1212
{
1313
throw match ($type) {
1414
'NotFoundError' => new ChromaNotFoundException($message, $code),
15+
'AuthorizationError' => new ChromaAuthorizationException($message, $code),
1516
'ValueError' => new ChromaValueException($message, $code),
1617
'UniqueConstraintError' => new ChromaUniqueConstraintException($message, $code),
1718
'DimensionalityError' => new ChromaDimensionalityException($message, $code),
@@ -25,6 +26,7 @@ public static function inferTypeFromMessage(string $message): string
2526
{
2627
return match (true) {
2728
str_contains($message, 'NotFoundError') => 'NotFoundError',
29+
str_contains($message, 'AuthorizationError') => 'AuthorizationError',
2830
str_contains($message, 'UniqueConstraintError') => 'UniqueConstraintError',
2931
str_contains($message, 'ValueError') => 'ValueError',
3032
str_contains($message, 'dimensionality') => 'DimensionalityError',

tests/ChromaDB.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,41 @@
44

55
use Codewithkyrian\ChromaDB\Client;
66
use Codewithkyrian\ChromaDB\ChromaDB;
7+
use Codewithkyrian\ChromaDB\Generated\Exceptions\ChromaAuthorizationException;
78

8-
it('can create a client instance', function () {
9+
it('can connect to a normal chroma server', function () {
910
$client = ChromaDB::client();
1011

1112
expect($client)->toBeInstanceOf(Client::class);
1213
});
1314

14-
it('can create a client instance via factory', function () {
15+
it('can connect to a chroma server using factory', function () {
1516
$client = ChromaDB::factory()
1617
->withHost('http://localhost')
1718
->withPort(8000)
1819
->connect();
1920

2021
expect($client)->toBeInstanceOf(Client::class);
2122
});
23+
24+
test('can connect to an API token authenticated chroma server', function () {
25+
$client = ChromaDB::factory()
26+
->withPort(8001)
27+
->withAuthToken('test-token')
28+
->connect();
29+
30+
expect($client)->toBeInstanceOf(Client::class);
31+
});
32+
33+
it('cannot connect to an API token authenticated chroma server with wrong token', function () {
34+
ChromaDB::factory()
35+
->withPort(8001)
36+
->withAuthToken('wrong-token')
37+
->connect();
38+
})->throws(ChromaAuthorizationException::class);
39+
40+
it('throws exception when connecting to API token authenticated chroma server with no token', function () {
41+
ChromaDB::factory()
42+
->withPort(8001)
43+
->connect();
44+
})->throws(ChromaAuthorizationException::class);

0 commit comments

Comments
 (0)