diff --git a/app/actions/authentication/user-authentication-action.ts b/app/actions/authentication/user-authentication-action.ts index db67dd1..a36a989 100644 --- a/app/actions/authentication/user-authentication-action.ts +++ b/app/actions/authentication/user-authentication-action.ts @@ -1,5 +1,6 @@ import UserAccountManager from "../../libraries/authentication/user-account-manager"; import SignInWithEmailPasswordResponse from "../../models/authentication/signin-with-email-password-response"; +import LoginInputErrors from "../../messages/authentication/login-input-errors"; /** * ユーザー認証を行うアクション。 @@ -14,6 +15,17 @@ export default class UserAuthenticationAction { ) { } + /** + * ログインのバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @param password パスワード。 + * @returns バリデーション結果。 + */ + public validateLogin(mailAddress: string, password: string): LoginInputErrors | null { + const result = this.userAccountManager.validateLogin(mailAddress, password); + return result; + } + /** * ログインする。 * @param mailAddress メールアドレス。 diff --git a/app/actions/authentication/user-registration-action.ts b/app/actions/authentication/user-registration-action.ts index a8aa94e..843f9b0 100644 --- a/app/actions/authentication/user-registration-action.ts +++ b/app/actions/authentication/user-registration-action.ts @@ -1,5 +1,6 @@ import UserAccountManager from "../../libraries/authentication/user-account-manager"; import SignUpResponse from "../../models/authentication/signup-response"; +import AuthenticationUserRegistrationInputErrors from "../../messages/authentication/authentication-user-registration-input-errors"; /** * 認証するユーザーの登録をを行うアクション。 @@ -14,6 +15,18 @@ export default class UserRegistrationAction { ) { } + /** + * ユーザー登録のバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @param password パスワード。 + * @param confirmPassword 再確認パスワード。 + * @returns バリデーション結果。 + */ + public validateRegistrationUser(mailAddress: string, password: string, confirmPassword: string): AuthenticationUserRegistrationInputErrors | null { + const result = this.userAccountManager.validateRegistrationUser(mailAddress, password, confirmPassword); + return result; + } + /** * ユーザーを登録する。 * @param mailAddress メールアドレス。 diff --git a/app/actions/user/sns-user-registration-action.ts b/app/actions/user/sns-user-registration-action.ts index d8c3b52..6a4ed43 100644 --- a/app/actions/user/sns-user-registration-action.ts +++ b/app/actions/user/sns-user-registration-action.ts @@ -1,4 +1,5 @@ import UserProfileManager from "../../libraries/user/user-profile-manager"; +import ClientUserRegistrationInputErrors from "../../messages/user/client-user-registration-input-errors"; /** * SNSのユーザー登録を行うアクション。 @@ -13,6 +14,17 @@ export default class SnsUserRegistrationAction { ) { } + /** + * ユーザー登録のバリデーションを行う。 + * @param authenticationProviderId 認証プロバイダID。 + * @param userName ユーザー名。 + * @returns バリデーション結果。 + */ + public validateRegistrationUser(authenticationProviderId: string, userName: string): ClientUserRegistrationInputErrors | null { + const result = this.userProfileManager.validateRegistrationUser(authenticationProviderId, userName); + return result; + } + /** * ユーザーを登録する。 * @param authenticationProviderId 認証プロバイダID。 diff --git a/app/libraries/authentication/authentication-validator.ts b/app/libraries/authentication/authentication-validator.ts new file mode 100644 index 0000000..ea4b009 --- /dev/null +++ b/app/libraries/authentication/authentication-validator.ts @@ -0,0 +1,28 @@ +import systemMessages from "../../messages/system-messages"; + +/** + * 認証のバリデーションを行うクラス。 + */ +export default class AuthenticationValidator { + /** + * メールアドレスのバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @returns エラーメッセージ。 + */ + protected static validateMailAddress(mailAddress: string): string[] { + const mailAddressErrors: string[] = []; + if (!mailAddress.match(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)) mailAddressErrors.push(systemMessages.error.invalidMailAddress); + return mailAddressErrors; + } + + /** + * パスワードのバリデーションを行う。 + * @param password パスワード。 + * @returns エラーメッセージ。 + */ + protected static validatePassword(password: string): string[] { + const passwordErrors: string[] = []; + if (password.length < 8 || !password.match(/^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i)) passwordErrors.push(systemMessages.error.invalidPasswordOnSetting); + return passwordErrors; + } +} \ No newline at end of file diff --git a/app/libraries/authentication/login-validator.ts b/app/libraries/authentication/login-validator.ts new file mode 100644 index 0000000..531e381 --- /dev/null +++ b/app/libraries/authentication/login-validator.ts @@ -0,0 +1,31 @@ +import AuthenticationValidator from './authentication-validator'; +import LoginInputErrors from '../../messages/authentication/login-input-errors'; + +/** + * ログインのバリデーションを行うクラス。 + */ +export default class LoginValidator extends AuthenticationValidator { + /** + * ログインのバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @param password パスワード。 + * @returns バリデーション結果。 + */ + public static validate(mailAddress: string, password: string): LoginInputErrors | null { + // 無効なメールアドレスの場合、エラーメッセージを保持する。 + const mailAddressErrors: string[] = AuthenticationValidator.validateMailAddress(mailAddress); + + // パスワードの長さが8文字未満、英数字が含まれていない場合、エラーメッセージを保持する。 + const passwordErrors: string[] = AuthenticationValidator.validatePassword(password); + + // エラーがない場合、nullを返す。 + if (mailAddressErrors.length === 0 && passwordErrors.length === 0) return null; + + // エラーがある場合、エラーメッセージを返す。 + const loginInputErrors: LoginInputErrors = { + mailAddress: mailAddressErrors, + password: passwordErrors, + }; + return loginInputErrors; + } +} \ No newline at end of file diff --git a/app/libraries/authentication/sign-up-validator.ts b/app/libraries/authentication/sign-up-validator.ts index d562aea..8afbdcb 100644 --- a/app/libraries/authentication/sign-up-validator.ts +++ b/app/libraries/authentication/sign-up-validator.ts @@ -1,22 +1,36 @@ import systemMessages from "../../messages/system-messages"; +import AuthenticationUserRegistrationInputErrors from "../../messages/authentication/authentication-user-registration-input-errors"; +import AuthenticationValidator from "./authentication-validator"; /** * サインアップのバリデーションを行うクラス。 */ -export default class SignUpValidator { +export default class SignUpValidator extends AuthenticationValidator { /** * サインアップのバリデーションを行う。 + * @param mailAddress メールアドレス。 * @param password パスワード。 * @param confirmPassword 再確認パスワード。 * @returns バリデーション結果。 - * @throws バリデーションに失敗した場合、エラーを投げる。 */ - public static validate(password: string, confirmPassword: string): boolean { - // パスワードの長さが8文字未満、英数字が含まれていない場合、エラーを投げる。 - if (password.length < 8 || !password.match(/^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i)) throw new Error(systemMessages.error.invalidPasswordOnSetting); + public static validate(mailAddress: string, password: string, confirmPassword: string): AuthenticationUserRegistrationInputErrors | null { + // 無効なメールアドレスの場合、エラーメッセージを保持する。 + const mailAddressErrors: string[] = AuthenticationValidator.validateMailAddress(mailAddress); - // パスワードと再確認パスワードが一致しない場合、エラーを返す。 - if (password !== confirmPassword) throw new Error(systemMessages.error.passwordMismatch); - return true; + // パスワードの長さが8文字未満、英数字が含まれていない場合、エラーメッセージを保持する。 + const passwordErrors: string[] = AuthenticationValidator.validatePassword(password); + + // パスワードと再確認パスワードが一致しない場合、エラーメッセージを保持する。 + if (password !== confirmPassword) passwordErrors.push(systemMessages.error.passwordMismatch); + + // エラーがない場合、nullを返す。 + if (mailAddressErrors.length === 0 && passwordErrors.length === 0) return null; + + // エラーがある場合、エラーメッセージを返す。 + const userRegistrationInputErrors: AuthenticationUserRegistrationInputErrors = { + mailAddress: mailAddressErrors, + password: passwordErrors, + }; + return userRegistrationInputErrors; } } \ No newline at end of file diff --git a/app/libraries/authentication/user-account-manager.ts b/app/libraries/authentication/user-account-manager.ts index 4eff29d..384461f 100644 --- a/app/libraries/authentication/user-account-manager.ts +++ b/app/libraries/authentication/user-account-manager.ts @@ -3,13 +3,16 @@ import SignUpResponse from "../../models/authentication/signup-response"; import IAuthenticationClient from "./i-authentication-client"; import SignInWithEmailPasswordResponse from "../../models/authentication/signin-with-email-password-response"; import SignUpValidator from "./sign-up-validator"; +import AuthenticationUserRegistrationInputErrors from "../../messages/authentication/authentication-user-registration-input-errors"; +import LoginInputErrors from "../../messages/authentication/login-input-errors"; +import LoginValidator from "./login-validator"; /** - * ユーザー管理を行うクラス。 + * 認証ユーザー管理を行うクラス。 */ export default class UserAccountManager { /** - * ユーザー管理を行うクラスを生成する。 + * 認証ユーザー管理を行うクラスを生成する。 * @param authenticationClient ユーザー認証のクライアント。 */ constructor( @@ -18,7 +21,19 @@ export default class UserAccountManager { } /** - * ユーザーを登録する。 + * 認証ユーザー登録のバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @param password パスワード。 + * @param confirmPassword 再確認パスワード。 + * @returns バリデーション結果。 + */ + public validateRegistrationUser(mailAddress: string, password: string, confirmPassword: string): AuthenticationUserRegistrationInputErrors | null { + const result = SignUpValidator.validate(mailAddress, password, confirmPassword); + return result; + } + + /** + * 認証ユーザーを登録する。 * @param mailAddress メールアドレス。 * @param password パスワード。 * @param confirmPassword 再確認パスワード。 @@ -26,9 +41,6 @@ export default class UserAccountManager { */ public async register(mailAddress: string, password: string, confirmPassword: string): Promise { try { - // ユーザー登録のバリデーションを行う。 - SignUpValidator.validate(password, confirmPassword); - // ユーザーを登録する。 const response = await this.authenticationClient.signUp(mailAddress, password); return response; @@ -40,7 +52,7 @@ export default class UserAccountManager { } /** - * ユーザーを削除する。 + * 認証ユーザーを削除する。 * @param token トークン。 */ public async delete(token: string): Promise { @@ -54,6 +66,17 @@ export default class UserAccountManager { } } + /** + * ログインのバリデーションを行う。 + * @param mailAddress メールアドレス。 + * @param password パスワード。 + * @returns バリデーション結果。 + */ + public validateLogin(mailAddress: string, password: string): LoginInputErrors | null { + const result = LoginValidator.validate(mailAddress, password); + return result; + } + /** * ログインする。 * @param mailAddress メールアドレス。 diff --git a/app/libraries/user/user-profile-manager.ts b/app/libraries/user/user-profile-manager.ts index 83bebd9..5f5b395 100644 --- a/app/libraries/user/user-profile-manager.ts +++ b/app/libraries/user/user-profile-manager.ts @@ -3,6 +3,7 @@ import IUserRepository from "../../repositories/user/i-user-repository"; import UserRegistrationValidator from "./user-registration-validator"; import ProfileIdCreator from "./profile-id-creator"; import UserSetting from "../../models/user/user-setting"; +import ClientUserRegistrationInputErrors from "../../messages/user/client-user-registration-input-errors"; /** * ユーザー情報の管理を行うクラス。 @@ -17,6 +18,18 @@ export default class UserProfileManager { ) { } + /** + * ユーザー登録のバリデーションを行う。 + * @param authenticationProviderId 認証プロバイダID。 + * @param userName ユーザー名。 + * @param currentReleaseInformationId 現在のリリース情報ID。 + * @returns バリデーション結果。 + */ + public validateRegistrationUser(authenticationProviderId: string, userName: string): ClientUserRegistrationInputErrors | null { + const result = UserRegistrationValidator.validate(authenticationProviderId, userName); + return result; + } + /** * ユーザーを登録する。 * @param authenticationProviderId 認証プロバイダID。 @@ -25,9 +38,6 @@ export default class UserProfileManager { */ public async register(authenticationProviderId: string, userName: string, currentReleaseInformationId: number): Promise { try { - // ユーザー登録のバリデーションを行う。 - UserRegistrationValidator.validate(authenticationProviderId, userName); - // ユーザーを登録する。 const profileId = ProfileIdCreator.create(userName); const response = await this.userRepository.create(profileId, authenticationProviderId, userName, currentReleaseInformationId); diff --git a/app/libraries/user/user-registration-validator.ts b/app/libraries/user/user-registration-validator.ts index 0ad87f2..dcde5c4 100644 --- a/app/libraries/user/user-registration-validator.ts +++ b/app/libraries/user/user-registration-validator.ts @@ -1,4 +1,5 @@ import systemMessages from "../../messages/system-messages"; +import ClientUserRegistrationInputErrors from "../../messages/user/client-user-registration-input-errors"; /** * ユーザー登録のバリデーター。 @@ -9,17 +10,22 @@ export default class UserRegistrationValidator { * @param authenticationProviderId 認証プロバイダID。 * @param userName ユーザー名。 * @returns バリデーション結果。 - * @throws バリデーションに失敗した場合、エラーを投げる。 */ - public static validate(authenticationProviderId: string, userName: string): boolean { + public static validate(authenticationProviderId: string, userName: string): ClientUserRegistrationInputErrors | null { // 認証プロバイダIDが不正な場合、エラーを投げる。 if (!authenticationProviderId) throw new Error(systemMessages.error.authenticationFailed); - // ユーザー名が不正な場合、エラーを投げる。 - const regex = /^[a-zA-Z0-9]*@{1}[a-zA-Z0-9]*$/; - if (!regex.test(userName)) throw new Error(systemMessages.error.invalidUserName); + // ユーザー名が不正な場合、エラーメッセージを保持する。 + const userNameErrors: string[] = []; + if (!userName.match(/^[a-zA-Z0-9]*@{1}[a-zA-Z0-9]*$/)) userNameErrors.push(systemMessages.error.invalidUserName); - // バリデーションを通過した場合、trueを返す。 - return true; + // エラーがない場合、nullを返す。 + if (userNameErrors.length === 0) return null; + + // エラーがある場合、エラーメッセージを返す。 + const clientUserRegistrationInputErrors: ClientUserRegistrationInputErrors = { + userName: userNameErrors, + }; + return clientUserRegistrationInputErrors; } } \ No newline at end of file diff --git a/app/messages/authentication/authentication-user-registration-input-errors.ts b/app/messages/authentication/authentication-user-registration-input-errors.ts new file mode 100644 index 0000000..1e1958a --- /dev/null +++ b/app/messages/authentication/authentication-user-registration-input-errors.ts @@ -0,0 +1,14 @@ +/** + * 認証ユーザー登録の入力エラーを保持するインターフェース。 + */ +export default interface AuthenticationUserRegistrationInputErrors { + /** + * メールアドレスのエラーメッセージ。 + */ + mailAddress: string[]; + + /** + * パスワードのエラーメッセージ。 + */ + password: string[]; +} \ No newline at end of file diff --git a/app/messages/authentication/login-input-errors.ts b/app/messages/authentication/login-input-errors.ts new file mode 100644 index 0000000..d8feb60 --- /dev/null +++ b/app/messages/authentication/login-input-errors.ts @@ -0,0 +1,14 @@ +/** + * ログイン入力エラーを保持するインターフェース。 + */ +export default interface LoginInputErrors { + /** + * メールアドレスのエラーメッセージ。 + */ + mailAddress: string[]; + + /** + * パスワードのエラーメッセージ。 + */ + password: string[]; +} \ No newline at end of file diff --git a/app/messages/system-messages.ts b/app/messages/system-messages.ts index 46df1bd..6c42661 100644 --- a/app/messages/system-messages.ts +++ b/app/messages/system-messages.ts @@ -24,7 +24,7 @@ const systemMessages: Messages = { authenticationFailed: "認証に失敗しました。ログインし直してください。", authenticationProviderIdRetrievalFailed: "認証プロバイダIDの取得に失敗しました。", logoutFailed: "ログアウトに失敗しました。", - invalidUserName: "ユーザー名は「username@world」で入力してください。", + invalidUserName: "ユーザー名は「UserName@World」で入力してください。", userRegistrationFailed: "ユーザー登録に失敗しました。", userNotExists: "ユーザーが存在しません。", userDeletionFailed: "ユーザーの削除に失敗しました。", diff --git a/app/messages/user/client-user-registration-input-errors.ts b/app/messages/user/client-user-registration-input-errors.ts new file mode 100644 index 0000000..2e6d293 --- /dev/null +++ b/app/messages/user/client-user-registration-input-errors.ts @@ -0,0 +1,9 @@ +/** + * ユーザー登録の入力エラーを保持するインターフェース。 + */ +export default interface ClientUserRegistrationInputErrors { + /** + * ユーザー名。 + */ + userName: string[]; +} \ No newline at end of file diff --git a/app/messages/user/user-registration-input-errors.ts b/app/messages/user/user-registration-input-errors.ts deleted file mode 100644 index ed4358c..0000000 --- a/app/messages/user/user-registration-input-errors.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * 入力エラーのメッセージを保持するインターフェース。 - */ -export default interface UserRegistrationInputErrors { - /** - * ユーザー名に関するエラーメッセージ。 - */ - userName: string[]; -} \ No newline at end of file diff --git a/app/routes/app.setting/route.tsx b/app/routes/app.setting/route.tsx index c74c977..5cb6e21 100644 --- a/app/routes/app.setting/route.tsx +++ b/app/routes/app.setting/route.tsx @@ -139,8 +139,10 @@ export default function Setting() {
- - {getReleaseVersionOptions()} +
diff --git a/app/routes/auth.login/route.tsx b/app/routes/auth.login/route.tsx index 8d770a0..8aa071e 100644 --- a/app/routes/auth.login/route.tsx +++ b/app/routes/auth.login/route.tsx @@ -61,8 +61,12 @@ export const action = async ({ const mailAddress = formData.get("mailAddress") as string; const password = formData.get("password") as string; - // ログインする。 + // バリデーションを行う。 const userAuthenticationAction = context.userAuthenticationAction; + const inputErrors = userAuthenticationAction.validateLogin(mailAddress, password); + if (inputErrors) return json(inputErrors); + + // ログインする。 const response = await userAuthenticationAction.login(mailAddress, password); // IDトークンとリフレッシュトークンをセッションに保存する。 @@ -111,7 +115,7 @@ export default function Login() { const loaderData = useLoaderData(); const loaderErrorMessage = loaderData && "errorMessage" in loaderData ? loaderData.errorMessage : ""; const actionData = useActionData(); - const actionErrorMessage = actionData ? actionData.errorMessage : ""; + const actionErrorMessage = actionData && "errorMessage" in actionData ? actionData.errorMessage : ""; // システムメッセージを表示する。 const { showSystemMessage } = useContext(SystemMessageContext); @@ -122,12 +126,29 @@ export default function Login() { showSystemMessage("error", actionErrorMessage); }, [actionData]); + // バリデーションエラーを取得する。 + const inputErrors = actionData && "mailAddress" in actionData ? actionData : null; + return (
+ {inputErrors && inputErrors.mailAddress.length > 0 && ( +
    + {inputErrors.mailAddress.map((errorMessage, index) => ( +
  • {errorMessage}
  • + ))} +
+ )} + {inputErrors && inputErrors.password.length > 0 && ( +
    + {inputErrors.password.map((errorMessage, index) => ( +
  • {errorMessage}
  • + ))} +
+ )}