def get_comments(sql_cursor, thread_id, since_comment_id): if not db.thread_exists(sql_cursor, thread_id): raise NonExistentThreadError(thread_id) # XXX: do we need to check if since_comment_id is valid..? return 200, {"comments": db.comments_since(sql_cursor, thread_id, since_comment_id)}
def post_comment(sql_cursor, thread_id, request_json, captcha_token): captcha_id = request_json["captcha_id"] if "captcha_id" in request_json else None post_json = request_json["post"] if "post" in request_json else None if captcha_token is None: raise MissingTokenError() # exists and not stale if not db.is_valid_captcha(sql_cursor, captcha_id): raise NonExistentCaptchaError(captcha_id) if not db.thread_exists(sql_cursor, thread_id): raise NonExistentThreadError(thread_id) if not is_token_valid(sql_cursor, captcha_id, captcha_token): raise InvalidTokenError() if not is_post_valid(post_json): raise InvalidPostError(post_json) title, comment_body, author_name = post_json["title"], post_json["body"], post_json["name"] # optional email field, argon2 hash it # TODO: check if valid! (potentially this https://stackoverflow.com/a/28982264 or https://stackoverflow.com/a/14485817) email = post_json["email"] if "email" in post_json else None email_hash = hash_email(email) if email is not None else None # TODO: get ip address to store # consume captcha token, plus post the comment to the database comment_id = db.submit_post(sql_cursor, thread_id, captcha_id, title, author_name, comment_body, email_hash) return 200, {"captcha_id": captcha_id, "comment_id": comment_id}
def make_captcha(sql_cursor, thread_id, challenge_type): """ @param thread_id: threads.id that the captcha is associated with @param challenge_type: either text/.... determines what kind of challenge will be returned @return status code, json output tuple This function generates a captcha, and returns the json to send back to the requesting user. This captcha challenge is stored in the db for later comparison. """ if not db.thread_exists(sql_cursor, thread_id): raise NonExistentThreadError(thread_id) # generate unique ID for each captcha captcha_id = secrets.token_urlsafe(settings.ID_BYTES) hint, answers = get_challenge(challenge_type) db.store_challenge(sql_cursor, captcha_id, thread_id, hint, answers) ret_json = {"id": captcha_id, "captcha": hint} return 200, ret_json
def validate_captcha(sql_cursor, thread_id, request_json): """ Given a user provided attempt, check whether we grant them a token or not """ provided_attempt = request_json["answer"] if "answer" in request_json else None captcha_id = request_json["id"] if "id" in request_json else None if provided_attempt is None: raise NonExistentAnswerError() # exists and not stale if not db.is_valid_captcha(sql_cursor, captcha_id) or\ not db.is_challenge_available(sql_cursor, captcha_id): raise NonExistentCaptchaError(captcha_id) if not db.thread_exists(sql_cursor, thread_id): raise NonExistentThreadError(thread_id) attempt_number = db.get_attempt_count(sql_cursor, captcha_id) + 1 # sanity check that we're not in a state where we didn't clean up captcha_id # after too many attempts if attempt_number > settings.MAX_ATTEMPTS: raise RuntimeError("captcha_id ({}) not removed after too many attempts".format(captcha_id)) # check if valid answer and increment number of attempts is_valid = db.crossvalidate_answer(sql_cursor, captcha_id, provided_attempt) if is_valid: expiry, captcha_token = generate_captcha_token(sql_cursor, captcha_id) ret_json = {"id": captcha_id, "status": "ok", "key": {"token": captcha_token, "expiry": expiry}} return 200, ret_json # failed on the last try? if attempt_number == settings.MAX_ATTEMPTS: # delete all data relating to this captcha_id db.remove_captcha(sql_cursor, captcha_id) # tell them they need to start from another captcha return 200, {"id": captcha_id, "status": "restart"} return 200, {"id": captcha_id, "status": "try again"}
def make_del_token(sql_cursor, thread_id, comment_id, request_json): # we check the email was set - we don't care if the email isn't valid, because it *shouldn't* exist in the db anyway # and will fail at the step of sending an email even if it somehow exists in the db email = request_json["email"] if "email" in request_json else None if email is None: raise InvalidEmailError() if not db.thread_exists(sql_cursor, thread_id): raise NonExistentThreadError(thread_id) if not db.comment_exists(sql_cursor, comment_id, thread_id): raise NonExistentCommentError(comment_id, thread_id) # create actual delete token (and store in DB) delete_token_id, delete_token_secret = generate_delete_token(sql_cursor, email, comment_id, thread_id) # TODO: remove print("delete token id", delete_token_id, "secret:", delete_token_secret) # TODO: send email to user (build the token string) # should contain a link: https://blog.pat.sh/comments/:thread_id/delete/:comment_id/?deltoken=:delete_token_id.delete_token_secret raise NotImplementedError("need to send the email to the user") return 200, {"success": "success"}