@benmore/bm 2.7.311 → 2.7.313

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/index.js +49 -1
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -45,7 +45,7 @@ async function request(method, path, body, options) {
45
45
  }
46
46
  }
47
47
  observeSessionResponse(method, path, res.ok, data);
48
- return { ok: res.ok, status: res.status, data };
48
+ return { ok: res.ok, status: res.status, data, redirected: !!res.redirected, url: res.url || '' };
49
49
  }
50
50
 
51
51
  // apiError builds the Error a failed request throws. The framework's JSON
@@ -200,9 +200,51 @@ async function refreshMe() {
200
200
  async function signIn(identifier, password) {
201
201
  const res = await request('POST', '/login', { email: identifier, password });
202
202
  if (!res.ok) throw apiError(res, 'Invalid credentials');
203
+ const step = signInStepURL('/login', res, _pageLocation());
204
+ if (step) return _goToSignInStep(step);
203
205
  return res.data?.user || null;
204
206
  }
205
207
 
208
+ // A sign-in or sign-up that still needs a second step (the emailed or texted
209
+ // code with auth.otp, an authenticator code with MFA) has not made a session:
210
+ // the server answers {otp_required: true, redirect} (or mfa_required), or
211
+ // redirects the request to the step's page. signInStepURL returns that page,
212
+ // same-origin and carrying the current page's ?next=, else null (also when
213
+ // there is no page: outside a browser the calls resolve null as before).
214
+ // Callers send the browser there, as a form post would, and do not return: code written as
215
+ // `await bm.auth.signIn(...); location.href = '/'` would otherwise leave the
216
+ // person on a page with no session and the code already emailed.
217
+ function signInStepURL(path, res, loc) {
218
+ if (!loc || typeof loc.assign !== 'function') return null;
219
+ const data = res && res.data;
220
+ let to = '';
221
+ if (data && typeof data === 'object' && (data.otp_required || data.mfa_required) && typeof data.redirect === 'string') {
222
+ to = data.redirect;
223
+ } else if (res && res.redirected && typeof res.url === 'string' && loc.origin && res.url.startsWith(loc.origin + '/')) {
224
+ const target = res.url.slice(loc.origin.length);
225
+ if (target.split(/[?#]/, 1)[0] !== String(path).split(/[?#]/, 1)[0]) to = target;
226
+ }
227
+ if (!to || to[0] !== '/' || to[1] === '/' || to[1] === '\\') return null;
228
+ const m = /[?&]next=([^&#]*)/.exec(loc.search || '');
229
+ if (m && !/[?&]next=/.test(to)) {
230
+ let next = '';
231
+ try { next = decodeURIComponent(m[1].replace(/\+/g, ' ')); } catch { next = ''; }
232
+ if (next[0] === '/' && next[1] !== '/' && next[1] !== '\\') to += (to.includes('?') ? '&' : '?') + 'next=' + encodeURIComponent(next);
233
+ }
234
+ return to;
235
+ }
236
+
237
+ function _pageLocation() {
238
+ return typeof location === 'undefined' ? null : location;
239
+ }
240
+
241
+ // The page is leaving for the step; the caller's code after the await must
242
+ // not run, so the promise never settles.
243
+ function _goToSignInStep(url) {
244
+ location.assign(url);
245
+ return new Promise(() => {});
246
+ }
247
+
206
248
  // signUp - POST /signup. Pass any extra signup_fields (configured in
207
249
  // app.yaml's `auth.signup_fields`) as additional keys on the object:
208
250
  // await bm.auth.signUp({ email, password, first_name: 'Ada' })
@@ -211,6 +253,8 @@ async function signUp(fields) {
211
253
  if (!res.ok) {
212
254
  throw apiError(res, 'Sign-up failed');
213
255
  }
256
+ const step = signInStepURL('/signup', res, _pageLocation());
257
+ if (step) return _goToSignInStep(step);
214
258
  return res.data?.user || null;
215
259
  }
216
260
 
@@ -285,6 +329,10 @@ const passkey = {
285
329
  userHandle: r.userHandle ? _bufToB64u(r.userHandle) : '',
286
330
  },
287
331
  }, 'Passkey sign-in failed');
332
+ // With auth.otp the passkey proves the account and the emailed code
333
+ // still has to be entered before there is a session.
334
+ const step = signInStepURL('/api/_auth/passkeys/login', { data }, _pageLocation());
335
+ if (step) return _goToSignInStep(step);
288
336
  return data.user || null;
289
337
  },
290
338
  async list() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benmore/bm",
3
- "version": "2.7.311",
3
+ "version": "2.7.313",
4
4
  "description": "Zero-dependency browser SDK for external Benmore frontends",
5
5
  "type": "module",
6
6
  "module": "./index.js",