Exemplo n.º 1
0
def inviteProxy(path):
    if checkInvite(path):
        log.info(f"Invite {path} used to request form")
        try:
            email = data_store.invites[path]["email"]
        except KeyError:
            email = ""
        return render_template(
            "form.html",
            bs5=config.getboolean("ui", "bs5"),
            css_file=css_file,
            contactMessage=config["ui"]["contact_message"],
            helpMessage=config["ui"]["help_message"],
            successMessage=config["ui"]["success_message"],
            jfLink=config["jellyfin"]["public_server"],
            validate=config.getboolean("password_validation", "enabled"),
            requirements=validator().getCriteria(),
            email=email,
            username=(not config.getboolean("email", "no_username")),
        )
    elif "admin.html" not in path and "admin.html" not in path:
        return app.send_static_file(path)
    else:
        log.debug("Attempted use of invalid invite")
        return render_template(
            "invalidCode.html",
            bs5=config.getboolean("ui", "bs5"),
            css_file=css_file,
            contactMessage=config["ui"]["contact_message"],
        )
Exemplo n.º 2
0
def admin():
    return render_template(
        "admin.html",
        bs5=config.getboolean("ui", "bs5"),
        css_file=css_file,
        contactMessage="",
        email_enabled=config.getboolean("invite_emails", "enabled"),
    )
Exemplo n.º 3
0
def format_datetime(dt):
    result = dt.strftime(config["email"]["date_format"])
    if config.getboolean("email", "use_24h"):
        result += f' {dt.strftime("%H:%M")}'
    else:
        result += f' {dt.strftime("%I:%M %p")}'
    return result
Exemplo n.º 4
0
def generateInvite():
    current_time = datetime.datetime.now()
    data = request.get_json()
    delta = datetime.timedelta(days=int(data["days"]),
                               hours=int(data["hours"]),
                               minutes=int(data["minutes"]))
    invite_code = secrets.token_urlsafe(16)
    invite = {}
    invite["created"] = format_datetime(current_time)
    if data["multiple-uses"]:
        if data["no-limit"]:
            invite["no-limit"] = True
        else:
            invite["remaining-uses"] = int(data["remaining-uses"])
    else:
        invite["remaining-uses"] = 1
    log.debug(f"Creating new invite: {invite_code}")
    valid_till = current_time + delta
    invite["valid_till"] = valid_till.strftime("%Y-%m-%dT%H:%M:%S.%f")
    if "email" in data and config.getboolean("invite_emails", "enabled"):
        address = data["email"]
        invite["email"] = address
        log.info(f"Sending invite to {address}")
        method = config["email"]["method"]
        if method == "mailgun":
            from jellyfin_accounts.email import Mailgun

            email = Mailgun(address)
        elif method == "smtp":
            from jellyfin_accounts.email import Smtp

            email = Smtp(address)
        email.construct_invite({"expiry": valid_till, "code": invite_code})
        response = email.send()
        if response is False or type(response) != bool:
            invite["email"] = f"Failed to send to {address}"
    if config.getboolean("notifications", "enabled"):
        if "notify-creation" in data:
            invite["notify-creation"] = data["notify-creation"]
        if "notify-expiry" in data:
            invite["notify-expiry"] = data["notify-expiry"]
    data_store.invites[invite_code] = invite
    log.info(f"New invite created: {invite_code}")
    return resp()
Exemplo n.º 5
0
def page_not_found(e):
    return (
        render_template(
            "404.html",
            bs5=config.getboolean("ui", "bs5"),
            css_file=css_file,
            contactMessage=config["ui"]["contact_message"],
        ),
        404,
    )
Exemplo n.º 6
0
def validator():
    if config.getboolean("password_validation", "enabled"):
        return PasswordValidator(
            config["password_validation"]["min_length"],
            config["password_validation"]["upper"],
            config["password_validation"]["lower"],
            config["password_validation"]["number"],
            config["password_validation"]["special"],
        )
    return PasswordValidator(0, 0, 0, 0, 0)
Exemplo n.º 7
0
def verify_password(username, password):
    user = None
    verified = False
    log.debug("Verifying auth")
    if config.getboolean("ui", "jellyfin_login"):
        try:
            jf_user = jf.getUsers(username, public=False)
            id = jf_user["Id"]
            user = accounts[id]
        except KeyError:
            if config.getboolean("ui", "admin_only"):
                if jf_user["Policy"]["IsAdministrator"]:
                    user = Account(username)
                    accounts[id] = user
                else:
                    log.debug(f"User {username} not admin.")
                    return False
            else:
                user = Account(username)
                accounts[id] = user
        except Jellyfin.UserNotFoundError:
            user = Account().verify_token(username, accounts)
            if user:
                verified = True
                if user in accounts:
                    user = accounts[user]
            if not user:
                log.debug(f"User {username} not found on Jellyfin")
                return False
    else:
        user = accounts["adminAccount"]
        verified = Account().verify_token(username, accounts)
    if not verified:
        if username == user.username and user.verify_password(password):
            g.user = user
            log.debug("HTTPAuth Allowed")
            return user
        else:
            log.debug("HTTPAuth Denied")
            return False
    g.user = user
    log.debug("HTTPAuth Allowed")
    return user
Exemplo n.º 8
0
def static_proxy(path):
    if "html" not in path:
        if "admin.js" in path:
            return (
                render_template(
                    "admin.js",
                    bsVersion=bsVersion(),
                    css_file=css_file,
                    notifications=config.getboolean("notifications", "enabled"),
                ),
                200,
                {"Content-Type": "text/javascript"},
            )
        return app.send_static_file(path)
    return (
        render_template(
            "404.html",
            bs5=config.getboolean("ui", "bs5"),
            css_file=css_file,
            contactMessage=config["ui"]["contact_message"],
        ),
        404,
    )
Exemplo n.º 9
0
 def verify_token(token, accounts):
     log.debug(f"verifying token {token}")
     s = Serializer(app.config["SECRET_KEY"])
     try:
         data = s.loads(token)
     except SignatureExpired:
         return None
     except BadSignature:
         return None
     if config.getboolean("ui", "jellyfin_login"):
         for account in accounts:
             if data["id"] == accounts[account].id:
                 return account
     else:
         return accounts["adminAccount"]
Exemplo n.º 10
0
def checkInvite(code, used=False, username=None):
    current_time = datetime.datetime.now()
    invites = dict(data_store.invites)
    match = False
    for invite in invites:
        if ("remaining-uses" not in invites[invite]
                and "no-limit" not in invites[invite]):
            invites[invite]["remaining-uses"] = 1
        expiry = datetime.datetime.strptime(invites[invite]["valid_till"],
                                            "%Y-%m-%dT%H:%M:%S.%f")
        if current_time >= expiry or ("no-limit" not in invites[invite] and
                                      invites[invite]["remaining-uses"] < 1):
            log.debug(f"Housekeeping: Deleting expired invite {invite}")
            if (config.getboolean("notifications", "enabled")
                    and "notify" in invites[invite]):
                for address in invites[invite]["notify"]:
                    if "notify-expiry" in invites[invite]["notify"][address]:
                        if invites[invite]["notify"][address]["notify-expiry"]:
                            method = config["email"]["method"]
                            if method == "mailgun":
                                email = Mailgun(address)
                            elif method == "smtp":
                                email = Smtp(address)
                            if email.construct_expiry({
                                    "code": invite,
                                    "expiry": expiry
                            }):
                                threading.Thread(target=email.send).start()
            del data_store.invites[invite]
        elif invite == code:
            match = True
            if used:
                delete = False
                inv = dict(data_store.invites[code])
                if "used-by" not in inv:
                    inv["used-by"] = []
                if "remaining-uses" in inv:
                    if inv["remaining-uses"] == 1:
                        delete = True
                        del data_store.invites[code]
                    elif "no-limit" not in invites[invite]:
                        inv["remaining-uses"] -= 1
                inv["used-by"].append(
                    [username, format_datetime(current_time)])
                if not delete:
                    data_store.invites[code] = inv
    return match
Exemplo n.º 11
0
def getInvites():
    log.debug("Invites requested")
    current_time = datetime.datetime.now()
    invites = dict(data_store.invites)
    for code in invites:
        checkInvite(code)
    invites = dict(data_store.invites)
    response = {"invites": []}
    for code in invites:
        expiry = datetime.datetime.strptime(invites[code]["valid_till"],
                                            "%Y-%m-%dT%H:%M:%S.%f")
        valid_for = expiry - current_time
        invite = {
            "code": code,
            "days": valid_for.days,
            "hours": valid_for.seconds // 3600,
            "minutes": (valid_for.seconds // 60) % 60,
        }
        if "created" in invites[code]:
            invite["created"] = invites[code]["created"]
        if "used-by" in invites[code]:
            invite["used-by"] = invites[code]["used-by"]
        if "no-limit" in invites[code]:
            invite["no-limit"] = invites[code]["no-limit"]
        if "remaining-uses" in invites[code]:
            invite["remaining-uses"] = invites[code]["remaining-uses"]
        else:
            invite["remaining-uses"] = 1
        if "email" in invites[code]:
            invite["email"] = invites[code]["email"]
        if "notify" in invites[code]:
            if config.getboolean("ui", "jellyfin_login"):
                address = data_store.emails[g.user.id]
            else:
                address = config["ui"]["email"]
            if address in invites[code]["notify"]:
                if "notify-expiry" in invites[code]["notify"][address]:
                    invite["notify-expiry"] = invites[code]["notify"][address][
                        "notify-expiry"]
                if "notify-creation" in invites[code]["notify"][address]:
                    invite["notify-creation"] = invites[code]["notify"][
                        address]["notify-creation"]
        response["invites"].append(invite)
    return jsonify(response)
Exemplo n.º 12
0
def setNotify():
    data = request.get_json()
    change = False
    for code in data:
        for key in data[code]:
            if key in ["notify-expiry", "notify-creation"]:
                inv = data_store.invites[code]
                if config.getboolean("ui", "jellyfin_login"):
                    address = data_store.emails[g.user.id]
                else:
                    address = config["ui"]["email"]
                if "notify" not in inv:
                    inv["notify"] = {}
                if address not in inv["notify"]:
                    inv["notify"][address] = {}
                inv["notify"][address][key] = data[code][key]
                log.debug(f"{code}: Notification settings changed")
                change = True
    if change:
        data_store.invites[code] = inv
        return resp()
    return resp(success=False)
Exemplo n.º 13
0
 def construct_created(self, invite):
     self.subject = "Notice: User created"
     log.debug(
         f'Constructing user creation notification for {invite["code"]}')
     created = format_datetime(invite["created"])
     if config.getboolean("email", "no_username"):
         email = "n/a"
     else:
         email = invite["address"]
     for key in ["text", "html"]:
         fpath = Path(config["notifications"]["created_" + key])
         with open(fpath, 'r') as f:
             template = Template(f.read())
         c = template.render(
             code=invite["code"],
             username=invite["username"],
             address=email,
             time=created,
         )
         self.content[key] = c
         log.info(f"{self.address}: {key} constructed")
     return True
Exemplo n.º 14
0
 def pretty_time(self, expiry, tzaware=False):
     if tzaware:
         current_time = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
     else:
         current_time = datetime.datetime.now()
     date = expiry.strftime(config["email"]["date_format"])
     if config.getboolean("email", "use_24h"):
         log.debug(f"{self.address}: Using 24h time")
         time = expiry.strftime("%H:%M")
     else:
         log.debug(f"{self.address}: Using 12h time")
         time = expiry.strftime("%-I:%M %p")
     expiry_delta = (expiry - current_time).seconds
     expires_in = {
         "hours": expiry_delta // 3600,
         "minutes": (expiry_delta // 60) % 60,
     }
     if expires_in["hours"] == 0:
         expires_in = f'{str(expires_in["minutes"])}m'
     else:
         expires_in = f'{str(expires_in["hours"])}h {str(expires_in["minutes"])}m'
     log.debug(f"{self.address}: Expires in {expires_in}")
     return {"date": date, "time": time, "expires_in": expires_in}
Exemplo n.º 15
0
def newUser():
    data = request.get_json()
    log.debug("Attempted newUser")
    if checkInvite(data["code"]):
        validation = validator().validate(data["password"])
        valid = True
        for criterion in validation:
            if validation[criterion] is False:
                valid = False
        if valid:
            log.debug("User password valid")
            try:
                user = jf.newUser(data["username"], data["password"])
            except Jellyfin.UserExistsError:
                error = f'User already exists named {data["username"]}'
                log.debug(error)
                return jsonify({"error": error})
            except:
                return jsonify({"error": "Unknown error"})
            invites = dict(data_store.invites)
            checkInvite(data["code"], used=True, username=data["username"])
            if (config.getboolean("notifications", "enabled")
                    and "notify" in invites[data["code"]]):
                for address in invites[data["code"]]["notify"]:
                    if "notify-creation" in invites[
                            data["code"]]["notify"][address]:
                        if invites[data["code"]]["notify"][address][
                                "notify-creation"]:
                            method = config["email"]["method"]
                            if method == "mailgun":
                                email = Mailgun(address)
                            elif method == "smtp":
                                email = Smtp(address)
                            if email.construct_created({
                                    "code":
                                    data["code"],
                                    "username":
                                    data["username"],
                                    "created":
                                    datetime.datetime.now(),
                            }):
                                threading.Thread(target=email.send).start()
            if user.status_code == 200:
                try:
                    policy = data_store.user_template
                    if policy != {}:
                        jf.setPolicy(user.json()["Id"], policy)
                    else:
                        log.debug("user policy was blank")
                except:
                    log.error("Failed to set new user policy")
                try:
                    configuration = data_store.user_configuration
                    displayprefs = data_store.user_displayprefs
                    if configuration != {} and displayprefs != {}:
                        if jf.setConfiguration(user.json()["Id"],
                                               configuration):
                            jf.setDisplayPreferences(user.json()["Id"],
                                                     displayprefs)
                            log.debug("Set homescreen layout.")
                    else:
                        log.debug(
                            "user configuration and/or displayprefs were blank"
                        )
                except:
                    log.error("Failed to set new user homescreen layout")
                if config.getboolean("password_resets", "enabled"):
                    data_store.emails[user.json()["Id"]] = data["email"]
                    log.debug("Email address stored")
                log.info("New user created")
            else:
                log.error(f"New user creation failed: {user.status_code}")
                return resp(False)
        else:
            log.debug("User password invalid")
        return jsonify(validation)
    else:
        log.debug("Attempted newUser unauthorized")
        return resp(False, code=401)
Exemplo n.º 16
0
            return None
        except BadSignature:
            return None
        if config.getboolean("ui", "jellyfin_login"):
            for account in accounts:
                if data["id"] == accounts[account].id:
                    return account
        else:
            return accounts["adminAccount"]


auth = HTTPBasicAuth()

accounts = {}

if config.getboolean("ui", "jellyfin_login"):
    log.debug("Using jellyfin for admin authentication")
else:
    log.debug("Using configured login details for admin authentication")
    accounts["adminAccount"] = Account(config["ui"]["username"],
                                       config["ui"]["password"])


@auth.verify_password
def verify_password(username, password):
    user = None
    verified = False
    log.debug("Verifying auth")
    if config.getboolean("ui", "jellyfin_login"):
        try:
            jf_user = jf.getUsers(username, public=False)
Exemplo n.º 17
0
def bsVersion():
    if config.getboolean("ui", "bs5"):
        return 5
    return 4
Exemplo n.º 18
0
        self.is_running = False
        self.next_call = time.time()
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self.next_call += self.interval
            self._timer = Timer(self.next_call - time.time(), self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False


def checkInvites():
    invites = dict(data_store.invites)
    # checkInvite already loops over everything, no point running it multiple times.
    if len(invites) != 0:
        checkInvite(list(invites.keys())[0])


if config.getboolean("notifications", "enabled"):
    inviteDaemon = Repeat(60, checkInvites)