サイトにお問い合わせFormが簡単に設置できるサービスをリリースしました

F-FORM top

まずはじめに

このたびF-FORMという自作アプリをリリースしました。

F-FORM top フィヨルドブートキャンプの最終課題の自作アプリをつくるの一環として作成しました。それについてのまとめ記事です。

どんな人が作ったの?

まず簡単に自己紹介をしたいと思います。私は現在フィヨルドブートキャンプというプログラマーとして就職を目指せるだけのスキルを身につけることを目標としたスクールに1年弱通っています。

bootcamp.fjord.jp

それ以前は独学でしばらく勉強をしてwebエンジニアとして短期間バイトしたことがあります。

ブートキャンプに参加する前はRailsJavaScriptだけチョットワカルぐらいでその他についてはほとんどわからない状態でした。

現在は勉強した甲斐もあり、当時と比べて1歩踏み込んだ知識や考え方を身に付けることができたと思います。

何を作ったの?

今回はF-FORMというフォーム作成サービスを作りました。サイト情報を登録すると、自分のサイトにHTMLでフォームを設置すれば、内容をメールで転送できるサービスです。フォームの項目もアプリ側での設定は必要なく、HTML側でinputを追加すれば簡単に項目を追加することができます。 f:id:hasehiro25:20200716182205p:plain またフォームにCSSを当てることができます。自分のサイトに合わせて見た目を自由にカスタマイズすることが可能です。

他にも場面によって使いやすくするために、JavaScriptでのフォームチェック、reCAPTCHAによるスパムチェックを実装しました。

どんな経緯で?

作るアプリとして最初はいろんなアイデアがあったものの、どれも問題定義が曖昧だったので、作る必要性のあるアプリが思いつきませんでした。

  • 部屋のモノを登録して整理できるアプリ → なぜ登録すると整理できるようになるのか
  • タスクをコミュニティ内で共有するアプリ → Trelloでできるのでは?
  • ジャズのカヴァー曲を一括でまとめるアプリ → ジャズの知識不足でどうやったら便利なアプリになるかわからない

悩んでるところにメンターの駒形さんから「こんなフォームアプリがあったらいいなぁ」というお話を聞き、私もそれが解決できたら素晴らしいと思ったので、今回はメンターにヒアリングしながらアプリを作る擬似請負の形で進めました。

どんな問題を解決したかったの?

お問い合わせ用のフォームを作るのって案外大変だと思います。それを解決するためにフォーム設置型サービスは既にいろいろあると思います。

しかし、内容を転送するだけというシンプルなサービスがなかなかありませんでした。また、それに合わせてCSSを当ててデザインを自由にカスタマイズできるようにしたら便利だと思いました。フォームは簡単に作れるけど、埋め込んでカスタマイズするのがちょっと大変だったりします。

なので今回は「フォーム内容をシンプルに返すもの」 「CSSでデザインを設定できるもの」の2つをテーマに取り組みました。

開発概要

実装環境

本アプリはRuby on Rails6で実装しました。デプロイ先は管理が簡単にできるHerokuを選びました。メール関係はHerokuで使えるsendgridに一任。

JavaScript周りは、フォームのフロント側のフォームチェックのためにJavaSciprtファイルが1個公開しており、クリップボード機能など細かいところで一部JavaSciptが使われてます。 (公開後にチェックするJavaScriptは別レポジトリで独立して管理したほうが良さそうと思いました。。。)

デザイン関係は時間を節約するためにbulmaを使って大部分を実装しました。細かい調整部分のみCSSを追加。

bulma.io

かかった期間

作るものが決まってから完成まで約3ヶ月かかりました。最初のうちはスクラム開発のプラクティスと被り、(turbolinksと格闘しながら)なかなか時間が取れない状況が続きました。

1ヶ月半は開発しながら実装に必要な情報収集したり(特にスパム周り)、試作品を作って動作のテストをしていました。残りの1ヶ月半で集中的に本アプリの実装に取り掛かかり、完成へ!

大変だったこと - 挑戦したこと

今回のアプリ実装で一番苦労したものと、個人的にちょっと挑戦的だったものを書きたいと思います。

大変 - スパム対策

今回の実装で一番悩んだのがスパム対策をどうするかでした。そもそも何をどうやってスパムを防げばいいのかがわからない状態からスタート。

いろいろ調べていくうちに、スパムに対していろんな対策やサービスがあることを知りました。

Honeypot Captcha

Honeypot Captchaとはスパムを対策するための手段のひとつです。

haacked.com

スパムボットはHTMLから<input>を探し出して自動入力して送信するという動きをするそうです。なのでそれを逆手にとって、入力されていたらそのフォームリクエストは無効というトラップを仕掛けます。

具体的な方法としては、HTML側にトラップのinputを用意します。そのinputはJavaScriptdisplay: noneなどを使って善意のユーザーには見えないように設定します。そしてサーバー側で、もしそのinputに何か値が入ってればそのリクエストは無効になるように設定します。善意のユーザーはフォームが見えないので入力することはおそらくないので、もし入力があればエラーを返すなり対処すればリクエストはそこで止まります。

Railsではこれを簡単に実装できるようにGemがあったりします。

github.com

ただ、最近のスパムボットには賢いものも出回ってるそうで、効果が本当にあるのか疑問が残ります。簡単に実装できるという点で有用だと思いますが、機械学習などによって将来的に完全に無効化されるとユーザーの手間が増えるだけなので、今回は見送りました。

アンチスパムサービス

次に探したのがスパムを見つけてくれるサービスでした。Wordpressにはakismetというスパムサービスがあるとお聞きし、それに似たサービスがないか調べました。その結果cleantalkというサービスにたどり着きました。

Anti Spam Plugin - Spam Protection by Cleantalk

このサービスはweb版のakismetみたいな感じで、独自でメールアドレスとIPを収集してスパム情報を蓄えてます。このサービスではAPIでリクエスト情報をチェックし、スパムに該当するかレスポンスを返してくれます。

大変便利で、しかも安い金額で対策してくれるのですが、スパム情報を外部が握っている状態になってしまうのが不安でした。例えば全く関係人がたまたまそのサービスにスパム認定されていたら、本人はどんな理由で問い合わせができないかわからず、困った状態になってしまいます。今回の場合、もしもエンドユーザーがこのような状況になったら対応が難しいと考えました。 もちろんcleantalk側はそのようなことが起きないように細心の注意を払ってると思いますが、今回は万一を考えて、こちらも採用しませんでした。

reCAPTCHA

最終的に採用したのがgoogleが提供しているキャプチャreCAPTCHAでした。

developers.google.com

実は最初からこれを使うことも考えていたのですが、キャプチャ認証がめんどかったり、ユーザーにreCAPTCHAのアカウントを作る必要がありそうなので敬遠していました。

reCAPTCHAにはv2,v3の2つが用意されており、v3に関してはキャプチャ認証をしなくても、google独自の技術でユーザーがボットかどうかを判定してくれます。ただし、正常に動かすにはreCAPTACHAを置くサイトの全てのページにreCAPTCHAのスクリプトを置くことが推奨されること、ボットか人間かどうかをスコアで出すので一律で適切な値の把握が難しく、今回はv3での実装は向いてないと判断しました。

最終的に確実にスパム対策すること、そして間違った判定を回避するには今回キャプチャ機能を外すことは難しいと考えました。ただ、ドメイン認証で正式なリクエストかどうかを判断する仕組みをバックエンド側で実装できることがわかったので、ユーザー側がキャプチャのサイトキーとシークレットキーを登録しなくてもこの機能を使えるようにできました。

キャプチャ認証という手間がかかるものの、今回のケースでは一番リスクが少ないと思ったのでreCAPTCHAを採用しました。

挑戦 - メール変更の認証機能追加

もしユーザーが誤って間違ったアドレスに変更した場合、フォーム内容が届かないと言う悲惨なシチュエーションが考えられます。deviseだとreconfirmableを有効にすれば良いのですが、今回採用したsorceryにはその実装が見当たらず。。。それを防ぐためにも、今回はメールアドレスを変更した場合、新アドレスを認証するまでは旧アドレスを使うような実装をしました。

フロー自体はパスワードリセットと同じでユニークなidがついたURLをメールアドレスに記載し、idからユーザーを見つけて処理をすれば良いと思いました。ただ「idはどう作るのだろう?」と悩み、sorceryとdeviseのコードを参照しました。そしたらdeviseのトークンを作るコードが非常に参考になったので、それを元にトークンを作るクラスを作成しました。

github.com

rawとencodedのトークンペアができたので、あとはパスワードリセットと同じ要領でトークンが有効の場合のみ処理をするように実装できました。 f:id:hasehiro25:20200720083756p:plain 全体でみると難しそうな機能だなぁと思いつつ、やりたいことを分解すればどんな機能が必要か、その機能を実装するにはどんな仕組みやコードが必要かの道筋が見えたような気がします。

最後に

アプリ開発に際し、最後までアドバイスをくださった駒形さんと町田さんには感謝しかありません。本当にありがとうございました!

まだまだ改善しないといけない点、あったら良いなぁという機能が山のようにあります。特に町田さんによるデザインレビュー、駒形さんによるコードレビューで指摘された点を優先的に直す計画をしております。

より良いサービスになるよう今後ちょっとずつ改善していきたいと思います。

(さきに「ふりかえり」を書いてしまい、今回の開発の反省や気づきはそちらに追記していこうと思います)