Chapter 19

TODOタスクとユーザを紐付けよう

req.isAuthenticated()でサインイン状態を判断しよう

7章では、セッションにユーザIDが保存されているかどうかでtrue、falseを返す定数sAuthを用意し、サインイン状態を判断しました。
本章では req.isAuthenticated() メソッドを利用してサインイン状態を判断するように修正します。
req.isAuthenticated() メソッドはpassport認証を通過 (サインイン) している場合は true を、そうでない場合は false を返すメソッドです。

まずindex.jsを修正する前に、index.jsのサインイン状態の判断について、どのような実装になっているかを確認してみましょう。

現時点のindex.jsはこちらです。

const express = require('express');
const router = express.Router();
const knex = require('../db/knex');

router.get('/', function (req, res, next) {
  const userId = req.session.userid;
  const isAuth = Boolean(userId);
  knex("tasks")
    .select("*")
    .then(function (results) {
      res.render('index', {
        title: 'ToDo App',
        todos: results,
        isAuth: isAuth,
      });
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

router.post('/', function (req, res, next) {
  const userId = req.session.userid;
  const isAuth = Boolean(userId);
  const todo = req.body.add;
  knex("tasks")
    .insert({user_id: 1, content: todo})
    .then(function () {
      res.redirect('/')
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

router.use('/signup', require('./signup'));
router.use('/signin', require('./signin'));
router.use('/logout', require('./logout'));

module.exports = router;

まず、const userId = req.session.userid;;でセッションに保存されているユーザIDを定数に格納し、const isAuth = Boolean(userId); で定数の中身をチェックしています。
isAuthがfalseであればセッションからユーザ情報を取得できなかった、つまりこのユーザはセッションにユーザ情報が保存されていないためサインインしていない、ということがわかります。
対して、trueであればサインイン済みとなるため、この定数isAuthでサインイン状態を判断しています。

前章でパスポート認証に適用したことで、不要になったコードを削除しましょう。

以下を削除してください。

  • const userId = req.session.userid;

次に、const isAuth = Boolean(userId);const isAuth = req.isAuthenticated(); に変更してください。

index.jsは次のようになります。

const express = require('express');
const router = express.Router();
const knex = require('../db/knex');

router.get('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  knex("tasks")
    .select("*")
    .then(function (results) {
      res.render('index', {
        title: 'ToDo App',
        todos: results,
        isAuth: isAuth,
      });
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

router.post('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  const todo = req.body.add;
  knex("tasks")
    .insert({user_id: 1, content: todo})
    .then(function () {
      res.redirect('/')
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

router.use('/signup', require('./signup'));
router.use('/signin', require('./signin'));
router.use('/logout', require('./logout'));

module.exports = router;

同様に、signup.js、signin.jsも修正してください。

signup.jsは次のようになります。

const express = require('express');
const router = express.Router();
const knex = require("../db/knex");
const bcrypt = require("bcrypt");

router.get('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  res.render('signup', {
    title: 'Sign up',
    isAuth: isAuth,
  });
});

router.post('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  const username = req.body.username;
  const password = req.body.password;
  const repassword = req.body.repassword;

  knex("users")
    .where({name: username})
    .select("*")
    .then(async function (result) {
      if (result.length !== 0) {
        res.render("signup", {
          title: "Sign up",
          errorMessage: ["このユーザ名は既に使われています"],
          isAuth: isAuth,
        })
      } else if (password === repassword) {
        const hashedPassword = await bcrypt.hash(password, 10);
        knex("users")
          .insert({name: username, password: hashedPassword})
          .then(function () {
            res.redirect("/");
          })
          .catch(function (err) {
            console.error(err);
            res.render("signup", {
              title: "Sign up",
              errorMessage: [err.sqlMessage],
              isAuth: isAuth,
            });
          });
      } else {
        res.render("signup", {
          title: "Sign up",
          errorMessage: ["パスワードが一致しません"],
          isAuth: isAuth,
        });
      }
    })
    .catch(function (err) {
      console.error(err);
      res.render("signup", {
        title: "Sign up",
        errorMessage: [err.sqlMessage],
        isAuth: isAuth,
      });
    });
});

module.exports = router;

signin.jsは次のようになります。

const express = require('express');
const router = express.Router();
const passport = require("passport");

router.get('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  res.render("signin", {
    title: "Sign in",
    isAuth: isAuth,
  });
});

router.post('/', passport.authenticate('local', {
    successRedirect: '/',
    failureRedirect: '/signin',
    failureFlash: true,
  }
));

module.exports = router;

それでは、サインインの状態の判断が可能になっているか確認してみましょう。
サーバを起動し、サインインしてください。

問題なくサインイン済みのトップページが表示されました。

適切なユーザIDに基づいてToDoタスクを追加、表示しよう

index.jsを開き、ToDoタスクを追加している処理を確認しましょう。
以下のようにuser_idに決め打ちで1を入れています。

.insert({user_id: 1, content: todo})

これを適切なユーザIDに変更します。

ユーザIDは req.user.id で取得できます。
このuserはpassport.jsのdeserializeUser()で作成されたオブジェクトで、models/user.jsのfindById()の結果 (ユーザ情報) が格納されています。
また、同様に、ユーザ名はreq.user.nameとすることで取得できます。

以上を踏まえて変更すると、index.jsのrouter.post()内は次のようになります。

router.post('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  const userId = req.user.id;
  const todo = req.body.add;
  knex("tasks")
    .insert({user_id: userId, content: todo})
    .then(function () {
      res.redirect('/')
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

const userId = req.user.id;を宣言し、.insert({user_id: userId, content: todo})に変更しました。

これでToDoタスクを入力し、「追加」を押下したユーザのIDがuser_idカラムに入るようになりました。

それでは、確認してみましょう。
MySQLにログインし、以下のSQLを実行してください。

select * from todo_app.tasks;

このような結果になります。

+----+---------+----------------------------------------+
| id | user_id | content                                |
+----+---------+----------------------------------------+
|  1 |       1 | 20時までに宿題を終わらせる                 |
|  2 |       1 | 部屋を掃除する                            |
|  3 |       1 | 食料品の買い出しに行く                     |
+----+---------+----------------------------------------+

これまでuser_idを決め打ちで1としていたため、各レコードのuser_idカラムには1が入っています。

次に、usersテーブルのレコードを確認してみましょう。
以下のSQLを実行してください。

select * from todo_app.users;

このような結果になります。

+----+------+--------------------------------------------------------------+
| id | name | password                                                     |
+----+------+--------------------------------------------------------------+
|  1 | root | $2b$10$.b.Bs3xSZm7aTG.mltswlOjOAjKqtTDiZCOw0PJoavnE0kMKF8iGK |
+----+------+--------------------------------------------------------------+

前章の最後にusersテーブルの全レコードを削除し、rootユーザを作り直したためこのようになっています。

ユーザIDが1のrootユーザでToDoタスクを追加しても変化が分からないため、もう1人ユーザを作成しましょう。
ユーザ名、パスワードは任意のもので構いません。
サーバを再起動し、サインアップしてください。

サインアップができ次第、再度usersテーブルを確認してみましょう。

select * from todo_app.users;

このような結果になります。

+----+------+--------------------------------------------------------------+
| id | name | password                                                     |
+----+------+--------------------------------------------------------------+
|  1 | root | $2b$10$.b.Bs3xSZm7aTG.mltswlOjOAjKqtTDiZCOw0PJoavnE0kMKF8iGK |
|  2 | test | $2b$10$I0fHPbxF.yabM1uOhSFp4.xR2JySTUFvbBYarx4nT.tQAfYuhTlP2 |
+----+------+--------------------------------------------------------------+

ユーザID2のユーザが追加されています。

それでは、作成したこのユーザID2のユーザでサインインし、ToDoタスクを追加してみましょう。

追加でき次第、再度tasksテーブルを確認してみましょう。

select * from todo_app.tasks;

このような結果になります。

+----+---------+-------------------------------------------------------+
| id | user_id | content                                               |
+----+---------+-------------------------------------------------------+
|  1 |       1 | 20時までに宿題を終わらせる                               |
|  2 |       1 | 部屋を掃除する                                          |
|  3 |       1 | 食料品の買い出しに行く                                   |
|  4 |       2 | 適切なユーザIDになっているか確認する                       |
+----+---------+-------------------------------------------------------+

今追加したToDoタスク(一番下のレコード)のuser_idカラムに2が入りました。
これで適切なIDで追加ができるようになりました。

次にToDoタスクの一覧表示処理を変更します。

現在、.whereで条件を付けていないので、tasksテーブルに保存されている全てのToDoタスクが取得され、表示されています。
これをそのユーザが追加したToDoタスクのみ、取得、表示されるよう変更します。

index.jsのrouter.get()内は次のようになります。

router.get('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  const userId = req.user.id;
  knex("tasks")
    .select("*")
    .where({user_id: userId})
    .then(function (results) {
      res.render('index', {
        title: 'ToDo App',
        todos: results,
        isAuth: isAuth,
      });
    })
    .catch(function (err) {
      console.error(err);
      res.render('index', {
        title: 'ToDo App',
        isAuth: isAuth,
        errorMessage: [err.sqlMessage],
      });
    });
});

const userId = req.user.id;を宣言し、.where({user_id: userId})で条件を追加しました。

それではサーバを再起動し、まずはrootユーザでサインインしてください。

Cannot read property 'id' of undefinedというエラーがでてしまいました。
これは未サインイン状態で、userオブジェクトにユーザデータが格納されていない中、const userId = req.user.id;でアクセスしようとしたためです。

定数isAuthの値で条件分岐させましょう。
index.jsのrouter.get()内は次のようになります。

router.get('/', function (req, res, next) {
  const isAuth = req.isAuthenticated();
  if (isAuth) {
    const userId = req.user.id;
    knex("tasks")
      .select("*")
      .where({user_id: userId})
      .then(function (results) {
        res.render('index', {
          title: 'ToDo App',
          todos: results,
          isAuth: isAuth,
        });
      })
      .catch(function (err) {
        console.error(err);
        res.render('index', {
          title: 'ToDo App',
          isAuth: isAuth,
          errorMessage: [err.sqlMessage],
        });
      });
  } else {
    res.render('index', {
      title: 'ToDo App',
      isAuth: isAuth,
    });
  }
});

これで定数isAuthがtrue(サインイン済み)の場合のみ、const userId = req.user.id;を宣言し、ToDoタスクを取得する処理に流れるようになりました。

サーバを再起動し、rootユーザでサインインしてください。

rootユーザのToDoタスクのみ表示されました。

次に先ほどサインアップした、ユーザID2のユーザでサインインしてください。

こちらも問題なく、このユーザが追加したToDoタスクのみ表示されました。

これで、ToDoタスクをユーザに紐づけることができました。