페트병 Rest API. 섹션 5: 비밀번호 재설정

섹션 5: 암호 재설정


안녕하세요.이 시리즈의 앞부분에서 Flask의 오류를 처리하고 클라이언트에게 의미 있는 오류 메시지를 보내는 방법을 배웠습니다.
이 부분에서 우리는 응용 프로그램에서 암호 리셋 기능을 실현할 것이다.
다음은 비밀번호 재설정 프로세스의 약도입니다.

암호 재설정 프로세스 맵
우리는 flask-jwt-extended 라이브러리를 사용하여 암호 리셋 영패를 생성할 것이다. 좋은 것은 우리가 신분 검증을 실현할 때 이미 그것을 설치했기 때문이다.우리는 사용자에게 리셋 영패를 이메일로 보내야 하기 때문에 Flask Mail 을 사용할 것입니다.
pipenv install flask-mail
이 메일 서버를 등록하려면 다음과 같이 하십시오.
#~/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'
...
이제 클라이언트에게 전자 메일을 보내고 새 폴더 app.py 와 새 파일 services 을 만드는 서비스를 만듭니다.새로 만든 파일에 다음 내용을 추가합니다.
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()
여기에서 볼 수 있듯이 우리는 함수mail_service.py를 만들었는데, 함수send_mail(), subject, sender, recipients, text_body를 매개 변수로 한다.그리고 메시지 대상을 만들고 단독 루트에서 실행합니다 html_body. 클라이언트에게 이메일을 보낼 때 구글, Outlook 등 단독 서비스에 중계해야 하기 때문입니다.
이러한 서비스가 실제로 전자메일을 보내는 데 시간이 좀 걸릴 수 있기 때문에, 우리는 고객에게 그들의 요청이 성공했다고 알리고, 단독 라인에서 전자메일을 보내기 시작할 것이다.
지금 우리는 비밀번호 리셋을 실시할 준비가 되어 있다.위의 그림에서 보듯이 우리는 이 때문에 두 개의 다른 단점을 만들 것이다.
1)send_async_email(): 이 노드에서 계정을 변경해야 하는 사용자/forget를 가져옵니다.그리고 이 단점은 비밀번호를 재설정하기 위해 영패 재설정을 포함하는 링크를 사용하여 사용자에게 이메일을 보냅니다.
2)email: 이 노드는 전자 메일에서 보낸 /reset 및 새 reset_token 를 수신합니다.password 폴더에 reset_password.py 을 만듭니다.코드는 다음과 같습니다.
#~/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
resources 자원에서 우리는 먼저 클라이언트가 제공한 ForgotPassword에 따라 사용자를 얻는다.그리고 email 기반 create_access_token() 영패를 만듭니다. 이 영패는 24시간 후에 만료됩니다.그리고 우리는 이메일을 고객에게 보낼 것이다.전자 우편에는 user.id 및 텍스트 형식 정보가 포함되어 있습니다.
이와 유사하게 HTML 자원에서 우리는 우선reset_영패의 사용자 id에 따라 사용자를 얻고 사용자가 제공한 비밀번호에 따라 사용자의 비밀번호를 리셋합니다.마지막으로 사용자에게 재설정 성공 이메일을 보냅니다.ResetPassword 에서 새로운 이상 EmailDoesnotExistsErrorBadTokenError 을 만듭니다.
#~/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과 텍스트 파일에 대한 템플릿을 만들어야 합니다.루트 디렉터리에 errors.py 폴더를 만들고, templates 에 다른 폴더 templates 를 만들고, 거기서 두 개의 새 파일 emailreset_password.html 을 만들 것입니다.
mkdir templates
cd templates
mkdir email
cd email
touch reset_password.html
touch reset_password.txt
reset_password에 있습니다.html에서는 다음을 추가합니다.
<!-- #~/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>

여기reset_password.txt는 우리가 이전에 {{ url }} 함수에서 보낸 URL을 대체했습니다.
마찬가지로 render_template() 에 다음을 추가합니다.
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
이제 우리는 이것reset_password.txt을 우리의 Resources에 연결할 준비를 하고 있다.
 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')

현재 응용 프로그램을 실행하려면 routes.py다음과 같은 오류가 표시됩니다.
ImportError: cannot import name 'initialize_routes' from 'resources.routes' (/home/paurakh/blog/flask/flask-restapi-series/movie-bag/resources/routes.py)
이는python에 순환 의존성 문제가 존재하기 때문이다.우리의 python app.py 중에서 우리는 reset_password.py 에서 send_mail 를 가져왔고, app 는 우리의 app.py 에서 정의되지 않았다.

이 문제를 해결하기 위해서, 우리는 루트 디렉터리에 다른 파일 app 을 만들 것입니다. 이 파일은 우리의 응용 프로그램을 실행하는 것을 책임집니다.또한 응용 프로그램을 초기화한 후에 루트/보기 기능을 초기화해야 합니다.
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()
Dellrun.py에서는 애플리케이션만 실행합니다.
#~/movie-bag/run.py

from app import app

app.run()
app.py에 추가run.py 구성

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
참고: 내보내기 MAIL_SERVER
전자 메일이 기존 사용자의 경우 실행 중인 서버의 터미널에서 전자 메일을 볼 수 있습니다.

<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의 형식은 다음과 같습니다..env, 이 영패를 ENV_FILE_LOCATION 단점으로 복사해서 수동으로 보내야 합니다.
주의: 우리는 전단 시리즈에서 자동 리셋을 실현하는 방법을 배울 것입니다. 그러나 현재는reset\u 영패를 수동으로 복사해야 합니다

비밀번호를 변경한 것을 축하합니다.이제 새 암호로 로그인할 수 있습니다.
암호가 재설정되었음을 알리는 이메일도 받아야 합니다.
<p>Password reset was successful</p>
지금까지 작성한 모든 코드를 찾을 수 있습니다 here

우리는 이 시리즈의 이 부분에서 무엇을 배웠습니까?

  • 사용자 비밀번호 재설정에 사용할 영패
  • 를 만드는 방법
  • 사용자에게 이메일을 보내는 방법 smtp
  • 사용자 암호 재설정 방법
  • 어떻게 플라스크의 순환 의존을 피할 수 있습니까?
  • 이 시리즈의 다음 섹션에서는 Flask REST API를 테스트하는 방법을 학습합니다.
    그 전에 즐거움 코드😊

    좋은 웹페이지 즐겨찾기