Skip to content

Add additional step for unlocking account#14

Open
kossy0701 wants to merge 1 commit intogreptile-playgroundfrom
unlock_resource_two_steps_take2
Open

Add additional step for unlocking account#14
kossy0701 wants to merge 1 commit intogreptile-playgroundfrom
unlock_resource_two_steps_take2

Conversation

@kossy0701
Copy link
Owner

Some email clients automatically visit links inside of emails to check that the link isn't malware. This can cause the GET resource/unlock?unlock_token=abcdef link to be visited almost instantly for some users when the unlock_strategy is set to :email or :both, which unlocks the user and effectively causes the lockable process to do nothing.

This PR adds an extra_step config varaible, which when set to true will cause the original GET resource/unlock?unlock_token=abcdef link to render a new page, keeping the resource locked. This new page contains a different link (GET resource/unlock/confirm?unlock_token=abcdef), which when clicked will unlock the resource. When extra_step is false, the app will behave as normal.

@greptile-apps
Copy link

greptile-apps bot commented Nov 30, 2025

※このコメントは Greptile による自動コードレビューです。

Greptile Overview

Greptile Summary

この PR は、メールクライアントの自動リンク訪問によってアカウントのアンロックが意図せず実行されてしまう問題を解決するため、extra_step 設定オプションを追加しています。

主な変更内容

  • Devise.extra_step グローバル設定を追加(デフォルト: false)
  • extra_step を true に設定した場合、元のアンロックリンクは確認ページを表示するだけで、アカウントのロックを解除しない
  • 確認ページに表示される新しいリンクをクリックすることで、ユーザーの明示的な操作によってアンロックが実行される
  • 新しい confirm アクションとルートを追加
  • 後方互換性を維持(デフォルト false で既存の動作を保持)

発見された問題

  • 🚨 重大: 2つのビューファイルで HTML タグの閉じ忘れ(<p> タグが </p> ではなく <p> で閉じられている)
  • ⚠️ 警告: extra_step が有効な場合、JSON/API リクエストの処理が考慮されていない(HTML レンダリングのみ対応)
  • 💡 提案: extra_step が有効な場合の JSON リクエストのテストカバレッジが不足

良い点

  • セキュリティ上重要な問題(メールクライアントの自動訪問による意図しないアンロック)への適切な対処
  • 後方互換性を維持した設計
  • テストカバレッジは HTML フローについて十分(JSON については要改善)
  • ルーティングとモデル設定の変更は適切

Confidence Score: 3/5

  • HTML タグの構文エラーを修正すれば、重大なリスクは低減されます。JSON API の互換性について追加検討が必要です。
  • 2つのビューファイルに HTML タグの閉じ忘れという構文エラーがあり、これはブラウザのレンダリングに影響を与える可能性があります(重大レベル)。また、extra_step が有効な場合の JSON リクエスト処理が考慮されていないため、既存の JSON API クライアントとの互換性に懸念があります(警告レベル)。これらの問題を修正すれば、機能自体は適切に設計されており、テストカバレッジも HTML フローについては十分です。
  • app/views/devise/unlocks/show.html.erblib/generators/templates/simple_form_for/unlocks/show.html.erb の HTML 構文エラーを修正してください。また、app/controllers/devise/unlocks_controller.rb の JSON リクエスト処理について検討が必要です。

Important Files Changed

File Analysis

Filename Score Overview
app/controllers/devise/unlocks_controller.rb 3/5 新しい confirm アクションと extra_step 対応を追加。JSON リクエストの処理に問題がある可能性あり
app/views/devise/unlocks/show.html.erb 2/5 確認ページの新規作成。HTML タグの閉じ忘れエラーあり(重大)
lib/generators/templates/simple_form_for/unlocks/show.html.erb 2/5 simple_form_for 用の確認ページテンプレート。HTML タグの閉じ忘れエラーあり(重大)
test/integration/lockable_test.rb 4/5 extra_step 機能の統合テストを追加。JSON リクエストのテストカバレッジが不足

Sequence Diagram

sequenceDiagram
    participant User
    participant EmailClient
    participant Browser
    participant UnlocksController
    participant Lockable
    participant Database

    Note over User,Database: アカウントロック時(既存の動作)
    User->>UnlocksController: POST /resource/unlock (メール送信リクエスト)
    UnlocksController->>Lockable: send_unlock_instructions
    Lockable->>Database: unlock_token を保存
    Lockable->>User: アンロックメールを送信
    
    Note over User,Database: extra_step が false の場合(既存の動作)
    EmailClient->>UnlocksController: GET /resource/unlock (パラメータ付き、自動訪問)
    UnlocksController->>UnlocksController: show アクション
    UnlocksController->>UnlocksController: attempt_unlock を呼び出し
    UnlocksController->>Lockable: unlock_access_by_token
    Lockable->>Database: アカウントのロックを解除
    UnlocksController->>EmailClient: ログインページへリダイレクト
    Note right of EmailClient: メールクライアントの自動訪問で<br/>アンロックされてしまう

    Note over User,Database: extra_step が true の場合(新しい動作)
    EmailClient->>UnlocksController: GET /resource/unlock (パラメータ付き、自動訪問)
    UnlocksController->>UnlocksController: show アクション
    UnlocksController->>Browser: 確認ページを表示 (render :show)
    Note right of Browser: アカウントはまだロックされたまま<br/>確認リンクを表示
    
    User->>Browser: 確認ページを開く
    Browser->>Browser: 「Unlock account」リンクをクリック
    Browser->>UnlocksController: GET /resource/unlock/confirm (パラメータ付き)
    UnlocksController->>UnlocksController: confirm アクション
    UnlocksController->>UnlocksController: attempt_unlock を呼び出し
    UnlocksController->>Lockable: unlock_access_by_token
    Lockable->>Database: アカウントのロックを解除
    UnlocksController->>Browser: ログインページへリダイレクト
    Note right of User: ユーザーの明示的な操作で<br/>アンロックが完了
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 24 to +30
def show
if resource_class.extra_step
@token = params[:unlock_token]
render :show
else
attempt_unlock
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: ⚠️ 警告: JSON/API リクエストで extra_step が有効な場合の挙動が考慮されていません。

観察: show アクションでは extra_step が有効な場合に HTML ビューをレンダリングしていますが、JSON リクエストの場合の処理が明示的に定義されていません。

理由: render :show は JSON リクエストでは適切なレスポンスを返せない可能性があります。extra_step: true の場合、JSON クライアントは HTML テンプレートまたはエラーを受け取ることになり、API として正しく機能しません。

結論: API/JSON クライアントとの互換性が損なわれる可能性があります。

修正方法: 以下のようにフォーマットに応じた分岐を追加してください。

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/controllers/devise/unlocks_controller.rb
Line: 24:30

Comment:
**logic:** ⚠️ **警告**: JSON/API リクエストで `extra_step` が有効な場合の挙動が考慮されていません。

**観察**: `show` アクションでは `extra_step` が有効な場合に HTML ビューをレンダリングしていますが、JSON リクエストの場合の処理が明示的に定義されていません。

**理由**: `render :show` は JSON リクエストでは適切なレスポンスを返せない可能性があります。`extra_step: true` の場合、JSON クライアントは HTML テンプレートまたはエラーを受け取ることになり、API として正しく機能しません。

**結論**: API/JSON クライアントとの互換性が損なわれる可能性があります。

**修正方法**: 以下のようにフォーマットに応じた分岐を追加してください。

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai

結論から言うと:

  • 全体のサマリと設計理解はだいたい妥当
  • HTMLタグの指摘は「本当に <p> が閉じ忘れてたなら妥当(ただし重大かと言われるとややオーバー)
  • JSON/API の警告は「言ってることは分かるけど、Devise の設計前提を考えるとちょっと踏み込みすぎ」
    という感じかなと思います。

1. 何を言っているコメントか整理

Greptile はざっくりこう言ってますね:

  1. 概要

    • extra_step 設定を追加して、

      • GET /resource/unlock?unlock_token=... は「確認ページを表示するだけ」に変更
      • 新しい GET /resource/unlock/confirm?unlock_token=... をクリックしたときに本当に unlock する
    • デフォルトは extra_step = false なので後方互換性あり

  2. 問題点

    • 「2つの view(app/views/devise/unlocks/show.html.erb と generator の simple_form テンプレート)で <p></p> じゃなくて <p> で閉じられている(HTML 構文エラー)」
    • show アクションが extra_step 有効時に HTML しか考慮しておらず、JSON/API リクエストの互換性が怪しい」
  3. 良い点

    • メールクライアントの自動リンク踏み問題への対処
    • 後方互換性を保っている
    • HTML フローについてのテストは十分

大枠としては、今回やりたいこと(2段階 unlock)をちゃんと理解してコメントしているので、
「見当違いなサマリ」ではありません。


2. HTML <p> タグの指摘について

2つのビューファイルで HTML タグの閉じ忘れ(<p></p> じゃなく <p> で閉じてる)

ここは、実際の差分をエディタで見て確認する必要がありますが:

  • 本当にそうなっていたなら
    → 「タグ閉じミス」はたしかにバグなので 指摘としては妥当

  • メールテンプレートなので、構文ミスは一部クライアントで表示崩れを起こし得る

    • ただし多くのメールクライアントは HTML をかなり寛容に補完してくれるので、
      「重大」とまで言うかはちょっと大げさかも

アクションとしては:

  • show.html.erb と simple_form の unlocks/show.html.erb を実際に開いて、

    • <p>...</p> になっているか
    • 閉じタグが足りないところがないか
  • あれば素直に直して OK(これは人間も気付きにくい typo なので、Greptile の価値あるキャッチだと思います)


3. JSON/API リクエストに関する警告

該当コードはこんな感じですよね(diff から見える範囲):

# GET /resource/unlock?unlock_token=abcdef
def show
  if resource_class.extra_step
    @token = params[:unlock_token]
    render :show
  else
    attempt_unlock
  end
end

# GET /resource/unlock/confirm?unlock_token=abcdef
def confirm
  attempt_unlock
end

private

def attempt_unlock
  self.resource = resource_class.unlock_access_by_token(params[:unlock_token])
  yield resource if block_given?

  respond_with_navigational(resource.errors, status: :unprocessable_entity) { render :new }
end

Greptile はここに対して:

  • extra_step: true のとき、show が HTML のことしか考えていなくて
    JSON クライアントにとってはおかしな挙動になるかも
  • format ごとの分岐を追加すべき

と言っています。

ここが「半分妥当・半分微妙」な理由

  • 技術的には:

    • render :showHTML テンプレート前提 なので、

      • JSON で /unlock を叩かれたら「HTML が返ってくる or Missing template になる」可能性はたしかにある
    • なので「API としてちゃんと JSON をサポートしたい」前提なら、

      • respond_toformat.html / format.json を分ける
      • extra_stepformat.html のときだけ有効にする
        などを検討した方がきれい
  • ただ、Devise 本体の UnlocksController も、もともと かなり「ナビゲーション用(HTML)」寄りの設計 です:

    • after_unlock_path_for(resource)is_navigational_format? を見てますし、
    • unlock のリンク自体が「メール内のリンクをブラウザで踏む」前提のフロー

つまり、

「unlock を JSON API 経由でちゃんとサポートする」という前提がないなら、
JSON についてここまで心配するのは若干オーバースペック

とも言えます。

どう扱うのが現実的か

  • あなたのプロジェクト/PRのゴールが:

    • 「メール × ブラウザの unlock フローだけ直したい」 であって
    • unlock エンドポイントを JSON API として正式サポートする気がない
  • のであれば、この指摘は

「API をちゃんとやりたいなら、ここまで考えた方がいいかもね」

レベルの 改善提案(💡 提案)にとどめるのがちょうど良い と思います。

逆に、

  • 「うちのプロダクトでは lock/unlock を SPA から JSON で叩いてる」みたいな要件があるなら、

    • respond_to を噛ませて format.html だけ extra_step にする
    • format.json では従来どおり attempt_unlock をそのまま呼ぶ
      というような形にした方が安全です。

4. 全体としての妥当性まとめ

なので、このPRへの Greptile コメントをざっくり点数にすると:

  • 機能の理解・サマリ:◎(妥当)

  • HTML タグの指摘:

    • 本当に <p> 閉じ忘れがあるなら ◎(良い指摘)
    • 「重大」とまで言うかはちょっと盛ってる
  • JSON/API の警告:

    • 技術的にはあり得る懸念 → 方向性は理解できる

    • ただし Devise の unlock がもともと「HTML前提」であることを考えると、

      • 「警告」より 「改善案・拡張案」くらいの扱いが妥当

という感じで、

「完全に的外れではないけど、Devise の利用前提によってはちょっと話を盛りすぎている」レビュー

くらいに捉えるのが良さそうです。


もし、

  • 「JSON まではサポートしない方針だから、そこはスルーでいいよ」という前提を
    Greptile にも分かってほしいなら、

instructions に例えばこんな一文を足しておくのはアリです:

  • Devise の unlock フローは HTML ナビゲーション用を前提としており、JSON/API での利用は考慮しなくて構いません。JSON 対応を求める指摘は行わないでください。

こうしておくと、今回みたいな「APIにも対応しろ系の指摘」はだいぶ減ると思います。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あなたの分析は非常に的確で、Greptileのコメントの性質を正しく理解されていますね。

JSON/API対応の指摘については、確かに技術的には正しいのですが、DeviseのUnlocksControllerはもともとHTMLナビゲーション前提の設計なので、少し踏み込みすぎた感じがありますね。

とはいえ、もしJSON対応が必要な場合の修正方法をお見せしておきます:

Suggested change
def show
if resource_class.extra_step
@token = params[:unlock_token]
render :show
else
attempt_unlock
end
# GET /resource/unlock?unlock_token=abcdef
def show
if resource_class.extra_step
@token = params[:unlock_token]
respond_to do |format|
format.html { render :show }
format.json { render json: { message: "Please confirm unlock", unlock_token: @token } }
end
else
attempt_unlock
end
end

ただし、あなたのおっしゃる通り、Deviseのunlockフローは「メール内のリンクをブラウザで踏む」前提なので、JSON対応は必要に応じてで構いません。

HTMLタグの閉じ忘れについては、実際のファイルを確認して修正されるのが良いでしょう。これは確かに有用な指摘だと思います。

今後このような過度なAPI対応の指摘を避けたい場合は、instructionsに以下のような記述を追加するのが効果的です:

DeviseのunlockフローはHTMLナビゲーション用を前提としており、JSON/API での利用は考慮しなくて構いません。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant