フラスコ静止API -パート5
パート5 :パスワードリセット
危ない!シリーズの前で、我々はフラスコでエラーを取り扱う方法を学び、意味のあるエラーメッセージをクライアントに送りました.
この部分では、我々のアプリケーションでパスワードをリセット機能を実装する予定です.
ここでは、パスワードのリセットフローがどのように見えるかの簡単な図です.
パスワードリセットフロー図
我々は、使用するつもりです
flask-jwt-extended
パスワードをリセットトークンを生成するライブラリは、良いことは、我々はすでに認証を実装しながら、それをインストールしているです.メールを通してユーザにリセットトークンを送る必要がありますFlask Mail .pipenv install flask-mail
このメールサーバを登録しましょうapp.py
:#~/movie-bag/app.py
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_jwt_extended import JWTManager
+from flask_mail import Mail
...
api = Api(app, errors=errors)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
+mail = Mail(app)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
...
さて、クライアントにメールを送るサービスを作成しましょうservices
と新しいファイルmail_service.py
インサイド.新しく作成したファイルに次のコンテンツを追加します.mkdir services
cd services
touch mail_service.py
#~/movie-bag/services/mail_service.py
from threading import Thread
from flask_mail import Message
from app import app
from app import mail
def send_async_email(app, msg):
with app.app_context():
try:
mail.send(msg)
except ConnectionRefusedError:
raise InternalServerError("[MAIL SERVER] not working")
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
Thread(target=send_async_email, args=(app, msg)).start()
ここでは、我々は機能を作成して見ることができますsend_mail()
どのテイクsubject
, sender
, recipients
, text_body
and html_body
引数として.メッセージオブジェクトを生成し、実行するsend_async_email()
別のスレッドでは、クライアントにメールを送信しながら、GoogleやOutlookなどの別のサービスに中継する必要があります.これらのサービスは、実際にメールを送信するのに時間がかかるので、私たちは、クライアントが要求を成功させ、別のスレッドでメールを送信するように指示するつもりです.
パスワードリセットを実装する準備が整いました.上の図に示すように、2つの異なるエンドポイントを作成します.
1).
/forget
: このエンドポイントはemail
アカウントが変更される必要があるユーザの.この終点は、パスワードをリセットするためにリセットトークンを含むリンクでユーザーにメールを送ります.2).
/reset
: このエンドポイントreset_token
電子メールと新しいpassword
.を作成しましょう
reset_password.py
インサイドresources
フォルダ.次のコードを使用します.#~/movie-bag/resources/reset_password.py
from flask import request, render_template
from flask_jwt_extended import create_access_token, decode_token
from database.models import User
from flask_restful import Resource
import datetime
from resources.errors import SchemaValidationError, InternalServerError, \
EmailDoesnotExistsError, BadTokenError
from jwt.exceptions import ExpiredSignatureError, DecodeError, \
InvalidTokenError
from services.mail_service import send_email
class ForgotPassword(Resource):
def post(self):
url = request.host_url + 'reset/'
try:
body = request.get_json()
email = body.get('email')
if not email:
raise SchemaValidationError
user = User.objects.get(email=email)
if not user:
raise EmailDoesnotExistsError
expires = datetime.timedelta(hours=24)
reset_token = create_access_token(str(user.id), expires_delta=expires)
return send_email('[Movie-bag] Reset Your Password',
sender='[email protected]',
recipients=[user.email],
text_body=render_template('email/reset_password.txt',
url=url + reset_token),
html_body=render_template('email/reset_password.html',
url=url + reset_token))
except SchemaValidationError:
raise SchemaValidationError
except EmailDoesnotExistsError:
raise EmailDoesnotExistsError
except Exception as e:
raise InternalServerError
class ResetPassword(Resource):
def post(self):
url = request.host_url + 'reset/'
try:
body = request.get_json()
reset_token = body.get('reset_token')
password = body.get('password')
if not reset_token or not password:
raise SchemaValidationError
user_id = decode_token(reset_token)['identity']
user = User.objects.get(id=user_id)
user.modify(password=password)
user.hash_password()
user.save()
return send_email('[Movie-bag] Password reset successful',
sender='[email protected]',
recipients=[user.email],
text_body='Password reset was successful',
html_body='<p>Password reset was successful</p>')
except SchemaValidationError:
raise SchemaValidationError
except ExpiredSignatureError:
raise ExpiredTokenError
except (DecodeError, InvalidTokenError):
raise BadTokenError
except Exception as e:
raise InternalServerError
ここではForgotPassword
リソースについては、まずemail
クライアントによって提供されます.私たちはその後create_access_token()
トークンを作成するuser.id
そして、このトークンは24時間で期限が切れます.その後、クライアントにメールを送信しています.メールの両方が含まれますHTML
とテキスト形式の情報.同様に
ResetPassword
ResournalトークンからユーザIDに基づいてユーザを取得し、ユーザが提供するパスワードに基づいてユーザのパスワードをリセットします.最後に、リセット成功メールがユーザーに送信されます.新しい例外を作りましょう
EmailDoesnotExistsError
and BadTokenError
我々の中でerrors.py
.#~/movie-bag/resources/errors.py
class UnauthorizedError(Exception):
pass
+class EmailDoesnotExistsError(Exception):
+ pass
+
+class BadTokenError(Exception):
+ pass
+
errors = {
"InternalServerError": {
"message": "Something went wrong",
@@ -54,5 +60,13 @@ errors = {
"UnauthorizedError": {
"message": "Invalid username or password",
"status": 401
+ },
+ "EmailDoesnotExistsError": {
+ "message": "Couldn't find the user with given email address",
+ "status": 400
+ },
+ "BadTokenError": {
+ "message": "Invalid token",
+ "status": 403
}
}
我々は、クライアントに送信する必要があるHTMLやテキストファイルのテンプレートを作成する必要があります.つくりましょうtemplates
ルートディレクトリ内のフォルダと内部templates
フォルダを作るemail
ここで2つの新しいファイルを作成しているreset_password.html
and reset_password.txt
.mkdir templates
cd templates
mkdir email
cd email
touch reset_password.html
touch reset_password.txt
RetsetRageパスワードで.以下を追加します.<!-- #~/movie-bag/templates/email/reset-password.html -->
<p>Dear, User</p>
<p>
To reset your password
<a href="{{ url }}">
click here
</a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url }}</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely</p>
<p>Movie-bag Support Team</p>
ヒア{{ url }}
私たちが以前に送ったURLをrender_template()
関数.同様に、次の
reset_password.txt
:Dear, User
To reset your password click on the following link:
{{ url }}
If you have not requested a password reset simply ignore this message.
Sincerely
Movie-bag Support Team
今、我々はこれを配線する準備が整いましたResources
我々にroutes.py
. from .movie import MoviesApi, MovieApi
from .auth import SignupApi, LoginApi
+from .reset_password import ForgotPassword, ResetPassword
def initialize_routes(api):
...
api.add_resource(LoginApi, '/api/auth/login')
+
+ api.add_resource(ForgotPassword, '/api/auth/forgot')
+ api.add_resource(ResetPassword, '/api/auth/reset')
さて、アプリケーションを実行しようとするとpython app.py
以下のようなエラーが表示されます.ImportError: cannot import name 'initialize_routes' from 'resources.routes' (/home/paurakh/blog/flask/flask-restapi-series/movie-bag/resources/routes.py)
これはPythonの循環依存問題のためです.我々の中でreset_password.py
, インポートするsend_mail
インポート先app
からapp.py
にapp
まだ定義されていないapp.py
.この問題を解決するために
run.py
我々のルートディレクトリでは、我々のアプリを実行する責任があります.また、我々のアプリケーションを初期化した後、我々のルート/ビュー機能を初期化する必要があります.touch run.py
さて、我々app.py
次のようになります.#~/movie-bag/app.py
from database.db import initialize_db
from flask_restful import Api
-from resources.routes import initialize_routes
from resources.errors import errors
app = Flask(__name__)
app.config.from_envvar('ENV_FILE_LOCATION')
+mail = Mail(app)
+
+# imports requiring app and mail
+from resources.routes import initialize_routes
api = Api(app, errors=errors)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
-mail = Mail(app)
...
initialize_db(app)
initialize_routes(api)
-
-app.run()
我々の中でrun.py
我々は、アプリケーションを実行します#~/movie-bag/run.py
from app import app
app.run()
設定の追加MAIL_SERVER
イン.env
JWT_SECRET_KEY = 't1NP63m4wnBg6nyHYKfmc2TpCOGI4nss'
+MAIL_SERVER = "localhost"
+MAIL_PORT = "1025"
+MAIL_USERNAME = "[email protected]"
+MAIL_PASSWORD = ""
次の端末でSMTPサーバを起動します.python -m smtpd -n -c DebuggingServer localhost:1025
これは、私たちの電子メール機能をテストするためのSMTPサーバーを作成します.今すぐアプリを実行する
python run.py
注:エクスポートを覚えてENV_FILE_LOCATION
電子メールの場合は、既存のユーザーの場合は
smtp
サーバとして:
<p>Dear, User</p>
<p>
To reset your password
<a href="http://localhost:3000/reset/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzgzOTU0ODUsIm5iZiI6MTU3ODM5NTQ4NSwianRpIjoiZTEyZDg3ODgtMTkwZS00NWI1LWI0YzYtZTdkMTYzZjc5ZGZlIiwiZXhwIjoxNTc4NDgxODg1LCJpZGVudGl0eSI6IjVlMTQxNTJmOWRlNzQxZDNjNGYwYmNiYiIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.dLJnhYTYMnLuLg_cHDdqi-jsXeISeMq75mb-ozaNxlw">
click here
</a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>http://localhost:3000/reset/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzgzOTU0ODUsIm5iZiI6MTU3ODM5NTQ4NSwianRpIjoiZTEyZDg3ODgtMTkwZS00NWI1LWI0YzYtZTdkMTYzZjc5ZGZlIiwiZXhwIjoxNTc4NDgxODg1LCJpZGVudGl0eSI6IjVlMTQxNTJmOWRlNzQxZDNjNGYwYmNiYiIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.dLJnhYTYMnLuLg_cHDdqi-jsXeISeMq75mb-ozaNxlw</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely</p>
<p>Movie-bag Support Team</p>
URLは次のようになります.http://localhost:3000/reset/<reset_token>
, このトークンを手動でコピーする必要があります/reset
エンドポイント.注:私たちはどのように自動的に我々のフロントエンドシリーズでリセットするために実装する方法を学びますが、今のところ我々は手動でresetRankトークンをコピーする必要があります
おめでとう、パスワードが正常に変更されます.今、あなたは新しいパスワードでログインできます.
また、あなたのパスワードが正常にリセットされたことを示すメールを取得する必要があります.
<p>Password reset was successful</p>
今まで書いたすべてのコードを見つけることができますhere 我々がシリーズのこの部分から学んだこと?
Flask-mail
それまで、ハッピーコーディング😊
Reference
この問題について(フラスコ静止API -パート5), 我々は、より多くの情報をここで見つけました https://dev.to/paurakhsharma/flask-rest-api-part-5-password-reset-2f2eテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol