Node.JSとsqlite3で簡易掲示板もどきにログイン機能を追加します。これまでは誰でも投稿文を変更したり削除することができました。これではよくありません。また投稿をするのであればユーザー登録したあとログインしてからでないとできないようにします。

どうすればできるでしょうか? SQLite3で投稿された文章、投稿時刻、最終更新時刻を管理していましたが、ここに投稿したユーザー名も保存するようにするのはどうでしょうか? これならログインしているユーザーと投稿をしたユーザーが同一かどうかをチェックできるようになります。

⇒ 動作確認はこちらから

パスワードの管理

ユーザー登録したときのユーザー名とパスワードもSQLite3に新しいテーブルをつくって管理します。ただしパスワードのような外部に流出したら困るようなものは暗号化して保存します。「外部に流出しないように気をつける」、これで外部流出を防げるのであれば誰も苦労しません。外部に流出しない仕組み作り、万一流出してもダメージを最小にする仕組み作りが必要です。

パスワードを暗号化(ハッシュ値)して保存しておき、ログインしようとしたユーザーがパスワードを入力したときにこれも暗号化して暗号化された状態で保存されているものと比較します。同じであれば正規のユーザーと判断することができます。暗号化されたものを復号する必要はありません。

同じ文字列 ‘123’のハッシュ値を2回取得してみることにします。

結果はこうなります。

最初の部分が同じようになっていますが、これは仕様です。bcrypt.genSaltSync()は29文字のハッシュ値を生成します。最初の$の後はバージョン情報、2番目の$の後はストレッチング回数、3番目の$の以降がソルト値になります。30文字目から60文字目までが実際のハッシュ値です。同じパスワードのハッシュ値なのにソルトが異なるため、まったく違う文字列になっています。

簡易掲示板にログイン機能を追加

それでは簡易掲示板にログイン機能を追加します。

最初にnpmでinstallすべきもの、requireする必要があるものを最初にまとめて示します。暗号化パッケージとしてbcrypt、 Expressでセッションを使うために express-sessionが必要です。

ユーザー情報を管理するためのテーブルをつくる

まずデータベースを作り直します。すでにテーブルが存在するときはなにもおきません。そこで前回作成したtest.dbは一旦削除してCreateTables関数を実行します。

前回の変更点としてテーブル table_postsには投稿者のユーザー名が追加されています。table_usersは新しくつくります。あとデータベースのパスだけでなくテーブル名も同じものを使うので定数として定義しました。

ユーザーはログインしているのか?

ログイン処理、ログインしているかどうかを確認するためにセッションを使います。そこで以下の処理が必要です。クッキーの有効期間は3600秒、すなわち1時間とします。

トップページにアクセスしたら

ユーザーがトップページにアクセスさたら投稿を修正したり削除できるリンクの表示/非表示をしなければなりません。

最初にindex.ejsの内容を示します。

ここでは更新または削除できるのは自分が投稿したものだけであること、投稿をするのであればログインが必要であることを示すとともに、ログインしていないときはログインページへのリンク、ログインしているときはログアウトするためのリンクを表示させています。

また投稿をしたのがログインしたユーザーである場合だけ、更新と削除のリンクが表示されるようにしています。

index.ejs

次にindex.jsの内容ですが、ログインしているのであればreq.session.usernameを調べればログインしているユーザー名がわかります。req.session.usernameでユーザー名が取得できるようにする処理はあとで示します。

index.js

投稿をするためのページにアクセスしたときの処理を示します。

new_update.ejsの内容は変わりません。index.jsではログインしていないのに投稿をしようとした場合はログインページにリダイレクトします。それ以外の部分は同じです。

index.js

新規投稿に関する処理

投稿ボタンがクリックされたときの処理を示します。SQLiteに登録するのは前回とはちがって、投稿名、投稿文、投稿時刻、最終更新時刻に加え、投稿者のユーザー名も登録しています。

SQL文をつくるときに単に文字列をつなげてつくるのは危険です。SQL文のなかに’がある場合、掲示板への書き込み内容に’が入っているとSQL文の内容が意図していたものとは違うものになってしまいます。それでエラーが発生するだけであればまだいいのですが、テーブルのデータをすべて消去したりユーザーのパスワードを表示させることもできてしまいます。

‘は”でエスケープ処理できます。これならエラーにはなりません。SQLインジェクション攻撃の定番である;DELETE FROM users–のような書き込みや検索をされてもそのように表示されるだけでSQLインジェクション攻撃を無力化させることができます。

index.js

更新に関する処理

トップページの[更新]がクリックされたときの処理を示します。ログインしていないと[更新]のリンクは表示されないのですが、urlを自分で編集されてしまえばアクセスは可能です。そこで/update:idにアクセスされたときの処理もしています。

想定される場合として、(1)投稿文を投稿したユーザーによるアクセス、(2)ログインしていないユーザーのアクセス、(3)ログインしているが投稿文を投稿したユーザーではないユーザーのアクセスが考えられます。(1)であれば従来どおりの処理でいいのですが、(2)と(3)の場合はトップページにリダイレクトさせます。

/update:idへpostされたときの処理を示します。投稿文を投稿したユーザーによるものであればデータベースを更新してトップページにリダイレクトします。それ以外の場合はなにもしないでトップページにリダイレクトさせます。

削除に関する処理

削除の処理を示します。投稿文を投稿したユーザーによるものであればデータベースから削除してトップページにリダイレクトします。それ以外の場合はなにもしないでトップページにリダイレクトさせます。

ユーザー登録に関する処理

さてログインの処理ですが、そのまえにユーザー登録できないとログインのしようがありません。ユーザー登録に関する処理を示します。

ユーザー登録のページを表示させるときに必要なsignup.ejsとsignup_err.ejsを示します。ユーザー登録でエラーが発生する可能性があります。

考えられるケースとして、パスワードを2回入力させるようにしているのですが、1回目と2回目が違う、ユーザー名またはパスワードが入力されていない、ユーザー登録しようとしているユーザー名が別の人に登録されているが考えられます。

このような場合はユーザーにエラーが発生したことを知らせ、もう一度ユーザー登録するように要請します。このようなことを考えないといけないのでsignup.ejsとsignup_err.ejsの2つを作成しています。

signup.ejs

signup_err.ejs

ユーザー登録するページの場合、getの場合はsignup_ejsを使うかsignup_err_ejsを使うかであり、たいした違いはありません。

index.js

登録ボタンをクリックしたときの処理を示します。どちらも/signupにpostされます。

2回入力したパスワードが異なる、すでに登録されているユーザー名で登録しようとした、そもそもユーザー名またはパスワードを入力していない場合はエラーです。/signup-errにリダイレクトさせます。そうでない場合はユーザーとしてデータベースに登録し、ログインページにリダイレクトさせます。

ログインに関する処理

ログインの処理ですが、先にlogin.ejsとlogin_err.ejsを示します。ログインに失敗した場合は失敗したということがわかるように1回目のログインページとは別のページを表示させます。

またログインページにリダイレクトされた場合、そのユーザーはユーザー登録をしていない場合もあるので、ユーザー登録ページへのリンクも設置します。

login.ejs

login_err.ejs

index.js

/loginにpostされたときの処理ですが、まずユーザー名がデータベースに登録されているか調べます。登録されている場合は入力されたパスワードのハッシュ値がデータベースに登録されているパスワードのハッシュ値と同じか調べます。同じであればログイン処理をおこないます。ログインしたらセッションにユーザー名を格納し、トップページにリダイレクトさせます。

ユーザー名がデータベースに登録されていない場合、ユーザー名がデータベースに登録されているが、パスワードが合わない場合はログインはできません。/login-errにリダイレクトさせます。

index.js

ログアウトするときの処理

ログアウトするときの処理を示します。req.session.destroy関数を実行して、トップページにリダイレクトします。

存在しないページにアクセスされたときの処理

存在しないページにアクセスされたときの処理は前回と同じです。