feat(profile): Passkey-Labels, Sicherheits-Checkliste und Geräte-Block
Erweitert die Profilseite um benennbare Passkeys, Sicherheitsübersicht, Gerät/Sync-Status, Backup-Hinweis in der Gefahrenzone und Dialog beim Löschen des letzten Passkeys. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -31,6 +31,13 @@ function previewCredentialId(credentialId: string): string {
|
||||
return `${credentialId.slice(0, 8)}…${credentialId.slice(-8)}`
|
||||
}
|
||||
|
||||
function normalizeCredentialLabel(label: unknown): string | null {
|
||||
if (typeof label !== 'string') return null
|
||||
const trimmed = label.trim()
|
||||
if (!trimmed) return null
|
||||
return trimmed.slice(0, 64)
|
||||
}
|
||||
|
||||
router.post('/register-options', async (req, res) => {
|
||||
try {
|
||||
const { username } = req.body
|
||||
@@ -416,6 +423,7 @@ router.get('/profile', requireUser, async (req: any, res) => {
|
||||
hasPrfEncryption: user.encryptedMasterKeyPrf != null,
|
||||
credentials: user.credentials.map((cred) => ({
|
||||
id: cred.id,
|
||||
label: cred.label,
|
||||
credentialIdPreview: previewCredentialId(cred.credentialId),
|
||||
transports: cred.transports
|
||||
})),
|
||||
@@ -479,6 +487,8 @@ router.post('/add-credential-verify', requireReauth, async (req: any, res) => {
|
||||
return res.status(400).json({ error: 'credentialResponse and challenge are required' })
|
||||
}
|
||||
|
||||
const label = normalizeCredentialLabel(req.body.label)
|
||||
|
||||
const challengeUserId = addCredentialChallenges.get(challenge)
|
||||
if (!challengeUserId) {
|
||||
return res.status(400).json({ error: 'Challenge not found or expired' })
|
||||
@@ -524,6 +534,7 @@ router.post('/add-credential-verify', requireReauth, async (req: any, res) => {
|
||||
data: {
|
||||
userId: req.userId,
|
||||
credentialId,
|
||||
label,
|
||||
publicKey: Buffer.from(credentialPublicKey),
|
||||
counter: BigInt(counter),
|
||||
transports: credentialResponse.response.transports || []
|
||||
@@ -534,6 +545,7 @@ router.post('/add-credential-verify', requireReauth, async (req: any, res) => {
|
||||
verified: true,
|
||||
credential: {
|
||||
id: credential.id,
|
||||
label: credential.label,
|
||||
credentialIdPreview: previewCredentialId(credential.credentialId),
|
||||
transports: credential.transports
|
||||
}
|
||||
@@ -544,6 +556,38 @@ router.post('/add-credential-verify', requireReauth, async (req: any, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.patch('/credentials/:id', requireUser, async (req: any, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const label = normalizeCredentialLabel(req.body?.label)
|
||||
|
||||
const credential = await prisma.credential.findUnique({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
if (!credential || credential.userId !== req.userId) {
|
||||
return res.status(404).json({ error: 'Credential not found' })
|
||||
}
|
||||
|
||||
const updated = await prisma.credential.update({
|
||||
where: { id },
|
||||
data: { label }
|
||||
})
|
||||
|
||||
return res.json({
|
||||
credential: {
|
||||
id: updated.id,
|
||||
label: updated.label,
|
||||
credentialIdPreview: previewCredentialId(updated.credentialId),
|
||||
transports: updated.transports
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error updating credential label:', error)
|
||||
return res.status(500).json({ error: error.message || 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/credentials/:id', requireReauth, async (req: any, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
Reference in New Issue
Block a user