Browse Source

Add password updating

Getty Ritter 1 year ago
parent
commit
d48e94fe1c
5 changed files with 61 additions and 3 deletions
  1. 21 1
      lc/app.py
  2. 11 2
      lc/model.py
  3. 16 0
      lc/request.py
  4. 10 0
      lc/view.py
  5. 3 0
      templates/config.mustache

+ 21 - 1
lc/app.py

@@ -109,11 +109,14 @@ class GetUser(Endpoint):
 class GetUserConfig(Endpoint):
     def html(self, user: str):
         u = self.require_authentication(user)
+        status_msg = flask.request.args.get("m", None)
+        if status_msg is not None:
+            status_msg = int(status_msg)
         return render(
             "main",
             v.Page(
                 title="configuration",
-                content=render("config", u.get_config()),
+                content=render("config", u.get_config(status_msg)),
                 user=self.user,
             ),
         )
@@ -127,6 +130,23 @@ class CreateInvite(Endpoint):
         return self.api_ok(f"/u/{user}/config", {"invite": invite.token})
 
 
+@endpoint("/u/<string:user>/password")
+class ChangePassword(Endpoint):
+    def api_post(self, user: str):
+        u = self.require_authentication(user)
+        config_url = u.config_url()
+        req = self.request_data(r.PasswordChange)
+        try:
+            req.require_match()
+        except e.MismatchedPassword:
+            raise e.LCRedirect(f"{config_url}?m=2")
+        try:
+            u.change_password(req)
+        except e.BadPassword:
+            raise e.LCRedirect(f"{config_url}?m=3")
+        return self.api_ok(f"{config_url}?m=1")
+
+
 @endpoint("/u/<string:user>/l")
 class CreateLink(Endpoint):
     def html(self, user: str):

+ 11 - 2
lc/model.py

@@ -37,6 +37,12 @@ class User(Model):
         except peewee.IntegrityError:
             raise e.UserExists(name=user.name)
 
+    def change_password(self, req: r.PasswordChange):
+        if not pwd.verify(req.old, self.passhash):
+            raise e.BadPassword(name=self.name)
+        self.passhash = pwd.hash(req.n1)
+        self.save()
+
     @staticmethod
     def from_invite(user: r.User, token: str) -> "User":
         invite = UserInvite.by_code(token)
@@ -72,6 +78,9 @@ class User(Model):
     def base_url(self) -> str:
         return f"/u/{self.name}"
 
+    def config_url(self) -> str:
+        return f"/u/{self.name}/config"
+
     def get_links(
         self, as_user: Optional["User"], page: int
     ) -> Tuple[List[v.Link], v.Pagination]:
@@ -97,7 +106,7 @@ class User(Model):
     def to_dict(self) -> dict:
         return {"id": self.id, "name": self.name}
 
-    def get_config(self) -> v.Config:
+    def get_config(self, status_msg: Optional[int]) -> v.Config:
         admin_pane = None
         if self.is_admin:
             user_invites = [
@@ -109,7 +118,7 @@ class User(Model):
                 for ui in UserInvite.select().where(UserInvite.created_by == self)
             ]
             admin_pane = v.AdminPane(invites=user_invites)
-        return v.Config(username=self.name, admin_pane=admin_pane,)
+        return v.Config(username=self.name, admin_pane=admin_pane, msg=status_msg)
 
     def import_pinboard_data(self, stream):
         try:

+ 16 - 0
lc/request.py

@@ -56,6 +56,22 @@ class NewUser(Request):
         return User(name=self.name, password=self.n1)
 
 
+@dataclass_json
+@dataclass
+class PasswordChange(Request):
+    n1: str
+    n2: str
+    old: str
+
+    @classmethod
+    def from_form(cls, form: Mapping[str, str]):
+        return cls(old=form["old"], n1=form["n1"], n2=form["n2"],)
+
+    def require_match(self):
+        if self.n1 != self.n2:
+            raise e.MismatchedPassword()
+
+
 @dataclass_json
 @dataclass
 class Link(Request):

+ 10 - 0
lc/view.py

@@ -45,6 +45,7 @@ class AdminPane(View):
 class Config(View):
     username: str
     admin_pane: Optional[AdminPane]
+    msg: Optional[int] = None
 
     def bookmarklet_link(self):
         return (
@@ -55,6 +56,15 @@ class Config(View):
             + "/l?name=${document.title}&url=${document.URL}`);})();"
         )
 
+    def message(self) -> Optional[str]:
+        if self.msg == 1:
+            return "Password changed."
+        elif self.msg == 2:
+            return "Mismatched new passwords; please try again."
+        elif self.msg == 3:
+            return "Incorrect old password; please try again."
+        return None
+
 
 @dataclass
 class Tag(View):

+ 3 - 0
templates/config.mustache

@@ -18,6 +18,9 @@
         <input type="submit" value="Change Password" />
       </div>
     </form>
+    {{#message}}
+      <p>{{message}}</p>
+    {{/message}}
   </div>
 </div>
 <div class="config-pane">