Skip to content

Commit

Permalink
パスワードチェック
Browse files Browse the repository at this point in the history
  • Loading branch information
u1-liquid committed Dec 21, 2024
1 parent 857dec5 commit 8f27787
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 118 deletions.
8 changes: 4 additions & 4 deletions packages/backend/src/server/api/EndpointsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_get_twofactor_enable from './endpoints/users/get-twofactor-enable.js';
import * as ep___users_get_security_info from './endpoints/users/get-security-info.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
Expand Down Expand Up @@ -752,6 +752,7 @@ const $users_following: Provider = { provide: 'ep:users/following', useClass: ep
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default };
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
const $users_getSecurityInfo: Provider = { provide: 'ep:users/get-security-info', useClass: ep___users_get_security_info.default };
const $users_getSkebStatus: Provider = { provide: 'ep:users/get-skeb-status', useClass: ep___users_getSkebStatus.default };
const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
Expand All @@ -777,7 +778,6 @@ const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-
const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default };
const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default };
const $users_twofactor_enable: Provider = { provide: 'ep:users/get-twofactor-enable', useClass: ep___users_get_twofactor_enable.default };
const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
Expand Down Expand Up @@ -1151,6 +1151,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_gallery_posts,
$users_getFollowingBirthdayUsers,
$users_getFrequentlyRepliedUsers,
$users_getSecurityInfo,
$users_getSkebStatus,
$users_featuredNotes,
$users_lists_create,
Expand All @@ -1177,7 +1178,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_show,
$users_stats,
$users_achievements,
$users_twofactor_enable,
$users_updateMemo,
$fetchRss,
$fetchExternalResources,
Expand Down Expand Up @@ -1542,6 +1542,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_gallery_posts,
$users_getFollowingBirthdayUsers,
$users_getFrequentlyRepliedUsers,
$users_getSecurityInfo,
$users_getSkebStatus,
$users_featuredNotes,
$users_lists_create,
Expand All @@ -1566,7 +1567,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_searchByUsernameAndHost,
$users_search,
$users_show,
$users_twofactor_enable,
$users_stats,
$users_achievements,
$users_updateMemo,
Expand Down
36 changes: 14 additions & 22 deletions packages/backend/src/server/api/SigninApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,33 +122,25 @@ export class SigninApiService {
return;
}

let user: MiLocalUser | null = null;
let profile: MiUserProfile | null = null;
// Fetch user
if (username.includes('@')) {
profile = await this.userProfilesRepository.findOneBy({
const profile = await this.userProfilesRepository.findOne({
relations: ['user'],
where: username.includes('@') ? {
email: username,
emailVerified: true,
}) as MiUserProfile;

user = await this.usersRepository.findOneBy({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
id: profile?.userId,
}) as MiLocalUser;
} else {
user = await this.usersRepository.findOneBy({
usernameLower: username.toLowerCase(),
host: IsNull(),
}) as MiLocalUser;

if (user !== null) {
profile = await this.userProfilesRepository.findOneByOrFail({
userId: user.id,
}) as MiUserProfile;
user: {
host: IsNull(),
}
} : {
user: {
usernameLower: username.toLowerCase(),
host: IsNull(),
}
}
}
});
const user = (profile?.user as MiLocalUser) ?? null;

if (user == null || profile == null) {
if (!user || !profile) {
logger.error('No such user.');
return error(403, {
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/server/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_get_twofactor_enable from './endpoints/users/get-twofactor-enable.js';
import * as ep___users_get_security_info from './endpoints/users/get-security-info.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
Expand Down Expand Up @@ -774,7 +774,7 @@ const eps = [
['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
['users/search', ep___users_search],
['users/show', ep___users_show],
['users/get-twofactor-enable', ep___users_get_twofactor_enable],
['users/get-security-info', ep___users_get_security_info],
['users/stats', ep___users_stats],
['users/achievements', ep___users_achievements],
['users/update-memo', ep___users_updateMemo],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Inject, Injectable } from '@nestjs/common';
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import bcrypt from 'bcryptjs';
import ms from 'ms';

export const meta = {
tags: ['users'],

requireCredential: false,

limit: {
duration: ms('1hour'),
max: 30,
},

res: {
type: 'object',
properties: {
Expand All @@ -28,8 +31,9 @@ export const paramDef = {
type: 'object',
properties: {
email: { type: 'string' },
password: { type: 'string' },
},
required: ['email'],
required: ['email', 'password'],
} as const;

@Injectable()
Expand All @@ -42,22 +46,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userSecurityKeysRepository: UserSecurityKeysRepository,
) {
super(meta, paramDef, async (ps, me) => {
const userProfile = await this.userProfilesRepository.findOneBy({
email: ps.email,
});

return userProfile ? {
twoFactorEnabled: userProfile.twoFactorEnabled,
usePasswordLessLogin: userProfile.usePasswordLessLogin,
securityKeys: userProfile.twoFactorEnabled
? await this.userSecurityKeysRepository.countBy({ userId: userProfile.userId }).then(result => result >= 1)
const profile = await this.userProfilesRepository.findOneBy({ email: ps.email });

const passwordMatched = await bcrypt.compare(ps.password, profile?.password ?? '');
if (!profile || !passwordMatched) {
return {
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
}

Check failure on line 57 in packages/backend/src/server/api/endpoints/users/get-security-info.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

Missing semicolon
}

return {
twoFactorEnabled: profile.twoFactorEnabled,
usePasswordLessLogin: profile.usePasswordLessLogin,
securityKeys: profile.twoFactorEnabled
? await this.userSecurityKeysRepository.countBy({ userId: profile.userId }).then(result => result >= 1)
: false,
} : {
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
};
});
}
}

4 changes: 2 additions & 2 deletions packages/frontend/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,9 @@ export async function openAccountMenu(opts: {
}
}

export function getAccountWithSigninDialog(emailMode = false): Promise<{ id: string, token: string } | null> {
export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> {
return new Promise((resolve) => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { emailMode }, {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => {
await addAccount(res.id, res.i);
resolve({ id: res.id, token: res.i });
Expand Down
70 changes: 22 additions & 48 deletions packages/frontend/src/components/MkSignin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,30 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="_gaps_m">
<div v-show="withAvatar && !emailMode" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
<div v-show="withAvatar && !loginWithEmailAddress" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
<MkInfo v-if="message">
{{ message }}
</MkInfo>
<div v-if="!totpLogin" class="normal-signin _gaps_m">
<MkInput v-model="username" :debounce="true" :placeholder="emailMode ? i18n.ts.emailAddress : i18n.ts.username" type="text" :pattern="emailMode ? '^[a-zA-Z0-9_@.]+$' : '^[a-zA-Z0-9_]+$'" :spellcheck="false" :autocomplete="emailMode ? 'email webauthn' : 'username webauthn'" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
<template v-if="!emailMode" #prefix>@</template>
<template v-if="!emailMode" #suffix>@{{ host }}</template>
<MkInput v-model="username" :debounce="true" :placeholder="loginWithEmailAddress ? i18n.ts.emailAddress : i18n.ts.username" type="text" :pattern="loginWithEmailAddress ? '^[a-zA-Z0-9_@.]+$' : '^[a-zA-Z0-9_]+$'" :spellcheck="false" :autocomplete="loginWithEmailAddress ? 'email webauthn' : 'username webauthn'" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
<template v-if="!loginWithEmailAddress" #prefix>@</template>
<template v-if="!loginWithEmailAddress" #suffix>@{{ host }}</template>
<template v-if="loginWithEmailAddress" #prefix><i class="ti ti-mail"></i></template>

Check failure on line 17 in packages/frontend/src/components/MkSignin.vue

View workflow job for this annotation

GitHub Actions / lint (frontend)

An element cannot have multiple '<template>' elements which are distributed to the same slot
<template #caption>
<button class="_textButton" type="button" tabindex="-1" @click="loginWithEmailAddress = !loginWithEmailAddress">{{ loginWithEmailAddress ? i18n.ts.usernameLogin : i18n.ts.emailAddressLogin }}</button>
</template>
</MkInput>
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password>
<template #prefix><i class="ti ti-lock"></i></template>
<template #caption>
<div class="_margin">
<button v-if="!emailMode" class="_textButton" type="button" @click="emailAddressLogin">{{ i18n.ts.emailAddressLogin }}</button>
<button v-if="emailMode" class="_textButton" type="button" @click="usernameLogin">{{ i18n.ts.usernameLogin }}</button>
</div>
<div class="_margin">
<button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button>
</div>
<button class="_textButton" type="button" tabindex="-1" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button>
</template>
</MkInput>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
<MkButton type="submit" large primary rounded :disabled="(!emailMode && !user) || captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
<MkButton type="submit" large primary rounded :disabled="(!loginWithEmailAddress && !user) || captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
Expand Down Expand Up @@ -72,14 +70,15 @@ import MkInfo from '@/components/MkInfo.vue';
import { host as configHost } from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { getAccountWithSigninDialog, login } from '@/account.js';
import { login } from '@/account.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';

const signing = ref(false);
const loginWithEmailAddress = ref(false);
const userAbortController = ref<AbortController>();
const user = ref<Credential | null>(null);
const user = ref<Misskey.entities.UserDetailed | null>(null);
const username = ref('');
const password = ref('');
const token = ref('');
Expand Down Expand Up @@ -107,7 +106,6 @@ const captchaFailed = computed((): boolean => {

const emit = defineEmits<{
(ev: 'login', v: any): void;
(ev: 'close', v: any): void;
}>();

const props = defineProps({
Expand All @@ -126,31 +124,11 @@ const props = defineProps({
required: false,
default: '',
},
emailMode: {
type: Boolean,
required: false,
default: false,
},
});

type Credential = {
twoFactorEnabled: boolean;
usePasswordLessLogin: boolean;
securityKeys: boolean;
};

async function onUsernameChange(): Promise<void> {
if (props.emailMode) {
let twofactorEnable = await misskeyApi('users/get-twofactor-enable', {
email: username.value,
});
user.value = {
twoFactorEnabled: twofactorEnable.twoFactorEnabled,
usePasswordLessLogin: twofactorEnable.usePasswordLessLogin,
securityKeys: twofactorEnable.securityKeys,
};
return;
}
if (loginWithEmailAddress.value) return;

if (userAbortController.value) {
userAbortController.value.abort();
}
Expand Down Expand Up @@ -201,6 +179,13 @@ async function queryKey(): Promise<void> {

async function onSubmit(): Promise<void> {
signing.value = true;
if (loginWithEmailAddress.value) {
user.value = await misskeyApi('users/get-security-info', {
email: username.value,
password: password.value,
});
}

if (!totpLogin.value && user.value?.twoFactorEnabled) {
if (webAuthnSupported() && user.value.securityKeys) {
misskeyApi('signin', {
Expand Down Expand Up @@ -288,17 +273,6 @@ function resetPassword(): void {
os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, {
}, 'closed');
}

function emailAddressLogin(): void {
getAccountWithSigninDialog(true);
emit('close', {});
}

function usernameLogin(): void {
getAccountWithSigninDialog();
emit('close', {});
}

</script>

<style lang="scss" module>
Expand Down
4 changes: 1 addition & 3 deletions packages/frontend/src/components/MkSigninDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header>{{ i18n.ts.login }}</template>

<MkSpacer :marginMin="20" :marginMax="28">
<MkSignin :autoSet="autoSet" :message="message" :emailMode="emailMode" @login="onLogin" @close="onClose"/>
<MkSignin :autoSet="autoSet" :message="message" @login="onLogin"/>
</MkSpacer>
</MkModalWindow>
</template>
Expand All @@ -28,11 +28,9 @@ import { i18n } from '@/i18n.js';
withDefaults(defineProps<{
autoSet?: boolean;
message?: string;
emailMode?: boolean;
}>(), {
autoSet: false,
message: '',
emailMode: false,
});

const emit = defineEmits<{
Expand Down
12 changes: 6 additions & 6 deletions packages/misskey-js/etc/misskey-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1776,8 +1776,8 @@ declare namespace entities {
UsersSearchResponse,
UsersShowRequest,
UsersShowResponse,
UsersGetTwofactorEnableRequest,
UsersGetTwofactorEnableResponse,
UsersGetSecurityInfoRequest,
UsersGetSecurityInfoResponse,
UsersStatsRequest,
UsersStatsResponse,
UsersAchievementsRequest,
Expand Down Expand Up @@ -3169,16 +3169,16 @@ type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-
type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];

// @public (undocumented)
type UsersGetSkebStatusRequest = operations['users___get-skeb-status']['requestBody']['content']['application/json'];
type UsersGetSecurityInfoRequest = operations['users___get-security-info']['requestBody']['content']['application/json'];

// @public (undocumented)
type UsersGetSkebStatusResponse = operations['users___get-skeb-status']['responses']['200']['content']['application/json'];
type UsersGetSecurityInfoResponse = operations['users___get-security-info']['responses']['200']['content']['application/json'];

// @public (undocumented)
type UsersGetTwofactorEnableRequest = operations['users___get-twofactor-enable']['requestBody']['content']['application/json'];
type UsersGetSkebStatusRequest = operations['users___get-skeb-status']['requestBody']['content']['application/json'];

// @public (undocumented)
type UsersGetTwofactorEnableResponse = operations['users___get-twofactor-enable']['responses']['200']['content']['application/json'];
type UsersGetSkebStatusResponse = operations['users___get-skeb-status']['responses']['200']['content']['application/json'];

// @public (undocumented)
type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json'];
Expand Down
Loading

0 comments on commit 8f27787

Please sign in to comment.