Example #1
0
def getUnverifiedUserByVeriCode(userId, veriCode):
    "Helper. Returns non-deactivated, unverified user."
    user = userMod.getUser(userId)
    if not user:
        return bu.abort("No such user or invitation.")
    if user.isVerified:
        return bu.abort("Already joined. Please log in.")
    if user.isDeactivated:
        return bu.abort("Account deactivated.")
    if not utils.checkPw(veriCode, user.hVeriCode):
        print("veriCode = %r" % veriCode)
        print("hVeriCode = %r" % user.hVeriCode)
        return bu.abort("Verification failed.")
    # ==> Valid veriCode.
    return user
Example #2
0
def getSesh (strict=True, validateCsrf=None, req=None):
    "Get sesh (session-like) object with current 'user' property.";
    req = req or bu.request;                                    # Defaults to (global) bu.request.
    if validateCsrf is None:
        validateCsrf = bool(req.method != "GET");               # Default behavior: false for GET, else true.
    # Check the 'userId' cookie:
    userId = bu.getCookie("userId",
        strict=strict, secret=K.AUTH_COOKIE_SECRET, req=req,
    );
    if not userId:
        assert not strict;
        return dotsi.fy({"user": None});
    # ==> Cookie found, signature valid.
    assert userId;
    if validateCsrf:
        # Ref: https://laravel.com/docs/5.8/csrf#csrf-x-csrf-token
        xCsrfToken = req.headers.get("X-Csrf-Token");
        assert validateXCsrfToken(xCsrfToken, userId);
        # ==> CSRF TOKEN IS VALID.
    # ==> CSRF PREVENTED, if applicable.
    user = userMod.getUser(userId);
    assert user and user.isVerified;                        # User shouldn't be able to log-in if not .isVerified. Asserting here.
    if user.isDeactivated:
        # XXX:Note: Below 'log out' should force CLI logout.
        return bu.abort("ACCOUNT DEACTIVATED\n\n" +
            "Your account has been deactivated by your admin." +
            "You shall now proceed to log out." #+
        );
    # ==> User exists, is verified, non-deactivated.
    return dotsi.fy({"user": user});
Example #3
0
def validateXCsrfToken (xCsrfToken, userId):
    try:
        #print("xCsrfToken = ", repr(xCsrfToken));
        #print(bu.getCookie("userId"));
        unwrappedUserId = bu.hasher.signUnwrap(
            xCsrfToken,
            secret = K.ANTI_CSRF_SECRET,                    # <-- Even though we use `bu.hasher`, we specify an overriding secret.
            maxExpiryInDays = K.REMEMBER_ME_DAY_COUNT,      # <-- Note: Can set to 0 or 0.0001 to see XSRF validation being enforced.
            # ^ This talks about signature-expiry, not cookie expiry.
        );
    except bu.hasher.SignatureInvalidError as e:
        return bu.abort("1. X-CSRF validation failed. Please log out and then log back in.");
    # ==> No unwrapping error.
    if unwrappedUserId != userId:
        return bu.abort("2. X-CSRF validation failed. Please log out and then log back in.");
    # ==> Unwrapped data is as expected.
    return True;
Example #4
0
def post_userCon_toggleUser_isDeactivated():
    jdata = bu.get_jdata(ensure="thatUserId, preToggle_isDeactivated")
    sesh = auth.getSesh()
    assert auth.validateAccessLevel("admin", sesh.user)
    thatUser = userMod.getUser({
        "_id": jdata.thatUserId,
        "isDeactivated": jdata.
        preToggle_isDeactivated,  # This helps ensure that we don't accidently perform the opposite op.
        # No "isVerified" condition.                        # <-- This is as unverified (invited/pre-joined) users can be deactivated.
    })
    if not thatUser:
        # ==> Couldn't find user to be de/reactivated.
        return bu.abort(
            "No such user. Can't de/reactivate. Please refresh (Ctrl+R) and retry."
        )
    # ==> User to be de/reactivated found.
    if thatUser._id == sesh.user._id:
        assert not thatUser.isDeactivated
        # Assert active. If deactivated, this line must be unreachable.
        return bu.abort("One can't deactivate their own account.")
    # ==> Not trying to deactivate own account.
    if thatUser.isRootAdmin:
        assert not thatUser.isDeactivated
        # Assert active. Root-admin user cannot be deactivated.
        return bu.abort("Can't de/reactivate root admin.")
    # ==> `thatUser` is NOT the root admin.
    assert (thatUser and thatUser._id != sesh.user._id
            and  # Like w/ db-query above, no 'isVerified' related assertion.
            thatUser.isDeactivated == jdata.preToggle_isDeactivated  # and
            )
    thatUser.update({
        "isDeactivated": not thatUser.isDeactivated,
    })
    assert userMod.validateUser(thatUser)
    userMod.replaceUser(thatUser)
    return {
        "user": userMod.snipUser(thatUser)
    }
Example #5
0
def get_kb_article (articleId):
    article = articleMod.getArticle({
        "_id": articleId,
        "status": "published_externally",
    });
    if not article:
        raise bu.abort("Artile not found. " + 
            "It may have been moved or deleted." #+
        );
    # ==> Found externally published article.
    return bu.render("extern-kb-article.html", **({
        "article": article,
        "bleachUp": bleachUp,
    }));
Example #6
0
def post_userCon_inviteUser():
    jdata = bu.get_jdata(ensure="""
        invitee_fname, invitee_lname, invitee_email,
        invitee_accessLevel,
    """)
    sesh = auth.getSesh()
    assert auth.validateAccessLevel("admin", sesh.user)
    inviter = sesh.user
    invitee = userMod.getUserByEmail(jdata.invitee_email)
    newVeriCode = userMod.genVeriCode()
    if not invitee:
        # ==> Invitee doesn't already exists. (fresh invite)
        invitee = userMod.buildUser(
            email=jdata.invitee_email,
            fname=jdata.invitee_fname,
            lname=jdata.invitee_lname,
            inviterId=inviter._id,
            veriCode=newVeriCode,
            accessLevel=jdata.invitee_accessLevel,
        )
    else:
        # ==> Invitee already exists. (re-invite)
        if invitee.isVerified:
            return bu.abort(
                "That email address is already associated with a confirmed user."
            )
        # ==> __NOT__ already verified.
        invitee.update({
            "fname": jdata.invitee_fname,  # Allow re-inviting w/ updated name.
            "lname": jdata.invitee_lname,
            "inviterId":
            inviter._id,  # <-- Here, we're updating to latest inviter's id.
            "hVeriCode": utils.hashPw(
                newVeriCode
            ),  # <-- On reinvite, new veriCode is gen'd, prev one expires.
            "accessLevel": jdata.invitee_accessLevel,
        })
        assert invitee.email == jdata.invitee_email
        # <-- fname/lname/inviterId/hVeriCode can change, but not email.
    # ==> `invitee` object is now available.
    userMod.upsertUser(invitee)
    if emailer.checkSendingEnabled():
        return_inviteLink = None
        sendInviteEmail(invitee, newVeriCode)
    else:
        return_inviteLink = genInviteLink(invitee, newVeriCode)
    return {
        "user": userMod.snipUser(invitee),
        "inviteLink": return_inviteLink,
    }
Example #7
0
def post_userCon_loginDo():
    email, pw = bu.unpack_jdata("email pw")
    # TODO: 'rememberMe'
    user = userMod.getUserByEmail(email)
    #print("user="******"User not found. Please retry or contact your admin for help.")
    # ==> User found.
    if not user.isVerified:
        return bu.abort(
            "Unconfirmed account. Please contact your admin for help.")
    # ==> User is verified.
    if user.isDeactivated:
        return bu.abort(
            "Account deactivated. Please contact your admin for help.")
    # ==> User is non-deactivated.
    if not utils.checkPw(pw, user.hpw):
        return bu.abort(
            "Login failed due to email/password mismatch. Please retry.")
    # ==> SUCCESS. User can log in.
    return auth.sendAuthSuccessResponse(user)
Example #8
0
def post_categoryCon_deleteCategory():
    jdata = bu.get_jdata(ensure="categoryId")
    sesh = auth.getSesh()
    category = categoryMod.getCategory(jdata.categoryId)
    assert category
    assert auth.validateCategoryDeletable(category, sesh.user)
    articleCount = articleMod.getArticleCount({
        "categoryId": category._id,
    })
    childCategoryCount = categoryMod.getCategoryCount({
        "parentId": category._id,  # i.e. child.parentId == category._id;
    })
    if not (articleCount == 0 == childCategoryCount):
        return bu.abort("Can't delete non-empty categories.")
    assert articleCount == 0 == childCategoryCount
    categoryMod.deleteCategory(category)
    return {
        "deletedCategoryId": category._id
    }
Example #9
0
def post_userCon_setupFirstUser():
    jdata = bu.get_jdata(ensure="email fname lname pw")
    email, fname, lname, pw = utils.unpack(jdata, "email fname lname pw")
    print(email, fname, lname, pw)
    userCount = userMod.getUserCount()
    if userCount >= 1:
        return bu.abort(
            "Setup already completed. Please visite /login to log in.")
    user = userMod.buildUser(
        email=email,
        fname=fname,
        lname=lname,
        pw=pw,
        isRootAdmin=True,
        isVerified=True,
        accessLevel=auth.alm.getMaxLevel(),
    )
    userMod.insertUser(user)
    return auth.sendAuthSuccessResponse(user)
Example #10
0
def validateNonCircularParentage (unsavedCategory):
    if checkCircularParentage(unsavedCategory):
        raise bu.abort("Circular parentage detected.");
    # ==> Non-circular, i.e. valid.
    return True;
Example #11
0
def validateArticleEditable (article, user):
    if not checkArticleEditable(article, user):
        raise bu.abort("Access level insufficient.");
    # otherwise ...
    return True;
Example #12
0
def validateAccessLevel(reqdAccessLevel, user):
    ok = alm.contains(user.accessLevel, reqdAccessLevel);
    if not ok:
        raise bu.abort("Access level insufficient.");
    # otherwise ... => ok
    return True;