PlanOpticon

planopticon / docs / api / auth.md
1
# Auth API Reference
2
3
::: video_processor.auth
4
5
---
6
7
## Overview
8
9
The `video_processor.auth` module provides a unified OAuth and authentication strategy for all PlanOpticon source connectors. It supports multiple authentication methods tried in a consistent order:
10
11
1. **Saved token** -- load from disk, auto-refresh if expired
12
2. **Client Credentials** -- server-to-server OAuth (e.g., Zoom S2S)
13
3. **OAuth 2.0 PKCE** -- interactive Authorization Code flow with PKCE
14
4. **API key fallback** -- environment variable lookup
15
16
Tokens are persisted to `~/.planopticon/` and automatically refreshed on expiry.
17
18
---
19
20
## AuthConfig
21
22
```python
23
from video_processor.auth import AuthConfig
24
```
25
26
Dataclass configuring authentication for a specific service. Defines OAuth endpoints, client credentials, API key fallback, scopes, and token storage.
27
28
### Fields
29
30
| Field | Type | Default | Description |
31
|---|---|---|---|
32
| `service` | `str` | *required* | Service identifier (e.g., `"zoom"`, `"notion"`) |
33
| `oauth_authorize_url` | `Optional[str]` | `None` | OAuth authorization endpoint URL |
34
| `oauth_token_url` | `Optional[str]` | `None` | OAuth token exchange endpoint URL |
35
| `client_id` | `Optional[str]` | `None` | OAuth client ID (direct value) |
36
| `client_secret` | `Optional[str]` | `None` | OAuth client secret (direct value) |
37
| `client_id_env` | `Optional[str]` | `None` | Environment variable for client ID |
38
| `client_secret_env` | `Optional[str]` | `None` | Environment variable for client secret |
39
| `api_key_env` | `Optional[str]` | `None` | Environment variable for API key fallback |
40
| `scopes` | `List[str]` | `[]` | OAuth scopes to request |
41
| `redirect_uri` | `str` | `"urn:ietf:wg:oauth:2.0:oob"` | Redirect URI for auth code flow |
42
| `account_id` | `Optional[str]` | `None` | Account ID for client credentials grant (direct value) |
43
| `account_id_env` | `Optional[str]` | `None` | Environment variable for account ID |
44
| `token_path` | `Optional[Path]` | `None` | Custom token storage path |
45
46
### Resolved Properties
47
48
These properties resolve values by checking the direct field first, then falling back to the environment variable.
49
50
| Property | Return Type | Description |
51
|---|---|---|
52
| `resolved_client_id` | `Optional[str]` | Client ID from `client_id` or `os.environ[client_id_env]` |
53
| `resolved_client_secret` | `Optional[str]` | Client secret from `client_secret` or `os.environ[client_secret_env]` |
54
| `resolved_api_key` | `Optional[str]` | API key from `os.environ[api_key_env]` |
55
| `resolved_account_id` | `Optional[str]` | Account ID from `account_id` or `os.environ[account_id_env]` |
56
| `resolved_token_path` | `Path` | Token file path: `token_path` or `~/.planopticon/{service}_token.json` |
57
| `supports_oauth` | `bool` | `True` if both `oauth_authorize_url` and `oauth_token_url` are set |
58
59
```python
60
from video_processor.auth import AuthConfig
61
62
config = AuthConfig(
63
service="notion",
64
oauth_authorize_url="https://api.notion.com/v1/oauth/authorize",
65
oauth_token_url="https://api.notion.com/v1/oauth/token",
66
client_id_env="NOTION_CLIENT_ID",
67
client_secret_env="NOTION_CLIENT_SECRET",
68
api_key_env="NOTION_API_KEY",
69
scopes=["read_content"],
70
)
71
72
# Check resolved values
73
print(config.resolved_client_id) # From NOTION_CLIENT_ID env var
74
print(config.supports_oauth) # True
75
print(config.resolved_token_path) # ~/.planopticon/notion_token.json
76
```
77
78
---
79
80
## AuthResult
81
82
```python
83
from video_processor.auth import AuthResult
84
```
85
86
Dataclass representing the result of an authentication attempt.
87
88
| Field | Type | Default | Description |
89
|---|---|---|---|
90
| `success` | `bool` | *required* | Whether authentication succeeded |
91
| `access_token` | `Optional[str]` | `None` | The access token (if successful) |
92
| `method` | `Optional[str]` | `None` | Auth method used: `"saved_token"`, `"oauth_pkce"`, `"client_credentials"`, `"api_key"` |
93
| `expires_at` | `Optional[float]` | `None` | Token expiration as Unix timestamp |
94
| `refresh_token` | `Optional[str]` | `None` | OAuth refresh token (if available) |
95
| `error` | `Optional[str]` | `None` | Error message (if failed) |
96
97
```python
98
result = manager.authenticate()
99
if result.success:
100
print(f"Authenticated via {result.method}")
101
print(f"Token: {result.access_token[:20]}...")
102
if result.expires_at:
103
import time
104
remaining = result.expires_at - time.time()
105
print(f"Expires in {remaining/60:.0f} minutes")
106
else:
107
print(f"Auth failed: {result.error}")
108
```
109
110
---
111
112
## OAuthManager
113
114
```python
115
from video_processor.auth import OAuthManager
116
```
117
118
Manages the full authentication lifecycle for a service. Tries auth methods in priority order and handles token persistence, refresh, and PKCE flow.
119
120
### Constructor
121
122
```python
123
def __init__(self, config: AuthConfig)
124
```
125
126
| Parameter | Type | Description |
127
|---|---|---|
128
| `config` | `AuthConfig` | Authentication configuration for the target service |
129
130
### authenticate()
131
132
```python
133
def authenticate(self) -> AuthResult
134
```
135
136
Run the full auth chain and return the result. Methods are tried in order:
137
138
1. **Saved token** -- checks `~/.planopticon/{service}_token.json`, refreshes if expired
139
2. **Client Credentials** -- if `account_id` is set and OAuth is configured, uses the client credentials grant (server-to-server)
140
3. **OAuth PKCE** -- if OAuth is configured and client ID is available, opens a browser for interactive authorization with PKCE
141
4. **API key** -- falls back to the environment variable specified in `api_key_env`
142
143
**Returns:** `AuthResult` -- success/failure with token and method details.
144
145
If all methods fail, returns an `AuthResult` with `success=False` and a helpful error message listing which environment variables to set.
146
147
### get_token()
148
149
```python
150
def get_token(self) -> Optional[str]
151
```
152
153
Convenience method: run `authenticate()` and return just the access token string.
154
155
**Returns:** `Optional[str]` -- the access token, or `None` if authentication failed.
156
157
### clear_token()
158
159
```python
160
def clear_token(self) -> None
161
```
162
163
Remove the saved token file for this service (effectively a logout). The next `authenticate()` call will require re-authentication.
164
165
---
166
167
## Authentication Flows
168
169
### Saved Token (auto-refresh)
170
171
Tokens are saved to `~/.planopticon/{service}_token.json` as JSON. On each `authenticate()` call, the saved token is loaded and checked:
172
173
- If the token has not expired (`time.time() < expires_at`), it is returned immediately
174
- If expired but a refresh token is available, the manager attempts to refresh using the OAuth token endpoint
175
- The refreshed token is saved back to disk
176
177
### Client Credentials Grant
178
179
Used for server-to-server authentication (e.g., Zoom Server-to-Server OAuth). Requires `account_id`, `client_id`, and `client_secret`. Sends a POST to the token endpoint with `grant_type=account_credentials`.
180
181
### OAuth 2.0 Authorization Code with PKCE
182
183
Interactive flow for user authentication:
184
185
1. Generates a PKCE code verifier and S256 challenge
186
2. Constructs the authorization URL with client ID, redirect URI, scopes, and PKCE challenge
187
3. Opens the URL in the user's browser
188
4. Prompts the user to paste the authorization code
189
5. Exchanges the code for tokens at the token endpoint
190
6. Saves the tokens to disk
191
192
### API Key Fallback
193
194
If no OAuth flow succeeds, falls back to checking the environment variable specified in `api_key_env`. Returns the value directly as the access token.
195
196
---
197
198
## KNOWN_CONFIGS
199
200
```python
201
from video_processor.auth import KNOWN_CONFIGS
202
```
203
204
Pre-built `AuthConfig` instances for supported services. These cover the most common cloud integrations and can be used directly or as templates for custom configurations.
205
206
| Service Key | Service | OAuth Endpoints | Client ID Env | API Key Env |
207
|---|---|---|---|---|
208
| `"zoom"` | Zoom | `zoom.us/oauth/...` | `ZOOM_CLIENT_ID` | -- |
209
| `"notion"` | Notion | `api.notion.com/v1/oauth/...` | `NOTION_CLIENT_ID` | `NOTION_API_KEY` |
210
| `"dropbox"` | Dropbox | `dropbox.com/oauth2/...` | `DROPBOX_APP_KEY` | `DROPBOX_ACCESS_TOKEN` |
211
| `"github"` | GitHub | `github.com/login/oauth/...` | `GITHUB_CLIENT_ID` | `GITHUB_TOKEN` |
212
| `"google"` | Google | `accounts.google.com/o/oauth2/...` | `GOOGLE_CLIENT_ID` | `GOOGLE_API_KEY` |
213
| `"microsoft"` | Microsoft | `login.microsoftonline.com/.../oauth2/...` | `MICROSOFT_CLIENT_ID` | -- |
214
215
### Zoom
216
217
Supports both Server-to-Server (via `ZOOM_ACCOUNT_ID`) and OAuth PKCE flows.
218
219
```bash
220
# Server-to-Server
221
export ZOOM_CLIENT_ID="..."
222
export ZOOM_CLIENT_SECRET="..."
223
export ZOOM_ACCOUNT_ID="..."
224
225
# Or interactive OAuth (omit ZOOM_ACCOUNT_ID)
226
export ZOOM_CLIENT_ID="..."
227
export ZOOM_CLIENT_SECRET="..."
228
```
229
230
### Google (Drive, Meet, Workspace)
231
232
Supports OAuth PKCE and API key fallback. Scopes include Drive and Docs read-only access.
233
234
```bash
235
export GOOGLE_CLIENT_ID="..."
236
export GOOGLE_CLIENT_SECRET="..."
237
# Or for API-key-only access:
238
export GOOGLE_API_KEY="..."
239
```
240
241
### GitHub
242
243
Supports OAuth PKCE and personal access token. Requests `repo` and `read:org` scopes.
244
245
```bash
246
# OAuth
247
export GITHUB_CLIENT_ID="..."
248
export GITHUB_CLIENT_SECRET="..."
249
# Or personal access token
250
export GITHUB_TOKEN="ghp_..."
251
```
252
253
---
254
255
## Helper Functions
256
257
### get_auth_config()
258
259
```python
260
def get_auth_config(service: str) -> Optional[AuthConfig]
261
```
262
263
Get a pre-built `AuthConfig` for a known service.
264
265
**Parameters:**
266
267
| Parameter | Type | Description |
268
|---|---|---|
269
| `service` | `str` | Service name (e.g., `"zoom"`, `"notion"`, `"github"`) |
270
271
**Returns:** `Optional[AuthConfig]` -- the config, or `None` if the service is not in `KNOWN_CONFIGS`.
272
273
### get_auth_manager()
274
275
```python
276
def get_auth_manager(service: str) -> Optional[OAuthManager]
277
```
278
279
Get an `OAuthManager` for a known service. Convenience wrapper that looks up the config and creates the manager in one call.
280
281
**Returns:** `Optional[OAuthManager]` -- the manager, or `None` if the service is not known.
282
283
---
284
285
## Usage Examples
286
287
### Quick authentication for a known service
288
289
```python
290
from video_processor.auth import get_auth_manager
291
292
manager = get_auth_manager("zoom")
293
if manager:
294
result = manager.authenticate()
295
if result.success:
296
print(f"Authenticated via {result.method}")
297
# Use result.access_token for API calls
298
else:
299
print(f"Failed: {result.error}")
300
```
301
302
### Custom service configuration
303
304
```python
305
from video_processor.auth import AuthConfig, OAuthManager
306
307
config = AuthConfig(
308
service="my_service",
309
oauth_authorize_url="https://my-service.com/oauth/authorize",
310
oauth_token_url="https://my-service.com/oauth/token",
311
client_id_env="MY_SERVICE_CLIENT_ID",
312
client_secret_env="MY_SERVICE_CLIENT_SECRET",
313
api_key_env="MY_SERVICE_API_KEY",
314
scopes=["read", "write"],
315
)
316
317
manager = OAuthManager(config)
318
token = manager.get_token() # Returns str or None
319
```
320
321
### Using auth in a custom source connector
322
323
```python
324
from pathlib import Path
325
from typing import List, Optional
326
327
from video_processor.auth import OAuthManager, AuthConfig
328
from video_processor.sources.base import BaseSource, SourceFile
329
330
class CustomSource(BaseSource):
331
def __init__(self):
332
self._config = AuthConfig(
333
service="custom",
334
api_key_env="CUSTOM_API_KEY",
335
)
336
self._manager = OAuthManager(self._config)
337
self._token: Optional[str] = None
338
339
def authenticate(self) -> bool:
340
self._token = self._manager.get_token()
341
return self._token is not None
342
343
def list_videos(self, **kwargs) -> List[SourceFile]:
344
# Use self._token to query the API
345
...
346
347
def download(self, file: SourceFile, destination: Path) -> Path:
348
# Use self._token for authenticated downloads
349
...
350
```
351
352
### Logout / clear saved token
353
354
```python
355
from video_processor.auth import get_auth_manager
356
357
manager = get_auth_manager("zoom")
358
if manager:
359
manager.clear_token()
360
print("Zoom token cleared")
361
```
362
363
### Token storage location
364
365
All tokens are stored under `~/.planopticon/`:
366
367
```
368
~/.planopticon/
369
zoom_token.json
370
notion_token.json
371
github_token.json
372
google_token.json
373
microsoft_token.json
374
dropbox_token.json
375
```
376
377
Each file contains a JSON object with `access_token`, `refresh_token` (if applicable), `expires_at`, and client credentials for refresh.
378

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button