Merge branch 'develop' of https://github.com/syuilo/misskey into develop
This commit is contained in:
		| @@ -1112,6 +1112,7 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   register-security-key: "キーの登録を完了" |   register-security-key: "キーの登録を完了" | ||||||
|   something-went-wrong: "わー! キーを登録する際に問題が発生しました:" |   something-went-wrong: "わー! キーを登録する際に問題が発生しました:" | ||||||
|   key-unregistered: "キーが削除されました" |   key-unregistered: "キーが削除されました" | ||||||
|  |   use-password-less-login: "パスワードなしのログインを使用" | ||||||
|  |  | ||||||
| common/views/components/media-image.vue: | common/views/components/media-image.vue: | ||||||
|   sensitive: "閲覧注意" |   sensitive: "閲覧注意" | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								migration/1562422242907-PasswordLessLogin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1562422242907-PasswordLessLogin.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  |  | ||||||
|  | export class PasswordLessLogin1562422242907 implements MigrationInterface { | ||||||
|  |  | ||||||
|  | 	public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  | 		await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "usePasswordLessLogin" boolean DEFAULT false NOT NULL`); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  | 		await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "usePasswordLessLogin"`); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -28,6 +28,10 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
|  | 			<ui-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0"> | ||||||
|  | 				{{ $t('use-password-less-login') }} | ||||||
|  | 			</ui-switch> | ||||||
|  |  | ||||||
| 			<ui-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</ui-info> | 			<ui-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</ui-info> | ||||||
| 			<ui-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('register') }}</ui-button> | 			<ui-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('register') }}</ui-button> | ||||||
|  |  | ||||||
| @@ -80,6 +84,7 @@ export default Vue.extend({ | |||||||
| 		return { | 		return { | ||||||
| 			data: null, | 			data: null, | ||||||
| 			supportsCredentials: !!navigator.credentials, | 			supportsCredentials: !!navigator.credentials, | ||||||
|  | 			usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin, | ||||||
| 			registration: null, | 			registration: null, | ||||||
| 			keyName: '', | 			keyName: '', | ||||||
| 			token: null | 			token: null | ||||||
| @@ -112,6 +117,9 @@ export default Vue.extend({ | |||||||
| 				if (canceled) return; | 				if (canceled) return; | ||||||
| 				this.$root.api('i/2fa/unregister', { | 				this.$root.api('i/2fa/unregister', { | ||||||
| 					password: password | 					password: password | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.usePasswordLessLogin = false; | ||||||
|  | 					this.updatePasswordLessLogin(); | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
| 					this.$notify(this.$t('unregistered')); | 					this.$notify(this.$t('unregistered')); | ||||||
| 					this.$store.state.i.twoFactorEnabled = false; | 					this.$store.state.i.twoFactorEnabled = false; | ||||||
| @@ -157,6 +165,9 @@ export default Vue.extend({ | |||||||
| 				return this.$root.api('i/2fa/remove-key', { | 				return this.$root.api('i/2fa/remove-key', { | ||||||
| 					password, | 					password, | ||||||
| 					credentialId: key.id | 					credentialId: key.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.usePasswordLessLogin = false; | ||||||
|  | 					this.updatePasswordLessLogin(); | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
| 					this.$notify(this.$t('key-unregistered')); | 					this.$notify(this.$t('key-unregistered')); | ||||||
| 				}); | 				}); | ||||||
| @@ -213,6 +224,11 @@ export default Vue.extend({ | |||||||
| 					this.registration.stage = -1; | 					this.registration.stage = -1; | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
|  | 		}, | ||||||
|  | 		updatePasswordLessLogin() { | ||||||
|  | 			this.$root.api('i/2fa/password-less', { | ||||||
|  | 				value: !!this.usePasswordLessLogin | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
| 			<template #prefix>@</template> | 			<template #prefix>@</template> | ||||||
| 			<template #suffix>@{{ host }}</template> | 			<template #suffix>@{{ host }}</template> | ||||||
| 		</ui-input> | 		</ui-input> | ||||||
| 		<ui-input v-model="password" type="password" :with-password-toggle="true" required> | 		<ui-input v-model="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> | ||||||
| 			<span>{{ $t('password') }}</span> | 			<span>{{ $t('password') }}</span> | ||||||
| 			<template #prefix><fa icon="lock"/></template> | 			<template #prefix><fa icon="lock"/></template> | ||||||
| 		</ui-input> | 		</ui-input> | ||||||
| @@ -28,6 +28,10 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<div class="twofa-group totp-group"> | 		<div class="twofa-group totp-group"> | ||||||
| 			<p style="margin-bottom:0;">{{ $t('enter-2fa-code') }}</p> | 			<p style="margin-bottom:0;">{{ $t('enter-2fa-code') }}</p> | ||||||
|  | 			<ui-input v-model="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> | ||||||
|  | 				<span>{{ $t('password') }}</span> | ||||||
|  | 				<template #prefix><fa icon="lock"/></template> | ||||||
|  | 			</ui-input> | ||||||
| 			<ui-input v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | 			<ui-input v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | ||||||
| 				<span>{{ $t('@.2fa') }}</span> | 				<span>{{ $t('@.2fa') }}</span> | ||||||
| 				<template #prefix><fa icon="gavel"/></template> | 				<template #prefix><fa icon="gavel"/></template> | ||||||
|   | |||||||
| @@ -81,6 +81,11 @@ export class UserProfile { | |||||||
| 	}) | 	}) | ||||||
| 	public securityKeysAvailable: boolean; | 	public securityKeysAvailable: boolean; | ||||||
|  |  | ||||||
|  | 	@Column('boolean', { | ||||||
|  | 		default: false, | ||||||
|  | 	}) | ||||||
|  | 	public usePasswordLessLogin: boolean; | ||||||
|  |  | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 128, nullable: true, | 		length: 128, nullable: true, | ||||||
| 		comment: 'The password hash of the User. It will be null if the origin of the user is local.' | 		comment: 'The password hash of the User. It will be null if the origin of the user is local.' | ||||||
|   | |||||||
| @@ -156,6 +156,7 @@ export class UserRepository extends Repository<User> { | |||||||
| 					detail: true | 					detail: true | ||||||
| 				}), | 				}), | ||||||
| 				twoFactorEnabled: profile!.twoFactorEnabled, | 				twoFactorEnabled: profile!.twoFactorEnabled, | ||||||
|  | 				usePasswordLessLogin: profile!.usePasswordLessLogin, | ||||||
| 				securityKeys: profile!.twoFactorEnabled | 				securityKeys: profile!.twoFactorEnabled | ||||||
| 					? UserSecurityKeys.count({ | 					? UserSecurityKeys.count({ | ||||||
| 						userId: user.id | 						userId: user.id | ||||||
| @@ -208,7 +209,6 @@ export class UserRepository extends Repository<User> { | |||||||
| 						select: ['id', 'name', 'lastUsed'] | 						select: ['id', 'name', 'lastUsed'] | ||||||
| 					}) | 					}) | ||||||
| 					: [] | 					: [] | ||||||
|  |  | ||||||
| 			} : {}), | 			} : {}), | ||||||
|  |  | ||||||
| 			...(relation ? { | 			...(relation ? { | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								src/server/api/endpoints/i/2fa/password-less.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/server/api/endpoints/i/2fa/password-less.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { UserProfiles } from '../../../../../models'; | ||||||
|  |  | ||||||
|  | export const meta = { | ||||||
|  | 	requireCredential: true, | ||||||
|  |  | ||||||
|  | 	secure: true, | ||||||
|  |  | ||||||
|  | 	params: { | ||||||
|  | 		value: { | ||||||
|  | 			validator: $.boolean | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default define(meta, async (ps, user) => { | ||||||
|  | 	await UserProfiles.update(user.id, { | ||||||
|  | 		usePasswordLessLogin: ps.value | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
| @@ -72,19 +72,25 @@ export default async (ctx: Koa.BaseContext) => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!same) { |  | ||||||
| 		await fail(403, { |  | ||||||
| 			error: 'incorrect password' |  | ||||||
| 		}); |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (!profile.twoFactorEnabled) { | 	if (!profile.twoFactorEnabled) { | ||||||
| 		signin(ctx, user); | 		if (same) { | ||||||
|  | 			signin(ctx, user); | ||||||
|  | 		} else { | ||||||
|  | 			await fail(403, { | ||||||
|  | 				error: 'incorrect password' | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (token) { | 	if (token) { | ||||||
|  | 		if (!same) { | ||||||
|  | 			await fail(403, { | ||||||
|  | 				error: 'incorrect password' | ||||||
|  | 			}); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		const verified = (speakeasy as any).totp.verify({ | 		const verified = (speakeasy as any).totp.verify({ | ||||||
| 			secret: profile.twoFactorSecret, | 			secret: profile.twoFactorSecret, | ||||||
| 			encoding: 'base32', | 			encoding: 'base32', | ||||||
| @@ -101,6 +107,13 @@ export default async (ctx: Koa.BaseContext) => { | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 	} else if (body.credentialId) { | 	} else if (body.credentialId) { | ||||||
|  | 		if (!same && !profile.usePasswordLessLogin) { | ||||||
|  | 			await fail(403, { | ||||||
|  | 				error: 'incorrect password' | ||||||
|  | 			}); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); | 		const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); | ||||||
| 		const clientData = JSON.parse(clientDataJSON.toString('utf-8')); | 		const clientData = JSON.parse(clientDataJSON.toString('utf-8')); | ||||||
| 		const challenge = await AttestationChallenges.findOne({ | 		const challenge = await AttestationChallenges.findOne({ | ||||||
| @@ -163,6 +176,13 @@ export default async (ctx: Koa.BaseContext) => { | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
|  | 		if (!same && !profile.usePasswordLessLogin) { | ||||||
|  | 			await fail(403, { | ||||||
|  | 				error: 'incorrect password' | ||||||
|  | 			}); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		const keys = await UserSecurityKeys.find({ | 		const keys = await UserSecurityKeys.find({ | ||||||
| 			userId: user.id | 			userId: user.id | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo