Verified Commit 8288b362 authored by Kevin Alberts's avatar Kevin Alberts
Browse files

Add admin panel to delete URLs from the browser. Add copyright notice.

parent 332d82e9
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -108,3 +108,6 @@ venv.bak/

# Databases
*.db

# Configuration files
config.yml
+1 −1
Original line number Diff line number Diff line
Copyright 2019 Steven Spangler
Copyright 2019 Steven Spangler, Kevin Alberts, I.C.T.S.V. Inter-Actief

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

config.default.yml

0 → 100644
+69 −0
Original line number Diff line number Diff line
# String: Username to make admin API requests
# Default: 'admin'
admin_username: 'admin'

# String: Plaintext password to make admin API requests
# Safe to remove if admin_hashed_password is set
# Default: unset
#admin_password:

# String: Hashed password (bcrypt) to make admin API requests - Preferred over plaintext, use securepass.sh to generate
# Please note that authentication takes noticeably longer than using plaintext password
# Don't include the <username>: segment, just the hash
# Default: unset (required to start application)
admin_hashed_password: ''

# Boolean: Disables API. If set to true, admin_password/admin_hashed_password do not need to be set.
# Default: false
disable_api: false

# String: Secret key used for cookies (used for storage of messages)
# This should be a 12-16 character randomized string with letters, numbers, and symbols
# Default: unset (required to start application)
secret_key: ''

# String: Filename of the URL database without extension
# Default: 'urls'
database_name: 'urls'

# Integer: Length of random short URLs by default
# Default: 4
random_length: 5

# String: Allowed URL characters
# Default: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
allowed_chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_©®‍‼⁉⃣™ℹ↔↕↖↗↘↙↩↪⌚⌛⌨⏏⏩⏪⏫⏬⏭⏮⏯⏰⏱⏲⏳⏸⏹⏺Ⓜ▪▫▶◀◻◼◽◾☀☁☂☃☄☎☑☔☕☘☝☠☢☣☦☪☮☯☸☹☺♀♂♈♉♊♋♌♍♎♏♐♑♒♓♟♠♣♥♦♨♻♾♿⚒⚓⚔⚕⚖⚗⚙⚛⚜⚠⚡⚪⚫⚰⚱⚽⚾⛄⛅⛈⛎⛏⛑⛓⛔⛩⛪⛰⛱⛲⛳⛴⛵⛷⛸⛹⛺⛽✂✅✈✉✊✋✌✍✏✒✔✖✝✡✨✳✴❄❇❌❎❓❔❕❗❣❤➕➖➗➡➰➿⤴⤵⬅⬆⬇⬛⬜⭐⭕〰〽㊗㊙️🀄🃏🅰🅱🅾🅿🆎🆑🆒🆓🆔🆕🆖🆗🆘🆙🆚🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿🈁🈂🈚🈯🈲🈳🈴🈵🈶🈷🈸🈹🈺🉐🉑🌀🌁🌂🌃🌄🌅🌆🌇🌈🌉🌊🌋🌌🌍🌎🌏🌐🌑🌒🌓🌔🌕🌖🌗🌘🌙🌚🌛🌜🌝🌞🌟🌠🌡🌤🌥🌦🌧🌨🌩🌪🌫🌬🌭🌮🌯🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹🌺🌻🌼🌽🌾🌿🍀🍁🍂🍃🍄🍅🍆🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍔🍕🍖🍗🍘🍙🍚🍛🍜🍝🍞🍟🍠🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🍻🍼🍽🍾🍿🎀🎁🎂🎄🎅🎆🎇🎈🎉🎊🎋🎌🎍🎎🎏🎐🎑🎒🎓🎖🎗🎙🎚🎛🎞🎟🎠🎡🎢🎣🎤🎥🎦🎧🎨🎩🎪🎫🎬🎭🎮🎯🎰🎱🎲🎳🎴🎵🎶🎷🎸🎹🎺🎻🎼🎽🎾🎿🏀🏁🏂🏃🏄🏅🏆🏇🏈🏉🏊🏋🏌🏍🏎🏏🏐🏑🏒🏓🏔🏕🏖🏗🏘🏙🏚🏛🏜🏝🏞🏟🏠🏡🏢🏣🏤🏥🏦🏧🏨🏩🏪🏫🏬🏭🏮🏯🏰🏳🏴🏵🏷🏸🏹🏺🏻🏼🏽🏾🏿🐀🐁🐂🐃🐄🐅🐆🐇🐈🐉🐊🐋🐌🐍🐎🐏🐐🐑🐒🐓🐔🐕🐖🐗🐘🐙🐚🐛🐜🐝🐞🐟🐠🐡🐢🐣🐤🐥🐦🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐱🐲🐳🐴🐵🐶🐷🐸🐹🐺🐻🐼🐽🐾🐿👀👁👂👃👄👅👆👇👈👉👊👋👌👍👎👏👐👑👒👓👔👕👖👗👘👙👚👛👜👝👞👟👠👡👢👣👤👥👦👧👨👩👪👫👬👭👮👯👰👱👲👳👴👵👶👷👸👹👺👻👼👽👿💀💁💂💃💄💅💆💇💈💉💊💋💌💍💎💏💐💑💒💓💔💕💖💗💘💙💚💛💜💝💞💟💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯💰💱💲💳💴💵💶💷💸💹💺💻💼💽💾💿📀📁📂📃📄📅📆📇📈📉📊📋📌📍📎📏📐📑📒📓📔📕📖📗📘📙📚📛📜📝📞📟📠📡📢📣📤📥📦📧📨📩📪📫📬📭📮📯📰📱📲📳📴📵📶📷📸📹📺📻📼📽📿🔀🔁🔂🔃🔄🔅🔆🔇🔈🔉🔊🔋🔌🔍🔎🔏🔐🔑🔒🔓🔔🔕🔖🔗🔘🔙🔚🔛🔜🔝🔞🔟🔠🔡🔢🔣🔤🔥🔦🔧🔨🔩🔪🔫🔬🔭🔮🔯🔰🔱🔲🔳🔴🔵🔶🔷🔸🔹🔺🔻🔼🔽🕉🕊🕋🕌🕍🕎🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧🕯🕰🕳🕴🕵🕶🕷🕸🕹🕺🖇🖊🖋🖌🖍🖐🖕🖖🖤🖥🖨🖱🖲🖼🗂🗃🗄🗑🗒🗓🗜🗝🗞🗡🗣🗨🗯🗳🗺🗻🗼🗽🗾🗿😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮😯😰😱😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀🙁🙂🙃🙄🙅🙆🙇🙈🙉🙊🙋🙌🙍🙎🙏🚀🚁🚂🚃🚄🚅🚆🚇🚈🚉🚊🚋🚌🚍🚎🚏🚐🚑🚒🚓🚔🚕🚖🚗🚘🚙🚚🚛🚜🚝🚞🚟🚠🚡🚢🚣🚤🚥🚦🚧🚨🚩🚪🚫🚬🚭🚮🚯🚰🚱🚲🚳🚴🚵🚶🚷🚸🚹🚺🚻🚼🚽🚾🚿🛀🛁🛂🛃🛄🛅🛋🛌🛍🛎🛏🛐🛑🛒🛠🛡🛢🛣🛤🛥🛩🛫🛬🛰🛳🛴🛵🛶🛷🛸🛹🤐🤑🤒🤓🤔🤕🤖🤗🤘🤙🤚🤛🤜🤝🤞🤟🤠🤡🤢🤣🤤🤥🤦🤧🤨🤩🤪🤫🤬🤭🤮🤯🤰🤱🤲🤳🤴🤵🤶🤷🤸🤹🤺🤼🤽🤾🥀🥁🥂🥃🥄🥅🥇🥈🥉🥊🥋🥌🥍🥎🥏🥐🥑🥒🥓🥔🥕🥖🥗🥘🥙🥚🥛🥜🥝🥞🥟🥠🥡🥢🥣🥤🥥🥦🥧🥨🥩🥪🥫🥬🥭🥮🥯🥰🥳🥴🥵🥶🥺🥼🥽🥾🥿🦀🦁🦂🦃🦄🦅🦆🦇🦈🦉🦊🦋🦌🦍🦎🦏🦐🦑🦒🦓🦔🦕🦖🦗🦘🦙🦚🦛🦜🦝🦞🦟🦠🦡🦢🦰🦱🦲🦳🦴🦵🦶🦷🦸🦹🧀🧁🧂🧐🧑🧒🧓🧔🧕🧖🧗🧘🧙🧚🧛🧜🧝🧞🧟🧠🧡🧢🧣🧤🧥🧦🧧🧨🧩🧪🧫🧬🧭🧮🧯🧰🧱🧲🧳🧴🧵🧶🧷🧸🧹🧺🧻🧼🧽🧾🧿󠁢󠁣󠁥󠁧󠁬󠁮󠁳󠁴󠁷󠁿'


# Amount of time in seconds to spend generating random short URLs until timeout
# Default: 5
random_gen_timeout: 5

# String: Name shown on tab while on site and on page header
# Default: 'liteshort'
site_name: 'I.C.T.S.V. Inter-/Actief/ URL Shortener'

# String: Domain where the shortlinks will be served from. Useful if using the web interface on a subdomain.
# If not set, it is automatically taken from the URL the shorten request is sent to.
# If you don't know, leave unset
# Default: unset
site_domain:

# String: Subdomain to host the web interface on.
# Useful if you want the shorturls on the short domain but the web interface on a subdomain.
# If you don't know, leave unset
# Default: unset
subdomain:

# String: URL which takes you to the most recent short URL's destination
# Short URLs cannot be created with this string if set
# Default: l
latest: 'l'

# Boolean: Show link to project repository on GitHub at bottom right corner of page
# Default: true
show_github_link: false

# Integer: How many items to show per page in the admin interface
# Default: 20
admin_links_per_page: 20

config.yml

deleted100644 → 0
+0 −65
Original line number Diff line number Diff line
# String: Username to make admin API requests
# Default: 'admin'
admin_username: 'admin'

# String: Plaintext password to make admin API requests
# Safe to remove if admin_hashed_password is set
# Default: unset
#admin_password:

# String: Hashed password (bcrypt) to make admin API requests - Preferred over plaintext, use securepass.sh to generate
# Please note that authentication takes noticeably longer than using plaintext password
# Don't include the <username>: segment, just the hash
# Default: unset (required to start application)
admin_hashed_password:

# Boolean: Disables API. If set to true, admin_password/admin_hashed_password do not need to be set.
# Default: false
disable_api: false

# String: Secret key used for cookies (used for storage of messages)
# This should be a 12-16 character randomized string with letters, numbers, and symbols
# Default: unset (required to start application)
secret_key:

# String: Filename of the URL database without extension
# Default: 'urls'
database_name: 'urls'

# Integer: Length of random short URLs by default
# Default: 4
random_length: 4

# String: Allowed URL characters
# Default: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
allowed_chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

# Amount of time in seconds to spend generating random short URLs until timeout
# Default: 5
random_gen_timeout: 5

# String: Name shown on tab while on site and on page header
# Default: 'liteshort'
site_name: 'liteshort'

# String: Domain where the shortlinks will be served from. Useful if using the web interface on a subdomain.
# If not set, it is automatically taken from the URL the shorten request is sent to.
# If you don't know, leave unset
# Default: unset
site_domain:

# String: Subdomain to host the web interface on.
# Useful if you want the shorturls on the short domain but the web interface on a subdomain.
# If you don't know, leave unset
# Default: unset
subdomain:

# String: URL which takes you to the most recent short URL's destination
# Short URLs cannot be created with this string if set
# Default: l
latest: 'l'

# Boolean: Show link to project repository on GitHub at bottom right corner of page
# Default: true
show_github_link: true
+79 −4
Original line number Diff line number Diff line
# Copyright (c) 2019 Steven Spangler <132@ikl.sh>
# Copyright (c) 2019 Steven Spangler <132@ikl.sh>, Kevin Alberts <kevin@kevinalberts.nl>
# This file is part of liteshort by 132ikl
# This software is license under the MIT license. It should be included in your copy of this software.
# A copy of the MIT license can be obtained at https://mit-license.org/

from flask import Flask, current_app, flash, g, jsonify, make_response, redirect, render_template, request, send_from_directory, url_for
from flask import Flask, current_app, flash, g, jsonify, make_response, redirect, render_template, request, \
    send_from_directory, url_for, session
import bcrypt
import os
import random
@@ -22,13 +23,14 @@ def load_config():
    req_options = {'admin_username': 'admin', 'database_name': "urls", 'random_length': 4,
                   'allowed_chars': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
                   'random_gen_timeout': 5, 'site_name': 'liteshort', 'site_domain': None, 'show_github_link': True,
                   'secret_key': None, 'disable_api': False, 'subdomain': '', 'latest': 'l'
                   'secret_key': None, 'disable_api': False, 'subdomain': '', 'latest': 'l', 'admin_links_per_page': 20
                   }

    config_types = {'admin_username': str, 'database_name': str, 'random_length': int,
                    'allowed_chars': str, 'random_gen_timeout': int, 'site_name': str,
                    'site_domain': (str, type(None)), 'show_github_link': bool, 'secret_key': str,
                    'disable_api': bool, 'subdomain': (str, type(None)), 'latest': (str, type(None))
                    'disable_api': bool, 'subdomain': (str, type(None)), 'latest': (str, type(None)),
                    'admin_links_per_page': int
                    }

    for option in req_options.keys():
@@ -128,6 +130,26 @@ def list_shortlinks():
    return result


def list_shortlinks_page(page, limit=50):
    assert page >= 1
    assert type(page) == int
    assert type(limit) == int
    start_index = (page - 1) * limit
    result = query_db('SELECT * FROM urls ORDER BY short LIMIT ? OFFSET ?', (limit, start_index), False, None)
    result = nested_list_to_dict(result)
    return result


def get_num_pages(limit=50):
    assert type(limit) == int
    result = query_db('SELECT COUNT(*) FROM urls', (), False, None)
    items = result[0][0]
    page_count = result[0][0] // limit
    if page_count * limit < items:
        page_count += 1
    return page_count, items


def nested_list_to_dict(l):
    d = {}
    for nl in l:
@@ -300,5 +322,58 @@ def main_post():
        return response(request, None, 'Long URL required')


@app.route('/login', methods=['POST'])
def login():
    if 'admin_hashed_password' not in app.config or app.config['admin_hashed_password'] is None:
        raise AssertionError("Login is disabled.")

    if authenticate(request.form['username'], request.form['password']):
        session['logged_in'] = True
    else:
        flash('Wrong password!', 'error')
    return make_response(redirect(url_for('admin')))


@app.route('/logout')
def logout():
    if 'logged_in' in session and session['logged_in']:
        session['logged_in'] = False
    return make_response(redirect(url_for('admin')))


@app.route('/delete/<short>')
def delete(short):
    if 'logged_in' in session and session['logged_in']:
        success = delete_url(short)
        if success:
            flash("Link '/{}' deleted.".format(short), "success")
        else:
            flash("Failed to delete URL.".format(short), "error")
        page = request.args.get('page', '1')
        return make_response(redirect(url_for('admin')+"?page="+page))
    else:
        return make_response(redirect(url_for('admin')))


@app.route('/admin')
def admin():
    if 'logged_in' in session and session['logged_in']:
        page_count, num_items = get_num_pages(app.config['admin_links_per_page'])
        page = request.args.get('page', '1')
        try:
            page = int(page)
            if page > page_count:
                return make_response(redirect(url_for('admin')+"?page="+str(page_count)))
            if page < 1:
                return make_response(redirect(url_for('admin')+"?page=1"))
        except ValueError:
            page = 1

        urls = list_shortlinks_page(page, app.config['admin_links_per_page'])
        return render_template('admin.html', urls=urls, page=page, page_count=page_count, num_items=num_items)
    else:
        return render_template('login.html')


if __name__ == '__main__':
    app.run()
Loading