diff --git a/LICENSE b/LICENSE index 1daeb54b4e5745c3bd0a38be883fd53c8b0d60ec..923585cf9f3a1f16bbfaf3aa1a9f0cc48471e7dc 100644 --- a/LICENSE +++ b/LICENSE @@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. url_shorter_docker - Copyright (C) 2020 Jonas Leder + Copyright (C) 2020 jonasled This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - url_shorter_docker Copyright (C) 2020 Jonas Leder + url_shorter_docker Copyright (C) 2020 jonasled This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index 78541bf7d725dd209d976f0d9e9316598e8d27e9..c2be3244a7cb7bdbe2f7c494e661de068cb3947f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This is a URL shorter written in Python with Flask als Webhandler. The Webserver * account system based on google and github oauth (user can view and delete his links) * qr code generation after link is shorten * easy deployment +* can run in offline enviorements, because no online files needed (behind a firewall) #### Why is this one better than other * no ADs @@ -22,6 +23,7 @@ This is a URL shorter written in Python with Flask als Webhandler. The Webserver * posibility to use multiple domains * open source --> posibility to expand * very small and leightweight (at the moment the sourcecode is about 2mb) +* can be used in offline enviorements, because no online files needed (behind a firewall) [](http://gitlab.jonasled.de/jonasled/url_shorter_docker/commits/master) diff --git a/api.py b/api.py index bf535ec2107456348f15df0ded1e3843b3d3d197..142c6935d178b2ec665f1045e2d2bf3fba658e61 100644 --- a/api.py +++ b/api.py @@ -2,15 +2,15 @@ from flask import jsonify, render_template from sqlite3 import connect from makeqr import makeQR -def apiGet(request, url_scheme, s, sAPI): +def apiGet(request, url_scheme, s, sAPI, passwordProtected): try: userID = sAPI.dumps(s.loads(request.cookies.get('userID'))) except: userID = "" - return render_template("apiDocs.html", apikey=userID, domain=request.headers['Host'], url_scheme=url_scheme) + return render_template("apiDocs.html", apikey=userID, domain=request.headers['Host'], url_scheme=url_scheme, passwordProtected=passwordProtected) -def apiPost(request, url_scheme, domain, sAPI): +def apiPost(request, url_scheme, domain, sAPI, passwordProtected, password): username = "error" try: username = sAPI.loads(request.form['apikey']) @@ -41,6 +41,21 @@ def apiPost(request, url_scheme, domain, sAPI): message="domain for short link is not in allowed domain list" ) + if passwordProtected: + try: + pw = request.form["password"] + if( pw != password): + return jsonify( + status="5", + message="Wrong password." + ) + except: + return jsonify( + status="5", + message="Wrong password." + ) + + with connect('db/urls.db') as conn: #Check if another user already used the short link cursor = conn.cursor() res = cursor.execute('SELECT LONG_URL FROM WEB_URL WHERE SHORT_URL=?', [short]) diff --git a/deletelink.py b/deletelink.py index b68b155fa737ff99dd123b03ecd1d506c028d282..f7213de798d96bdc4d9abbe8245db1c3f420f544 100644 --- a/deletelink.py +++ b/deletelink.py @@ -7,16 +7,17 @@ def deleteLink(request, s): loginbar = "Hello " + s.loads(request.cookies.get('username')) + ' (<a href="/user/logout" style="color:white">logout</a>)' # generate the login form loginbar = loginbar + "" #to hide the unused variable message except: - return redirect("/user/login") # if user is not logged in redirect him to the login page + abort(404) #if the user is not logged in, hide this page and return not found linkToDelete = request.args.get('link') #get the link, which the user want's to delete from the parameter in the url. with connect('db/urls.db') as conn: cursor = conn.cursor() try: - cursor.execute('DELETE FROM WEB_URL WHERE SHORT_URL=? AND USERNAME=?', [linkToDelete, userID]) #Delete the entrie + cursor.execute('DELETE FROM WEB_URL WHERE SHORT_URL=? AND USERNAME=?', [linkToDelete, userID]) #Delete the mapping + cursor.execute('DELETE FROM ANALYTICS WHERE SHORT_URL=?', [linkToDelete]) #Delete the statistics return "OK" #response is only for ajax request except: - abort(500) + abort(500) #return internal server error, if something fails if (__name__ == "__main__"): print("This file is not made fore direct call, please run the main.py") diff --git a/docker-compose-build.yml b/docker-compose-build.yml index 24efe4b98b77a48a015c63612f4085f61e9ef2c5..6cfdf3401c88e740edfcb6a93fa0ef5549a1614b 100644 --- a/docker-compose-build.yml +++ b/docker-compose-build.yml @@ -21,6 +21,7 @@ services: - GITHUB_CLIENT_SECRET= - GOOGLE_CLIENT_ID= - GOOGLE_CLIENT_SECRET= + - passwordToShort= #Insert here a password, if you wan't that only you can short links. - cookieNotice=1 #If you don't want to see the cookie notice set this to 0. Cookies are only used for login. volumes: url_shorter_db: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4055b139c77cdbfce6318295ae96502bcd59cf4d..34063dfda0ff45970798e18102f39caa43e68197 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - GITHUB_CLIENT_SECRET= - GOOGLE_CLIENT_ID= - GOOGLE_CLIENT_SECRET= + - passwordToShort= #Insert here a password, if you wan't that only you can short links. - cookieNotice=1 #If you don't want to see the cookie notice set this to 0. Cookies are only used for login. volumes: url_shorter_db: diff --git a/home.py b/home.py index f801c05536527ad95bf37e5f10c8ee4e6536c8bc..7933f7ec6575c552f3e7bc777ca8f0fddf09fe87 100644 --- a/home.py +++ b/home.py @@ -1,12 +1,12 @@ from flask import render_template, abort -def home(request, builddate, version, domain_prepared, recaptchaPublicKey, showDomainSelect, cookieNotice, domain_to_index, s, loginEnabled): +def home(request, builddate, version, domain_prepared, recaptchaPublicKey, showDomainSelect, cookieNotice, domain_to_index, s, loginEnabled, passwordProtected): try: loginbar = "Hello " + s.loads(request.cookies.get('username')) + ' (<a href="/user/links" >your links</a>, <a href="/user/logout" >logout</a>)' except: loginbar = '<a href="#" onClick="showLogin()" >login</a>' try: - return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginbar=loginbar, cookieNotice=cookieNotice ,domain_prefilled=domain_to_index[request.headers["host"]], loginEnabled=loginEnabled) #return the default site to create a new shorten link + return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginbar=loginbar, cookieNotice=cookieNotice ,domain_prefilled=domain_to_index[request.headers["host"]], loginEnabled=loginEnabled, passwordProtected=passwordProtected) #return the default site to create a new shorten link except: abort(500) diff --git a/main.py b/main.py index 1cea7c3fdbf0f8150d29403bd361e1a3b3aab016..5ef70e402c235e6eb9fe34533df7b6cebf2fa2e6 100644 --- a/main.py +++ b/main.py @@ -82,6 +82,17 @@ try: except: loginEnabled = False +try: + if(environ["passwordToShort"] != ""): + passwordProtected = True + password = environ["passwordToShort"] + else: + passwordProtected = False + password = "" +except: + passwordProtected = False + password = "" + if(loginEnabled): try: #Try to get the oauth keys, if it fails, abort and print a message to console GITHUB_CLIENT_ID = environ['GITHUB_CLIENT_ID'] @@ -132,11 +143,11 @@ app = Flask(__name__) @app.route('/', methods=['GET']) def home_get(): - return home(request, builddate, version, domain_prepared, recaptchaPublicKey, showDomainSelect, cookieNotice, domain_to_index, s, loginEnabled) + return home(request, builddate, version, domain_prepared, recaptchaPublicKey, showDomainSelect, cookieNotice, domain_to_index, s, loginEnabled, passwordProtected) @app.route('/', methods=['POST']) #This function is used to create a new url def home_post(): - return newurl(request, skipCaptcha, recaptchaPrivateKey, recaptchaPublicKey, builddate, version, domain_prepared, domain_to_index, showDomainSelect, cookieNotice, s, url_scheme, loginEnabled) + return newurl(request, skipCaptcha, recaptchaPrivateKey, recaptchaPublicKey, builddate, version, domain_prepared, domain_to_index, showDomainSelect, cookieNotice, s, url_scheme, loginEnabled, passwordProtected, password) @app.route('/favicon.ico') #Redirect to the static url of the favicon def favicon(): @@ -190,12 +201,12 @@ def makeQrCode(): @app.route('/user/api', methods=['POST']) def api(): - return apiPost(request, url_scheme, domain, sAPI) + return apiPost(request, url_scheme, domain, sAPI, passwordProtected, password) @app.route('/user/api', methods=['GET']) def apiDocs(): - return apiGet(request, url_scheme, s, sAPI) + return apiGet(request, url_scheme, s, sAPI, passwordProtected) def startup(production): diff --git a/newurl.py b/newurl.py index 7a399e559435a369b47345caf0733b6dcae693f2..17f97e100359ed44873c2ab2f2c993c328124ef6 100644 --- a/newurl.py +++ b/newurl.py @@ -3,7 +3,7 @@ from flask import render_template from sqlite3 import connect from makeqr import makeQR -def newurl(request, skipCaptcha, recaptchaPrivateKey, recaptchaPublicKey, builddate, version, domain_prepared, domain_to_index, showDomainSelect, cookieNotice, s, url_scheme, loginEnabled): +def newurl(request, skipCaptcha, recaptchaPrivateKey, recaptchaPublicKey, builddate, version, domain_prepared, domain_to_index, showDomainSelect, cookieNotice, s, url_scheme, loginEnabled, passwordProtected, password): try: userID = s.loads(request.cookies.get('userID')) loginbar = "Hello " + s.loads(request.cookies.get('username')) + ' (<a href="/user/links" >your links</a>, <a href="/user/logout" >logout</a>)' @@ -11,12 +11,13 @@ def newurl(request, skipCaptcha, recaptchaPrivateKey, recaptchaPublicKey, buildd userID = "null" loginbar = '<a href="/user/login" >login</a>' if not grecaptcha_verify(request, skipCaptcha, recaptchaPrivateKey): - return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="There was an error validating, that you are a human, please try again.", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice) #return the user the prefilled form with an error message, because no url to short was provided - + return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="There was an error validating, that you are a human, please try again.", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice, passwordProtected=passwordProtected) #return the user the prefilled form with an error message, because recaptcha failed + if passwordProtected and (request.form.get("password") != password): + return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="Wrong password, please try again.", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice, passwordProtected=passwordProtected) #return the user the prefilled form with an error message, because the password was wrong if (request.form.get('url').replace(" ", "") == ""): - return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="Please enter a url to short, before submitting this form", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice) #return the user the prefilled form with an error message, because no url to short was provided + return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="Please enter a url to short, before submitting this form", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice, passwordProtected=passwordProtected) #return the user the prefilled form with an error message, because no url to short was provided if (request.form.get('short').replace(" ", "") == ""): - return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="Please enter a short name, before submitting this form", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice) #return the user the prefilled form with an error message, because no short link was provided + return render_template('home.html', builddate=builddate, version=version, domain=domain_prepared, snackbar="Please enter a short name, before submitting this form", long_url_prefilled=request.form.get('url'), short_url_prefilled=request.form.get('short').lower(), domain_prefilled=domain_to_index[request.form.get('domain')], recaptchaPublicKey=recaptchaPublicKey, showDomainSelect=showDomainSelect, loginEnabled=loginEnabled, loginbar=loginbar, cookieNotice=cookieNotice, passwordProtected=passwordProtected) #return the user the prefilled form with an error message, because no short link was provided shorturl = (request.form.get('domain') + "/" + request.form.get('short').replace(" ", "_").replace("/", "").replace("?","")).lower() url = request.form.get('url') diff --git a/templates/apiDocs.html b/templates/apiDocs.html index 83441f089bd76b3c0d837b1c93645b8ee6863983..915e9b49c4f566e802ca8842562268b9a05e884b 100644 --- a/templates/apiDocs.html +++ b/templates/apiDocs.html @@ -58,6 +58,9 @@ <li>long: the long URL you want to short</li> <li>qr: if you add this parameter you will get an base64 encoded QR code image</li> <li>apikey: you can add this argument, if you want to assign this link to your account.</li> + {% if passwordProtected %} + <li>password: this value contains the password, that is needed to short a link.</li> + {% endif %} </ul> <p>example API call with curl: <i>curl -d "{% if apikey %}apikey={{apikey}}&{% endif %}short={{domain}}/example&long=http://example.com" -X POST {{url_scheme}}://{{domain}}/user/api</i> @@ -96,6 +99,12 @@ <td>4</td> <td>The short URL is already taken.</td> </tr> + {% if passwordProtected %} + <tr> + <td>5</td> + <td>Wrong Password</td> + </tr> + {% endif %} </table> diff --git a/templates/home.html b/templates/home.html index fa49ca7041c8ee0fc6d8c71580714aff4bf4677d..61b030b073f6c7bebaef6e50ca66f0decd7ec147 100644 --- a/templates/home.html +++ b/templates/home.html @@ -36,6 +36,9 @@ <input type="hidden" name="domain" id="domain" value={{domain}}> {% endif %} <input id="short" name="short" type="text" placeholder="short name" value="{{short_url_prefilled}}"/> + {% if passwordProtected %} + <input id="password" name="password" type="password" placeholder="Password"/> + {% endif %} {% if recaptchaPublicKey %} <button class="g-recaptcha" data-sitekey="{{recaptchaPublicKey}}" data-callback='onSubmit'>short</button>