Repos / pytaku / bb3839b866
commit bb3839b86675853b857ba905f1dda75ae52f3e2b
Author: Bùi Thành Nhân <hi@imnhan.com>
Date: Wed Aug 26 20:53:39 2020 +0700
cookie-based auth
Gave up on session/localStorate. WHY THE FUCK is sessionStorage not
shared between tabs? It's impossible to implement a straightforward "log
out when browser closes" scenario.
diff --git a/src/pytaku/decorators.py b/src/pytaku/decorators.py
index 22015e7..01d1a8d 100644
--- a/src/pytaku/decorators.py
+++ b/src/pytaku/decorators.py
@@ -9,15 +9,11 @@ def process_token(required=True):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
- header = request.headers.get("Authorization")
- if not header or not header.startswith("Bearer "):
+ token = request.cookies.get("token")
+ if not token:
if required:
return (
- jsonify(
- {
- "message": "Missing `Authorization: Bearer <token>` header."
- }
- ),
+ jsonify({"message": "Missing 'token' cookie."}),
401,
)
else:
@@ -25,7 +21,6 @@ def decorated_function(*args, **kwargs):
request.user_id = None
return f(*args, **kwargs)
- token = header[len("Bearer ") :]
user_id = verify_token(token)
if user_id is None:
return jsonify({"message": "Invalid token."}), 401
diff --git a/src/pytaku/main.py b/src/pytaku/main.py
index 09970cf..2574598 100644
--- a/src/pytaku/main.py
+++ b/src/pytaku/main.py
@@ -309,7 +309,11 @@ def api_login():
return jsonify({"message": "Wrong username/password combination."}), 401
token = create_token(user_id, remember)
- return jsonify({"user_id": user_id, "token": token}), 200
+ resp = make_response(jsonify({"user_id": user_id, "token": token}))
+ resp.set_cookie(
+ "token", token, max_age=31536000 if remember else None, samesite="Strict"
+ )
+ return resp
@app.route("/api/verify-token", methods=["GET"])
@@ -324,7 +328,9 @@ def api_logout():
num_deleted = delete_token(request.token)
if num_deleted != 1:
return jsonify({"message": "Invalid token."}), 401
- return "{}", 200
+ resp = make_response("{}", 200)
+ resp.set_cookie("token", "", expires=0)
+ return resp
@app.route("/api/follows", methods=["GET"])
diff --git a/src/pytaku/static/js/models.js b/src/pytaku/static/js/models.js
index 0159684..e683512 100644
--- a/src/pytaku/static/js/models.js
+++ b/src/pytaku/static/js/models.js
@@ -1,7 +1,18 @@
+function readCookie(cookieString) {
+ // This is terrible and wrong but it's enough for what we need i.e. just get
+ // the damn token.
+ const result = {};
+ cookieString.split(";").forEach((pair) => {
+ const [key, val] = pair.split("=");
+ result[key] = val;
+ });
+ return result;
+}
+
const Auth = {
username: sessionStorage.getItem("username"),
userId: sessionStorage.getItem("userId"),
- token: localStorage.getItem("token"),
+ token: readCookie(document.cookie).token || null,
isLoggedIn: () => Auth.username !== null && Auth.userId !== null,
init: () => {
// Already logged in, probably from another tab:
@@ -21,7 +32,6 @@ const Auth = {
.request({
method: "GET",
url: "/api/verify-token",
- headers: { Authorization: `Bearer ${Auth.token}` },
})
.then((result) => {
// Success! Set user info for this session now
@@ -41,21 +51,8 @@ const Auth = {
},
saveLoginResults: ({ userId, username, token, remember }) => {
- // FIXME: currently, even when remember=false we're still storing the token
- // in localStorage, simply because sessionStorage isn't shared across tabs.
- // Unfortunately this means when user logs in without checking "remember
- // me", the token will still linger in localstorage after browser is
- // closed, and if an adversary reopens browser within the token's
- // server-enforced lifespan (1 day), then user is pwned.
- //
- // Either that or we stick to sessionStorage and forget multitab support.
- // _OR_ we do a convoluted song and dance with storage events:
- // > https://stackoverflow.com/a/32766809
- //
- // 0 days since web APIs last made me sad.
sessionStorage.setItem("userId", userId);
sessionStorage.setItem("username", username);
- localStorage.setItem("token", token);
Auth.userId = userId;
Auth.username = username;
Auth.token = token;
@@ -77,15 +74,16 @@ const Auth = {
Auth.username = null;
Auth.token = null;
Auth.userId = null;
- localStorage.clear();
sessionStorage.clear();
},
request: (options) => {
- if (Auth.isLoggedIn()) {
- options.headers = { Authorization: `Bearer ${Auth.token}` };
- }
- return m.request(options);
+ return m.request(options).catch((err) => {
+ if (err.code == 401) {
+ Auth.clearCredentials();
+ }
+ throw err;
+ });
},
};