@startanaicompany/cli 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,785 @@
1
+ # Session Token Authentication - Implementation Guide
2
+
3
+ **Date**: 2026-01-25
4
+ **From**: Coolify Wrapper API Team
5
+ **To**: SAAC CLI Team
6
+ **Subject**: New Session-Based Authentication System
7
+
8
+ ---
9
+
10
+ ## Executive Summary
11
+
12
+ The Coolify Wrapper API now supports **session-based authentication** with 1-year expiring tokens. This provides better security than permanent API keys while maintaining a great user experience.
13
+
14
+ **Key Changes**:
15
+ - New `/auth/login` endpoint to exchange API keys for session tokens
16
+ - Session tokens expire after 1 year (vs permanent API keys)
17
+ - Multiple concurrent sessions per user (laptop, desktop, etc.)
18
+ - Server-side revocation capability
19
+ - Backward compatible - permanent API keys still work
20
+
21
+ ---
22
+
23
+ ## Why Session Tokens?
24
+
25
+ ### Current Problem
26
+ ```javascript
27
+ // Current flow - LESS SECURE
28
+ ~/.saac/config.json:
29
+ {
30
+ "email": "user@example.com",
31
+ "apiKey": "cw_permanent_key..." // Never expires, full access forever
32
+ }
33
+ ```
34
+
35
+ **Risk**: If `~/.saac/config.json` is compromised, attacker has permanent access until user manually regenerates API key.
36
+
37
+ ### New Solution
38
+ ```javascript
39
+ // Session token flow - MORE SECURE
40
+ ~/.saac/config.json:
41
+ {
42
+ "email": "user@example.com",
43
+ "sessionToken": "st_xyz...", // Expires in 1 year
44
+ "expiresAt": "2026-01-25T...",
45
+ "verified": true
46
+ }
47
+ ```
48
+
49
+ **Benefits**:
50
+ - ✅ Limited time window (1 year vs forever)
51
+ - ✅ Server can revoke tokens remotely
52
+ - ✅ User can see all active sessions
53
+ - ✅ Matches industry standards (GitHub CLI, AWS CLI, Vercel CLI)
54
+
55
+ ---
56
+
57
+ ## New API Endpoints
58
+
59
+ ### 1. POST /api/v1/auth/login
60
+
61
+ **Purpose**: Exchange permanent API key for a session token (1-year expiry)
62
+
63
+ **Request**:
64
+ ```bash
65
+ POST https://apps.startanaicompany.com/api/v1/auth/login
66
+ Headers:
67
+ X-API-Key: cw_RJ1gH8Sd1nvmPF4lWigu2g3Nkjt1mwEJXYd2aycD0IIniNPhImE5XgWaz3Tcz
68
+ Content-Type: application/json
69
+
70
+ Body (optional):
71
+ {
72
+ "email": "ryan88@goryan.io" // For validation
73
+ }
74
+ ```
75
+
76
+ **Response (Success - 200)**:
77
+ ```json
78
+ {
79
+ "session_token": "st_abc123defghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTU",
80
+ "expires_at": "2026-01-25T12:34:56.789Z",
81
+ "user": {
82
+ "id": "550e8400-e29b-41d4-a716-446655440000",
83
+ "email": "ryan88@goryan.io",
84
+ "verified": true,
85
+ "max_applications": 50
86
+ }
87
+ }
88
+ ```
89
+
90
+ **Response (Error - 401)**:
91
+ ```json
92
+ {
93
+ "error": "Invalid API key",
94
+ "code": "UNAUTHORIZED",
95
+ "timestamp": "2026-01-25T12:34:56.789Z"
96
+ }
97
+ ```
98
+
99
+ **Token Format**:
100
+ - Prefix: `st_`
101
+ - Length: 64 characters total
102
+ - Pattern: `st_` + 61 random alphanumeric characters
103
+ - Example: `st_kgzfNByNNrtrDsAW07h6ORwTtP3POK6O98klH9Rm8jTt9ByHojeH7zDmGwaF`
104
+
105
+ **Security Notes**:
106
+ - Session token is returned in plaintext **only once**
107
+ - Server stores SHA-256 hash of the token
108
+ - Token expires exactly 1 year after creation
109
+ - Token can be revoked remotely by user or server
110
+
111
+ ---
112
+
113
+ ### 2. POST /api/v1/auth/logout
114
+
115
+ **Purpose**: Revoke the current session token
116
+
117
+ **Request**:
118
+ ```bash
119
+ POST https://apps.startanaicompany.com/api/v1/auth/logout
120
+ Headers:
121
+ X-Session-Token: st_abc123...
122
+ ```
123
+
124
+ **Response (Success - 200)**:
125
+ ```json
126
+ {
127
+ "success": true,
128
+ "message": "Session revoked successfully"
129
+ }
130
+ ```
131
+
132
+ **Use Case**: User wants to logout from current device only
133
+
134
+ ---
135
+
136
+ ### 3. POST /api/v1/auth/logout-all
137
+
138
+ **Purpose**: Revoke ALL sessions for the current user
139
+
140
+ **Request**:
141
+ ```bash
142
+ POST https://apps.startanaicompany.com/api/v1/auth/logout-all
143
+ Headers:
144
+ X-Session-Token: st_abc123...
145
+ # OR
146
+ X-API-Key: cw_permanent_key...
147
+ ```
148
+
149
+ **Response (Success - 200)**:
150
+ ```json
151
+ {
152
+ "success": true,
153
+ "sessions_revoked": 3,
154
+ "message": "3 session(s) revoked successfully"
155
+ }
156
+ ```
157
+
158
+ **Use Cases**:
159
+ - Device lost or stolen
160
+ - Security breach suspected
161
+ - User wants to force re-login on all devices
162
+
163
+ ---
164
+
165
+ ### 4. GET /api/v1/auth/sessions
166
+
167
+ **Purpose**: List all active sessions for the current user
168
+
169
+ **Request**:
170
+ ```bash
171
+ GET https://apps.startanaicompany.com/api/v1/auth/sessions
172
+ Headers:
173
+ X-Session-Token: st_abc123...
174
+ # OR
175
+ X-API-Key: cw_permanent_key...
176
+ ```
177
+
178
+ **Response (Success - 200)**:
179
+ ```json
180
+ {
181
+ "sessions": [
182
+ {
183
+ "id": "550e8400-e29b-41d4-a716-446655440000",
184
+ "expires_at": "2026-01-25T12:34:56.789Z",
185
+ "created_at": "2025-01-25T12:34:56.789Z",
186
+ "last_used_at": "2025-01-25T14:22:10.123Z",
187
+ "created_ip": "192.168.1.100",
188
+ "created_user_agent": "saac-cli/1.0.0 (Darwin; x64)"
189
+ },
190
+ {
191
+ "id": "660e9511-f39c-52e5-b827-557766551111",
192
+ "expires_at": "2026-01-20T08:15:30.456Z",
193
+ "created_at": "2025-01-20T08:15:30.456Z",
194
+ "last_used_at": "2025-01-23T09:45:22.789Z",
195
+ "created_ip": "192.168.1.105",
196
+ "created_user_agent": "saac-cli/1.0.0 (Linux; x64)"
197
+ }
198
+ ],
199
+ "total": 2
200
+ }
201
+ ```
202
+
203
+ **Use Case**: User wants to see which devices are logged in
204
+
205
+ ---
206
+
207
+ ### 5. DELETE /api/v1/auth/sessions/:sessionId
208
+
209
+ **Purpose**: Revoke a specific session by ID
210
+
211
+ **Request**:
212
+ ```bash
213
+ DELETE https://apps.startanaicompany.com/api/v1/auth/sessions/550e8400-e29b-41d4-a716-446655440000
214
+ Headers:
215
+ X-Session-Token: st_abc123...
216
+ # OR
217
+ X-API-Key: cw_permanent_key...
218
+ ```
219
+
220
+ **Response (Success - 200)**:
221
+ ```json
222
+ {
223
+ "success": true,
224
+ "message": "Session revoked successfully"
225
+ }
226
+ ```
227
+
228
+ **Use Case**: User sees unfamiliar session and wants to revoke it
229
+
230
+ ---
231
+
232
+ ## Authentication Header Priority
233
+
234
+ The middleware checks headers in this order:
235
+
236
+ 1. **X-Session-Token** (session token) - Checked first
237
+ 2. **X-API-Key** (permanent API key) - Fallback
238
+
239
+ **Examples**:
240
+
241
+ ```bash
242
+ # Option 1: Use session token (recommended for CLI users)
243
+ curl https://apps.startanaicompany.com/api/v1/users/me \
244
+ -H "X-Session-Token: st_abc123..."
245
+
246
+ # Option 2: Use permanent API key (for CI/CD, scripts)
247
+ curl https://apps.startanaicompany.com/api/v1/users/me \
248
+ -H "X-API-Key: cw_abc123..."
249
+ ```
250
+
251
+ **Note**: If both headers are provided, `X-Session-Token` takes priority.
252
+
253
+ ---
254
+
255
+ ## CLI Implementation Guide
256
+
257
+ ### Recommended Changes
258
+
259
+ #### 1. Update `src/lib/api.js`
260
+
261
+ ```javascript
262
+ const axios = require('axios');
263
+ const { getUser } = require('./config');
264
+
265
+ function createClient() {
266
+ const user = getUser();
267
+ const envApiKey = process.env.SAAC_API_KEY; // For CI/CD
268
+
269
+ const headers = {
270
+ 'Content-Type': 'application/json',
271
+ };
272
+
273
+ // Priority order:
274
+ // 1. Environment variable (for CI/CD, scripts)
275
+ // 2. Session token (for CLI users)
276
+ // 3. API key (backward compatibility)
277
+ if (envApiKey) {
278
+ headers['X-API-Key'] = envApiKey;
279
+ } else if (user?.sessionToken) {
280
+ headers['X-Session-Token'] = user.sessionToken;
281
+ } else if (user?.apiKey) {
282
+ headers['X-API-Key'] = user.apiKey;
283
+ }
284
+
285
+ return axios.create({
286
+ baseURL: 'https://apps.startanaicompany.com/api/v1',
287
+ timeout: 30000,
288
+ headers,
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Login and get session token
294
+ */
295
+ async function login(email, apiKey) {
296
+ const client = axios.create({
297
+ baseURL: 'https://apps.startanaicompany.com/api/v1',
298
+ timeout: 30000,
299
+ headers: {
300
+ 'Content-Type': 'application/json',
301
+ 'X-API-Key': apiKey, // Use API key for login
302
+ },
303
+ });
304
+
305
+ const response = await client.post('/auth/login', { email });
306
+ return response.data;
307
+ }
308
+
309
+ module.exports = {
310
+ createClient,
311
+ login,
312
+ };
313
+ ```
314
+
315
+ #### 2. Update `src/lib/config.js`
316
+
317
+ ```javascript
318
+ const Conf = require('conf');
319
+ const config = new Conf();
320
+
321
+ /**
322
+ * Save user data including session token
323
+ */
324
+ function saveUser(userData) {
325
+ config.set('user', {
326
+ email: userData.email,
327
+ userId: userData.userId,
328
+ sessionToken: userData.sessionToken, // NEW
329
+ expiresAt: userData.expiresAt, // NEW
330
+ verified: userData.verified,
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Check if user is authenticated and token is valid
336
+ */
337
+ function isAuthenticated() {
338
+ const user = getUser();
339
+
340
+ if (!user || !user.email) {
341
+ return false;
342
+ }
343
+
344
+ // Check for session token
345
+ if (user.sessionToken) {
346
+ // Check if token is expired
347
+ if (user.expiresAt) {
348
+ const expirationDate = new Date(user.expiresAt);
349
+ const now = new Date();
350
+
351
+ if (now >= expirationDate) {
352
+ return false; // Token expired
353
+ }
354
+ }
355
+ return true;
356
+ }
357
+
358
+ // Fallback: Check for API key (backward compatibility)
359
+ return !!user.apiKey;
360
+ }
361
+
362
+ /**
363
+ * Check if session token expires soon (within 7 days)
364
+ */
365
+ function isTokenExpiringSoon() {
366
+ const user = getUser();
367
+ if (!user?.expiresAt) return false;
368
+
369
+ const expirationDate = new Date(user.expiresAt);
370
+ const now = new Date();
371
+ const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
372
+
373
+ return expirationDate <= sevenDaysFromNow;
374
+ }
375
+
376
+ module.exports = {
377
+ saveUser,
378
+ getUser,
379
+ isAuthenticated,
380
+ isTokenExpiringSoon,
381
+ };
382
+ ```
383
+
384
+ #### 3. Update `src/commands/login.js`
385
+
386
+ ```javascript
387
+ const inquirer = require('inquirer');
388
+ const validator = require('validator');
389
+ const api = require('../lib/api');
390
+ const { saveUser } = require('../lib/config');
391
+ const logger = require('../lib/logger');
392
+
393
+ async function login(options) {
394
+ try {
395
+ logger.section('Login to StartAnAiCompany');
396
+
397
+ // Get credentials
398
+ let email = options.email;
399
+ let apiKey = options.apiKey;
400
+
401
+ if (!email) {
402
+ const answers = await inquirer.prompt([
403
+ {
404
+ type: 'input',
405
+ name: 'email',
406
+ message: 'Email address:',
407
+ validate: (input) => validator.isEmail(input) || 'Invalid email',
408
+ },
409
+ ]);
410
+ email = answers.email;
411
+ }
412
+
413
+ if (!apiKey) {
414
+ const answers = await inquirer.prompt([
415
+ {
416
+ type: 'password',
417
+ name: 'apiKey',
418
+ message: 'API Key:',
419
+ mask: '*',
420
+ },
421
+ ]);
422
+ apiKey = answers.apiKey;
423
+ }
424
+
425
+ const spin = logger.spinner('Logging in...').start();
426
+
427
+ try {
428
+ // Call new /auth/login endpoint
429
+ const result = await api.login(email, apiKey);
430
+
431
+ spin.succeed('Login successful!');
432
+
433
+ // Save session token and expiration
434
+ saveUser({
435
+ email: result.user.email || email,
436
+ userId: result.user.id,
437
+ sessionToken: result.session_token, // NEW: Store session token
438
+ expiresAt: result.expires_at, // NEW: Store expiration
439
+ verified: result.user.verified,
440
+ });
441
+
442
+ logger.newline();
443
+ logger.success('You are now logged in!');
444
+ logger.newline();
445
+ logger.field('Email', email);
446
+ logger.field('Verified', result.user.verified ? 'Yes' : 'No');
447
+
448
+ // Show expiration date
449
+ if (result.expires_at) {
450
+ const expirationDate = new Date(result.expires_at);
451
+ logger.field('Session expires', expirationDate.toLocaleDateString());
452
+ }
453
+
454
+ } catch (error) {
455
+ spin.fail('Login failed');
456
+ throw error;
457
+ }
458
+ } catch (error) {
459
+ logger.error(error.response?.data?.message || error.message);
460
+ process.exit(1);
461
+ }
462
+ }
463
+
464
+ module.exports = login;
465
+ ```
466
+
467
+ #### 4. Add New Command: `src/commands/logout.js`
468
+
469
+ ```javascript
470
+ const api = require('../lib/api');
471
+ const { clearUser } = require('../lib/config');
472
+ const logger = require('../lib/logger');
473
+
474
+ async function logout() {
475
+ try {
476
+ logger.section('Logout from StartAnAiCompany');
477
+
478
+ const spin = logger.spinner('Logging out...').start();
479
+
480
+ try {
481
+ // Revoke current session on server
482
+ const client = api.createClient();
483
+ await client.post('/auth/logout');
484
+
485
+ // Clear local config
486
+ clearUser();
487
+
488
+ spin.succeed('Logout successful!');
489
+ logger.success('You have been logged out.');
490
+
491
+ } catch (error) {
492
+ // Even if server call fails, clear local config
493
+ clearUser();
494
+ spin.warn('Logged out locally (server error)');
495
+ }
496
+ } catch (error) {
497
+ logger.error(error.message);
498
+ process.exit(1);
499
+ }
500
+ }
501
+
502
+ module.exports = logout;
503
+ ```
504
+
505
+ ---
506
+
507
+ ## Migration Strategy
508
+
509
+ ### Phase 1: Backward Compatible (Recommended)
510
+
511
+ **Support both authentication methods** during transition:
512
+
513
+ ```javascript
514
+ // CLI automatically uses the best available authentication:
515
+ // 1. SAAC_API_KEY env var (for CI/CD)
516
+ // 2. Session token (for CLI users)
517
+ // 3. API key (legacy, still works)
518
+
519
+ createClient() {
520
+ if (process.env.SAAC_API_KEY) {
521
+ return { 'X-API-Key': process.env.SAAC_API_KEY };
522
+ }
523
+ if (user.sessionToken) {
524
+ return { 'X-Session-Token': user.sessionToken };
525
+ }
526
+ if (user.apiKey) {
527
+ return { 'X-API-Key': user.apiKey };
528
+ }
529
+ }
530
+ ```
531
+
532
+ **Benefits**:
533
+ - ✅ No breaking changes
534
+ - ✅ Users can upgrade CLI whenever convenient
535
+ - ✅ CI/CD pipelines continue working
536
+ - ✅ Gradual migration
537
+
538
+ ### Phase 2: Encourage Session Tokens
539
+
540
+ **Show warnings for API key usage**:
541
+
542
+ ```javascript
543
+ if (user.apiKey && !user.sessionToken) {
544
+ logger.warn('You are using a permanent API key.');
545
+ logger.warn('Run `saac login` to get a session token (more secure).');
546
+ }
547
+ ```
548
+
549
+ ### Phase 3: Optional - Force Migration
550
+
551
+ **After 6-12 months**, optionally deprecate permanent API keys for CLI usage:
552
+
553
+ ```javascript
554
+ if (user.apiKey && !user.sessionToken) {
555
+ logger.error('Permanent API keys are deprecated for CLI usage.');
556
+ logger.error('Please run `saac login` to get a session token.');
557
+ process.exit(1);
558
+ }
559
+ ```
560
+
561
+ **Note**: Keep permanent API keys working for CI/CD via environment variable!
562
+
563
+ ---
564
+
565
+ ## Testing the Implementation
566
+
567
+ ### Test 1: Login Flow
568
+
569
+ ```bash
570
+ # Clear existing config
571
+ rm -rf ~/.saac
572
+
573
+ # Login with API key
574
+ saac login -e ryan88@goryan.io -k cw_RJ1gH8Sd1nvmPF4lWigu2g3Nkjt1mwEJXYd2aycD0IIniNPhImE5XgWaz3Tcz
575
+
576
+ # Verify session token saved
577
+ cat ~/.saac/config.json | jq
578
+ # Should show sessionToken and expiresAt
579
+ ```
580
+
581
+ ### Test 2: Authenticated Request
582
+
583
+ ```bash
584
+ # Get user info (should use session token)
585
+ saac whoami
586
+
587
+ # Verify it works
588
+ ```
589
+
590
+ ### Test 3: Token Expiration
591
+
592
+ ```bash
593
+ # Manually set expired token in config
594
+ # Edit ~/.saac/config.json:
595
+ {
596
+ "sessionToken": "st_abc123...",
597
+ "expiresAt": "2020-01-01T00:00:00.000Z" // Past date
598
+ }
599
+
600
+ # Try authenticated request
601
+ saac whoami
602
+ # Should prompt to login again
603
+ ```
604
+
605
+ ### Test 4: Multiple Sessions
606
+
607
+ ```bash
608
+ # Login on device 1
609
+ saac login -e user@example.com -k cw_key1
610
+
611
+ # Login on device 2
612
+ saac login -e user@example.com -k cw_key1
613
+
614
+ # List sessions
615
+ saac sessions
616
+ # Should show 2 active sessions
617
+ ```
618
+
619
+ ### Test 5: Logout
620
+
621
+ ```bash
622
+ # Logout from current session
623
+ saac logout
624
+
625
+ # Try authenticated request
626
+ saac whoami
627
+ # Should prompt to login
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Environment Variable Support (CI/CD)
633
+
634
+ For CI/CD pipelines and scripts, **permanent API keys via environment variable still work**:
635
+
636
+ ```bash
637
+ # Set API key as environment variable
638
+ export SAAC_API_KEY="cw_RJ1gH8Sd1nvmPF4lWigu2g3Nkjt1mwEJXYd2aycD0IIniNPhImE5XgWaz3Tcz"
639
+
640
+ # Run CLI commands (no login required)
641
+ saac create myapp
642
+ saac deploy myapp
643
+ saac logs myapp
644
+ ```
645
+
646
+ **This approach is recommended for**:
647
+ - GitHub Actions
648
+ - GitLab CI
649
+ - Jenkins
650
+ - Docker containers
651
+ - Automated scripts
652
+
653
+ ---
654
+
655
+ ## Security Best Practices
656
+
657
+ ### For CLI Users
658
+
659
+ 1. ✅ **Use session tokens** (via `saac login`)
660
+ 2. ✅ **Don't commit** `~/.saac/config.json` to git
661
+ 3. ✅ **Set file permissions**: `chmod 600 ~/.saac/config.json`
662
+ 4. ✅ **Revoke sessions** when changing devices
663
+ 5. ✅ **Use `saac logout-all`** if device is lost
664
+
665
+ ### For CI/CD
666
+
667
+ 1. ✅ **Use environment variables** for API keys
668
+ 2. ✅ **Use secrets management** (GitHub Secrets, GitLab Variables)
669
+ 3. ✅ **Never log API keys** in CI output
670
+ 4. ✅ **Rotate keys periodically**
671
+
672
+ ---
673
+
674
+ ## Timeline
675
+
676
+ - ✅ **2026-01-25**: Session token system deployed to production
677
+ - ⏳ **Next Week**: CLI team implements session token support
678
+ - ⏳ **Next Month**: Gradual user migration to session tokens
679
+ - ⏳ **6-12 Months**: Consider deprecating API keys for CLI (keep for CI/CD)
680
+
681
+ ---
682
+
683
+ ## FAQ
684
+
685
+ ### Q: Do existing API keys still work?
686
+
687
+ **A**: Yes! Permanent API keys (`cw_...`) continue to work exactly as before. The server accepts both `X-API-Key` and `X-Session-Token` headers.
688
+
689
+ ### Q: Can I have multiple active sessions?
690
+
691
+ **A**: Yes! Each device can have its own session token. Great for users with multiple computers.
692
+
693
+ ### Q: What happens when a session token expires?
694
+
695
+ **A**: The CLI will receive a 401 error. You should detect this and prompt the user to login again via `saac login`.
696
+
697
+ ### Q: Can I revoke a session remotely?
698
+
699
+ **A**: Yes! Use `POST /auth/logout-all` to revoke all sessions, or `DELETE /auth/sessions/:id` to revoke a specific session.
700
+
701
+ ### Q: How do I rotate my permanent API key?
702
+
703
+ **A**: Use the existing `POST /users/regenerate-key` endpoint. This doesn't affect session tokens (they remain valid until expiry).
704
+
705
+ ### Q: Are session tokens secure?
706
+
707
+ **A**: Yes! They are:
708
+ - 64 characters of cryptographically random data
709
+ - Hashed with SHA-256 before storage
710
+ - Transmitted over HTTPS only
711
+ - Automatically expire after 1 year
712
+ - Revocable server-side
713
+
714
+ ---
715
+
716
+ ## Support
717
+
718
+ If you have questions about implementing session tokens in the CLI:
719
+
720
+ 1. Check this documentation
721
+ 2. Test the endpoints manually with `curl`
722
+ 3. Contact the Coolify Wrapper API team
723
+ 4. Review the implementation in `/home/milko/projects/coolifywrapper/src/`
724
+
725
+ ---
726
+
727
+ ## Appendix: Complete Example
728
+
729
+ Here's a complete example of the login flow:
730
+
731
+ ```bash
732
+ # 1. User runs login command
733
+ $ saac login -e ryan88@goryan.io -k cw_RJ1gH8Sd1nvmPF4lWigu2g3Nkjt1mwEJXYd2aycD0IIniNPhImE5XgWaz3Tcz
734
+
735
+ # 2. CLI calls API
736
+ POST https://apps.startanaicompany.com/api/v1/auth/login
737
+ Headers: X-API-Key: cw_RJ1gH8Sd1nvmPF4lWigu2g3Nkjt1mwEJXYd2aycD0IIniNPhImE5XgWaz3Tcz
738
+
739
+ # 3. Server responds
740
+ {
741
+ "session_token": "st_kgzfNByNNrtrDsAW07h6ORwTtP3POK6O98klH9Rm8jTt9ByHojeH7zDmGwaF",
742
+ "expires_at": "2026-01-25T12:34:56.789Z",
743
+ "user": {
744
+ "id": "550e8400-e29b-41d4-a716-446655440000",
745
+ "email": "ryan88@goryan.io",
746
+ "verified": true,
747
+ "max_applications": 50
748
+ }
749
+ }
750
+
751
+ # 4. CLI saves to ~/.saac/config.json
752
+ {
753
+ "user": {
754
+ "email": "ryan88@goryan.io",
755
+ "userId": "550e8400-e29b-41d4-a716-446655440000",
756
+ "sessionToken": "st_kgzfNByNNrtrDsAW07h6ORwTtP3POK6O98klH9Rm8jTt9ByHojeH7zDmGwaF",
757
+ "expiresAt": "2026-01-25T12:34:56.789Z",
758
+ "verified": true
759
+ }
760
+ }
761
+
762
+ # 5. User runs other commands
763
+ $ saac whoami
764
+
765
+ # 6. CLI uses session token
766
+ GET https://apps.startanaicompany.com/api/v1/users/me
767
+ Headers: X-Session-Token: st_kgzfNByNNrtrDsAW07h6ORwTtP3POK6O98klH9Rm8jTt9ByHojeH7zDmGwaF
768
+
769
+ # 7. Server validates and responds
770
+ {
771
+ "id": "550e8400-e29b-41d4-a716-446655440000",
772
+ "email": "ryan88@goryan.io",
773
+ "gitea_username": "ryan",
774
+ "created_at": "2025-01-20T08:15:30.456Z",
775
+ "application_count": 5,
776
+ "max_applications": 50,
777
+ "email_verified": true
778
+ }
779
+ ```
780
+
781
+ ---
782
+
783
+ **End of Report**
784
+
785
+ Good luck with the implementation! The session token system is live and ready to use. 🚀