Browse Source

Start to add user authentication

Getty Ritter 4 years ago
parent
commit
4ff110d3d1
4 changed files with 93 additions and 32 deletions
  1. 64 29
      lc/app.py
  2. 4 2
      lc/config.py
  3. 17 0
      lc/error.py
  4. 8 1
      lc/model.py

+ 64 - 29
lc/app.py

@@ -21,24 +21,54 @@ def render(name, **kwargs):
 
 
 class Endpoint:
+    def __init__(self):
+        self.user = None
+
+        # try finding the token
+        token = None
+        if (auth := flask.request.headers['Authorization']):
+            token = auth.split()[1]
+        elif flask.session['auth']:
+            token = flask.session['auth']
+
+        if token and (payload := c.SERIALIZER.loads(token)):
+            if 'name' not in payload or 'password' not in payload:
+                return
+
+            try:
+                u = m.User.by_slug(payload['name'])
+            except e.LCException:
+                return
+
+            if u.authenticate(payload['password']):
+                self.user = u
+
+    def require_authentication(self, name: user):
+        if name != self.user.name:
+            raise e.BadPermissions()
+
     def api_post(self, *args, **kwargs) -> dict:
-        raise NotImplemented
+        raise e.NotImplemented()
 
-    def public(self, *args, **kwargs):
-        raise NotImplemented
+    def api_get(self, *args, **kwargs) -> dict:
+        raise e.NotImplemented()
 
-    def private(self, user, *args, **kwargs):
-        return flask.redirect("/")
+    def html(self, *args, **kwargs):
+        raise e.NotImplemented()
 
     def route(self, *args, **kwargs):
-        if flask.request.method == "POST":
-            try:
-                return self.api_post(*args, **kwargs)
-            except e.LCException as exn:
-                return ({"status": exn.http_code(), "error": str(exn)}, exn.http_code())
+        try:
+            if flask.request.method == "POST":
+                require_authentication()
+                return flask.jsonify(self.api_post(*args, **kwargs))
+            elif (flask.request.method in ["GET", "HEAD"] and
+                  flask.request.content_type == "application/json"):
+                return flask.jsonify(self.api_get(*args, **kwargs))
+        except e.LCException as exn:
+            return ({"status": exn.http_code(), "error": str(exn)}, exn.http_code())
 
         try:
-            return self.public(*args, **kwargs)
+            return self.html(*args, **kwargs)
         except e.LCException as exn:
             page = render(
                 "main", title="error", content=f"shit's fucked yo: {exn}", user=None,
@@ -57,33 +87,38 @@ def endpoint(cls):
 @app.route("/")
 @endpoint
 class Index(Endpoint):
-    def public(self):
-        return render("main", title="main", content="whoo", user=None)
+    def html(self):
+        return render("main", title="main", content="whoo", user=self.user)
 
 
-@app.route("/auth")
+@app.route("/auth", methods=["GET", "POST"])
 @endpoint
 class Auth(Endpoint):
     def api_post(self):
-        u = m.User.login(r.User.from_json(flask.request.data))
-        return flask.redirect(u.base_url())
+        return m.User.login(r.User.from_json(flask.request.data))
 
 
-@app.route("/u")
-def create_user():
-    print(flask.request.data)
-    u = m.User.from_request(r.User.from_json(flask.request.data))
-    return flask.redirect(u.base_url())
+@app.route("/u", methods=["GET", "POST"])
+@endpoint
+class CreateUser(Endpoint):
+    def api_post(self):
+        u = m.User.from_request(r.User.from_json(flask.request.data))
+        return flask.redirect(u.base_url())
 
 
-@app.route("/u/<string:user>")
-def get_user(user: str):
-    u = m.User.by_slug(user)
-    pg = int(flask.request.args.get("page", 0))
-    links = u.get_links(page=pg)
-    return render(
-        "main", title=f"user {u.name}", content=render("linklist", links=links), user=u,
-    )
+@app.route("/u/<string:slug>")
+@endpoint
+class GetUser(Endpoint):
+    def html(self, slug: str):
+        u = m.User.by_slug(slug)
+        pg = int(flask.request.args.get("page", 0))
+        links = u.get_links(page=pg)
+        return render(
+            "main", title=f"user {u.name}", content=render("linklist", links=links), user=self.user,
+        )
+
+    def api_get(self, current_user, slug: str):
+        return m.User.by_slug(slug).to_dict()
 
 
 @app.route("/u/<string:user>/l")

+ 4 - 2
lc/config.py

@@ -1,15 +1,17 @@
 import os
 import sys
 
+import itsdangerous
 import playhouse.sqlite_ext
 
 DB = playhouse.sqlite_ext.SqliteExtDatabase(None)
 PER_PAGE = 50
+SERIALIZER = itsdangerous.URLSafeSerializer('TEMP KEY')
 
 if sys.stderr.isatty():
 
     def log(msg):
-        sys.stderr.write(msg)
+        sys.stderr.write(str(msg))
         sys.stderr.write("\n")
 
 
@@ -17,5 +19,5 @@ else:
 
     def log(msg):
         sys.stderr.write("\x1b[31m")
-        sys.stderr.write(msg)
+        sys.stderr.write(str(msg))
         sys.stderr.write("\x1b[39m\n")

+ 17 - 0
lc/error.py

@@ -34,3 +34,20 @@ class BadPassword(LCException):
 
     def __str__(self):
         return f"Wrong password for user {self.name}."
+
+@dataclass
+class NotImplemented(LCException):
+    def __str__(self):
+        return f"Bad request: no handler for route."
+
+    def http_code(self) -> int:
+        return 404
+
+
+@dataclass
+class BadPermissions(LCException):
+    def __str__(self):
+        return f"Insufficient permissions."
+
+    def http_code(self) -> int:
+        return 400

+ 8 - 1
lc/model.py

@@ -1,6 +1,7 @@
 import datetime
 from passlib.apps import custom_app_context as pwd
 import peewee
+import playhouse.shortcuts
 import typing
 
 import lc.config as c
@@ -12,6 +13,9 @@ class Model(peewee.Model):
     class Meta:
         database = c.DB
 
+    def to_dict(self):
+        return playhouse.shortcuts.model_to_dict(self)
+
 
 # TODO: figure out authorization for users (oauth? passwd?)
 class User(Model):
@@ -38,7 +42,7 @@ class User(Model):
         u = User.by_slug(user.name)
         if not u.authenticate(user.password):
             raise e.BadPassword(name=user.name)
-        return u
+        return c.SERIALIZER.dumps(user.to_dict())
 
     @staticmethod
     def by_slug(slug: str) -> "User":
@@ -56,6 +60,9 @@ class User(Model):
     def get_tag(self, tag_name: str) -> "Tag":
         return Tag.get((Tag.user == self) & (Tag.name == tag_name))
 
+    def to_dict(self):
+        return {"id": self.id, "name": self.name}
+
 
 class Link(Model):
     """