primitive message editing support; new sanity checks
parent
a9634b9aea
commit
e892d20e18
44
bbj.el
44
bbj.el
|
@ -8,7 +8,8 @@
|
||||||
;; blah blah user servicable parts blah blaheiu hre ;;;;;;;;;;r;r;r;r;;;q;q;;;
|
;; blah blah user servicable parts blah blaheiu hre ;;;;;;;;;;r;r;r;r;;;q;q;;;
|
||||||
(defvar bbj-old-p (eq emacs-major-version 24))
|
(defvar bbj-old-p (eq emacs-major-version 24))
|
||||||
(defvar bbj-logged-in nil)
|
(defvar bbj-logged-in nil)
|
||||||
(defvar bbj-user nil)
|
(defvar bbj-username nil)
|
||||||
|
(defvar bbj-userid nil)
|
||||||
(defvar bbj-hash nil)
|
(defvar bbj-hash nil)
|
||||||
(make-variable-buffer-local
|
(make-variable-buffer-local
|
||||||
(defvar bbj-*usermap* nil))
|
(defvar bbj-*usermap* nil))
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
(local-set-key (kbd "+") 'bbj-compose)
|
(local-set-key (kbd "+") 'bbj-compose)
|
||||||
(local-set-key (kbd "c") 'bbj-compose)
|
(local-set-key (kbd "c") 'bbj-compose)
|
||||||
|
|
||||||
|
(local-set-key (kbd "e") 'bbj-edit-post)
|
||||||
(local-set-key (kbd "C-c C-c") 'bbj-aux)
|
(local-set-key (kbd "C-c C-c") 'bbj-aux)
|
||||||
(local-set-key (kbd "r") 'bbj-quote-current-post))
|
(local-set-key (kbd "r") 'bbj-quote-current-post))
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ for all JSON tourists."
|
||||||
;; json-false/json-nil are bound as nil here to stop them from being silly keywords
|
;; json-false/json-nil are bound as nil here to stop them from being silly keywords
|
||||||
(let (json message json-false json-null
|
(let (json message json-false json-null
|
||||||
(data (list
|
(data (list
|
||||||
(cons 'user bbj-user)
|
(cons 'user bbj-username)
|
||||||
(cons 'auth_hash bbj-hash)
|
(cons 'auth_hash bbj-hash)
|
||||||
(cons 'method method))))
|
(cons 'method method))))
|
||||||
;; populate a query with our hash and username, then the func arguments
|
;; populate a query with our hash and username, then the func arguments
|
||||||
|
@ -112,9 +114,12 @@ for all JSON tourists."
|
||||||
(point-min) (point-max)
|
(point-min) (point-max)
|
||||||
shell-file-name t t nil ;; meow meow
|
shell-file-name t t nil ;; meow meow
|
||||||
"-c" (format "nc %s %s" bbj-host bbj-port))
|
"-c" (format "nc %s %s" bbj-host bbj-port))
|
||||||
|
(when (eq (point-min) (point-max))
|
||||||
|
(user-error "Server is down"))
|
||||||
(setq json (progn (goto-char (point-min)) (json-read))))
|
(setq json (progn (goto-char (point-min)) (json-read))))
|
||||||
|
|
||||||
(if (eq json t) t ;; a few enpoints just return true/false
|
;; if the response is an atom, just return it. otherwise check for errors
|
||||||
|
(if (not (and (listp json) (eq json nil))) json
|
||||||
(setq message (bbj-descend json 'error 'description))
|
(setq message (bbj-descend json 'error 'description))
|
||||||
(case (bbj-descend json 'error 'code)
|
(case (bbj-descend json 'error 'code)
|
||||||
;; haha epic handling
|
;; haha epic handling
|
||||||
|
@ -128,7 +133,6 @@ for all JSON tourists."
|
||||||
(defun bbj-sethash (&optional password)
|
(defun bbj-sethash (&optional password)
|
||||||
"Either prompt for or take the arg `PASSWORD', and then sha256-hash it.
|
"Either prompt for or take the arg `PASSWORD', and then sha256-hash it.
|
||||||
Sets it globally and also returns it."
|
Sets it globally and also returns it."
|
||||||
(interactive)
|
|
||||||
(unless password (setq password
|
(unless password (setq password
|
||||||
(read-from-minibuffer "(Password)> ")))
|
(read-from-minibuffer "(Password)> ")))
|
||||||
(setq bbj-hash (secure-hash 'sha256 password)))
|
(setq bbj-hash (secure-hash 'sha256 password)))
|
||||||
|
@ -139,18 +143,20 @@ Sets it globally and also returns it."
|
||||||
care of that. Jumps to the index afterward. This function only needs to be used
|
care of that. Jumps to the index afterward. This function only needs to be used
|
||||||
once per emacs session."
|
once per emacs session."
|
||||||
(interactive)
|
(interactive)
|
||||||
(setq bbj-user (read-from-minibuffer "(BBJ Username)> "))
|
(setq bbj-username (read-from-minibuffer "(BBJ Username)> "))
|
||||||
(cond
|
(cond
|
||||||
((bbj-request "is_registered" 'target_user bbj-user)
|
((bbj-request "is_registered" 'target_user bbj-username)
|
||||||
(bbj-sethash)
|
(bbj-sethash)
|
||||||
(if (bbj-request "check_auth")
|
(if (bbj-request "check_auth")
|
||||||
(progn
|
(progn
|
||||||
(setq bbj-logged-in t)
|
(setq bbj-logged-in t
|
||||||
|
bbj-userid (bbj-request "user_name_to_id"
|
||||||
|
'target_user bbj-username))
|
||||||
(bbj-browse-index)
|
(bbj-browse-index)
|
||||||
(message "Logged in as %s!" bbj-user))
|
(message "Logged in as %s!" bbj-username))
|
||||||
(message "(Invalid Password!)")
|
(message "(Invalid Password!)")
|
||||||
(run-at-time 1 nil #'bbj-login)))
|
(run-at-time 1 nil #'bbj-login)))
|
||||||
((y-or-n-p (format "Register for BBJ as %s? " bbj-user))
|
((y-or-n-p (format "Register for BBJ as %s? " bbj-username))
|
||||||
(bbj-sethash)
|
(bbj-sethash)
|
||||||
(let ((response
|
(let ((response
|
||||||
(bbj-request "user_register"
|
(bbj-request "user_register"
|
||||||
|
@ -160,7 +166,7 @@ once per emacs session."
|
||||||
(message "%s" (alist-get 'error response))
|
(message "%s" (alist-get 'error response))
|
||||||
(setq bbj-logged-in t)
|
(setq bbj-logged-in t)
|
||||||
(bbj-browse-index)
|
(bbj-browse-index)
|
||||||
(message "Logged in as %s!" bbj-user))))))
|
(message "Logged in as %s!" bbj-username))))))
|
||||||
|
|
||||||
|
|
||||||
;;;; user navigation shit. a LOT of user navigation shit. ;;;;
|
;;;; user navigation shit. a LOT of user navigation shit. ;;;;
|
||||||
|
@ -339,7 +345,6 @@ assign CALLBACK to C-c C-c."
|
||||||
(defun bbj-consume-window (buffer)
|
(defun bbj-consume-window (buffer)
|
||||||
"Consume all text in the current buffer, delete the window if
|
"Consume all text in the current buffer, delete the window if
|
||||||
it is one, and kill the buffer. Returns property-free string."
|
it is one, and kill the buffer. Returns property-free string."
|
||||||
(interactive)
|
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
(let ((content (buffer-substring-no-properties
|
(let ((content (buffer-substring-no-properties
|
||||||
(point-min) (point-max))))
|
(point-min) (point-max))))
|
||||||
|
@ -460,6 +465,23 @@ it worked on emacs 24."
|
||||||
'type 'end))))
|
'type 'end))))
|
||||||
|
|
||||||
|
|
||||||
|
(defun bbj-edit-post ()
|
||||||
|
(interactive)
|
||||||
|
(let ((adminp (bbj-request "is_admin" 'target_user bbj-username))
|
||||||
|
(callback `(lambda ()
|
||||||
|
(let* ((message (bbj-consume-window (current-buffer)))
|
||||||
|
(request (bbj-request "edit_post"
|
||||||
|
'post_id ,(alist-get 'post_id (bbj-post-prop 'data))
|
||||||
|
'body message 'thread_id ,thread-id)))
|
||||||
|
(if (numberp (bbj-descend request 'error 'code))
|
||||||
|
(message bbj-descend request 'error 'description)
|
||||||
|
(message "post edited")
|
||||||
|
(bbj-enter-thread ,thread-id))))))
|
||||||
|
(cond
|
||||||
|
((and (not (eq ))))
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
(defun bbj-browse-index ()
|
(defun bbj-browse-index ()
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((inhibit-read-only t)
|
(let* ((inhibit-read-only t)
|
||||||
|
|
|
@ -143,41 +143,47 @@ the problem. `description` is intended for human consumption; in your client
|
||||||
code, use the error codes to handle conditions. The `presentable` column
|
code, use the error codes to handle conditions. The `presentable` column
|
||||||
indicates whether the `description` should be shown to users verbatim.
|
indicates whether the `description` should be shown to users verbatim.
|
||||||
|
|
||||||
| Code | Presentable | Documentation |
|
| Code | Presentable | Documentation |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 0 | Never, fix | Malformed json input. `description` is the error |
|
| 0 | Never, fix | Malformed json input. `description` is the error |
|
||||||
| | your client | string thrown by the server-side json decoder. |
|
| | your client | string thrown by the server-side json decoder. |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 1 | Not a good | Internal server error. Unaltered exception text |
|
| 1 | Not a good | Internal server error. Unaltered exception text |
|
||||||
| | idea, the | is returned as `description`. This shouldn't |
|
| | idea, the | is returned as `description`. This shouldn't |
|
||||||
| | exceptions | happen, and if it does, make a bug report. |
|
| | exceptions | happen, and if it does, make a bug report. |
|
||||||
| | are not | clients should not attempt to intelligently |
|
| | are not | clients should not attempt to intelligently |
|
||||||
| | helpful | recover from any errors of this class. |
|
| | helpful | recover from any errors of this class. |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 2 | Nadda. | Unknown `method` was requested. |
|
| 2 | Nadda. | Unknown `method` was requested. |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 3 | Fix. Your. | Missing or malformed parameter values for the |
|
| 3 | Fix. Your. | Missing, malformed, or otherwise incorrect |
|
||||||
| | Client. | requested `method`. |
|
| | Client. | parameters or values for the requested `method`. |
|
||||||
|------|--------------|---------------------------------------------------|
|
| | | This is returned, for example, when a request to |
|
||||||
| 4 | Only during | Invalid or unprovided `user`. |
|
| | | `edit_post` tries to edit a post_id that does |
|
||||||
| | registration | |
|
| | | not exist. Its also used to indicate a lack of |
|
||||||
| | | During registration, this code is returned with a |
|
| | | required arguments for a method. This is a generic |
|
||||||
| | | `description` that should be shown to the user. |
|
| | | error class that can cover programming errors |
|
||||||
| | | It could indicate an invalid name input, an |
|
| | | but never user errors. |
|
||||||
| | | occupied username, invalid/missing `auth_hash`, |
|
|------+--------------+----------------------------------------------------|
|
||||||
| | | etc. |
|
| 4 | Only during | Invalid or unprovided `user`. |
|
||||||
|------|--------------|---------------------------------------------------|
|
| | registration | |
|
||||||
| 5 | Always | `user` is not registered. |
|
| | | During registration, this code is returned with a |
|
||||||
|------|--------------|---------------------------------------------------|
|
| | | `description` that should be shown to the user. |
|
||||||
| 6 | Always | User `auth_hash` failed or was not provided. |
|
| | | It could indicate an invalid name input, an |
|
||||||
|------|--------------|---------------------------------------------------|
|
| | | occupied username, invalid/missing `auth_hash`, |
|
||||||
| 7 | Always | Requested thread does not exist. |
|
| | | etc. |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 8 | Always | Requested thread does not allow posts. |
|
| 5 | Always | `user` is not registered. |
|
||||||
|------|--------------|---------------------------------------------------|
|
|------+--------------+----------------------------------------------------|
|
||||||
| 9 | Always | Message edit failed; there is a 24hr limit for |
|
| 6 | Always | User `auth_hash` failed or was not provided. |
|
||||||
| | | editing posts. |
|
|------+--------------+----------------------------------------------------|
|
||||||
|------|--------------|---------------------------------------------------|
|
| 7 | Always | Requested thread does not exist. |
|
||||||
| 10 | Always | User action requires `admin` privilege. |
|
|------+--------------+----------------------------------------------------|
|
||||||
|------|--------------|---------------------------------------------------|
|
| 8 | Always | Requested thread does not allow posts. |
|
||||||
| 11 | Always | Invalid formatting directives in text submission. |
|
|------+--------------+----------------------------------------------------|
|
||||||
|
| 9 | Always | Message edit failed; there is a 24hr limit for |
|
||||||
|
| | | editing posts. |
|
||||||
|
|------+--------------+----------------------------------------------------|
|
||||||
|
| 10 | Always | User action requires `admin` privilege. |
|
||||||
|
|------+--------------+----------------------------------------------------|
|
||||||
|
| 11 | Always | Invalid formatting directives in text submission. |
|
||||||
|
|
127
src/db.py
127
src/db.py
|
@ -23,6 +23,7 @@ except FileNotFoundError:
|
||||||
f.write(json.dumps(USERDB))
|
f.write(json.dumps(USERDB))
|
||||||
path.os.chmod(path.join(PATH, "userdb"), 0o600)
|
path.os.chmod(path.join(PATH, "userdb"), 0o600)
|
||||||
|
|
||||||
|
|
||||||
### THREAD MANAGEMENT ###
|
### THREAD MANAGEMENT ###
|
||||||
|
|
||||||
def thread_index(key="lastmod", markup=True):
|
def thread_index(key="lastmod", markup=True):
|
||||||
|
@ -36,8 +37,10 @@ def thread_index(key="lastmod", markup=True):
|
||||||
|
|
||||||
def thread_create(author, body, title, tags):
|
def thread_create(author, body, title, tags):
|
||||||
ID = uuid1().hex
|
ID = uuid1().hex
|
||||||
# make sure None, False, and empty arrays are always repped consistently
|
if tags:
|
||||||
tags = tags if tags else []
|
tags = [tag.strip() for tag in tags.split(",")]
|
||||||
|
else: # make sure None, False, and empty arrays are always repped consistently
|
||||||
|
tags = []
|
||||||
scheme = schema.thread(ID, author, body, title, tags)
|
scheme = schema.thread(ID, author, body, title, tags)
|
||||||
thread_dump(ID, scheme)
|
thread_dump(ID, scheme)
|
||||||
return scheme
|
return scheme
|
||||||
|
@ -46,14 +49,7 @@ def thread_create(author, body, title, tags):
|
||||||
def thread_load(ID, markup=True):
|
def thread_load(ID, markup=True):
|
||||||
try:
|
try:
|
||||||
with open(path.join(PATH, "threads", ID), "r") as f:
|
with open(path.join(PATH, "threads", ID), "r") as f:
|
||||||
thread = json.loads(f.read())
|
return json.loads(f.read())
|
||||||
if not markup:
|
|
||||||
thread["body"] = formatting.cleanse(thread["body"])
|
|
||||||
for x in range(len(thread["replies"])):
|
|
||||||
thread["replies"][x]["body"] = formatting.cleanse(
|
|
||||||
thread["replies"][x]["body"])
|
|
||||||
return thread
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -82,6 +78,44 @@ def thread_reply(ID, author, body):
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def index_reply(reply_list, post_id):
|
||||||
|
for index, reply in enumerate(reply_list):
|
||||||
|
if reply["post_id"] == post_id:
|
||||||
|
return index
|
||||||
|
else:
|
||||||
|
raise IndexError
|
||||||
|
|
||||||
|
|
||||||
|
def edit_handler(json, thread=None):
|
||||||
|
try:
|
||||||
|
target_id = json["post_id"]
|
||||||
|
if not thread:
|
||||||
|
thread = thread_load(json["thread_id"])
|
||||||
|
if not thread:
|
||||||
|
return False, schema.error(7, "Requested thread does not exist.")
|
||||||
|
|
||||||
|
|
||||||
|
if target_id == 1:
|
||||||
|
target = thread
|
||||||
|
else:
|
||||||
|
target = thread["replies"][
|
||||||
|
index_reply(thread["replies"], target_id)]
|
||||||
|
|
||||||
|
if not user_is_admin(json["user"]):
|
||||||
|
if json["user"] != target["author"]:
|
||||||
|
return False, schema.error(10,
|
||||||
|
"non-admin attempt to edit another user's message")
|
||||||
|
|
||||||
|
elif (time() - target["created"]) > 86400:
|
||||||
|
return False, schema.error(9,
|
||||||
|
"message is too old to edit (24hr limit)")
|
||||||
|
|
||||||
|
return True, target
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
return False, schema.error(3, "post_id out of bounds for requested thread")
|
||||||
|
|
||||||
|
|
||||||
### USER MANAGEMENT ###
|
### USER MANAGEMENT ###
|
||||||
|
|
||||||
def user_dbdump(dictionary):
|
def user_dbdump(dictionary):
|
||||||
|
@ -104,6 +138,15 @@ def user_register(auth_hash, name, quip, bio):
|
||||||
if USERDB["namemap"].get(name):
|
if USERDB["namemap"].get(name):
|
||||||
return schema.error(4, "Username taken.")
|
return schema.error(4, "Username taken.")
|
||||||
|
|
||||||
|
for ok, error in [
|
||||||
|
user_namecheck(name),
|
||||||
|
user_authcheck(auth_hash),
|
||||||
|
user_quipcheck(quip),
|
||||||
|
user_biocheck(bio)]:
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
return error
|
||||||
|
|
||||||
ID = uuid1().hex
|
ID = uuid1().hex
|
||||||
scheme = schema.user_internal(ID, auth_hash, name, quip, bio, False)
|
scheme = schema.user_internal(ID, auth_hash, name, quip, bio, False)
|
||||||
USERDB.update({ID: scheme})
|
USERDB.update({ID: scheme})
|
||||||
|
@ -123,6 +166,70 @@ def user_auth(ID, auth_hash):
|
||||||
return auth_hash == USERDB[ID]["auth_hash"]
|
return auth_hash == USERDB[ID]["auth_hash"]
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_admin(ID):
|
||||||
|
return USERDB[ID]["admin"]
|
||||||
|
|
||||||
|
|
||||||
def user_update(ID, **params):
|
def user_update(ID, **params):
|
||||||
USERDB[ID].update(params)
|
USERDB[ID].update(params)
|
||||||
return USERDB[ID]
|
return USERDB[ID]
|
||||||
|
|
||||||
|
|
||||||
|
### SANITY CHECKS ###
|
||||||
|
|
||||||
|
def contains_nonspaces(string):
|
||||||
|
return any([char in string for char in "\t\n\r\x0b\x0c"])
|
||||||
|
|
||||||
|
|
||||||
|
def user_namecheck(name):
|
||||||
|
if not name:
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Username may not be empty.")
|
||||||
|
|
||||||
|
elif contains_nonspaces(name):
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Username cannot contain whitespace chars besides spaces.")
|
||||||
|
|
||||||
|
elif len(username) > 24:
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Username is too long (max 24 chars)")
|
||||||
|
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
|
||||||
|
def user_authcheck(auth_hash):
|
||||||
|
if not auth_hash:
|
||||||
|
return schema.error(3,
|
||||||
|
"auth_hash may not be empty")
|
||||||
|
|
||||||
|
elif len(auth_hash) != 64:
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Client error: invalid SHA-256 hash.")
|
||||||
|
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
|
||||||
|
def user_quipcheck(quip):
|
||||||
|
if not quip:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
elif contains_nonspaces(quip):
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Quip cannot contain whitespace chars besides spaces.")
|
||||||
|
|
||||||
|
elif len(quip) > 120:
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Quip is too long (max 120 chars)")
|
||||||
|
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
|
||||||
|
def user_biocheck(bio):
|
||||||
|
if not bio:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
elif len(bio) > 4096:
|
||||||
|
return False, schema.error(4,
|
||||||
|
"Bio is too long (max 4096 chars)")
|
||||||
|
|
||||||
|
return True, True
|
||||||
|
|
103
src/endpoints.py
103
src/endpoints.py
|
@ -1,17 +1,22 @@
|
||||||
from src import formatting
|
from src import formatting
|
||||||
from src import schema
|
from src import schema
|
||||||
|
from time import time
|
||||||
from src import db
|
from src import db
|
||||||
from json import dumps
|
|
||||||
|
|
||||||
endpoints = {
|
endpoints = {
|
||||||
"check_auth": ["user", "auth_hash"],
|
"check_auth": ["user", "auth_hash"],
|
||||||
"is_registered": ["target_user"],
|
"is_registered": ["target_user"],
|
||||||
"thread_load": ["thread_id"],
|
"is_admin": ["target_user"],
|
||||||
"thread_index": [],
|
"thread_index": [],
|
||||||
"thread_create": ["title", "body", "tags"],
|
"thread_load": ["thread_id"],
|
||||||
"thread_reply": ["thread_id", "body"],
|
"thread_create": ["title", "body", "tags"],
|
||||||
"user_register": ["user", "auth_hash", "quip", "bio"],
|
"thread_reply": ["thread_id", "body"],
|
||||||
"user_get": ["target_user"],
|
"edit_post": ["thread_id", "post_id", "body"],
|
||||||
|
"can_edit": ["thread_id", "post_id"],
|
||||||
|
"user_register": ["user", "auth_hash", "quip", "bio"],
|
||||||
|
"user_get": ["target_user"],
|
||||||
|
"user_name_to_id": ["target_user"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +26,8 @@ authless = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# this is not actually an endpoint, but produces a required
|
||||||
|
# element of thread responses.
|
||||||
def create_usermap(thread, index=False):
|
def create_usermap(thread, index=False):
|
||||||
if index:
|
if index:
|
||||||
return {user: db.user_get(user) for user in
|
return {user: db.user_get(user) for user in
|
||||||
|
@ -31,23 +38,81 @@ def create_usermap(thread, index=False):
|
||||||
return {x: db.user_get(x) for x in result}
|
return {x: db.user_get(x) for x in result}
|
||||||
|
|
||||||
|
|
||||||
|
def user_name_to_id(json):
|
||||||
|
"""
|
||||||
|
Returns a string of the target_user's ID when it is
|
||||||
|
part of the database: a non-existent user will return
|
||||||
|
a boolean false.
|
||||||
|
"""
|
||||||
|
return db.user_resolve(json["target_user"])
|
||||||
|
|
||||||
|
|
||||||
def is_registered(json):
|
def is_registered(json):
|
||||||
|
"""
|
||||||
|
Returns true or false whether target_user is registered
|
||||||
|
in the system. This function only takes usernames: not
|
||||||
|
user IDs.
|
||||||
|
"""
|
||||||
return bool(db.USERDB["namemap"].get(json["target_user"]))
|
return bool(db.USERDB["namemap"].get(json["target_user"]))
|
||||||
|
|
||||||
|
|
||||||
def check_auth(json):
|
def check_auth(json):
|
||||||
|
"Returns true or false whether auth_hashes matches user."
|
||||||
return bool(db.user_auth(json["user"], json["auth_hash"]))
|
return bool(db.user_auth(json["user"], json["auth_hash"]))
|
||||||
|
|
||||||
|
|
||||||
|
def is_admin(json):
|
||||||
|
"""
|
||||||
|
Returns true or false whether target_user is a system
|
||||||
|
administrator. Takes a username or user ID. Nonexistent
|
||||||
|
users return false.
|
||||||
|
"""
|
||||||
|
user = db.user_resolve(json["target_user"])
|
||||||
|
if user:
|
||||||
|
return db.user_is_admin(user)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def user_register(json):
|
def user_register(json):
|
||||||
|
"""
|
||||||
|
Registers a new user into the system. Returns the new internal user
|
||||||
|
object on success, or an error response.
|
||||||
|
|
||||||
|
auth_hash should be a hexadecimal SHA-256 string, produced from a
|
||||||
|
UTF-8 password string.
|
||||||
|
|
||||||
|
user should be a string containing no newlines and
|
||||||
|
under 24 characters in length.
|
||||||
|
|
||||||
|
quip is a string, up to 120 characters, provided by the user
|
||||||
|
the acts as small bio, suitable for display next to posts
|
||||||
|
if the client wants to. Whitespace characters besides space
|
||||||
|
are not allowed. The string may be empty.
|
||||||
|
|
||||||
|
bio is a string, up to 4096 chars, provided by the user that
|
||||||
|
can be shown on profiles. There are no character type limits
|
||||||
|
for this entry. The string may be empty.
|
||||||
|
|
||||||
|
All errors for this endpoint with code 4 should show the
|
||||||
|
description direcrtly to the user.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
return schema.response(
|
return schema.response(
|
||||||
db.user_register(
|
db.user_register(
|
||||||
json["auth_hash"],
|
|
||||||
json["user"],
|
json["user"],
|
||||||
|
json["auth_hash"],
|
||||||
json["quip"],
|
json["quip"],
|
||||||
json["bio"]))
|
json["bio"]))
|
||||||
|
|
||||||
|
|
||||||
|
def user_get(json):
|
||||||
|
user = db.user_resolve(json["target_user"])
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
return db.user_get(user)
|
||||||
|
|
||||||
|
|
||||||
def thread_index(json):
|
def thread_index(json):
|
||||||
index = db.thread_index(markup=not json.get("nomarkup"))
|
index = db.thread_index(markup=not json.get("nomarkup"))
|
||||||
return schema.response({"threads": index}, create_usermap(index, True))
|
return schema.response({"threads": index}, create_usermap(index, True))
|
||||||
|
@ -79,3 +144,21 @@ def thread_reply(json):
|
||||||
if json.get("nomarkup"):
|
if json.get("nomarkup"):
|
||||||
reply["body"] = formatting.cleanse(reply["body"])
|
reply["body"] = formatting.cleanse(reply["body"])
|
||||||
return schema.response(reply)
|
return schema.response(reply)
|
||||||
|
|
||||||
|
|
||||||
|
def can_edit(json):
|
||||||
|
return db.edit_handler(json)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def edit_post(json):
|
||||||
|
thread = db.thread_load(json["thread_id"])
|
||||||
|
admin = db.user_is_admin(json["user"])
|
||||||
|
target_id = json["post_id"]
|
||||||
|
query, obj = db.edit_handler(json, thread)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
obj["body"] = json["body"]
|
||||||
|
obj["lastmod"] = time()
|
||||||
|
obj["edited"] = True
|
||||||
|
db.thread_dump(json["thread_id"], thread)
|
||||||
|
return obj
|
||||||
|
|
|
@ -43,6 +43,13 @@ class RequestHandler(StreamRequestHandler):
|
||||||
db.user_auth(user, request.get("auth_hash")):
|
db.user_auth(user, request.get("auth_hash")):
|
||||||
return self.reply(schema.error(6, "Authorization failed."))
|
return self.reply(schema.error(6, "Authorization failed."))
|
||||||
|
|
||||||
|
# post_ids are always returned as integers, but for callers who
|
||||||
|
# provide them as something else, try to convert them.
|
||||||
|
if isinstance(request.get("post_id"), (float, str)):
|
||||||
|
try: request["post_id"] = int(request["post_id"])
|
||||||
|
except Exception:
|
||||||
|
return schema.error(3, "Non-numeric post_id")
|
||||||
|
|
||||||
# exception handling is now passed to the endpoints;
|
# exception handling is now passed to the endpoints;
|
||||||
# anything unhandled beyond here is a code 1
|
# anything unhandled beyond here is a code 1
|
||||||
self.reply(eval("endpoints." + endpoint)(request))
|
self.reply(eval("endpoints." + endpoint)(request))
|
||||||
|
|
Loading…
Reference in New Issue