@dotrino/identity 0.97.0 → 0.99.0
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.
- package/package.json +1 -1
- package/src/index.js +30 -0
- package/vault/vault.js +113 -1
- package/vault/vendor/vault/VERSION.txt +1 -1
- package/vault/vendor/vault/index.js +2 -2
- package/vault/vendor/vault/passwordLogins.js +34 -19
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -561,6 +561,36 @@ export class Identity {
|
|
|
561
561
|
/** Suscribe a eventos del self-vault ('selfVault'): { running?, pending?, error? }. */
|
|
562
562
|
onSelfVault (handler) { return this.on('selfVault', handler) }
|
|
563
563
|
|
|
564
|
+
// ----- entrar con usuario y contraseña, cuando la bóveda es ESTA pestaña -----
|
|
565
|
+
//
|
|
566
|
+
// La pestaña ya atendía el inicio de sesión desde el primer día; lo que faltaba era poder
|
|
567
|
+
// CREARLO, y sin eso solo el binario daba de alta el aparato — que rompe la regla de las
|
|
568
|
+
// tres versiones. Todo esto exige que esta pestaña sea la bóveda activa; si no lo es, se
|
|
569
|
+
// dice con `code: 'not-the-vault'` en vez de contestar una lista vacía.
|
|
570
|
+
|
|
571
|
+
/** Los aparatos que se abren con usuario y contraseña, con sus sesiones abiertas. */
|
|
572
|
+
async selfVaultLogins () { return this._call('selfVaultLogins') }
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Da de alta uno. La contraseña **no sale del iframe**: se comprueba con OPAQUE y de ella
|
|
576
|
+
* deriva la llave que cierra el paquete con las privadas del aparato nuevo.
|
|
577
|
+
*
|
|
578
|
+
* Devuelve su dirección `nombre@AB12-CD34-EF56`, que es lo que hay que teclear al entrar.
|
|
579
|
+
*/
|
|
580
|
+
async selfVaultLoginAdd (opts) { return this._call('selfVaultLoginAdd', opts || {}, 60000) }
|
|
581
|
+
|
|
582
|
+
/** Cambia la contraseña: hace falta la vieja, y lo que estuviera abierto se cierra. */
|
|
583
|
+
async selfVaultLoginPasswd (opts) { return this._call('selfVaultLoginPasswd', opts || {}, 60000) }
|
|
584
|
+
|
|
585
|
+
/** Cierra un inicio de sesión abierto (sin `sid`, todos los de ese usuario). */
|
|
586
|
+
async selfVaultLoginClose (opts) { return this._call('selfVaultLoginClose', opts || {}) }
|
|
587
|
+
|
|
588
|
+
/** Quita la espera que dejan los intentos fallidos. */
|
|
589
|
+
async selfVaultLoginUnblock (user) { return this._call('selfVaultLoginUnblock', { user }) }
|
|
590
|
+
|
|
591
|
+
/** Lo quita, y saca su llave del acta: las dos cosas, o ninguna. */
|
|
592
|
+
async selfVaultLoginRemove (user) { return this._call('selfVaultLoginRemove', { user }, 30000) }
|
|
593
|
+
|
|
564
594
|
// ----- multi-perfil por dispositivo -----
|
|
565
595
|
// Podés tener varios perfiles (identidades) en el mismo navegador, cada uno conectado o no
|
|
566
596
|
// a su propio vault. Crear/cambiar setea el perfil activo; la app RECARGA la página y toma
|
package/vault/vault.js
CHANGED
|
@@ -383,7 +383,119 @@ import { pubkeyId } from './capabilities.js'
|
|
|
383
383
|
},
|
|
384
384
|
// Presencia online (ping/pong) de las máquinas enroladas. Requiere que ESTE
|
|
385
385
|
// iframe sea el daemon activo (tiene el cliente del proxy); si no, devuelve [].
|
|
386
|
-
selfVaultProbe: async ({ pubkeys }) => ({ online: [...(await probeOnline(pubkeys || []))] })
|
|
386
|
+
selfVaultProbe: async ({ pubkeys }) => ({ online: [...(await probeOnline(pubkeys || []))] }),
|
|
387
|
+
|
|
388
|
+
// ----- ENTRAR CON USUARIO Y CONTRASEÑA, desde la PESTAÑA -----
|
|
389
|
+
//
|
|
390
|
+
// La bóveda-pestaña ya sabía ATENDER un inicio de sesión desde el primer día; lo que no
|
|
391
|
+
// había era forma de CREAR uno: el mostrador estaba montado y sus operaciones no
|
|
392
|
+
// asomaban por aquí, así que solo el binario podía dar de alta el aparato. Eso rompe la
|
|
393
|
+
// regla de las tres versiones (`sealed-passwords.md` §2.7), y esto la cumple.
|
|
394
|
+
//
|
|
395
|
+
// El OPAQUE de las dos puntas corre AQUÍ DENTRO: el alta necesita la mitad del cliente
|
|
396
|
+
// —la que tiene la contraseña— y la mitad del servidor, y las dos están en esta pestaña
|
|
397
|
+
// cuando es bóveda. La contraseña cruza el `postMessage` como ya lo hace la del perfil
|
|
398
|
+
// (`unlockProfile`), y no sale de este origen.
|
|
399
|
+
selfVaultLogins: async () => (daemon ? daemon.listLogins() : []),
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* DAR DE ALTA un aparato que se abre con usuario y contraseña.
|
|
403
|
+
*
|
|
404
|
+
* Las llaves del aparato NACEN aquí y salen ya cerradas con lo que deriva la contraseña:
|
|
405
|
+
* la bóveda guarda un paquete que no puede abrir. Es el mismo camino que `logins add`
|
|
406
|
+
* del binario, con la misma pieza compartida.
|
|
407
|
+
*/
|
|
408
|
+
selfVaultLoginAdd: async ({ user, password, label = '', scope = null, unattended = false } = {}) => {
|
|
409
|
+
const d = pidaDaemon()
|
|
410
|
+
const { client: opaque } = await import('@dotrino/opaque')
|
|
411
|
+
const { makeDeviceKey, makeDeviceEncKey } = await import('@dotrino/identity/capabilities')
|
|
412
|
+
const { sealDeviceKeys, loginAddress, accountFingerprint } = await import('@dotrino/vault/password-logins')
|
|
413
|
+
if (typeof password !== 'string' || password.length < 12) {
|
|
414
|
+
throw Object.assign(new Error('the password must be at least 12 characters'), { code: 'weak-password' })
|
|
415
|
+
}
|
|
416
|
+
const nombre = String(label || 'equipo prestado')
|
|
417
|
+
const reg = opaque.registrationStart({ password })
|
|
418
|
+
const { response } = await d.loginRegisterBegin({ user, request: reg.request })
|
|
419
|
+
const fin = opaque.registrationFinish({ state: reg.state, response, password })
|
|
420
|
+
const device = await makeDeviceKey({ label: nombre })
|
|
421
|
+
const enc = await makeDeviceEncKey()
|
|
422
|
+
const blob = await sealDeviceKeys(fin.exportKey, { sign: device.privateJwk, enc: enc.encPrivateJwk })
|
|
423
|
+
const r = await d.loginRegisterFinish({
|
|
424
|
+
user, upload: fin.upload, pub: device.publickey, encPub: enc.encPublickey,
|
|
425
|
+
label: nombre, blob, ...(Array.isArray(scope) && scope.length ? { scope } : {}), unattended: !!unattended
|
|
426
|
+
})
|
|
427
|
+
return { ...r, address: loginAddress(user, await accountFingerprint(selfIdentity)) }
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* CAMBIAR LA CONTRASEÑA es abrir y volver a cerrar: el aparato, su llave y su papel
|
|
432
|
+
* siguen siendo los mismos. Por eso hace falta la vieja — sin ella no hay nada que
|
|
433
|
+
* volver a cerrar — y por eso lo que estuviera abierto se cierra.
|
|
434
|
+
*/
|
|
435
|
+
selfVaultLoginPasswd: async ({ user, oldPassword, newPassword } = {}) => {
|
|
436
|
+
const d = pidaDaemon()
|
|
437
|
+
const { client: opaque } = await import('@dotrino/opaque')
|
|
438
|
+
const { sealDeviceKeys, openDeviceKeys } = await import('@dotrino/vault/password-logins')
|
|
439
|
+
if (typeof newPassword !== 'string' || newPassword.length < 12) {
|
|
440
|
+
throw Object.assign(new Error('the password must be at least 12 characters'), { code: 'weak-password' })
|
|
441
|
+
}
|
|
442
|
+
const start = opaque.loginStart({ password: oldPassword })
|
|
443
|
+
const begun = await d.loginBegin({ user, request: start.request })
|
|
444
|
+
let fin
|
|
445
|
+
try { fin = opaque.loginFinish({ state: start.state, response: begun.response, password: oldPassword }) }
|
|
446
|
+
catch (_) { throw Object.assign(new Error('wrong password'), { code: 'login-failed' }) }
|
|
447
|
+
const entered = await d.loginEnd({ lid: begun.lid, finalization: fin.finalization, label: 'consola' })
|
|
448
|
+
const keys = await openDeviceKeys(fin.exportKey, entered.blob)
|
|
449
|
+
|
|
450
|
+
const reg = opaque.registrationStart({ password: newPassword })
|
|
451
|
+
const { response } = await d.loginRegisterBegin({ user, request: reg.request, replace: true })
|
|
452
|
+
const nueva = opaque.registrationFinish({ state: reg.state, response, password: newPassword })
|
|
453
|
+
await d.loginRegisterFinish({
|
|
454
|
+
user, upload: nueva.upload, blob: await sealDeviceKeys(nueva.exportKey, keys), replace: true
|
|
455
|
+
})
|
|
456
|
+
return { ok: true, user }
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
/** Cerrar un inicio de sesión abierto (sin `sid`, todos los de ese usuario). */
|
|
460
|
+
selfVaultLoginClose: async ({ user, sid = null } = {}) => {
|
|
461
|
+
const d = pidaDaemon()
|
|
462
|
+
if (sid) return d.closeLogin({ user, sid })
|
|
463
|
+
const fila = d.listLogins().find((x) => x.user === user)
|
|
464
|
+
for (const s of fila?.sessions || []) d.closeLogin({ user, sid: s.sid })
|
|
465
|
+
return { ok: true, closed: (fila?.sessions || []).length }
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
/** Quitar la espera de los intentos fallidos, desde la máquina de la bóveda. */
|
|
469
|
+
selfVaultLoginUnblock: async ({ user } = {}) => pidaDaemon().clearLoginBlock({ user }),
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* QUITARLO. Se va de aquí **y su llave sale del acta**: borrar solo el inicio de sesión
|
|
473
|
+
* dejaba un miembro que ya no puede entrar y sigue siendo de la cuenta.
|
|
474
|
+
*/
|
|
475
|
+
selfVaultLoginRemove: async ({ user } = {}) => {
|
|
476
|
+
const d = pidaDaemon()
|
|
477
|
+
const fila = d.listLogins().find((x) => x.user === user)
|
|
478
|
+
const r = d.removeLogin({ user })
|
|
479
|
+
if (r?.ok && fila?.pub) {
|
|
480
|
+
try { await handlers.revokeDevice({ sub: fila.pub }) } catch (e) {
|
|
481
|
+
throw Object.assign(new Error(`the login is gone but its key is still in the record: ${e.message}`), { code: 'revoke-failed' })
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return { ...r, deviceId: fila?.deviceId || null }
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* El mostrador solo existe mientras ESTA pestaña sea la bóveda activa. Se dice con esas
|
|
490
|
+
* palabras porque es lo que hay que hacer: abrirla y dejarla visible.
|
|
491
|
+
*/
|
|
492
|
+
function pidaDaemon () {
|
|
493
|
+
if (!daemon) {
|
|
494
|
+
throw Object.assign(
|
|
495
|
+
new Error('this tab is not the active vault: open it as a visible tab and turn on «this device is a vault»'),
|
|
496
|
+
{ code: 'not-the-vault' })
|
|
497
|
+
}
|
|
498
|
+
return daemon
|
|
387
499
|
}
|
|
388
500
|
|
|
389
501
|
window.addEventListener('message', async (event) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/vault@0.
|
|
1
|
+
Copia vendorizada de @dotrino/vault@0.68.0 (dotrino-vault/lib/src/{index,enroll,protocol,passwordLogins,loginClient,b64}.js).
|
|
2
2
|
NO se edita a mano: la escribe `node vendor.mjs` y la vigila test/vendor-up-to-date.test.mjs.
|
|
3
3
|
index.js importa ./enroll.js y ./protocol.js (relativos, van en esta misma copia),
|
|
4
4
|
@dotrino/identity/{capabilities,acta} (= ../../{capabilities,acta}.js) y
|
|
@@ -394,7 +394,7 @@ export async function startDeviceVault (identity, { proxyUrl, client: injectedCl
|
|
|
394
394
|
async function handleLoginStart (from, p) {
|
|
395
395
|
if (!logins) return noLogins(from)
|
|
396
396
|
try {
|
|
397
|
-
const { lid, response } = logins.loginBegin({ user: p?.user, request: p?.request })
|
|
397
|
+
const { lid, response } = await logins.loginBegin({ user: p?.user, request: p?.request })
|
|
398
398
|
send(from, { type: MSG.LOGIN_RESPONSE, lid, response })
|
|
399
399
|
} catch (e) { loginError(from, e) }
|
|
400
400
|
}
|
|
@@ -402,7 +402,7 @@ export async function startDeviceVault (identity, { proxyUrl, client: injectedCl
|
|
|
402
402
|
async function handleLoginFinish (from, p) {
|
|
403
403
|
if (!logins) return noLogins(from)
|
|
404
404
|
try {
|
|
405
|
-
const r = logins.loginEnd({ lid: p?.lid, finalization: p?.finalization, label: p?.label })
|
|
405
|
+
const r = await logins.loginEnd({ lid: p?.lid, finalization: p?.finalization, label: p?.label })
|
|
406
406
|
const acta = (await identity.profileActa?.().catch(() => null))?.acta || null
|
|
407
407
|
send(from, { type: MSG.LOGIN_OK, sid: r.sid, blob: r.blob, cert: r.cert, iss: r.iss || iss, acta })
|
|
408
408
|
} catch (e) { loginError(from, e) }
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* binario, `lib/src/index.js` en la pestaña—. Lo único que no es puro es `registerLogin`, al
|
|
25
25
|
* final, porque hace falta la identidad que firma.
|
|
26
26
|
*/
|
|
27
|
-
import { server as
|
|
27
|
+
import { server as opaquePorDefecto, suiteId as suitePorDefecto } from '@dotrino/opaque'
|
|
28
28
|
import { pubkeyId } from '@dotrino/identity/capabilities'
|
|
29
29
|
import { deviceIdOf, scopeToCaps, scopeToCn } from './enroll.js'
|
|
30
30
|
import { SCOPE } from './protocol.js'
|
|
@@ -181,10 +181,25 @@ export async function openDeviceKeys (exportKey, blob) {
|
|
|
181
181
|
* `load()` devuelve el estado guardado (o `null` la primera vez)
|
|
182
182
|
* `save(s)` lo guarda
|
|
183
183
|
* `now()` el reloj, para poder probar el freno sin esperar una hora
|
|
184
|
+
* `opaque` la mitad de SERVIDOR de OPAQUE. Por defecto la del paquete, que es WASM y
|
|
185
|
+
* corre en el mismo hilo. Se puede inyectar OTRA porque en una extensión MV3 el WASM
|
|
186
|
+
* vive al otro lado de un `postMessage` —en una página sandbox, para no tener que
|
|
187
|
+
* abrirle la CSP a toda la extensión—, y desde allí solo puede contestar una promesa.
|
|
188
|
+
*
|
|
189
|
+
* **Por eso los métodos que tocan OPAQUE son ASÍNCRONOS**, también con la implementación de
|
|
190
|
+
* casa: una firma que cambia según quién la use son dos mostradores, y de eso va la regla
|
|
191
|
+
* de las tres versiones.
|
|
184
192
|
*/
|
|
185
|
-
export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
193
|
+
export function createLoginDesk ({ load, save, now = () => Date.now(), opaque = opaquePorDefecto } = {}) {
|
|
186
194
|
if (typeof load !== 'function' || typeof save !== 'function') throw new Error('createLoginDesk: load and save are required')
|
|
187
195
|
|
|
196
|
+
/**
|
|
197
|
+
* QUÉ SUITE se está usando, dicha por quien hace el cálculo. Con el OPAQUE de casa es la
|
|
198
|
+
* del paquete; con uno inyectado, la suya — preguntárselo a él es lo único que no miente,
|
|
199
|
+
* porque el registro que queda guardado lo hizo él.
|
|
200
|
+
*/
|
|
201
|
+
const suite = () => (typeof opaque.suiteId === 'function' ? opaque.suiteId() : suitePorDefecto())
|
|
202
|
+
|
|
188
203
|
/** Intercambios a medias: viven en memoria, y por eso un reinicio los tira. */
|
|
189
204
|
const exchanges = new Map()
|
|
190
205
|
|
|
@@ -195,9 +210,9 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
/** La preparación del servidor nace con el primer usuario y no se toca nunca más. */
|
|
198
|
-
function setupOf (state) {
|
|
213
|
+
async function setupOf (state) {
|
|
199
214
|
if (state.setup) return state.setup
|
|
200
|
-
state.setup = opaque.createSetup()
|
|
215
|
+
state.setup = await opaque.createSetup()
|
|
201
216
|
return state.setup
|
|
202
217
|
}
|
|
203
218
|
|
|
@@ -234,30 +249,30 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
234
249
|
* ALTA, primer paso. La contraseña no llega hasta aquí: lo que llega es el mensaje de
|
|
235
250
|
* registro de OPAQUE, que no la lleva ni permite adivinarla.
|
|
236
251
|
*/
|
|
237
|
-
registerBegin ({ user, request, replace = false } = {}) {
|
|
252
|
+
async registerBegin ({ user, request, replace = false } = {}) {
|
|
238
253
|
const state = read()
|
|
239
254
|
const existing = userOf(state, user)
|
|
240
255
|
if (existing && !replace) throw err('user-exists', `there is already a login called "${user}"`)
|
|
241
256
|
if (!existing && replace) throw err('no-user', `there is no login called "${user}"`)
|
|
242
257
|
if (typeof request !== 'string' || !request) throw err('bad-input', 'request required')
|
|
243
|
-
const setup = setupOf(state)
|
|
244
|
-
const response = opaque.registrationResponse({ setup, request, credentialId: user })
|
|
258
|
+
const setup = await setupOf(state)
|
|
259
|
+
const response = await opaque.registrationResponse({ setup, request, credentialId: user })
|
|
245
260
|
save(state)
|
|
246
|
-
return { response, suite:
|
|
261
|
+
return { response, suite: await suite() }
|
|
247
262
|
},
|
|
248
263
|
|
|
249
264
|
/**
|
|
250
265
|
* ALTA, segundo paso. `blob` son las llaves privadas del aparato, ya cerradas por quien
|
|
251
266
|
* creó el aparato con la llave que sale de la contraseña: aquí no se abre nunca.
|
|
252
267
|
*/
|
|
253
|
-
registerFinish ({ user, upload, pub, encPub = null, deviceId = null, label = '', blob, replace = false } = {}) {
|
|
268
|
+
async registerFinish ({ user, upload, pub, encPub = null, deviceId = null, label = '', blob, replace = false } = {}) {
|
|
254
269
|
const state = read()
|
|
255
270
|
const existing = userOf(state, user)
|
|
256
271
|
if (existing && !replace) throw err('user-exists', `there is already a login called "${user}"`)
|
|
257
272
|
if (!existing && replace) throw err('no-user', `there is no login called "${user}"`)
|
|
258
273
|
if (typeof blob !== 'string' || !blob) throw err('bad-input', 'blob required (the device keys, sealed with the password)')
|
|
259
274
|
if (!replace && (typeof pub !== 'string' || !pub)) throw err('bad-input', 'pub required')
|
|
260
|
-
const record = opaque.registrationFinish({ upload })
|
|
275
|
+
const record = await opaque.registrationFinish({ upload })
|
|
261
276
|
const t = now()
|
|
262
277
|
state.users[user] = {
|
|
263
278
|
...(existing || {}),
|
|
@@ -267,7 +282,7 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
267
282
|
label: label || existing?.label || '',
|
|
268
283
|
record,
|
|
269
284
|
blob,
|
|
270
|
-
suite:
|
|
285
|
+
suite: await suite(),
|
|
271
286
|
createdAt: existing?.createdAt || t,
|
|
272
287
|
passwordChangedAt: t,
|
|
273
288
|
// Cambiar la contraseña cierra lo abierto: si alguien entró con la vieja, deja de
|
|
@@ -277,7 +292,7 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
277
292
|
nextTryAt: 0
|
|
278
293
|
}
|
|
279
294
|
save(state)
|
|
280
|
-
return { user, suite:
|
|
295
|
+
return { user, suite: await suite() }
|
|
281
296
|
},
|
|
282
297
|
|
|
283
298
|
/** El certificado del aparato, que firma la bóveda al admitirlo en el acta. Es público. */
|
|
@@ -298,7 +313,7 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
298
313
|
* registro inventado: desde fuera no se puede averiguar qué usuarios hay. Y cuenta
|
|
299
314
|
* contra el mismo freno, para que tampoco se pueda medir por el tiempo.
|
|
300
315
|
*/
|
|
301
|
-
loginBegin ({ user, request } = {}) {
|
|
316
|
+
async loginBegin ({ user, request } = {}) {
|
|
302
317
|
sweepExchanges()
|
|
303
318
|
const state = read()
|
|
304
319
|
if (typeof request !== 'string' || !request) throw err('bad-input', 'request required')
|
|
@@ -314,8 +329,8 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
314
329
|
u.fails = (u.fails || 0) + 1
|
|
315
330
|
u.nextTryAt = t + backoffFor(u.fails)
|
|
316
331
|
}
|
|
317
|
-
const setup = setupOf(state)
|
|
318
|
-
const { state: serverState, response } = opaque.loginStart({
|
|
332
|
+
const setup = await setupOf(state)
|
|
333
|
+
const { state: serverState, response } = await opaque.loginStart({
|
|
319
334
|
setup,
|
|
320
335
|
record: u?.record || null,
|
|
321
336
|
request,
|
|
@@ -332,7 +347,7 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
332
347
|
* INICIO DE SESIÓN, segundo paso. Si la contraseña es la buena, devuelve el paquete de
|
|
333
348
|
* llaves —que solo abre esa contraseña—, el certificado y el acta.
|
|
334
349
|
*/
|
|
335
|
-
loginEnd ({ lid, finalization, label = '' } = {}) {
|
|
350
|
+
async loginEnd ({ lid, finalization, label = '' } = {}) {
|
|
336
351
|
sweepExchanges()
|
|
337
352
|
const x = exchanges.get(lid)
|
|
338
353
|
if (!x) throw err('no-exchange', 'that login is not in flight any more: start again')
|
|
@@ -341,7 +356,7 @@ export function createLoginDesk ({ load, save, now = () => Date.now() } = {}) {
|
|
|
341
356
|
const u = x.known ? state.users[x.user] : null
|
|
342
357
|
|
|
343
358
|
try {
|
|
344
|
-
opaque.loginFinish({ state: x.state, finalization })
|
|
359
|
+
await opaque.loginFinish({ state: x.state, finalization })
|
|
345
360
|
} catch (e) {
|
|
346
361
|
// Contraseña equivocada, usuario inexistente o mensaje alterado: el MISMO error.
|
|
347
362
|
// Distinguirlos diría qué usuarios existen. El intento ya está contado desde
|
|
@@ -438,7 +453,7 @@ export async function registerLogin ({
|
|
|
438
453
|
scope, unattended = false, replace = false
|
|
439
454
|
} = {}) {
|
|
440
455
|
if (replace) {
|
|
441
|
-
logins.registerFinish({ user, upload, blob, replace: true })
|
|
456
|
+
await logins.registerFinish({ user, upload, blob, replace: true })
|
|
442
457
|
return { ok: true, user, replaced: true }
|
|
443
458
|
}
|
|
444
459
|
// PERMISOS, no tipos: los del scope, más `unattended` si quien lo crea lo eligió
|
|
@@ -452,7 +467,7 @@ export async function registerLogin ({
|
|
|
452
467
|
throw Object.assign(new Error('this vault cannot add devices to the account record: no login was created'), { code: 'admit-unavailable' })
|
|
453
468
|
}
|
|
454
469
|
const deviceId = await deviceIdOf(pub)
|
|
455
|
-
logins.registerFinish({ user, upload, pub, encPub, deviceId, label, blob })
|
|
470
|
+
await logins.registerFinish({ user, upload, pub, encPub, deviceId, label, blob })
|
|
456
471
|
try {
|
|
457
472
|
const { cert } = await identity.signDelegation(pub, scopes, { label: label || `login:${user}` })
|
|
458
473
|
const cn = scopeToCn(scopes)
|