必要なもの一覧
app.py
app.pyは、全体のコントロールをするところになりますので、これからも何回か出てきますが、今回はLOGInに関するところだけ記述します。
app = Flask(__name__)
bcrypt = Bcrypt(app)
app.secret_key = 'thequickbrownfox'
app.permanent_session_lifetime = datetime.timedelta(minutes=3)
#connect & cursor public
con = None
cur = None
script_directory = os.path.dirname(os.path.abspath(__file__))
def connect():
global mdb
global con
global cur
mdb = PADb()
con = mdb.getConnect()
cur = mdb.getDictCursor()
@app.route("/login", methods = ["POST", "GET"])
def login():
if request.method == "POST":
session.permanent = True
userid = request.form.get("id")
password = request.form.get("password")
passByte = bcrypt.generate_password_hash(password=password)
passHash = passByte.decode('utf-8')
connect()
if request.form.get("signin"):
# 存在チェック
#timeout check
users = User(con,cur)
try:
checkid = users.getUser(userid)
except:
return render_template("login.html",errmess="タイムアウトしました。再ロードしてください")
if userid == checkid.get("uid",0):
if bcrypt.check_password_hash(checkid.get("pass"),password):
session["id"] = userid
return redirect(url_for("top"))
else:
return render_template("login.html",errmess="idまたはpasswordが違います")
else:
return render_template("login.html",errmess="idまたはpasswordが違います")
elif request.form.get("signup"):
# 存在チェック
users = User(con,cur)
checkid = users.getUser(userid)
if checkid != "" and passHash != "":
if userid == checkid.get('uid',0):
return render_template("login.html",errmess="idがすでに使われています")
else:
users.newUser(userid,passHash)
session["id"] = userid
return render_template("signup.html",id=userid)
else:
return render_template("login.html",errmess="idを指定してください")
# login済みであれば入場できる
else:
if "id" in session:
return redirect(url_for("top"))
return render_template("login.html")
loginに来た時に、データベースへのコネクトを行います。ただ、しばらく放置した場合、タイムアウトしてしまうので、その時はまたloginに戻るのでその時に再度コネクトします。
アカウントを持たないときはsignupに行きます。
LOGIN画面
つづいてlogin用のhtmlが必要なので作成します。画面の作成はBootstrap5のサンプル画面をそのまま使いました。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>Event Photos</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" type="text/css"
href="{{ url_for('static', filename='css/style.css')}}">
</head>
<body class="text-center">
<div id="login-form" class="container">
<form class="form-signin" action="/login" method="POST">
<h1 class="h3 mb-3 fw-normal">Event Photos Sign in</h1>
<p class="mt-1 mb-1 text-muted"><small>小さな写真を共有するサイトです</small></p>
<div class="form-floating">
<input type="text" class="form-control" id="floatingInput" name="id" placeholder="your id" required>
<label for="floatingInput">Id</label>
</div>
<div class="form-floating">
<input type="password" class="form-control" id="floatingPassword" name="password" placeholder="Password" required>
<label for="floatingPassword">Password</label>
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit" name="signin" value="signin">Sign in</button>
<button class="w-100 btn btn-lg btn-secondary" type="submit" name="signup" value="singup">Sign Up(初回)</button>
<p class="text-danger">{{errmess}}</p>
<p class="mt-5 mb-3 text-muted">eternalkagosima © 2022</p>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
</body>
</html>
static/css/style.css
html,
body {
height: 100%;
}
body {
padding-bottom: 40px;
background-color: #f5f5f5;
}
/*
* Singin
*/
#login-form {
display: flex;
align-items: center;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/*
* index
*/
#index-header {
display: inline-flex;
}
.navbar {
margin-bottom: 20px;
}
.event-dir {
background-color: #f1f4f4;
}
.message-dir {
background-color: #f1f4f4;
}
.list-bgcolor {
background-color: rgb(134, 157, 199);
}
Bootstrap5の説明は省略します。idとpasswordをformで宣言して、buttonでsubmitします。pythonanywhereにメールでもついていればちゃんと確認メールするのですが、今回はないので無検査となります。初回だけSing Upを押してもらい、次回以降はSign inでログインしてもらいますので、その区別をつけています。
flaskで書いてあります。app.pyとHTMLとのデータの渡しと関係するクラスで書いてあります。
rootアドレスへの参照によってまだここに出てきていないtop()関数に処理が渡ります。で、ログインしているかセッションでチェックして、入ってなければlogin()関数が呼ばれます。
プログラムのいたるところでセッションのチェックで異常があればloginに帰ってきます。
ID,PASSWORDをフォームから受信して、passwordはHASH化しておきます。このメソッドは呼び出すたびにシードを変えているので違う結果を返します。作られたhashの中にシードも入れてあるので、check_password_hashでそれを再現して、同じになるかで確認します。
最初の判別は、signinとsignupです。どちらのボタンを押されたかで変わってきます。
signinならば、データベースからidで検索して、入力されたpasswordとデータベースのpasswordをcheck_password_hashで確認して同じならばセッションを設定してtopに移動します。
signupならば、idに空きがあるかチェックして空いていればデータベースにidとhashを入れて、signup.htmlに移動します。簡単な仕様を表示します。