nukebase 2.0.1 → 2.0.2

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/lib/login.js +54 -16
  2. package/package.json +1 -1
package/lib/login.js CHANGED
@@ -31,7 +31,13 @@ function page(mark, title, message, accent) {
31
31
  }
32
32
 
33
33
  // A one-shot server on 127.0.0.1 for the browser to come back to. Resolves
34
- // with the callback's query once one arrives carrying the right state.
34
+ // with { query, reply } once a callback arrives carrying the right state.
35
+ // The browser's request is held open until the caller calls reply(), so the
36
+ // browser only lands on "signed in" once the CLI really has its token:
37
+ // reply({ redirect }) send the browser on (the dashboard)
38
+ // reply({ error }) show what went wrong
39
+ // reply() a plain "signed in, close this tab" page
40
+ // If reply() never comes, the page is sent after 30 seconds anyway.
35
41
  function startLoopback(state) {
36
42
  return new Promise((resolveStart, rejectStart) => {
37
43
  let finish;
@@ -44,14 +50,30 @@ function startLoopback(state) {
44
50
  res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
45
51
  return res.end(page('✕', 'Sign-in error', 'This sign-in could not be verified. Return to your terminal and try again.', '#dc2626'));
46
52
  }
47
- const denied = q.error === 'access_denied';
48
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
49
- res.end(denied
50
- ? page('✕', 'Cancelled', 'The CLI was not signed in. You can close this tab.', '#64748b')
51
- : page('✓', 'CLI signed in', 'You can close this tab and return to your terminal.', '#16a34a'));
52
53
  clearTimeout(timer);
53
54
  server.close();
54
- finish.res(q);
55
+ if (q.error === 'access_denied') {
56
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
57
+ res.end(page('✕', 'Cancelled', 'The CLI was not signed in. You can close this tab.', '#64748b'));
58
+ return finish.res({ query: q, reply: () => {} });
59
+ }
60
+ let sent = false;
61
+ const reply = ({ redirect, error } = {}) => {
62
+ if (sent) return;
63
+ sent = true;
64
+ clearTimeout(fallback);
65
+ if (redirect) {
66
+ res.writeHead(302, { Location: redirect });
67
+ return res.end();
68
+ }
69
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
70
+ res.end(error
71
+ ? page('✕', 'Sign-in failed', `${String(error).replace(/[<>&"]/g, '')} Return to your terminal.`, '#dc2626')
72
+ : page('✓', 'CLI signed in', 'You can close this tab and return to your terminal.', '#16a34a'));
73
+ };
74
+ const fallback = setTimeout(() => reply(), 30 * 1000);
75
+ fallback.unref();
76
+ finish.res({ query: q, reply });
55
77
  });
56
78
  const timer = setTimeout(() => { server.close(); finish.rej(new Error('No response within 5 minutes. Run nukebase login again.')); }, LOGIN_TIMEOUT_MS);
57
79
  server.on('error', rejectStart);
@@ -104,13 +126,20 @@ async function browser(api, { email } = {}) {
104
126
  console.log(`\n Still waiting. No account yet? Sign up at ${api}/start.html first, then run this again.`);
105
127
  }, 90 * 1000);
106
128
  hint.unref();
107
- const q = await result.finally(() => clearTimeout(hint));
129
+ const { query: q, reply } = await result.finally(() => clearTimeout(hint));
108
130
  if (q.error) throw new Error('Sign-in was cancelled in the browser.');
109
- if (!q.code) throw new Error('The browser did not return a sign-in code.');
110
- const r = await postJson(`${api}/cli/token`, { code: q.code, verifier });
111
- needsNewerServer(r, api);
112
- if (!r.ok) throw new Error(r.data.error || `Sign-in failed (${r.status})`);
113
- return r.data;
131
+ try {
132
+ if (!q.code) throw new Error('The browser did not return a sign-in code.');
133
+ const r = await postJson(`${api}/cli/token`, { code: q.code, verifier });
134
+ needsNewerServer(r, api);
135
+ if (!r.ok) throw new Error(r.data.error || `Sign-in failed (${r.status})`);
136
+ // Signed in: finish the browser on the dashboard, which says so.
137
+ reply({ redirect: `${api}/?cli=signed-in` });
138
+ return r.data;
139
+ } catch (e) {
140
+ reply({ error: e.message });
141
+ throw e;
142
+ }
114
143
  }
115
144
 
116
145
  async function device(api) {
@@ -156,9 +185,18 @@ async function emailLink(api, email) {
156
185
  console.log(`\n ✉ Sign-in link sent to ${email} (if it has a NukeBase account).`);
157
186
  console.log(' Open it in a browser on this computer.');
158
187
  console.log('\n Waiting for the link to be clicked… (expires in 5 minutes)');
159
- const q = await result;
160
- if (!q.uid || !q.token) throw new Error('The sign-in link did not return a session.');
161
- return exchange(api, q.uid, q.token);
188
+ // This browser has no dashboard session (the link signed in the CLI, not
189
+ // the browser), so it gets the plain "signed in" page rather than a redirect.
190
+ const { query: q, reply } = await result;
191
+ try {
192
+ if (!q.uid || !q.token) throw new Error('The sign-in link did not return a session.');
193
+ const out = await exchange(api, q.uid, q.token);
194
+ reply();
195
+ return out;
196
+ } catch (e) {
197
+ reply({ error: e.message });
198
+ throw e;
199
+ }
162
200
  }
163
201
 
164
202
  async function password(api, username, pass) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukebase",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "NukeBase CLI: create, deploy and manage NukeBase projects",
5
5
  "bin": {
6
6
  "nukebase": "bin/nukebase.js"