🍊

FastAPIでパスワードのバリデーションを正規表現でやる方法

2024/08/07に公開

FastAPIのパスワードの正規表現でハマった話をしたいと思います。

パスワードに大文字・小文字・数字を少なくとも1つずつ含めるパターンありますよね。

これをFastAPIのバリデーションで正規表現を使って検証しようと思ったんですね。

↓イメージとしてはこんな感じです。

class PostUsersRequest(BaseModel):
    email: EmailStr
    password: str = Field(
        default="Passw0rd", pattern=r"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)^[A-Za-z0-9]{8,24}$"
    )

これでuvicorn立ち上げたら↓こういうエラーが出るんですね。

pydantic_core._pydantic_core.SchemaError: Error building "model" validator:
  SchemaError: Error building "model-fields" validator:
  SchemaError: Field "password":
  SchemaError: Error building "default" validator:
  SchemaError: Error building "str" validator:
  SchemaError: regex parse error:
    ^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)^[A-Za-z0-9]{8,24}$
     ^^^
error: look-around, including look-ahead and look-behind, is not supported

要はこのタイプの正規表現使えませんと。

で、調べたところですね。
FastAPIが型定義というかバリデーションに使っているPydanticのバージョンが2から使えなくなったらしいんですよ。

どういうことかというと、概ねこんな感じらしいです。

PydanticをV1からV2にするときにRustで書き直した

Rustの正規表現エンジンに依存することになったので、複雑な正規表現(計算量のかかるやつ)に対応できなくなった

もし使いたいなら、遅くなるけどPythonのエンジン使ってね(後述します)

関連のGitHubのIssue: https://github.com/pydantic/pydantic/issues/7058

それでもこの正規表現を使いたい

結論から言うと、こう書き換えると使えます。

from pydantic import ConfigDict

class PostUsersRequest(BaseModel):
    model_config = ConfigDict(regex_engine='python-re')
    email: EmailStr
    password: str = Field(
        default="Passw0rd", pattern=r"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)^[A-Za-z0-9]{8,24}$"
    )

PydanticからConfigDictをインストールして...

from pydantic import ConfigDict

↓これをモデルに差し込みます。

model_config = ConfigDict(regex_engine='python-re')

明示的にPythonの正規表現エンジンを使いますよと書く感じです。

Pydantic公式の参考資料: https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.regex_engine

ただ使えないことには、ちゃんと理由があります

で、なぜPydanticはというか、Rustはこの複雑なタイプの正規表現を使えないことがデフォルトなのかというと、そこにはちゃんと理由があるそうです。

正直不勉強だったなーと思いました。

Pydantic公式もこのように言ってます。

rust-regex uses the regex Rust crate, which is non-backtracking and therefore more DDoS resistant, but does not support all regex features.

そもそも正規表現というのは比較的重たい処理なので、例えばすごい長い文字列とかを正規表現にマッチするかを計算するのってCPUの使用率があがったりとかマシンに負荷がかかるわけですね。
なおさらDDoS的にやられたら最悪ですよね。

特に、従量課金で動くクラウドだと、お金がめちゃくちゃかかるかもしれませんね。
もちろんレート制限をかけましょうという話ではありますが。

正規表現がどう重いかについてはこちらの記事が大変参考になります。
https://qiita.com/Tatamo/items/68a10c6274953e695354

非常にmake senseですよね。

まとめ

なんでもそうなのですが、副作用を分かった上でちゃんと使いましょうということですね。

Discussion