必要なもの一覧
userデータベース
最低IDとパスワードは必要ですので、それらを作ります
user.py
class User:
def __init__(self):
self.db = base.Base()
self.user = self.db.openBase("users")
def newUser(self, userid, password):
self.user.put({"id":userid,"password": password})
このdeta.spaceでは、データベースにNoSQLを使っていて、テーブル作成のUIはないので、プログラムで作成していきます。
user.pyのコンストラクタを呼び出すと、usersという名前のデータベースのフォルダーのようなものができます。ここに辞書型のデータを出力するとそれがデータベースになります。ただpasswordをそのまま保存してはセキュリティ的にまずいので暗号化しますが、それはmainでやることにします。
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>1MByte以下の小さな写真を共有するサイトです</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>
Bootstrap5の説明は省略します。idとpasswordをformで宣言して、buttonでsubmitします。deta.shにメールでもついていればちゃんと確認メールするのですが、今回はないので無検査となります。初回だけSing Upを押してもらい、次回以降はSign inでログインしてもらいますので、その区別をつけています。
main.py
mainは、全体のコントロールをするところになりますので、これからも何回か出てきますが、今回はLOGInに関するところだけ記述します。最後に全体のmain.pyを載せます。
from flask import Flask, redirect, jsonify, url_for, render_template, request, session
from flask_bcrypt import Bcrypt
import datetime
import user
import event
import driveFile
import photoBase
import paticip
import os
import json
app = Flask(__name__)
bcrypt = Bcrypt(app)
app.secret_key = 'hogehogetaratara'
app.permanent_session_lifetime = datetime.timedelta(minutes=3)
@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')
if request.form.get("signin"):
# 存在チェック
users = user.User()
checkid = users.getUser({"id":userid})
if checkid.count > 0 and userid == checkid.items[0].get("id"):
if bcrypt.check_password_hash(checkid.items[0].get("password"),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.User()
checkid = users.getUser({"id":userid})
if checkid != "" and passHash != "":
if userid in [i['id'] for i in checkid.items]:
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")
@app.route("/signup", methods=["GET"])
def help():
return render_template("signup.html",id=session["id"])
flaskで書いてあります。main.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に移動します。簡単な仕様を表示します。