【もりけん塾】JS課題28 – Vanilla JSでパスワード変更ページを作成 –

フロントエンド

パスワードをお忘れの方ページで登録済みのメールアドレスを入れると、パスワード変更ページへ遷移し、パスワードの再発行ができる機能を作成しました。

こんにちは。Webコーダーのはるです。

現在、所属している「もりけん塾」でJavaScriptのハンズオン課題 に取り組み、レビューをいただいています。

今日は、課題28 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)

実装する過程で学んだことを、学習ノートとして記録していきます。

認識の間違えている箇所がありましたら、お問い合わせからご指摘いただけるとうれしいです。

JavaScript課題28の仕様

    (もりけん先生のJavaScriptハンズオン課題より)

    JavaScript課題28の記録

    会員登録された内容をローカルストレージに保存

    この課題から以下の仕様が追加されたので、まずこの部分を修正していきました。

    – 会員登録をした内容をローカルストレージに保存しておく
    – ログイン時は、入力値とローカルストレージの値を照合する

    ※ローカルストレージは、セキュリティの観点から仕様するべきではないですが、あくまで学習のために使用しています。( ref: ローカルストレージは使うな

    会員登録の時

    会員登録でsubmitボタンを押すと、inputに入力された情報がローカルストレージに登録されます。

    まず、inputされたデータを取得してオブジェクトに格納。

    submitButton.addEventListener("click", (e) => {
     e.preventDefault();
    
     const inputsData = {
      name: nameOfInput.value,
      email: emailOfInput.value,
      password: passwordOfInput.value
     };
    すでに登録されているメールアドレスでないかを先にチェックします。
    – ローカルストレージに”registeredData”というキー名のデータがあるか
    – データ内のメールアドレスと、入力したメールアドレスが同じか
    すでに登録されていれば、「一致するアカウントが見つかりませんでした」としてエラー文を出します
    const registeredData = JSON.parse(localStorage.getItem("registeredData"));
    if (registeredData && inputsData.email === registeredData.email) {
     submitButton.disabled = true;
     showErrorMessage(e.target);
     return;
    }

    メールアドレスが未登録だった場合は、入力値を”registerdData”としてローカルストレージに保存。

    会員登録完了ページへ遷移します。
    localStorage.setItem("registeredData", JSON.stringify(inputsData));
     window.location.href = "./register-done.html";
    });
     JSON.stringify と JSON.parse
    ローカルストレージは文字列しか保存ができないため、値にオブジェクトをセットしたい場合は、JSON.stringifyでJSON文字列に変換する必要があります。ローカルストレージから値を取得して、オブジェクトに戻したい場合も JSON.parseで変換する必要があります。

    ログインの時

    ログインするときは、入力値がローカルストレージに登録されている”registeredData”の値と照合します。

    照合は、ローカルストレージ(仮想サーバーの代わり)との通信をする想定なので、promise を使用しました。

    const checkToRegistered = () => {
     return new Promise((resolve, reject) => {
      const inputsValues = {
       userId: userIdOfInput.value,
       password: passwordOfInput.value
      }
     const registeredData = JSON.parse(localStorage.getItem("registeredData"));
    
     if ((inputsValues.userId === registeredData.name || inputsValues.userId === registeredData.email) && (inputsValues.password === registeredData.password)) {
      resolve({ token: chance.apple_token(), ok: true, code: 200 });
     } else {
      reject({ ok: false, code: 401 });
     }
    })
    }

    照合の条件式、

    • 入力ユーザーIDと登録ユーザー名 or 登録メールアドレス が等しい

    かつ

    • 入力パスワードと登録メールアドレス が等しい

    が満たされれば、tokenが発行されます。

    /* この部分 */
    
    if ((inputsValues.userId === registeredData.name || inputsValues.userId === registeredData.email) && (inputsValues.password === registeredData.password)) {
      resolve({ token: chance.apple_token(), ok: true, code: 200 });
     } else {
      reject({ ok: false, code: 401 });
     }
    }

    照合後、resolve, rejectで渡されるオブジェクトが 変数resultに格納されます。

    const tryToLogin = async() => {
     let result;
     try {
      result = await checkToRegistered();
      localStorage.setItem("token", result.token);
     } catch(rejectObj) {
      result = rejectObj;
     } finally {
      window.location.href = result.token ? "./index.html" : "./notautherize.html";
     }
    }

    finallyで、渡ってきたオブジェクトに “token” の値があればログインができる仕組みです。

    これらは、submitボタンが押されたときに実行されます。

    submitButton.addEventListener("click", tryToLogin);

    パスワードをお忘れの方へページの作成

    新しいページを作成。

    会員登録済みのメールアドレスを入力してSubmitボタンを押すと、パスワード変更のページへ遷移するようにしました。

    ここでもpromiseを使用して、emailOfInput.value === registeredData.email の場合はtokenを発行しました。

    const checkToRegistered = () => {
     return new Promise((resolve, reject) => {
      const registeredData = JSON.parse(localStorage.getItem("registeredData"));
    
      if (emailOfInput.value === registeredData.email) {
       resolve({ token: chance.apple_token(), ok: true, code: 200 });
      } else {
       reject({ ok: false, code: 401 });
      }
     })
    }
    ログイン時のtokenと区別するために “passwordReissueToken” として発行。
    localStorage.setItem("passwordReissueToken", result.token);
    ログイン時のtokenと区別する?
    同じtokenにしてしまうと、パスワード変更ページでメールアドレスの照合に成功してtokenが発行されるとそれだけでログインが可能になってしまう
    const tryToSubmit = async() => {
     let result;
      try {
       result = await checkToRegistered();
       localStorage.setItem("passwordReissueToken", result.token);
      } catch(rejectObj) {
       result = rejectObj;
       submitButton.nextElementSibling.textContent = "一致するアカウントが見つかりませんでした";
       submitButton.disabled = true;
       return;
      }
      const urlParameter = `?token=${result.token}`;
      window.location.href = `./register/password.html${urlParameter}`;
     }
    }
    レビュー
    元々、finallyを使用して if(!result.token) 条件下は、パスワード変更ページへ遷移するようにしていましたが、catch節の場合はreturnで抜けて、finallyは使わない方がわかりやすいコードになると教えていただきました。

    メールアドレスが合っていた場合に発行されたtokenは、URLパラメータに埋め込まれます。

    /* この部分 */
    const urlParameter = `?token=${result.token}`;
    window.location.href = `./register/password.html${urlParameter}`;

    tryToSubmit関数 は、submitボタンを押したときに実行されます。

    submitButton.addEventListener("click", tryToSubmit);

    パスワード変更ページの作成

    このようなぺージを作成しました。

    URLSearchParams

    このページに遷移してきた時、

    • URLパラメータのtokenとローカルストレージのtokenが等しいか

    確認をして、間違っていた場合は権限なしページへ移動するようにします。

    (これは、ローカルストレージがサーバー側の役割をしている想定です。)

    const urlParameter = Object.fromEntries(new URLSearchParams(window.location.search));
    const currentPageToken = urlParameter.token;
    const registeredToken = localStorage.getItem("passwordReissueToken");
    
    if(currentPageToken !== registeredToken) window.location.href = "./../notautherize.html";
    URLパラメータとは、サーバーに情報を送るためにURLに付与するパラメータのことで、?の後に key=valueの形で書かれる。

    URLパラメータをJavaScriptで扱うときは、URLSearchParamsを使用すると簡単に扱うことができます。

    URLSearchParams() コンストラクターは、新しい URLSearchParams オブジェクトを作成して返します。 先頭の ‘?’ 文字は無視されます。(MDNより)

    URLSearchParams オブジェクトをObject.FromEntries()することで、URLパラメータのキーと値をオブジェクトに変換することができます。

    const urlParameter = Object.fromEntries(new URLSearchParams(window.location.search));
    
    // output
    // {token: '8474a1996c12bd1d1bb68dac4983e8e505ab9596005b5303f6e42904a118b465'}

    バリデーション

    新規パスワード入力と、確認のためのパスワード入力の2つのinputがあり、

    • 空欄でないか
    • 8文字以上の大小英数字か
    • 2つのinputが同じ値か

    をチェックしました。

    レビュー
    会員登録やログインページのバリデーションは modules/validation.js ファイルを作成して共通のものを使用していました。
    今回は、空欄チェック・8文字以上の大小英数字はmodulesを使い回し、2つのinputが同じ値かのチェックについては、パスワード変更ページのjsファイルに書いた方が良いとアドバイスいただきました。
    後者は、他のページで使用する予定はなく、このページ特有のものだからです。
    ※修正後のコードです
    formElements.forEach(element => {
     element.classList.add("invalid");
    
     element.addEventListener("blur", () => {
      checkFormValidityInBlur(submitButton, element);
    
      if (document.getElementsByClassName("invalid").length === 0) {
       showErrorMessageInNotMatchInputsValues();
       checkFormValidityToEnableSubmitButton();
      }
     });
    });

    formElementsは、2つのinputが配列化されています。

    それぞれのinputがblurされると、modulesのバリデーション(空欄・8文字以上大小英数字チェック)が走ります。

    checkFormValidityInBlur(submitButton, element);

    バリデーションが通っていないinputに付与しているinvalidクラスがなければ、2つのinputの値が等しいかのチェックが走ります。

    if (document.getElementsByClassName("invalid").length === 0) {
      showErrorMessageInNotMatchInputsValues();
      checkFormValidityToEnableSubmitButton();
    }
    
    /* それぞれの関数 */
    const showErrorMessageInNotMatchInputsValues = () => errorOfConfirmPassword.textContent = passwordOfInput.value !== confirmPasswordOfInput.value ? "上記のpasswordと異なります。もう一度入力してください。": "";
    const checkFormValidityToEnableSubmitButton = () => submitButton.disabled = passwordOfInput.value !== confirmPasswordOfInput.value;
    

     

    パスワードを表示・非表示させる

    仕様にはありませんでしたが、ユーザーがパスワードを表示・非表示の切り替えをできるようにしました。2つのinputが合っているかのチェックにも合ったほうがわかりやすいと思ったからです。

    目のマークを押すと、togglePasswordDisplay関数が実行されます。

    eyeIcons.forEach(icon => {
     icon.addEventListener("click", (e) => togglePasswordDisplay(e.target))
    })
    
    const togglePasswordDisplay = target => {
     const selectedInput = target.nextElementSibling; 
     selectedInput.type = selectedInput.type === "password" ? "text" : "password";
     target.classList.toggle("is-open");
    }
    レビュー
    togglePasswordDisplay()関数は、元々if文で条件式を書いており冗長なコードだったのですが、三項演算子を使用することで3行でスッキリまとめることができました。

    // 修正前のif文

    if (selectedInput.type === "password"){
     selectedInput.type = "text";
     target.classList.add("is-open");
    } else {
     selectedInput.type = "password";
     target.classList.remove("is-open");
    }

    パスワード変更

    パスワード変更の関数を作成しました。

    流れは、

    ① ローカルストレージの “registeredData”を取得して、オブジェクトに変換
    ② オブジェクト内のpasswordキーの値だけ、input入力値に変更
    ③ 変更済みのオブジェクトをローカルストレージの”regiseredData”に上書き保存

    const changePassword = () => {
     const passwordValue = passwordOfInput.value;
     const userData = JSON.parse(localStorage.getItem("registeredData"));
     userData.password = passwordValue;
     localStorage.setItem("registeredData", JSON.stringify(userData));
    }
    レビュー
    ・関数名を、changeRegisteredDataOfPassword() から changePassword() へ修正。
    → 長々と書かなくても十分に意味が伝わるため
    ・変数名を、newPasswordからpasswordValueに修正。
    → inputの値を取得した時点では、まだ新しいパスワードではないため
    inputのバリデーションが通って、submitボタンをクリックするとパスワードが変更されます。
    submitButton.addEventListener("click", () => {
     changePassword();
    
     const token = chance.apple_token();
     const newUrlParameter = `?token=${token}`;
    
     localStorage.setItem("passwordReissueToken", token);
     window.location.href = `./passworddone.html${newUrlParameter}`;
    });

    それに伴って、新しいpasswordReissueTokenが発行されて、パスワード変更完了ページへ遷移します。

    レビュー
    変数名newTokenを tokenに修正。
    → このファイル内に、newに対抗するoldTokenを取得するコードが存在しないため。区別する必要がないと教えていただきました。

     

    パスワード変更完了ページを作成

    このようなぺージを作成しました。

    このページに遷移してきたとき、パスワード変更ページと同様に、ローカルストレージのtokenとURLパラメータのtokenが等しいかどうかの確認をします。

    const urlParameter = Object.fromEntries(new URLSearchParams(window.location.search));
    const currentPageToken = urlParameter.token;
    const registeredToken = localStorage.getItem("passwordReissueToken");
    
    if(currentPageToken !== registeredToken) window.location.href = "./../notautherize.html";
    
    localStorage.removeItem("passwordReissueToken");

    間違っていた場合は、権限なしページへ遷移。

    合っていた場合は、passwordReissueTokenを削除するようにしました。

    この後は、ログインページに戻り新しいパスワードでログインできるようになります。

    今回のコード

    Sign Upから会員登録をした後、「パスワードをお忘れの方はこちら」から実際にパスワード変更手続きが可能です。

    //
    学習に使用している本は、JavaScript本格入門・独習JavaScriptです。

      

    あとがき

    もりけん先生(@terrace_tec)、さえさん(@sae_prog)、まいさん(@mai2022web)、ちひろさん(@chihiro7029) お忙しい中レビューいただきありがとうございました!

    今回も、細かなバリデーションの動作確認や、違和感を感じた部分をご指摘いただき、Refactorを重ねてより良いコードになりました。

    一部仕様を満たせていない旨ご指摘もあり、反省です。

    また、関数名と変数名はいまだに命名がうまくできていないと知りました。

    いかに短くシンプルな命名で、コード内容を的確に表すかを考えて次に活かしたいと思います。

    今日は以上です。

    //

    【もりけん塾で勉強しています】

    もりけん先生(@terrace_tec)のHPはこちら