Fix WebAuthn login (#5103)
This commit is contained in:
		| @@ -1,67 +0,0 @@ | ||||
| import $ from 'cafy'; | ||||
| import * as bcrypt from 'bcryptjs'; | ||||
| import * as crypto from 'crypto'; | ||||
| import define from '../../../define'; | ||||
| import { UserProfiles, UserSecurityKeys, AttestationChallenges } from '../../../../../models'; | ||||
| import { ensure } from '../../../../../prelude/ensure'; | ||||
| import { promisify } from 'util'; | ||||
| import { hash } from '../../../2fa'; | ||||
| import { genId } from '../../../../../misc/gen-id'; | ||||
|  | ||||
| export const meta = { | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	secure: true, | ||||
|  | ||||
| 	params: { | ||||
| 		password: { | ||||
| 			validator: $.str | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| const randomBytes = promisify(crypto.randomBytes); | ||||
|  | ||||
| export default define(meta, async (ps, user) => { | ||||
| 	const profile = await UserProfiles.findOne(user.id).then(ensure); | ||||
|  | ||||
| 	// Compare password | ||||
| 	const same = await bcrypt.compare(ps.password, profile.password!); | ||||
|  | ||||
| 	if (!same) { | ||||
| 		throw new Error('incorrect password'); | ||||
| 	} | ||||
|  | ||||
| 	const keys = await UserSecurityKeys.find({ | ||||
| 		userId: user.id | ||||
| 	}); | ||||
|  | ||||
| 	if (keys.length === 0) { | ||||
| 		throw new Error('no keys found'); | ||||
| 	} | ||||
|  | ||||
| 	// 32 byte challenge | ||||
| 	const entropy = await randomBytes(32); | ||||
| 	const challenge = entropy.toString('base64') | ||||
| 		.replace(/=/g, '') | ||||
| 		.replace(/\+/g, '-') | ||||
| 		.replace(/\//g, '_'); | ||||
|  | ||||
| 	const challengeId = genId(); | ||||
|  | ||||
| 	await AttestationChallenges.save({ | ||||
| 		userId: user.id, | ||||
| 		id: challengeId, | ||||
| 		challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), | ||||
| 		createdAt: new Date(), | ||||
| 		registrationChallenge: false | ||||
| 	}); | ||||
|  | ||||
| 	return { | ||||
| 		challenge, | ||||
| 		challengeId, | ||||
| 		securityKeys: keys.map(key => ({ | ||||
| 			id: key.id | ||||
| 		})) | ||||
| 	}; | ||||
| }); | ||||
| @@ -9,6 +9,7 @@ import { ILocalUser } from '../../../models/entities/user'; | ||||
| import { genId } from '../../../misc/gen-id'; | ||||
| import { ensure } from '../../../prelude/ensure'; | ||||
| import { verifyLogin, hash } from '../2fa'; | ||||
| import { randomBytes } from 'crypto'; | ||||
|  | ||||
| export default async (ctx: Koa.BaseContext) => { | ||||
| 	ctx.set('Access-Control-Allow-Origin', config.url); | ||||
| @@ -99,7 +100,7 @@ export default async (ctx: Koa.BaseContext) => { | ||||
| 			}); | ||||
| 			return; | ||||
| 		} | ||||
| 	} else { | ||||
| 	} else if (body.credentialId) { | ||||
| 		const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); | ||||
| 		const clientData = JSON.parse(clientDataJSON.toString('utf-8')); | ||||
| 		const challenge = await AttestationChallenges.findOne({ | ||||
| @@ -131,7 +132,7 @@ export default async (ctx: Koa.BaseContext) => { | ||||
| 		const securityKey = await UserSecurityKeys.findOne({ | ||||
| 			id: Buffer.from( | ||||
| 				body.credentialId | ||||
| 					.replace(/\-/g, '+') | ||||
| 					.replace(/-/g, '+') | ||||
| 					.replace(/_/g, '/'), | ||||
| 					'base64' | ||||
| 			).toString('hex') | ||||
| @@ -161,7 +162,44 @@ export default async (ctx: Koa.BaseContext) => { | ||||
| 			}); | ||||
| 			return; | ||||
| 		} | ||||
| 	} else { | ||||
| 		const keys = await UserSecurityKeys.find({ | ||||
| 			userId: user.id | ||||
| 		}); | ||||
|  | ||||
| 		if (keys.length === 0) { | ||||
| 			await fail(403, { | ||||
| 				error: 'no keys found' | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		// 32 byte challenge | ||||
| 		const challenge = randomBytes(32).toString('base64') | ||||
| 			.replace(/=/g, '') | ||||
| 			.replace(/\+/g, '-') | ||||
| 			.replace(/\//g, '_'); | ||||
|  | ||||
| 		const challengeId = genId(); | ||||
|  | ||||
| 		await AttestationChallenges.save({ | ||||
| 			userId: user.id, | ||||
| 			id: challengeId, | ||||
| 			challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), | ||||
| 			createdAt: new Date(), | ||||
| 			registrationChallenge: false | ||||
| 		}); | ||||
|  | ||||
| 		ctx.body = { | ||||
| 			challenge, | ||||
| 			challengeId, | ||||
| 			securityKeys: keys.map(key => ({ | ||||
| 				id: key.id | ||||
| 			})) | ||||
| 		}; | ||||
| 		ctx.status = 200; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	await fail(); | ||||
| 	return; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Satsuki Yanagi
					Satsuki Yanagi