moontraze 1.0.6 → 1.0.8

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/auth.js +60 -81
  2. package/package.json +1 -1
package/lib/auth.js CHANGED
@@ -1,7 +1,5 @@
1
1
  const prompts = require('prompts');
2
2
  const chalk = require('chalk');
3
- const http = require('http');
4
- const { URL } = require('url');
5
3
  const { getConfig, setConfig } = require('./config');
6
4
 
7
5
  async function login(tokenArg, options = {}) {
@@ -10,10 +8,8 @@ async function login(tokenArg, options = {}) {
10
8
  process.env.MOON_API ||
11
9
  getConfig().apiUrl ||
12
10
  'https://api.moontraze.com';
13
-
14
11
  const label = process.env.MOON_LABEL || process.env.MOON_PLATFORM || 'Moontraze';
15
12
 
16
- // --token (hidden power user)
17
13
  if (tokenArg && String(tokenArg).trim()) {
18
14
  return saveAndValidateToken(String(tokenArg).trim(), { apiUrl, label });
19
15
  }
@@ -72,31 +68,47 @@ async function loginWithEmail({ apiUrl, label }) {
72
68
  });
73
69
 
74
70
  const data = await res.json().catch(() => ({}));
75
-
76
71
  if (!res.ok) throw new Error(data.error || 'Invalid email or password');
77
72
  if (!data.token) throw new Error('Login succeeded but no token returned');
78
73
 
79
74
  setConfig({ token: data.token, apiUrl });
80
-
81
75
  console.log('');
82
76
  console.log(chalk.green('✓ Logged in'));
83
- if (data.user?.email) console.log(chalk.gray(` ${data.user.email}`));
84
- console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
77
+ if (data.user?.email) console.log(chalk.gray(` ${data.user.email}`));
78
+ console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
85
79
  console.log('');
86
80
  }
87
81
 
88
82
  async function loginWithMoonID({ apiUrl, label }) {
89
- const state = Math.random().toString(36).substring(2, 12);
90
- const port = 17321 + Math.floor(Math.random() * 100);
91
- const redirectUri = `http://127.0.0.1:${port}/callback`;
92
- const appId = 'com.moonstorage.app';
83
+ const fetch = require('node-fetch');
84
+ const nonce = Math.random().toString(36).substring(2, 12);
85
+ const state = `${nonce}.login`;
93
86
 
87
+ // ★ Same flow as website (working)
88
+ const startRes = await fetch(`${apiUrl}/api/platform/moonid/web-start`, {
89
+ method: 'POST',
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: JSON.stringify({ state, action: 'login' }),
92
+ });
93
+ const startData = await startRes.json().catch(() => ({}));
94
+ if (!startRes.ok) {
95
+ throw new Error(startData.error || 'Could not start MoonID session');
96
+ }
97
+
98
+ const notifyUrl = `${apiUrl}/api/platform/moonid/web-complete`;
99
+ const redirectUri = `${apiUrl}/api/platform/moonid/done?state=${encodeURIComponent(
100
+ state
101
+ )}`;
102
+
103
+ const appId = 'com.moonstorage.app';
94
104
  const deepLink =
95
105
  `moonid://auth` +
96
106
  `?app_id=${encodeURIComponent(appId)}` +
97
107
  `&action=login` +
98
108
  `&redirect_uri=${encodeURIComponent(redirectUri)}` +
99
- `&state=${encodeURIComponent(state)}`;
109
+ `&notify_url=${encodeURIComponent(notifyUrl)}` +
110
+ `&state=${encodeURIComponent(state)}` +
111
+ `&source=cli`;
100
112
 
101
113
  const qrImageUrl = `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(
102
114
  deepLink
@@ -105,7 +117,7 @@ async function loginWithMoonID({ apiUrl, label }) {
105
117
  console.log('');
106
118
  console.log(chalk.cyan(`${label} — MoonID Login`));
107
119
  console.log(chalk.gray('1. Open MoonID app on your phone'));
108
- console.log(chalk.gray('2. Scan the QR code (open link below in browser)'));
120
+ console.log(chalk.gray('2. Scan the QR code (open the link below in a browser if needed)'));
109
121
  console.log(chalk.gray('3. Approve login'));
110
122
  console.log('');
111
123
  console.log(chalk.yellow('QR code image:'));
@@ -114,75 +126,45 @@ async function loginWithMoonID({ apiUrl, label }) {
114
126
  console.log(chalk.gray('Waiting for approval... (Ctrl+C to cancel)'));
115
127
  console.log('');
116
128
 
117
- const token = await waitForMoonIDCallback({ port, state, timeoutMs: 120000 });
129
+ const token = await pollWebStatus({ apiUrl, state, timeoutMs: 180000 });
118
130
  if (!token) throw new Error('MoonID login timed out or denied');
119
131
 
120
132
  await saveAndValidateToken(token, { apiUrl, label });
121
133
  }
122
134
 
123
- function waitForMoonIDCallback({ port, state, timeoutMs }) {
124
- return new Promise((resolve) => {
125
- const server = http.createServer((req, res) => {
126
- try {
127
- const u = new URL(req.url, `http://127.0.0.1:${port}`);
128
- if (u.pathname !== '/callback') {
129
- res.writeHead(404);
130
- res.end('Not found');
131
- return;
132
- }
133
-
134
- const returnedState = u.searchParams.get('state');
135
- const token =
136
- u.searchParams.get('token') ||
137
- u.searchParams.get('access_token') ||
138
- u.searchParams.get('jwt');
139
- const error = u.searchParams.get('error');
140
-
141
- if (error) {
142
- res.writeHead(200, { 'Content-Type': 'text/html' });
143
- res.end(
144
- '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>Login denied</h2><p>You can close this tab.</p></body></html>'
145
- );
146
- server.close();
147
- resolve(null);
148
- return;
149
- }
150
-
151
- if (returnedState && returnedState !== state) {
152
- res.writeHead(400, { 'Content-Type': 'text/plain' });
153
- res.end('Invalid state');
154
- return;
155
- }
156
-
157
- if (!token) {
158
- res.writeHead(400, { 'Content-Type': 'text/html' });
159
- res.end(
160
- '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>No token received</h2></body></html>'
161
- );
162
- return;
163
- }
164
-
165
- res.writeHead(200, { 'Content-Type': 'text/html' });
166
- res.end(
167
- '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>✓ Login successful</h2><p>You can close this tab.</p></body></html>'
168
- );
169
- server.close();
170
- resolve(token);
171
- } catch {
172
- res.writeHead(500);
173
- res.end('Error');
135
+ async function pollWebStatus({ apiUrl, state, timeoutMs }) {
136
+ const fetch = require('node-fetch');
137
+ const start = Date.now();
138
+ const interval = 2000;
139
+
140
+ while (Date.now() - start < timeoutMs) {
141
+ await new Promise((r) => setTimeout(r, interval));
142
+ try {
143
+ const res = await fetch(
144
+ `${apiUrl}/api/platform/moonid/status?state=${encodeURIComponent(state)}`
145
+ );
146
+ const data = await res.json().catch(() => ({}));
147
+
148
+ // web endpoint returns status: "success"
149
+ if (
150
+ (data.status === 'success' || data.status === 'done') &&
151
+ data.token
152
+ ) {
153
+ return data.token;
174
154
  }
175
- });
176
-
177
- server.listen(port, '127.0.0.1');
178
- const timer = setTimeout(() => {
179
- try {
180
- server.close();
181
- } catch (_) {}
182
- resolve(null);
183
- }, timeoutMs);
184
- server.on('close', () => clearTimeout(timer));
185
- });
155
+ if (data.status === 'error' || data.status === 'denied') {
156
+ throw new Error(data.error || 'MoonID login denied');
157
+ }
158
+ } catch (e) {
159
+ if (
160
+ e.message &&
161
+ /denied|failed|not found|mismatch|expired|signup/i.test(e.message)
162
+ ) {
163
+ throw e;
164
+ }
165
+ }
166
+ }
167
+ return null;
186
168
  }
187
169
 
188
170
  async function saveAndValidateToken(token, { apiUrl, label }) {
@@ -190,16 +172,13 @@ async function saveAndValidateToken(token, { apiUrl, label }) {
190
172
  const res = await fetch(`${apiUrl}/api/hosting/quota`, {
191
173
  headers: { Authorization: `Bearer ${token}` },
192
174
  });
193
-
194
175
  if (res.status === 401) {
195
176
  throw new Error('Invalid or expired token');
196
177
  }
197
-
198
178
  setConfig({ token, apiUrl });
199
-
200
179
  console.log('');
201
180
  console.log(chalk.green('✓ Logged in'));
202
- console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
181
+ console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
203
182
  console.log('');
204
183
  }
205
184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moontraze",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Deploy to Moontraze hosting — like vercel CLI",
5
5
  "bin": {
6
6
  "moon": "./bin/moon.js",