Merge 50e4ad9cf1
into 1c284c8154
This commit is contained in:
commit
33b08cb80b
|
@ -11,6 +11,7 @@
|
|||
- Fix: お知らせ作成時に画像URL入力欄を空欄に変更できないのを修正 ( #14976 )
|
||||
- Enhance: 依存関係の更新
|
||||
- Enhance: l10nの更新
|
||||
- Enhance: 登録時の処理のエラーの内容を詳細に表示するように
|
||||
|
||||
### Client
|
||||
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
|
||||
|
|
|
@ -7164,6 +7164,36 @@ export interface Locale extends ILocale {
|
|||
* 入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。
|
||||
*/
|
||||
"emailSent": ParameterizedString<"email">;
|
||||
"_errors": {
|
||||
/**
|
||||
* メールアドレスが入力されていないか、不正な値です。
|
||||
*/
|
||||
"emailInvalid": string;
|
||||
/**
|
||||
* このメールアドレスを使用して登録することはできません。
|
||||
*/
|
||||
"emailNotAllowed": string;
|
||||
/**
|
||||
* 招待コードが入力されていないか、不正な値です。
|
||||
*/
|
||||
"invitationCodeInvalid": string;
|
||||
/**
|
||||
* 招待コードが見つからなかったか、既に使用されています。
|
||||
*/
|
||||
"invitationCodeNotFoundOrUsed": string;
|
||||
/**
|
||||
* 招待コードの有効期限が切れています。
|
||||
*/
|
||||
"invitationCodeExpired": string;
|
||||
/**
|
||||
* このユーザー名は既に使用されています。
|
||||
*/
|
||||
"usernameAlreadyUsed": string;
|
||||
/**
|
||||
* このユーザー名で登録することはできません。
|
||||
*/
|
||||
"usernameNotAllowed": string;
|
||||
};
|
||||
};
|
||||
"_accountDelete": {
|
||||
/**
|
||||
|
|
|
@ -1856,6 +1856,14 @@ _signup:
|
|||
almostThere: "ほとんど完了です"
|
||||
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
|
||||
emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。"
|
||||
_errors:
|
||||
emailInvalid: "メールアドレスが入力されていないか、不正な値です。"
|
||||
emailNotAllowed: "このメールアドレスを使用して登録することはできません。"
|
||||
invitationCodeInvalid: "招待コードが入力されていないか、不正な値です。"
|
||||
invitationCodeNotFoundOrUsed: "招待コードが見つからなかったか、既に使用されています。"
|
||||
invitationCodeExpired: "招待コードの有効期限が切れています。"
|
||||
usernameAlreadyUsed: "このユーザー名は既に使用されています。"
|
||||
usernameNotAllowed: "このユーザー名で登録することはできません。"
|
||||
|
||||
_accountDelete:
|
||||
accountDelete: "アカウントの削除"
|
||||
|
|
|
@ -18,6 +18,7 @@ import generateUserToken from '@/misc/generate-native-user-token.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import UsersChart from '@/core/chart/charts/users.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { UserService } from '@/core/UserService.js';
|
||||
|
@ -59,13 +60,13 @@ export class SignupService {
|
|||
|
||||
// Validate username
|
||||
if (!this.userEntityService.validateLocalUsername(username)) {
|
||||
throw new Error('INVALID_USERNAME');
|
||||
throw new IdentifiableError('be85f7f4-1dd3-4107-bce4-07cdb0cbb0c3', 'INVALID_USERNAME');
|
||||
}
|
||||
|
||||
if (password != null && passwordHash == null) {
|
||||
// Validate password
|
||||
if (!this.userEntityService.validatePassword(password)) {
|
||||
throw new Error('INVALID_PASSWORD');
|
||||
throw new IdentifiableError('d5f4959c-a881-41e8-b755-718fbf161258', 'INVALID_PASSWORD');
|
||||
}
|
||||
|
||||
// Generate hash of password
|
||||
|
@ -78,12 +79,12 @@ export class SignupService {
|
|||
|
||||
// Check username duplication
|
||||
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||
throw new Error('DUPLICATED_USERNAME');
|
||||
throw new IdentifiableError('d412327a-1bd7-4b70-a982-7eec000db8fc', 'DUPLICATED_USERNAME');
|
||||
}
|
||||
|
||||
// Check deleted username duplication
|
||||
if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
|
||||
throw new Error('USED_USERNAME');
|
||||
throw new IdentifiableError('dd5f52be-2c95-4c39-ba45-dc2d74b3dd81', 'USED_USERNAME');
|
||||
}
|
||||
|
||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
||||
|
@ -91,7 +92,7 @@ export class SignupService {
|
|||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
||||
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||
if (isPreserved) {
|
||||
throw new Error('USED_USERNAME');
|
||||
throw new IdentifiableError('adad138b-9c63-41bf-931e-6b050fd3bb8d', 'DENIED_USERNAME');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +122,9 @@ export class SignupService {
|
|||
host: IsNull(),
|
||||
});
|
||||
|
||||
if (exist) throw new Error(' the username is already used');
|
||||
if (exist) {
|
||||
throw new IdentifiableError('d412327a-1bd7-4b70-a982-7eec000db8fc', 'DUPLICATED_USERNAME');
|
||||
}
|
||||
|
||||
account = await transactionalEntityManager.save(new MiUser({
|
||||
id: this.idService.gen(),
|
||||
|
|
|
@ -16,6 +16,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||
import { EmailService } from '@/core/EmailService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
|
@ -74,6 +75,11 @@ export class SignupApiService {
|
|||
) {
|
||||
const body = request.body;
|
||||
|
||||
function error(status: number, error: { id: string, [x: string]: string }) {
|
||||
reply.code(status);
|
||||
return { error };
|
||||
}
|
||||
|
||||
// Verify *Captcha
|
||||
// ただしテスト時はこの機構は障害となるため無効にする
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
|
@ -115,24 +121,30 @@ export class SignupApiService {
|
|||
const emailAddress = body['emailAddress'];
|
||||
|
||||
if (this.meta.emailRequiredForSignup) {
|
||||
if (emailAddress == null || typeof emailAddress !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
if (emailAddress == null || typeof emailAddress !== 'string' || emailAddress === '') {
|
||||
return error(400, {
|
||||
id: '33b104c9-2f22-4640-b27a-40979bde4a77',
|
||||
message: 'Email address is not present or is invalid.',
|
||||
});
|
||||
}
|
||||
|
||||
const res = await this.emailService.validateEmailForAccount(emailAddress);
|
||||
if (!res.available) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: '75ece55a-7869-49b1-b796-c0634224fcae',
|
||||
message: 'You cannot use this email address.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let ticket: MiRegistrationTicket | null = null;
|
||||
|
||||
if (this.meta.disableRegistration) {
|
||||
if (invitationCode == null || typeof invitationCode !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
if (invitationCode == null || typeof invitationCode !== 'string' || invitationCode === '') {
|
||||
return error(400, {
|
||||
id: 'c8324ccf-7153-47a0-90a3-682eb06ba10d',
|
||||
message: 'Invitation code is not present or is invalid.',
|
||||
});
|
||||
}
|
||||
|
||||
ticket = await this.registrationTicketsRepository.findOneBy({
|
||||
|
@ -140,47 +152,69 @@ export class SignupApiService {
|
|||
});
|
||||
|
||||
if (ticket == null || ticket.usedById != null) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: 'f08118af-8358-441c-b992-b5b0bbd337d2',
|
||||
message: 'Invitation code not found or already used.',
|
||||
});
|
||||
}
|
||||
|
||||
if (ticket.expiresAt && ticket.expiresAt < new Date()) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: '3277822c-29dd-4bc9-ad57-47af702f78b8',
|
||||
message: 'Invitation code has expired.',
|
||||
});
|
||||
}
|
||||
|
||||
// メアド認証が有効の場合
|
||||
if (this.meta.emailRequiredForSignup) {
|
||||
// メアド認証済みならエラー
|
||||
if (ticket.usedBy) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: 'f08118af-8358-441c-b992-b5b0bbd337d2',
|
||||
message: 'Invitation code not found or already used.',
|
||||
});
|
||||
}
|
||||
|
||||
// 認証しておらず、メール送信から30分以内ならエラー
|
||||
if (ticket.usedAt && ticket.usedAt.getTime() + (1000 * 60 * 30) > Date.now()) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: 'f08118af-8358-441c-b992-b5b0bbd337d2',
|
||||
message: 'Invitation code not found or already used.',
|
||||
});
|
||||
}
|
||||
} else if (ticket.usedAt) {
|
||||
reply.code(400);
|
||||
return;
|
||||
return error(400, {
|
||||
id: 'f08118af-8358-441c-b992-b5b0bbd337d2',
|
||||
message: 'Invitation code not found or already used.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.meta.emailRequiredForSignup) {
|
||||
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
|
||||
return error(400, {
|
||||
id: '9c20a0c3-c9e7-418f-8058-767f4e345bd4',
|
||||
code: 'DUPLICATED_USERNAME',
|
||||
message: 'Username already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
// Check deleted username duplication
|
||||
if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
|
||||
throw new FastifyReplyError(400, 'USED_USERNAME');
|
||||
return error(400, {
|
||||
id: '90e84f35-599a-468c-b420-98139fe9f988',
|
||||
code: 'USED_USERNAME',
|
||||
message: 'Username was previously used by another user.',
|
||||
});
|
||||
}
|
||||
|
||||
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||
if (isPreserved) {
|
||||
throw new FastifyReplyError(400, 'DENIED_USERNAME');
|
||||
return error(400, {
|
||||
id: 'e26cbcc3-7a0c-4cf4-988f-533f56ca72bf',
|
||||
code: 'DENIED_USERNAME',
|
||||
message: 'This username is not allowed.',
|
||||
});
|
||||
}
|
||||
|
||||
const code = secureRndstr(16, { chars: L_CHARS });
|
||||
|
@ -236,6 +270,41 @@ export class SignupApiService {
|
|||
token: secret,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
case 'be85f7f4-1dd3-4107-bce4-07cdb0cbb0c3':
|
||||
return error(400, {
|
||||
id: 'f6bff66c-a3f9-48b8-b56c-3c3ffc49bfdd',
|
||||
code: 'INVALID_USERNAME',
|
||||
message: 'Username is invalid.',
|
||||
});
|
||||
case 'd5f4959c-a881-41e8-b755-718fbf161258':
|
||||
return error(400, {
|
||||
id: '6dffa54e-9f5f-4c07-9662-e5c75ab63ee5',
|
||||
code: 'INVALID_PASSWORD',
|
||||
message: 'Password is invalid.',
|
||||
});
|
||||
case 'd412327a-1bd7-4b70-a982-7eec000db8fc':
|
||||
return error(400, {
|
||||
id: '9c20a0c3-c9e7-418f-8058-767f4e345bd4',
|
||||
code: 'DUPLICATED_USERNAME',
|
||||
message: 'Username already exists.',
|
||||
});
|
||||
case 'dd5f52be-2c95-4c39-ba45-dc2d74b3dd81':
|
||||
return error(400, {
|
||||
id: '90e84f35-599a-468c-b420-98139fe9f988',
|
||||
code: 'USED_USERNAME',
|
||||
message: 'Username was previously used by another user.',
|
||||
});
|
||||
case 'adad138b-9c63-41bf-931e-6b050fd3bb8d':
|
||||
return error(400, {
|
||||
id: 'e26cbcc3-7a0c-4cf4-988f-533f56ca72bf',
|
||||
code: 'DENIED_USERNAME',
|
||||
message: 'This username is not allowed.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString());
|
||||
}
|
||||
}
|
||||
|
@ -247,11 +316,20 @@ export class SignupApiService {
|
|||
|
||||
const code = body['code'];
|
||||
|
||||
function error(status: number, error: { id: string, [x: string]: string }) {
|
||||
reply.code(status);
|
||||
return { error };
|
||||
}
|
||||
|
||||
try {
|
||||
const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code });
|
||||
|
||||
if (this.idService.parse(pendingUser.id).date.getTime() + (1000 * 60 * 30) < Date.now()) {
|
||||
throw new FastifyReplyError(400, 'EXPIRED');
|
||||
return error(400, {
|
||||
id: 'e8b5b1ce-c7fe-456f-b06b-467cd16c060f',
|
||||
code: 'EXPIRED',
|
||||
message: 'This link has expired.',
|
||||
});
|
||||
}
|
||||
|
||||
const { account, secret } = await this.signupService.signup({
|
||||
|
|
|
@ -268,6 +268,7 @@ async function onSubmit(): Promise<void> {
|
|||
|
||||
const res = await fetch(`${config.apiUrl}/signup`, {
|
||||
method: 'POST',
|
||||
redirect: 'error',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
@ -277,7 +278,8 @@ async function onSubmit(): Promise<void> {
|
|||
return null;
|
||||
});
|
||||
|
||||
if (res && res.ok) {
|
||||
if (res) {
|
||||
if (res.ok) {
|
||||
if (res.status === 204 || instance.emailRequiredForSignup) {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
|
@ -295,6 +297,52 @@ async function onSubmit(): Promise<void> {
|
|||
await login(resJson.token);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const resJson = (await res.json()) as {
|
||||
error?: {
|
||||
id: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
let message: string | null = null;
|
||||
|
||||
if (resJson.error != null) {
|
||||
if (resJson.error.message != null) {
|
||||
message = resJson.error.message;
|
||||
} else if (resJson.error.id != null) {
|
||||
message = i18n.ts.somethingHappened + '\n' + resJson.error.id;
|
||||
}
|
||||
|
||||
switch (resJson.error.id) {
|
||||
case '33b104c9-2f22-4640-b27a-40979bde4a77':
|
||||
message = i18n.ts._signup._errors.emailInvalid;
|
||||
break;
|
||||
case '75ece55a-7869-49b1-b796-c0634224fcae':
|
||||
message = i18n.ts._signup._errors.emailNotAllowed;
|
||||
break;
|
||||
case 'c8324ccf-7153-47a0-90a3-682eb06ba10d':
|
||||
message = i18n.ts._signup._errors.invitationCodeInvalid;
|
||||
break;
|
||||
case '3277822c-29dd-4bc9-ad57-47af702f78b8':
|
||||
message = i18n.ts._signup._errors.invitationCodeExpired;
|
||||
break;
|
||||
case 'f08118af-8358-441c-b992-b5b0bbd337d2':
|
||||
message = i18n.ts._signup._errors.invitationCodeNotFoundOrUsed;
|
||||
break;
|
||||
case '9c20a0c3-c9e7-418f-8058-767f4e345bd4':
|
||||
case '90e84f35-599a-468c-b420-98139fe9f988':
|
||||
message = i18n.ts._signup._errors.usernameAlreadyUsed;
|
||||
break;
|
||||
case 'e26cbcc3-7a0c-4cf4-988f-533f56ca72bf':
|
||||
message = i18n.ts._signup._errors.usernameNotAllowed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onSignupApiError(message ? { title: i18n.ts.somethingHappened, text: message } : undefined);
|
||||
}
|
||||
} else {
|
||||
onSignupApiError();
|
||||
}
|
||||
|
@ -302,17 +350,16 @@ async function onSubmit(): Promise<void> {
|
|||
submitting.value = false;
|
||||
}
|
||||
|
||||
function onSignupApiError() {
|
||||
function onSignupApiError(message?: { title?: string; text: string }): void {
|
||||
submitting.value = false;
|
||||
hcaptcha.value?.reset?.();
|
||||
mcaptcha.value?.reset?.();
|
||||
recaptcha.value?.reset?.();
|
||||
turnstile.value?.reset?.();
|
||||
testcaptcha.value?.reset?.();
|
||||
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.somethingHappened,
|
||||
...(message ?? { text: i18n.ts.somethingHappened }),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue