Django rest frameworkを使ったAPI経由のパスワードリセットを行う方法

Djangoでrest frameworkをつかってAPIを実装していて、ユーザのパスワードリセット機能を実装しようとした時の備忘録です
Djangoでは`django.contrib.auth`という強力なauth機能がありますが、今回はAPI実装なので簡単にはいきませんでした
API用のライブラリ[django-rest-auth](https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/serializers.py)があったので、こちらを使って進めていきます

# パスワードリセットの流れ
### ユーザ
- トップページからパスワードリセット画面に遷移
- メールアドレス入力・送信
- URLを含んだメール受信
- パスワード入力・送信

### API
- メールアドレスを含むリクエスト受信
- リセット画面へのURLを含むメール送信
- パスワードを含むリクエスト受信

の流れになります。よくある流れです
`django.contrib.auth`をそのまま使うならテンプレートを設定するだけでほぼできあがるのですが、今回APIのエンドポイント作成になるので、ライブラリを利用していきます

# django-rest-authとは
[Documentation](http://django-rest-auth.readthedocs.io/en/latest/)によると、ユーザ登録、ログインログアウト、パスワード変更やリセットまでを行ってくれるライブラリのようです
便利だけどドキュメントが絶妙に不親切だったので、ソースコード読みながら実装しました

ひとまず

```
pip install django-rest-auth
```

# django-rest-authにおける実装
## API
ライブラリにまかせておくなら、`urls.py`に次の2行を追加すれば動きます

```urls.py
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^', include('django.contrib.auth.urls')),
```

このままだとメールのテンプレートが設定できません
テンプレートの設定を行わないと、メールに記載されるURLのドメインの設定ができませんでした

設定するためにはdjango-rest-authのserializerをOverrideする必要があるようです
`get_email_options`で`email_template_name`などを指定します。他にも設定できる項目はあるようです


```serializers.py
from rest_auth.serializers import PasswordResetSerializer

class CustomPasswordResetSerializer(PasswordResetSerializer):
	def get_email_options(self):
		data = {
			'email_template_name': 'email/password_reset.html',
			'subject_template_name': 'email/password_reset_subject.txt',
		}
		return data
```

Emailテンプレートは[djangoのデフォルトのテンプレート](https://github.com/django/django/blob/master/django/contrib/admin/templates/registration/password_reset_email.html)があるので、これを参考にしてみてください
`{{ protocol }}://{{ domain }}`でフロントのURLに誘導してください

```django/django/contrib/admin/templates/registration/password_reset_email.html
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}

{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endautoescape %}
```


そして`views.py`もこれに合わせて調整します
[ここ](https://github.com/Tivix/django-rest-auth/blob/49ccfe92a9e691ac4224de7a878c87402181c09e/rest_auth/views.py#L132)を参考にしました


```views.py
from rest_framework.generics import GenericAPIView
from user.serializers import CustomPasswordResetSerializer

class PasswordResetView(GenericAPIView):
	permission_classes = (permissions.AllowAny,)
	serializer_class = CustomPasswordResetSerializer

	def post(self, request):
		serializer = self.get_serializer(data=request.data)
		if serializer.is_valid():
			serializer.save()
			return Response('Password reset e-mail has been sent.', status=200)
		return Response(serializer.errors, status=400)
```

最後に`urls.py`でこのviewのルーティングを行います(1行目)

```urls.py
url(r'^/password/reset/$', PasswordResetView.as_view()),
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^', include('django.contrib.auth.urls')),
```

これで2つのAPIができました

- `/password/reset/`: メールアドレス送信用
- `/rest-auth/password/reset/confirm/`: パスワード送信用

パスワード送信用は`django-rest-auth`にまかせて問題ないと思います

## フロント
フロント側で準備するのは下の2つのようです(他に遷移元・先なども準備してください)
ビューで登録させる内容と、APIは次の通りです

### メールアドレスを入力するビュー
メールアドレスを入力するフォームを作成します

`/password/reset/`

- email

### パスワードを入力するビュー
パスワードを入力するフォームを確認用も含めて2つ作成します

`/rest-auth/password/reset/confirm/`

- new_password1
- new_password2
- uid
- token

uidとtokenはメールに記載されたURLに含まれている文字列を指定します。

`https://***/reset/{ uid }/{ token }/`


# まとめ
かなり強引な方法の気がします
もっと良い方法がないかなあと考えてはいますが