moontraze 2.0.4 → 2.0.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.
Files changed (2) hide show
  1. package/lib/auth.js +95 -10
  2. package/package.json +1 -1
package/lib/auth.js CHANGED
@@ -68,11 +68,23 @@ async function loginWithEmail({ apiUrl, label }) {
68
68
  });
69
69
 
70
70
  const data = await res.json().catch(() => ({}));
71
+
71
72
  if (!res.ok) throw new Error(data.error || 'Invalid email or password');
73
+
74
+ // ---------- TOTP required ----------
75
+ if (data.requiresTotp && data.loginTicket) {
76
+ const token = await promptAndVerifyTotp({
77
+ apiUrl,
78
+ loginTicket: data.loginTicket,
79
+ label,
80
+ });
81
+ await saveAndValidateToken(token, { apiUrl, label });
82
+ return;
83
+ }
84
+
72
85
  if (!data.token) throw new Error('Login succeeded but no token returned');
73
86
 
74
87
  setConfig({ token: data.token, apiUrl });
75
-
76
88
  console.log('');
77
89
  console.log(chalk.green('✓ Logged in'));
78
90
  if (data.user?.email) console.log(chalk.gray(` ${data.user.email}`));
@@ -123,12 +135,33 @@ async function loginWithMoonID({ apiUrl, label }) {
123
135
  console.log(chalk.gray('Waiting for approval... (Ctrl+C to cancel)'));
124
136
  console.log('');
125
137
 
126
- const token = await pollWebStatus({ apiUrl, state, timeoutMs: 180000 });
127
- if (!token) throw new Error('MoonID login timed out or denied');
138
+ const result = await pollWebStatus({ apiUrl, state, timeoutMs: 180000 });
128
139
 
129
- await saveAndValidateToken(token, { apiUrl, label });
140
+ if (!result) throw new Error('MoonID login timed out or denied');
141
+
142
+ // ---------- TOTP required ----------
143
+ if (result.requiresTotp && result.loginTicket) {
144
+ const token = await promptAndVerifyTotp({
145
+ apiUrl,
146
+ loginTicket: result.loginTicket,
147
+ label,
148
+ });
149
+ await saveAndValidateToken(token, { apiUrl, label });
150
+ return;
151
+ }
152
+
153
+ if (!result.token) throw new Error('MoonID login succeeded but no token returned');
154
+
155
+ await saveAndValidateToken(result.token, { apiUrl, label });
130
156
  }
131
157
 
158
+ /**
159
+ * Poll /moonid/status
160
+ * Returns:
161
+ * { token } → success
162
+ * { requiresTotp, loginTicket } → need authenticator code
163
+ * null → timeout
164
+ */
132
165
  async function pollWebStatus({ apiUrl, state, timeoutMs }) {
133
166
  const fetch = require('node-fetch');
134
167
  const start = Date.now();
@@ -136,18 +169,26 @@ async function pollWebStatus({ apiUrl, state, timeoutMs }) {
136
169
 
137
170
  while (Date.now() - start < timeoutMs) {
138
171
  await new Promise((r) => setTimeout(r, interval));
172
+
139
173
  try {
140
174
  const res = await fetch(
141
175
  `${apiUrl}/api/platform/moonid/status?state=${encodeURIComponent(state)}`
142
176
  );
143
177
  const data = await res.json().catch(() => ({}));
144
178
 
145
- if (
146
- (data.status === 'success' || data.status === 'done') &&
147
- data.token
148
- ) {
149
- return data.token;
179
+ if ((data.status === 'success' || data.status === 'done') && data.token) {
180
+ return { token: data.token };
150
181
  }
182
+
183
+ // Authenticator required
184
+ if (data.status === 'requires_totp' && data.loginTicket) {
185
+ return {
186
+ requiresTotp: true,
187
+ loginTicket: data.loginTicket,
188
+ user: data.user,
189
+ };
190
+ }
191
+
151
192
  if (data.status === 'error' || data.status === 'denied') {
152
193
  throw new Error(data.error || 'MoonID login denied');
153
194
  }
@@ -160,9 +201,54 @@ async function pollWebStatus({ apiUrl, state, timeoutMs }) {
160
201
  }
161
202
  }
162
203
  }
204
+
163
205
  return null;
164
206
  }
165
207
 
208
+ /**
209
+ * Prompt for 6-digit code and verify against loginTicket
210
+ */
211
+ async function promptAndVerifyTotp({ apiUrl, loginTicket, label }) {
212
+ console.log('');
213
+ console.log(chalk.yellow('MOON Authenticator required'));
214
+ console.log(chalk.gray('Enter the 6-digit code from the MOON Authenticator app'));
215
+ console.log('');
216
+
217
+ const { code } = await prompts({
218
+ type: 'text',
219
+ name: 'code',
220
+ message: 'Authenticator code',
221
+ validate: (v) =>
222
+ /^\d{6}$/.test(String(v || '').trim())
223
+ ? true
224
+ : 'Enter a valid 6-digit code',
225
+ });
226
+
227
+ if (!code) throw new Error('Login cancelled');
228
+
229
+ const fetch = require('node-fetch');
230
+ const res = await fetch(`${apiUrl}/api/authenticator/verify-login`, {
231
+ method: 'POST',
232
+ headers: { 'Content-Type': 'application/json' },
233
+ body: JSON.stringify({
234
+ loginTicket,
235
+ code: String(code).trim(),
236
+ }),
237
+ });
238
+
239
+ const data = await res.json().catch(() => ({}));
240
+
241
+ if (!res.ok) {
242
+ throw new Error(data.error || 'Invalid authenticator code');
243
+ }
244
+
245
+ if (!data.token) {
246
+ throw new Error('Verification succeeded but no token returned');
247
+ }
248
+
249
+ return data.token;
250
+ }
251
+
166
252
  async function saveAndValidateToken(token, { apiUrl, label }) {
167
253
  const fetch = require('node-fetch');
168
254
  const res = await fetch(`${apiUrl}/api/hosting/quota`, {
@@ -174,7 +260,6 @@ async function saveAndValidateToken(token, { apiUrl, label }) {
174
260
  }
175
261
 
176
262
  setConfig({ token, apiUrl });
177
-
178
263
  console.log('');
179
264
  console.log(chalk.green('✓ Logged in'));
180
265
  console.log(chalk.gray(` Config: ${GLOBAL_FILE}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moontraze",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Deploy to Moontraze hosting",
5
5
  "main": "./bin/moontraze.js",
6
6
  "bin": {