@deerdesign/deertools 1.2.6 → 1.2.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/login.js +39 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deerdesign/deertools",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
package/src/login.js CHANGED
@@ -33,9 +33,9 @@ function normalizeScope(input) {
33
33
  return VALID_SCOPES.filter((s) => set.has(s)).join(' ');
34
34
  }
35
35
 
36
- function buildAuthorizeUrl(authorizationEndpoint, { redirectUri, state, scope, clientName, challenge }) {
36
+ function buildAuthorizeUrl(authorizationEndpoint, { redirectUri, state, scope, clientName, challenge, clientId = CLIENT_ID }) {
37
37
  const u = new URL(authorizationEndpoint);
38
- u.searchParams.set('client_id', CLIENT_ID);
38
+ u.searchParams.set('client_id', clientId);
39
39
  u.searchParams.set('redirect_uri', redirectUri);
40
40
  u.searchParams.set('response_type', 'code');
41
41
  u.searchParams.set('code_challenge', challenge);
@@ -67,13 +67,13 @@ function evaluateCallback(query, { expectedState }) {
67
67
  }
68
68
 
69
69
  /** Wymiana kodu na klucz: bezpośrednie zapytanie CLI -> serwer (klucz nigdy nie przechodzi przez przeglądarkę). */
70
- async function exchangeCode(product, tokenEndpoint, { code, verifier, redirectUri, fetchImpl = fetch }) {
70
+ async function exchangeCode(product, tokenEndpoint, { code, verifier, redirectUri, clientId = CLIENT_ID, fetchImpl = fetch }) {
71
71
  let res;
72
72
  try {
73
73
  res = await fetchImpl(tokenEndpoint, {
74
74
  method: 'POST',
75
75
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
76
- body: JSON.stringify({ grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: redirectUri, client_id: CLIENT_ID }),
76
+ body: JSON.stringify({ grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: redirectUri, client_id: clientId }),
77
77
  });
78
78
  } catch (e) {
79
79
  throw new Error(`Nie udało się połączyć z serwerem produktu ${product.name}: ${e.message}`);
@@ -162,6 +162,28 @@ function openBrowser(url) {
162
162
  exec(cmd, () => { /* gdy się nie uda, użytkownik ma adres wypisany w terminalu */ });
163
163
  }
164
164
 
165
+ async function registerDynamicClient(registrationEndpoint, { redirectUri, clientName, fetchImpl = fetch }) {
166
+ if (!registrationEndpoint) return null;
167
+ try {
168
+ const res = await fetchImpl(registrationEndpoint, {
169
+ method: 'POST',
170
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
171
+ body: JSON.stringify({
172
+ client_name: clientName || 'DeerTools CLI',
173
+ redirect_uris: [redirectUri],
174
+ token_endpoint_auth_method: 'none',
175
+ grant_types: ['authorization_code', 'refresh_token'],
176
+ response_types: ['code'],
177
+ }),
178
+ });
179
+ if (!res.ok) return null;
180
+ const json = await res.json();
181
+ return json.client_id || null;
182
+ } catch (_) {
183
+ return null;
184
+ }
185
+ }
186
+
165
187
  /**
166
188
  * Logowanie OAuth 2.1 dla aplikacji natywnej (RFC 8252): kod autoryzacyjny + PKCE, powrót na 127.0.0.1 (losowy port).
167
189
  * opts.metadata pozwala podać adresy bez pobierania (testy). Zwraca wpis poświadczeń zapisany na dysku.
@@ -179,6 +201,7 @@ async function login(product, opts = {}) {
179
201
  return new Promise((resolve, reject) => {
180
202
  let finished = false;
181
203
  let redirectUri = null;
204
+ let activeClientId = CLIENT_ID;
182
205
 
183
206
  const server = http.createServer(async (req, res) => {
184
207
  const url = new URL(req.url, 'http://127.0.0.1');
@@ -192,7 +215,7 @@ async function login(product, opts = {}) {
192
215
  return;
193
216
  }
194
217
  try {
195
- const entry = await exchangeCode(product, meta.token_endpoint, { code: result.code, verifier: pkce.verifier, redirectUri, fetchImpl: opts.fetchImpl });
218
+ const entry = await exchangeCode(product, meta.token_endpoint, { code: result.code, verifier: pkce.verifier, redirectUri, clientId: activeClientId, fetchImpl: opts.fetchImpl });
196
219
  credentials.saveProduct(product.id, entry);
197
220
  res.writeHead(200, headers);
198
221
  res.end(page('Zalogowano', `Klucz do produktu ${product.name} został zapisany w terminalu. Możesz zamknąć to okno.`, true));
@@ -215,9 +238,17 @@ async function login(product, opts = {}) {
215
238
  }
216
239
 
217
240
  server.on('error', (e) => done(e));
218
- server.listen(opts.port || 0, '127.0.0.1', () => {
241
+ server.listen(opts.port || 0, '127.0.0.1', async () => {
219
242
  redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
220
- const url = buildAuthorizeUrl(meta.authorization_endpoint, { redirectUri, state, scope, clientName, challenge: pkce.challenge });
243
+ let authScope = scope;
244
+ if (meta.registration_endpoint) {
245
+ const dynId = await registerDynamicClient(meta.registration_endpoint, { redirectUri, clientName, fetchImpl: opts.fetchImpl });
246
+ if (dynId) {
247
+ activeClientId = dynId;
248
+ authScope = 'openid email profile offline_access';
249
+ }
250
+ }
251
+ const url = buildAuthorizeUrl(meta.authorization_endpoint, { redirectUri, state, scope: authScope, clientName, challenge: pkce.challenge, clientId: activeClientId });
221
252
  log(`Zakres: ${scope}. Otwieram przeglądarkę — zaloguj się w produkcie ${product.name} i kliknij „Zezwól na dostęp”.`);
222
253
  log(`Jeśli przeglądarka się nie otworzy, wejdź na:\n${url}\n`);
223
254
  if (open) open(url);
@@ -225,4 +256,4 @@ async function login(product, opts = {}) {
225
256
  });
226
257
  }
227
258
 
228
- module.exports = { CLIENT_ID, createPkce, normalizeScope, buildAuthorizeUrl, evaluateCallback, exchangeCode, discoverOAuth, login };
259
+ module.exports = { CLIENT_ID, createPkce, normalizeScope, buildAuthorizeUrl, evaluateCallback, exchangeCode, discoverOAuth, login, registerDynamicClient };