sheetlink 0.1.3 → 0.1.5
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/package.json +1 -1
- package/src/commands/auth.js +43 -41
package/package.json
CHANGED
package/src/commands/auth.js
CHANGED
|
@@ -18,9 +18,10 @@ import { randomBytes } from 'crypto';
|
|
|
18
18
|
import { writeConfig, getApiUrl } from '../config.js';
|
|
19
19
|
import { getTierStatus, listItems } from '../api.js';
|
|
20
20
|
|
|
21
|
-
const GOOGLE_CLIENT_ID = '967710910027-
|
|
21
|
+
const GOOGLE_CLIENT_ID = '967710910027-j88nejbs5rnjb5b4801er8sffkv4crdb.apps.googleusercontent.com';
|
|
22
|
+
const GOOGLE_CLIENT_SECRET = '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';
|
|
22
23
|
const REDIRECT_PORT = 9876;
|
|
23
|
-
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}
|
|
24
|
+
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`;
|
|
24
25
|
|
|
25
26
|
export async function cmdAuth(options) {
|
|
26
27
|
// --- API key path ---
|
|
@@ -69,60 +70,38 @@ export async function cmdAuth(options) {
|
|
|
69
70
|
async function googleOAuthFlow() {
|
|
70
71
|
return new Promise((resolve, reject) => {
|
|
71
72
|
const state = randomBytes(16).toString('hex');
|
|
72
|
-
const nonce = randomBytes(16).toString('hex');
|
|
73
73
|
|
|
74
|
+
// Authorization code flow (Desktop app client)
|
|
74
75
|
const params = new URLSearchParams({
|
|
75
76
|
client_id: GOOGLE_CLIENT_ID,
|
|
76
77
|
redirect_uri: REDIRECT_URI,
|
|
77
|
-
response_type: '
|
|
78
|
+
response_type: 'code',
|
|
78
79
|
scope: 'openid email profile',
|
|
79
80
|
state,
|
|
80
|
-
|
|
81
|
+
access_type: 'online',
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
84
85
|
|
|
85
|
-
// Start local server to catch the redirect
|
|
86
|
+
// Start local server to catch the authorization code redirect
|
|
86
87
|
const server = http.createServer((req, res) => {
|
|
87
88
|
const url = new URL(req.url, `http://localhost:${REDIRECT_PORT}`);
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
if (url.pathname === '/' || url.pathname === '/callback') {
|
|
91
|
+
const code = url.searchParams.get('code');
|
|
92
|
+
const returnedState = url.searchParams.get('state');
|
|
93
|
+
const error = url.searchParams.get('error');
|
|
94
|
+
|
|
92
95
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
93
|
-
res.end(
|
|
94
|
-
|
|
95
|
-
<body>
|
|
96
|
-
<script>
|
|
97
|
-
const params = new URLSearchParams(location.hash.slice(1));
|
|
98
|
-
fetch('/token', {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
headers: {'Content-Type': 'application/json'},
|
|
101
|
-
body: JSON.stringify({ id_token: params.get('id_token'), state: params.get('state') })
|
|
102
|
-
}).then(() => {
|
|
103
|
-
document.body.innerHTML = '<p>Authenticated! You can close this tab.</p>';
|
|
104
|
-
});
|
|
105
|
-
</script>
|
|
106
|
-
</body>
|
|
107
|
-
</html>`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
96
|
+
res.end('<!DOCTYPE html><html><body><p>Authenticated! You can close this tab.</p></body></html>');
|
|
97
|
+
server.close();
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
server.close();
|
|
118
|
-
try {
|
|
119
|
-
const { id_token, state: returnedState } = JSON.parse(body);
|
|
120
|
-
if (returnedState !== state) return reject(new Error('State mismatch — possible CSRF'));
|
|
121
|
-
resolve(id_token);
|
|
122
|
-
} catch (e) {
|
|
123
|
-
reject(e);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
99
|
+
if (error) return reject(new Error(`OAuth error: ${error}`));
|
|
100
|
+
if (returnedState !== state) return reject(new Error('State mismatch — possible CSRF'));
|
|
101
|
+
if (!code) return reject(new Error('No authorization code received'));
|
|
102
|
+
|
|
103
|
+
// Exchange code for id_token via token endpoint
|
|
104
|
+
exchangeCodeForIdToken(code).then(resolve).catch(reject);
|
|
126
105
|
return;
|
|
127
106
|
}
|
|
128
107
|
|
|
@@ -144,6 +123,29 @@ async function googleOAuthFlow() {
|
|
|
144
123
|
});
|
|
145
124
|
}
|
|
146
125
|
|
|
126
|
+
async function exchangeCodeForIdToken(code) {
|
|
127
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
130
|
+
body: new URLSearchParams({
|
|
131
|
+
code,
|
|
132
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
133
|
+
client_secret: GOOGLE_CLIENT_SECRET,
|
|
134
|
+
redirect_uri: REDIRECT_URI,
|
|
135
|
+
grant_type: 'authorization_code',
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
const err = await res.json().catch(() => ({}));
|
|
141
|
+
throw new Error(`Token exchange failed: ${err.error_description || err.error || res.statusText}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const data = await res.json();
|
|
145
|
+
if (!data.id_token) throw new Error('No id_token in token response');
|
|
146
|
+
return data.id_token;
|
|
147
|
+
}
|
|
148
|
+
|
|
147
149
|
async function exchangeGoogleToken(idToken) {
|
|
148
150
|
const res = await fetch(`${getApiUrl()}/auth/login`, {
|
|
149
151
|
method: 'POST',
|