γεια σας,我是Charlie!
在Day25當中我們完成了Email訂單通知,而今天我們將完成忘記密碼的部分。
================================◉‿◉=================================
我們的忘記密碼是要做成Email驗證的機制,所以必須要在使用者按下忘記密碼後後端發送Email,讓使用者點選Email中的連結後可以重設密碼。
所以我們先建立一個新的APP:resetPWD,並且加上URL連結:
keyboardmarket\urls.py
url('reset',include('resetPWD.urls')),
resetPWD\urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$',views.resetPWD)
]
接著是views的部分,這裡的話會分成三種:
第一種是POST請求,而第二種是GET請求,第三種是PUT請求。
而我們這邊的TOKEN產生使用PyJWT,因為需要有期限、加密,故這邊使用。
首先先建立產生token跟解譯token的方法:
def makeResetToken(user):
key = "Your random generate key"
now = datetime.datetime.now()
expiretime = now + datetime.timedelta(hours = 1)
username = user.name
payload = {
"username":username,
"exp":expiretime.timestamp()
}
return jwt.encode(payload,key,algorithm = 'HS256')
def decodeResetToken(token):
key = "Your random generate key"
try:
res = jwt.decode(token,key,algorithms = ['HS256'])
except jwt.ExpiredSignatureError:
return R.badRequest("驗證超時,請重新操作!")
except Exception as e:
return R.internalServerError(str(e))
else:
return res["username"]
接著我們先建立post裡面的基本程式碼:
if request.method == "POST":
req = request.body
data = json.loads(req)
if "username" not in data:
return R.badRequest("username does not exist!")
user = User.objects.filter(name = data["username"])
if not user:
return R.badRequest("user not found!")
token = makeResetToken(user[0])
return R.ok("reset password request success")
能產生token之後,接著我們要發送token到使用者的email,在templates當中建立reset資料夾,建立resetpassword.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>重設密碼</title>
</head>
<body>
<h1>{{ username }}您好</h1>
<p>您在keyboardmarket發起了重置密碼的請求</p>
<p>密碼重置連結為:</p>
<p>{{ token }}</p>
<p>請在一小時內完成重置密碼動作,否則失效</p>
<hr>
<p>本信件為自動發送,請勿回覆</p>
</body>
</html>
接著在emailClient中新建send_reset_message方法:
def send_reset_message(self,
username,
token,
user_email):
email_template = render_to_string(
'reset/resetpassword.html',
{
"username":username,
"token":"http://localhost:8080/#/reset?token=" + token
}
)
email = EmailMessage(
"鍵盤貿易 - 重設密碼通知信",
email_template,
self.EMAIL_HOST_USER,
[user_email]
)
email.content_subtype = 'html'
email.fail_silently = False
email.send()
然後到resetPWD views中新增寄送方法:
token = makeResetToken(user[0])
username = user[0].name
user_email = user[0].email
client.send_reset_message(username,token,user_email)
return R.ok("reset password request success")
再來是GET方法,GET方法是驗證token是否合法,所以在GET方法中建立檢查:
if request.method == "GET":
req = request.GET
if "token" not in req:
return R.badRequest("token not found")
token = req["token"]
username = decodeResetToken(token)
if type(username) != str:
return username
user = User.objects.filter(name = username)
if not user:
return R.badRequest("user not found")
return R.ok("request is valid")
再來是PUT方法,修改密碼的部分:
if request.method == "PUT":
req = request.body
data = json.loads(req)
if "password" not in data or "token" not in data:
return R.badRequest("required parameter not found")
username = decodeResetToken(data["token"])
if type(username) != str:
return username
user = User.objects.filter(name = username)
if not user:
return R.badRequest("User not found")
user = user[0]
md5 = hashlib.md5()
passwordString = data["password"] + username
md5.update(passwordString.encode())
pwd = md5.hexdigest()
user.password = pwd
user.save()
return R.ok("update success")
再來是前端的部分,先在loginPage裡面加上超連結:
<a href="/#/createreset">忘記密碼?</a>
接著在components當中建立createreset.vue,讓使用者可以輸入用戶名稱以獲取email訊息:
<template>
<html lang="zh-Hant-TW">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/x-con" href="@/assets/favicon.ico">
<div id="app">
<headerComponent></headerComponent>
<div id="resetHeader" style="width:100%;height:100px;background-image: linear-gradient(to right,#C9C9C9,#F2FFFF,#00E6E6);padding-top: 40px;">
<h3>重置密碼</h3>
</div>
<div id="resetForm">
<b-form @submit="onSubmit" style="width: 100%;">
<b-form-group
id="username+group"
label="用戶名:"
label-for="username"
style="margin:10px;"
>
<b-form-input
id="username"
placeholder="請輸入用戶名"
v-model="username"
required
>
</b-form-input>
</b-form-group>
<b-button type="submit" variant="info" style="width: 80px;margin: 10px;">發送重置連結</b-button>
<div id="result">
<p>{{ resetText }}</p>
</div>
</b-form>
</div>
</div>
</html>
</template>
接著在apis當中建立reset.js,新增發送重置密碼訊息的方法:
import { host,port } from '@/apis/constant.js'
import axios from 'axios'
export function createReset(username){
return axios.post(`http://${host()}:${port()/reset`,{
"username":username
})
}
然後在createResetPage當中引入,並且新增onSubmit方法:
methods:{
onSubmit(e){
e.preventDefault()
createReset(this.username).then((response) => {
if(response.data.code == STATUS_OK){
this.resetText = "重置信發送成功,請至當初登記信箱查收"
}else{
this.resetText = response.data.data
}
})
}
}
可以測試看看能不能收的到信:
再來是reset的部分,先建立reset.vue,並加上路徑:
<template>
<html lang="zh-Hant-TW">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/x-con" href="@/assets/favicon.ico">
<div id="app">
<headerComponent></headerComponent>
<div id="resetHeader" style="width:100%;height:100px;background-image: linear-gradient(to right,#C9C9C9,#F2FFFF,#00E6E6);padding-top: 40px;">
<h3>重置密碼</h3>
</div>
<div id="resetForm" style="width: 50%;height: 500px;">
<b-form @submit="onSubmit" style="width:100%;">
<b-form-group
id="password_group"
label="密碼:"
label-for="password"
style="margin:10px;"
>
<b-form-input
id="password"
placeholder="請輸入密碼"
v-model="password"
required
>
</b-form-input>
</b-form-group>
<b-form-group
id="password1_group"
label="密碼2:"
label-for="password1"
style="margin:10px;"
>
<b-form-input
id="password1"
placeholder="請再次輸入密碼"
v-model="password1"
required
>
</b-form-input>
</b-form-group>
<b-button type="submit" variant="info" style="width: 80px;margin: 10px;">重設</b-button>
</b-form>
</div>
</div>
</html>
</template>
接著先建立驗證token的API程式碼:
export function resetValidate(token){
return axios.get(`http://${host()}:${port()}/reset`,{
params:{
"token":token
}
})
}
然後在vue創立時驗證:
created(){
var token = this.$route.query.token
resetValidate(token).then((response) => {
if(request.data.code == STATUS_OK){
this.$fire({type:"success",text:"token驗證成功,請進行重置"})
}else{
this.$fire({type:"error",text:response.data.data}).then(() => {
location.href = "/#/index"
})
}
})
}
接著創立重置密碼的API方法,在reset.js中新增resetPassword方法:
export function resetPassword(password,token){
return axios.put(`http://${host()}:${port()}/reset`,{
"password":password,
"token":token
})
}
並修改onSubmit方法,成功的話則返回login Page:
onSubmit(e){
e.preventDefault()
if(this.password != this.password1){
this.$fire({type:"error",text:"兩次密碼不一致"}).then(() => {
return
})
}
var token = this.$route.query.token
resetPassword(this.password,token).then((response) => {
if(response.data.code == STATUS_OK){
this.$fire({type:"success",text:"修改密碼成功,將導回登入頁"}).then(() => {
location.href = "/#/login"
})
}else{
this.$fire({type:"error",text:response.data.data}).then(() => {
location.href = "/#/index"
})
}
})
}
就可以正常修改密碼了。
================================◉‿◉=================================
Day26結束了!在今天我們完成了修改密碼的前端跟後端的部分,而明天我們將開始實作google recaptcha的部分。