Enroll Passkey PRF key on first login recovery phase

This commit is contained in:
2026-05-29 07:53:46 +02:00
parent 9b3297dff5
commit 8985afac38
2 changed files with 67 additions and 1 deletions
+30 -1
View File
@@ -147,6 +147,7 @@ export interface LoginResult {
encryptedMasterKeyRecTag: string
userId: string
username: string
prfFirst?: ArrayBuffer
}
}
@@ -231,7 +232,8 @@ export async function loginUser(username?: string): Promise<LoginResult> {
encryptedMasterKeyRecIv: result.encryptedMasterKeyRecIv,
encryptedMasterKeyRecTag: result.encryptedMasterKeyRecTag,
userId: result.userId,
username: resolvedUsername
username: resolvedUsername,
prfFirst: prfResults?.results?.first
}
}
}
@@ -245,6 +247,7 @@ export async function completeLoginWithRecovery(
encryptedMasterKeyRecIv: string
encryptedMasterKeyRecTag: string
userId: string
prfFirst?: ArrayBuffer
}
): Promise<boolean> {
try {
@@ -255,6 +258,32 @@ export async function completeLoginWithRecovery(
encryptedPayloads.encryptedMasterKeyRecTag,
recoveryKey
)
// If PRF results are available from the login challenge, enroll them now
if (encryptedPayloads.prfFirst) {
try {
const prfKey = await deriveKeyFromPrf(encryptedPayloads.prfFirst)
const encryptedPrf = await encryptBuffer(decryptedMaster, prfKey)
const enrollRes = await fetch(`${API_BASE}/enroll-prf`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-User-Id': encryptedPayloads.userId
},
body: JSON.stringify({
encryptedMasterKeyPrf: encryptedPrf.ciphertext,
encryptedMasterKeyPrfIv: encryptedPrf.iv,
encryptedMasterKeyPrfTag: encryptedPrf.tag
})
})
if (!enrollRes.ok) {
console.warn('Server rejected PRF enrollment')
}
} catch (err) {
console.error('Failed to encrypt/enroll master key with PRF key:', err)
}
}
setActiveMasterKey(decryptedMaster)
localStorage.setItem('active_username', username)
localStorage.setItem('active_userid', encryptedPayloads.userId)
+37
View File
@@ -253,4 +253,41 @@ router.delete('/delete-account', async (req: any, res) => {
}
})
// 6. Enroll PRF encrypted master key
router.post('/enroll-prf', async (req: any, res) => {
try {
const userId = req.headers['x-user-id']
if (!userId) {
return res.status(401).json({ error: 'Unauthorized: X-User-Id header missing' })
}
const { encryptedMasterKeyPrf, encryptedMasterKeyPrfIv, encryptedMasterKeyPrfTag } = req.body
if (!encryptedMasterKeyPrf || !encryptedMasterKeyPrfIv || !encryptedMasterKeyPrfTag) {
return res.status(400).json({ error: 'Missing required PRF key fields' })
}
if (
typeof encryptedMasterKeyPrf !== 'string' ||
typeof encryptedMasterKeyPrfIv !== 'string' ||
typeof encryptedMasterKeyPrfTag !== 'string'
) {
return res.status(400).json({ error: 'Invalid PRF key fields format' })
}
await prisma.user.update({
where: { id: userId },
data: {
encryptedMasterKeyPrf,
encryptedMasterKeyPrfIv,
encryptedMasterKeyPrfTag
}
})
return res.json({ success: true })
} catch (error: any) {
console.error('Error enrolling PRF key:', error)
return res.status(500).json({ error: error.message || 'Internal server error' })
}
})
export default router