@startanaicompany/cli 1.4.8 → 1.4.10
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.
- package/OAUTH_FIX_COMPLETE.md +166 -0
- package/package.json +1 -1
- package/src/lib/oauth.js +24 -20
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# OAuth CLI Fix - Complete
|
|
2
|
+
|
|
3
|
+
## Status: ✅ Ready for Testing
|
|
4
|
+
|
|
5
|
+
All OAuth authentication issues have been fixed. The CLI version 1.4.9 is ready to be published to npm.
|
|
6
|
+
|
|
7
|
+
## What Was Fixed
|
|
8
|
+
|
|
9
|
+
### Backend Fixes (Deployed ✅)
|
|
10
|
+
1. **Schema Mismatch** - Fixed oauth.js to use correct table (`session_tokens`) and columns (`token_hash`)
|
|
11
|
+
2. **Email Retrieval** - Added JOIN with users table to get email field
|
|
12
|
+
3. **Deployed** - Backend changes deployed to production at commit a43192f
|
|
13
|
+
|
|
14
|
+
### CLI Fixes (Ready for Publish 📦)
|
|
15
|
+
1. **Wait Time** - Increased from 5s to 60s to give users time to authorize
|
|
16
|
+
2. **Error Handling** - Continue polling on 401/404 errors instead of failing immediately
|
|
17
|
+
3. **HTTP Headers** - Use correct header based on token type:
|
|
18
|
+
- Session tokens (`st_*`) → `X-Session-Token` header
|
|
19
|
+
- API keys (`cw_*`) → `X-API-Key` header
|
|
20
|
+
4. **Version** - Bumped to 1.4.9
|
|
21
|
+
|
|
22
|
+
## Publishing the CLI
|
|
23
|
+
|
|
24
|
+
**The CLI cannot auto-publish due to npm 2FA requirement.**
|
|
25
|
+
|
|
26
|
+
To publish manually:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd /home/milko/projects/saac-cli
|
|
30
|
+
|
|
31
|
+
# Get 2FA code from authenticator app
|
|
32
|
+
npm publish --access public --otp=<YOUR_CODE>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Testing the OAuth Flow
|
|
36
|
+
|
|
37
|
+
Once published, test with:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Install latest version
|
|
41
|
+
npm install -g @startanaicompany/cli
|
|
42
|
+
|
|
43
|
+
# Test OAuth connection
|
|
44
|
+
saac git connect git.startanaicompany.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Expected Behavior:**
|
|
48
|
+
1. ✅ CLI opens browser to OAuth page
|
|
49
|
+
2. ✅ User has 60 seconds to authorize
|
|
50
|
+
3. ✅ CLI polls using `X-Session-Token` header
|
|
51
|
+
4. ✅ Browser shows: "✅ Authorization Successful"
|
|
52
|
+
5. ✅ CLI detects completion: "Connected to git.startanaicompany.com as <username>"
|
|
53
|
+
|
|
54
|
+
## What Changed in the Code
|
|
55
|
+
|
|
56
|
+
### Backend: `/home/milko/projects/coolifywrapper/src/routes/oauth.js`
|
|
57
|
+
|
|
58
|
+
**Before (Broken):**
|
|
59
|
+
```javascript
|
|
60
|
+
const sessionResult = await db.query(
|
|
61
|
+
'SELECT user_id, email FROM sessions WHERE session_token = $1',
|
|
62
|
+
[token]
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**After (Fixed):**
|
|
67
|
+
```javascript
|
|
68
|
+
const sessionResult = await db.query(
|
|
69
|
+
`SELECT st.user_id, u.email
|
|
70
|
+
FROM session_tokens st
|
|
71
|
+
JOIN users u ON st.user_id = u.id
|
|
72
|
+
WHERE st.token_hash = $1 AND st.expires_at > NOW() AND st.revoked_at IS NULL`,
|
|
73
|
+
[crypto.createHash('sha256').update(token).digest('hex')]
|
|
74
|
+
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### CLI: `/home/milko/projects/saac-cli/src/lib/oauth.js`
|
|
78
|
+
|
|
79
|
+
**Before (Broken):**
|
|
80
|
+
```javascript
|
|
81
|
+
// Always used X-API-Key header
|
|
82
|
+
const response = await axios.get(
|
|
83
|
+
`${baseUrl}/oauth/poll/${sessionId}`,
|
|
84
|
+
{
|
|
85
|
+
headers: {
|
|
86
|
+
'X-API-Key': apiKey,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**After (Fixed):**
|
|
93
|
+
```javascript
|
|
94
|
+
// Use correct header based on token type
|
|
95
|
+
const headers = apiKey.startsWith('st_')
|
|
96
|
+
? { 'X-Session-Token': apiKey }
|
|
97
|
+
: { 'X-API-Key': apiKey };
|
|
98
|
+
|
|
99
|
+
const response = await axios.get(
|
|
100
|
+
`${baseUrl}/oauth/poll/${sessionId}`,
|
|
101
|
+
{ headers }
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Timeline of Fixes
|
|
106
|
+
|
|
107
|
+
1. **Backend schema fix** - Commit a43192f (deployed)
|
|
108
|
+
2. **CLI wait time** - Changed to 60s (version 1.4.8)
|
|
109
|
+
3. **CLI header fix** - Commit 493a512 (version 1.4.9, ready to publish)
|
|
110
|
+
|
|
111
|
+
## Architecture Notes
|
|
112
|
+
|
|
113
|
+
### Why Two Token Types?
|
|
114
|
+
|
|
115
|
+
- **Session Tokens (`st_*`)** - Short-lived (1 day), browser-based, for CLI login
|
|
116
|
+
- **API Keys (`cw_*`)** - Long-lived, for programmatic access
|
|
117
|
+
|
|
118
|
+
### Why Different Headers?
|
|
119
|
+
|
|
120
|
+
The backend authentication middleware checks headers in this priority:
|
|
121
|
+
|
|
122
|
+
1. `X-Session-Token` - For session tokens
|
|
123
|
+
2. `X-API-Key` - For API keys
|
|
124
|
+
|
|
125
|
+
If you send `st_*` tokens via `X-API-Key` header, the backend validates them as API keys and rejects them (API keys must start with `cw_`).
|
|
126
|
+
|
|
127
|
+
### How OAuth Flow Works
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
1. CLI calls: saac git connect git.startanaicompany.com
|
|
131
|
+
2. CLI generates session_id (random hex)
|
|
132
|
+
3. CLI opens browser: /oauth/authorize?git_host=...&session_id=...&token=st_...
|
|
133
|
+
4. User clicks "Authorize" in browser
|
|
134
|
+
5. Browser redirects to Gitea OAuth
|
|
135
|
+
6. User approves in Gitea
|
|
136
|
+
7. Gitea redirects to: /oauth/callback?code=...&state=...
|
|
137
|
+
8. Backend exchanges code for access token
|
|
138
|
+
9. Backend stores connection in database
|
|
139
|
+
10. Backend updates oauth_cli_sessions to 'completed'
|
|
140
|
+
11. CLI polls /oauth/poll/:session_id (with X-Session-Token header)
|
|
141
|
+
12. CLI receives status='completed' and displays success
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Troubleshooting
|
|
145
|
+
|
|
146
|
+
### If "Request failed with status code 401"
|
|
147
|
+
- Check that CLI version is 1.4.9 or higher
|
|
148
|
+
- Verify backend is deployed (commit a43192f or later)
|
|
149
|
+
|
|
150
|
+
### If "Authorization timed out"
|
|
151
|
+
- User has 5 minutes total (60s initial wait + 150 polls × 2s)
|
|
152
|
+
- Check that user completed OAuth in browser
|
|
153
|
+
|
|
154
|
+
### If browser shows success but CLI keeps polling
|
|
155
|
+
- This was the header mismatch bug - fixed in 1.4.9
|
|
156
|
+
- Ensure using latest CLI version
|
|
157
|
+
|
|
158
|
+
## Next Steps
|
|
159
|
+
|
|
160
|
+
1. **Publish CLI** - Run `npm publish --access public --otp=<code>`
|
|
161
|
+
2. **Test OAuth** - Run `saac git connect git.startanaicompany.com`
|
|
162
|
+
3. **Verify** - Browser should show success, CLI should detect it
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
**All issues resolved. Ready for production testing.**
|
package/package.json
CHANGED
package/src/lib/oauth.js
CHANGED
|
@@ -97,13 +97,14 @@ async function pollForCompletion(sessionId, apiKey) {
|
|
|
97
97
|
await sleep(pollInterval);
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
|
+
// Use correct header based on token type
|
|
101
|
+
const headers = apiKey.startsWith('st_')
|
|
102
|
+
? { 'X-Session-Token': apiKey }
|
|
103
|
+
: { 'X-API-Key': apiKey };
|
|
104
|
+
|
|
100
105
|
const response = await axios.get(
|
|
101
106
|
`${baseUrl}/oauth/poll/${sessionId}`,
|
|
102
|
-
{
|
|
103
|
-
headers: {
|
|
104
|
-
'X-API-Key': apiKey,
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
+
{ headers }
|
|
107
108
|
);
|
|
108
109
|
|
|
109
110
|
const { status, gitUsername, gitHost } = response.data;
|
|
@@ -144,13 +145,14 @@ async function pollForCompletion(sessionId, apiKey) {
|
|
|
144
145
|
*/
|
|
145
146
|
async function getConnection(gitHost, apiKey) {
|
|
146
147
|
try {
|
|
148
|
+
// Use correct header based on token type
|
|
149
|
+
const headers = apiKey.startsWith('st_')
|
|
150
|
+
? { 'X-Session-Token': apiKey }
|
|
151
|
+
: { 'X-API-Key': apiKey };
|
|
152
|
+
|
|
147
153
|
const response = await axios.get(
|
|
148
154
|
`${getApiUrl()}/users/me/oauth`,
|
|
149
|
-
{
|
|
150
|
-
headers: {
|
|
151
|
-
'X-API-Key': apiKey,
|
|
152
|
-
},
|
|
153
|
-
}
|
|
155
|
+
{ headers }
|
|
154
156
|
);
|
|
155
157
|
|
|
156
158
|
const connection = response.data.connections.find(
|
|
@@ -169,13 +171,14 @@ async function getConnection(gitHost, apiKey) {
|
|
|
169
171
|
* @returns {Promise<array>} - Array of connection objects
|
|
170
172
|
*/
|
|
171
173
|
async function listConnections(apiKey) {
|
|
174
|
+
// Use correct header based on token type
|
|
175
|
+
const headers = apiKey.startsWith('st_')
|
|
176
|
+
? { 'X-Session-Token': apiKey }
|
|
177
|
+
: { 'X-API-Key': apiKey };
|
|
178
|
+
|
|
172
179
|
const response = await axios.get(
|
|
173
180
|
`${getApiUrl()}/users/me/oauth`,
|
|
174
|
-
{
|
|
175
|
-
headers: {
|
|
176
|
-
'X-API-Key': apiKey,
|
|
177
|
-
},
|
|
178
|
-
}
|
|
181
|
+
{ headers }
|
|
179
182
|
);
|
|
180
183
|
|
|
181
184
|
return response.data.connections || [];
|
|
@@ -187,13 +190,14 @@ async function listConnections(apiKey) {
|
|
|
187
190
|
* @param {string} apiKey - User's API key
|
|
188
191
|
*/
|
|
189
192
|
async function revokeConnection(gitHost, apiKey) {
|
|
193
|
+
// Use correct header based on token type
|
|
194
|
+
const headers = apiKey.startsWith('st_')
|
|
195
|
+
? { 'X-Session-Token': apiKey }
|
|
196
|
+
: { 'X-API-Key': apiKey };
|
|
197
|
+
|
|
190
198
|
await axios.delete(
|
|
191
199
|
`${getApiUrl()}/users/me/oauth/${encodeURIComponent(gitHost)}`,
|
|
192
|
-
{
|
|
193
|
-
headers: {
|
|
194
|
-
'X-API-Key': apiKey,
|
|
195
|
-
},
|
|
196
|
-
}
|
|
200
|
+
{ headers }
|
|
197
201
|
);
|
|
198
202
|
}
|
|
199
203
|
|