Compare commits

...

14 Commits

18 changed files with 640 additions and 32 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.env
.venv/
__pycache__/
avatars/
@@ -10,3 +10,4 @@ certs/
cookies.json
sites/
*.hurl

324
main.py
View File

@@ -8,6 +8,7 @@ import render
import secrets
import nginx
import threading
import nostr as nostr_module
app = Flask(__name__)
dotenv.load_dotenv()
@@ -26,6 +27,11 @@ try:
except:
IP = "Error"
# Get HSD API
HSD_API = os.getenv('HSD_API', '')
HSD_IP = os.getenv('HSD_IP', '127.0.0.1')
HSD_PORT = os.getenv('HSD_PORT', 12037)
# Load cookies
cookies = []
@@ -50,6 +56,10 @@ if not os.path.isdir('certs'):
def send_report(path):
return send_from_directory('templates/assets', path)
@app.route('/https.js')
def httpsJS():
return send_from_directory('templates', 'https.js')
@app.route('/favicon.png')
def faviconPNG():
return send_from_directory('templates/assets/img', 'favicon.png')
@@ -64,7 +74,7 @@ def index():
for i in cookies:
if i['cookie'] == auth:
return render_template('index.html',varo="window.location = '/site';", year=datetime.datetime.now().year)
return render_template('index.html',varo=render.varo_login(), year=datetime.datetime.now().year)
return render_template('index.html',varo=render.hnslogin(), year=datetime.datetime.now().year)
# Remove any ports
host = request.host.split(':')[0]
# Get content from site
@@ -72,9 +82,13 @@ def index():
with open(f'sites/{host}.json') as file:
data = json.load(file)
return render.site(data, True)
return render.site(data, host)
return redirect(f'https://{DOMAINS[0]}')
@app.route('/terms')
def terms():
return render_template('terms.html')
@app.route('/site')
def site():
# Get auth domain
@@ -109,7 +123,8 @@ def site():
"btn_bg": "#2c54cf",
"btn_fg": "#ffffff",
"socials": [],
"address": []
"address": [],
"nostrs": []
}
@@ -298,6 +313,131 @@ def publish():
response.set_cookie('auth', '', expires=0)
return response
@app.route('/renew')
def renew():
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Load site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
def regenerate_ssl_and_write_nginx():
tlsa = nginx.generate_ssl(i['name'])
data['tlsa'] = tlsa
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
nginx.write_nginx_conf(i['name'])
threading.Thread(target=regenerate_ssl_and_write_nginx).start()
return redirect('/publishing')
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr')
def nostr():
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Load site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
nostr = []
if 'nostr' in data:
nostr = data['nostr']
return render_template('nostr.html',year=datetime.datetime.now().year, domain=i['name'],nostr=nostr)
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr', methods=['POST'])
def nostr_post():
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Get site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
else:
return redirect('/site')
nostr = []
if 'nostr' in data:
nostr = data['nostr']
# Check for new nostr links
if 'new-name' in request.form and 'new-pub' in request.form:
name = request.form['new-name']
pub = request.form['new-pub']
id = len(nostr)
for link in nostr:
if link['name'] == name:
link['pub'] = pub
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
if link['id'] >= id:
id = link['id'] + 1
nostr.append({'name': name, 'pub': pub, 'id': id})
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr/delete/<int:id>')
def nostr_delete(id):
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Get site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
else:
return redirect('/site')
nostr = []
if 'nostr' in data:
nostr = data['nostr']
nostr = [i for i in nostr if i['id'] != id]
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/.well-known/wallets/<path:path>')
def wallets(path):
# Check if host is in domains
@@ -333,6 +473,43 @@ def wallets(path):
return response
return render_template('404.html', year=datetime.datetime.now().year), 404
@app.route('/.well-known/nostr.json')
def nostr_account():
# Check if host is in domains
if request.host in DOMAINS:
# Check if user is logged in
if 'auth' not in request.cookies:
return redirect(f'https://{DOMAINS[0]}')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Load site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
if 'nostr' in data:
nostr = data['nostr']
# Return as plain text
response = make_response(nostr_module.json(nostr))
response.headers['Content-Type'] = 'text/plain'
response.headers.add('Access-Control-Allow-Origin', '*')
return response
# Get wallet from domain
host = request.host.split(':')[0]
if os.path.isfile(f'sites/{host}.json'):
with open(f'sites/{host}.json') as file:
data = json.load(file)
if 'nostr' in data:
nostr = data['nostr']
# Return as plain text
response = make_response(nostr_module.json(nostr))
response.headers['Content-Type'] = 'text/plain'
response.headers.add('Access-Control-Allow-Origin', '*')
return response
return render_template('404.html', year=datetime.datetime.now().year), 404
@app.route('/publishing')
def publishing():
@@ -356,6 +533,61 @@ def auth():
resp.set_cookie('auth', auth_cookie)
return resp
@app.route('/auth', methods=['GET'])
def auth_get():
global cookies
if 'username' not in request.args:
return redirect('/?error=Failed to login&reason=No username')
username = request.args['username']
if 'token' not in request.args and 'signature' not in request.args:
return redirect('/?error=Failed to login&reason=No token')
if 'token' in request.args:
token = request.args['token']
# Check if user is valid
r = requests.get(f'https://login.hns.au/auth/user?token={token}')
if r.status_code != 200:
print(r.text,flush=True)
return redirect('/?error=Failed to login&reason=Failed to connect to HNS Login')
try:
r = r.json()
except:
print(r.text,flush=True)
return redirect('/?error=Failed to login&reason=Failed to connect to HNS Login')
if 'error' in r:
return redirect('/?error=Failed to login&reason=' + r['error'])
if r['username'] != username:
return redirect('/?error=Failed to login&reason=Username mismatch')
else: # Signature based login
signature = request.args['signature']
r = requests.post(f'http://x:{HSD_API}@{HSD_IP}:{HSD_PORT}', json={
'method': 'verifymessagewithname',
'params': [username, signature, "hns-links"]
})
if r.status_code != 200:
return jsonify({'error': 'Failed to connect to HSD',"success":False}), 500
r = r.json()
if 'result' not in r:
return jsonify({'error': 'Failed to verify signature',"success":False}), 400
if r['result'] != True:
return jsonify({'error': 'Failed to verify signature',"success":False}), 400
auth_cookie = secrets.token_hex(12 // 2)
cookies.append({'name': username, 'cookie': auth_cookie})
with open('cookies.json', 'w') as file:
json.dump(cookies, file)
resp = make_response(redirect('/site'))
resp.set_cookie('auth', auth_cookie)
return resp
@app.route('/logout')
def logout():
global cookies
@@ -403,6 +635,92 @@ def tokens(path):
return send_from_directory('templates/assets/img/tokens', f'{token}W.png')
return send_from_directory('templates/assets/img/tokens', f'{token}.png')
# region API routes
@app.route('/api/v1/site', methods=['POST'])
def api_site_post():
if not request.json:
return jsonify({'error': 'No JSON data provided',"success":False}), 400
if 'domain' not in request.json:
return jsonify({'error': 'No domain provided',"success":False}), 400
if 'signature' not in request.json:
return jsonify({'error': 'No signature provided',"success":False}), 400
if 'data' not in request.json:
return jsonify({'error': 'No data provided',"success":False}), 400
domain = request.json['domain']
signature = request.json['signature']
data = request.json['data']
# Verify signature
r = requests.post(f'http://x:{HSD_API}@{HSD_IP}:{HSD_PORT}', json={
'method': 'verifymessagewithname',
'params': [domain, signature, "hns-links"]
})
if r.status_code != 200:
return jsonify({'error': 'Failed to connect to HSD',"success":False}), 500
r = r.json()
if 'result' not in r:
return jsonify({'error': 'Failed to verify signature',"success":False}), 400
if r['result'] != True:
return jsonify({'error': 'Failed to verify signature',"success":False}), 400
keys = ['title', 'link_0', 'link_1', 'link_2', 'link_3', 'link_0_url', 'link_1_url', 'link_2_url', 'link_3_url', 'fg_0', 'bg_0', 'bg_1', 'btn_bg', 'btn_fg', 'image']
preset = {"bg_0": "#001665", "bg_1": "#000000", "fg_0": "#ffffff", "btn_bg": "#2c54cf", "btn_fg": "#ffffff"}
for key in keys:
if key not in data:
if key in preset:
data[key] = preset[key]
else:
data[key] = ''
if os.path.exists(f'sites/{domain}.json'):
with open(f'sites/{domain}.json') as file:
old_data = json.load(file)
if 'tlsa' in old_data:
data['tlsa'] = old_data['tlsa']
for key in old_data:
if key not in data:
data[key] = old_data[key]
if data[key] == '':
data[key] = old_data[key]
if 'socials' not in data:
data['socials'] = []
if 'address' not in data:
data['address'] = []
with open(f'sites/{domain}.json', 'w') as file:
json.dump(data, file)
if 'tlsa' in data:
return jsonify({'error': None, "success":True,"TLSA": data['tlsa'],"IP":IP}), 200
def generate_ssl_and_write_nginx():
tlsa = nginx.generate_ssl(domain)
data['tlsa'] = tlsa
with open(f'sites/{domain}.json', 'w') as file:
json.dump(data, file)
nginx.write_nginx_conf(domain)
threading.Thread(target=generate_ssl_and_write_nginx).start()
return jsonify({'error': None, "success":True,"TLSA":None,"IP":IP}), 200
@app.route('/api/v1/site', methods=['GET'])
def api_site_get():
if 'domain' not in request.args:
return jsonify({'error': 'No domain provided',"success":False}), 400
domain = request.args['domain']
if os.path.exists(f'sites/{domain}.json'):
with open(f'sites/{domain}.json') as file:
data = json.load(file)
return jsonify({'error': None, "success":True,"data":data,"IP":IP}), 200
return jsonify({'error': 'Site not found',"success":False}), 404
# endregion
# 404 catch all
@app.errorhandler(404)
def not_found(e):

29
nostr.py Normal file
View File

@@ -0,0 +1,29 @@
from base64 import b32decode
from base64 import b16encode
from bech32 import bech32_decode
from bech32 import bech32_encode
B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
def add_padding(base32_len):
bits = base32_len * 5
padding_size = (8 - (bits % 8)) % 8
return padding_size
def npub_to_hex(npub):
hrd, data = bech32_decode(npub)
b32_data = [B32[index] for index in data]
data_str = "".join(b32_data)
data_length = len(data_str)
data_str += "=" * add_padding(data_length)
decoded_data = b32decode(data_str)
b16_encoded_data = b16encode(decoded_data)
hex_str = b16_encoded_data.decode("utf-8").lower()
return hex_str
def json(links):
names = {}
for link in links:
names[link['name']] = npub_to_hex(link['pub'])
return {'names':names}

View File

@@ -11,6 +11,14 @@ def varo_login():
}
});'''
def hnslogin():
# Redirect to https://login.hns.au/auth?return={{scheme}}{{host}}/auth
return """
const { protocol, hostname, port } = window.location;
const rootUrl = `${protocol}//${hostname}${port ? `:${port}` : ''}/auth`;
window.location.href = `https://login.hns.au/auth?return=${encodeURIComponent(rootUrl)}`;
"""
def preview(data):
title = data['title']
@@ -63,7 +71,7 @@ def address_links(addresses,foreground):
html = ''
for address in addresses:
token = address['token'].upper()
html += f'<a href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
html += f'<a style="margin:5px;" href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
return html
def tokenImage(token,foreground):
@@ -91,7 +99,7 @@ def site(data, injectSSL = False):
ssl = ''
if injectSSL:
ssl = '<script src="https://nathan.woodburn/https.js" async=""></script>'
ssl = f'<script src="https://{injectSSL}/https.js" async=""></script>'
page = "page.html"
if data['image'] == "":
page = "page_no_image.html"

View File

@@ -3,3 +3,4 @@ python-dotenv
gunicorn
requests
apscheduler
bech32

View File

@@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
@@ -45,7 +53,6 @@ setTimeout(callback(), 5000);
</script>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
<script src="/assets/js/404.min.js"></script>
</body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}
.p-3{padding:1rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}@media (min-width:768px){.px-md-5{padding-left:3rem!important;padding-right:3rem!important}}@media (min-width:992px){.p-lg-5{padding:3rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}}@media (min-width:1200px){.pt-xl-5{padding-top:3rem!important}}.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.nostr{margin-top:1em}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}

View File

@@ -1 +1 @@
!function(){"use strict";!function(){if("requestAnimationFrame"in window&&!/Mobile|Android/.test(navigator.userAgent)){var e=document.querySelectorAll("[data-bss-parallax]");if(e.length){var n,t=[];window.addEventListener("scroll",a),window.addEventListener("resize",a),a()}}function a(){t.length=0;for(var a=0;a<e.length;a++){var o=e[a].getBoundingClientRect(),i=parseFloat(e[a].getAttribute("data-bss-parallax-speed"),10)||.5;o.bottom>0&&o.top<window.innerHeight&&t.push({speed:i,node:e[a]})}cancelAnimationFrame(n),t.length&&(n=requestAnimationFrame(r))}function r(){for(var e=0;e<t.length;e++){var n=t[e].node,a=t[e].speed;n.style.transform="translate3d(0, "+-window.scrollY*a+"px, 0)"}}}()}();var varo=new Varo;
!function(){"use strict";!function(){if("requestAnimationFrame"in window&&!/Mobile|Android/.test(navigator.userAgent)){var e=document.querySelectorAll("[data-bss-parallax]");if(e.length){var t,n=[];window.addEventListener("scroll",a),window.addEventListener("resize",a),a()}}function a(){n.length=0;for(var a=0;a<e.length;a++){var i=e[a].getBoundingClientRect(),o=parseFloat(e[a].getAttribute("data-bss-parallax-speed"),10)||.5;i.bottom>0&&i.top<window.innerHeight&&n.push({speed:o,node:e[a]})}cancelAnimationFrame(t),n.length&&(t=requestAnimationFrame(r))}function r(){for(var e=0;e<n.length;e++){var t=n[e].node,a=n[e].speed;t.style.transform="translate3d(0, "+-window.scrollY*a+"px, 0)"}}}()}();

6
templates/https.js Normal file
View File

@@ -0,0 +1,6 @@
if (location.protocol !== 'https:') {
location.replace(`https:${location.href.substring(location.protocol.length)}`);
}
else{
console.log("Already Https");
}

View File

@@ -5,7 +5,23 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"name": "HNS Links",
"url": "https://links.hns.au"
}
</script>
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
@@ -61,29 +77,29 @@
<h3 class="fw-bold">What we can do for you</h3>
</div>
</div>
<div class="py-5 p-lg-5">
<div class="p-lg-5 py-5">
<div class="row row-cols-1 row-cols-md-2 mx-auto" style="max-width: 900px;">
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="card-body px-4 px-md-5 py-5">
<h5 class="fw-bold card-title">Easy to create site</h5>
<p class="text-muted card-text mb-4">We use varo auth to allow you to login with any Handshake domain to allow building your page.<br>Once you login just fill in some info and publish your site in minutes.</p>
<p class="text-muted mb-4 card-text">We use varo auth to allow you to login with any Handshake domain to allow building your page.<br>Once you login just fill in some info and publish your site in minutes.</p>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="card-body px-4 px-md-5 py-5">
<h5 class="fw-bold card-title">Add wallet addresses for HIP05</h5>
<p class="text-muted card-text mb-4">Allow anyone to send HNS to @yourdomain by adding your domain in the page info.</p>
<p class="text-muted mb-4 card-text">Allow anyone to send HNS to @yourdomain by adding your domain in the page info.</p>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="card-body px-4 px-md-5 py-5">
<h5 class="fw-bold card-title">Open Source</h5>
<p class="text-muted card-text mb-4">Want full control over your website? Deploy this entire system on your server for free</p><a class="btn btn-primary shadow" role="button" target="_blank" href="https://git.woodburn.au/nathanwoodburn/hns-links">Learn more</a>
<p class="text-muted mb-4 card-text">Want full control over your website? Deploy this entire system on your server for free</p><a class="btn btn-primary shadow" role="button" target="_blank" href="https://git.woodburn.au/nathanwoodburn/hns-links">Learn more</a>
</div>
</div>
</div>
@@ -91,7 +107,7 @@
</div>
</div>
</section>
<section id="contact" class="py-5">
<section class="py-5" id="contact">
<div class="container">
<div class="row mb-5">
<div class="col-md-8 col-xl-6 text-center mx-auto">
@@ -101,7 +117,7 @@
</div>
<div class="row d-flex justify-content-center">
<div class="col-md-4 col-xl-4 d-flex justify-content-center justify-content-xl-start">
<div class="d-flex flex-wrap flex-md-column justify-content-md-start align-items-md-start h-100">
<div class="d-flex h-100 flex-wrap flex-md-column justify-content-md-start align-items-md-start">
<div class="d-flex align-items-center p-3">
<div class="bs-icon-md bs-icon-circle bs-icon-primary shadow d-flex flex-shrink-0 justify-content-center align-items-center d-inline-block bs-icon bs-icon-md"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord">
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
@@ -137,6 +153,7 @@
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li><a href="/terms">Terms</a></li>
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
</ul>
@@ -154,7 +171,6 @@
</footer>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>

111
templates/nostr.html Normal file
View File

@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Nostr - HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="/assets/css/styles.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script async src="https://umami.woodburn.au/script.js" data-website-id="a165bb1c-eef8-4737-8066-fcd8b4a380bd"></script>
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="45px"><span>&nbsp;HNS Links</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navcol-1">
<ul class="navbar-nav mx-auto"></ul><a class="btn btn-primary shadow" role="button" href="/preview" style="margin-right: 10px;">Preview</a><a class="btn btn-primary shadow" role="button" href="/logout">Logout</a>
</div>
</div>
</nav>
<header class="bg-dark">
<div class="container pt-4 pt-xl-5">
<div class="row pt-5">
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
<div class="text-center">
<p class="fw-bold text-success mb-2">{{domain}}/</p>
<h1 class="fw-bold">Manage your site</h1>
</div>
</div>
</div>
</div>
</header>
<section>
<div style="padding: 1em;padding-bottom: 25px;max-width: 800px;margin: auto;">
<form method="post" enctype="multipart/form-data">
<h1>Nostr</h1>
<div style="margin-bottom: 25px;">
<h4>Nostr Links</h4>{% for link in nostr %}
<div class='nostr'>
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.name}}" readonly/>
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.pub}}" readonly/>
<a class="btn btn-primary" role="button" href="/nostr/delete/{{link.id}}"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path>
</svg></a>
</div>
{% endfor %}
<div class='nostr'>
<span>Link a new nostr profile</span><br>
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-name" placeholder="User Name" />
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-pub" placeholder="npub..." />
<br><br>
<input class="btn btn-primary" type="submit" value="Link" style="margin-right: 20px;" />
</div>
</div>
</form>
</div>
</section>
<section></section>
<footer class="bg-dark">
<div class="container py-4 py-lg-5">
<div class="row justify-content-center">
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">Services</h3>
<ul class="list-unstyled">
<li><a href="https://hnshosting.au" target="_blank">Wordpress Hosting</a></li>
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
</ul>
</div>
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li><a href="/terms">Terms</a></li>
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><span>HNS Links</span></div>
<p class="text-muted">Easy to manage links page on Handshake domains</p>
</div>
</div>
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0">Copyright © {{year}} Nathan.Woodburn/</p>
</div>
</div>
</footer>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>

View File

@@ -5,6 +5,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:title" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta name="description" content="{{title}}">
@@ -51,7 +55,6 @@ html {
</div>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>

View File

@@ -5,6 +5,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:title" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta name="description" content="{{title}}">
@@ -51,7 +55,6 @@ html {
</div>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>

View File

@@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
@@ -31,7 +39,6 @@
<p>We are generating you SSL certificates and adding your site to our webserver.<br>We will redirect you when your site is ready.</p>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>

View File

@@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Home - HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
@@ -85,10 +93,10 @@
<div>
<p style="display: inline-block;">Text</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" name="btn_fg" value="{{btn_fg}}">
</div>
</div><input class="btn btn-primary" type="submit" value="Save" style="margin-right: 20px;"><a class="btn btn-primary" role="button" href="/publish">Publish</a>
</div><input class="btn btn-primary" type="submit" value="Save" style="margin: 10px;margin-right: 20px;"><a class="btn btn-primary" role="button" href="/publish" style="margin: 10px;">Publish</a><a class="btn btn-primary" role="button" href="/renew" style="margin: 10px;">Refresh SSL cert</a>
</form>
</div>
<div class="col-md-6" style="text-align: center;">{{preview|safe}}</div>
<div class="col-md-6" style="text-align: center;">{{preview|safe}}<a class="btn btn-primary" role="button" href="/nostr" style="margin-top: 1em;">Link Nostr</a></div>
</div>
</div>
</div>
@@ -135,6 +143,7 @@
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li><a href="/terms">Terms</a></li>
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
</ul>
@@ -152,7 +161,6 @@
</footer>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>

90
templates/terms.html Normal file
View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Terms - HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta property="og:description" content="Create a links page for your domain">
<meta name="twitter:description" content="Create a links page for your domain">
<meta name="description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="/assets/css/styles.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script async src="https://umami.woodburn.au/script.js" data-website-id="a165bb1c-eef8-4737-8066-fcd8b4a380bd"></script>
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="45px"><span>&nbsp;HNS Links</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navcol-1">
<ul class="navbar-nav mx-auto">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/#contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<section class="py-5">
<div class="container py-5">
<div class="row mb-5">
<div class="col-md-8 col-xl-6 text-center mx-auto">
<p class="fw-bold text-success mb-2">Terms</p>
</div>
</div>
<div class="row d-sm-flex">
<div class="col mb-4">
<div>
<p>Terms of Service for HNS Links Last Updated: 11/10/24<br><br>Welcome to HNS Links! Please carefully read the following terms before using our hosting services.<br><br>By using our website hosting services, you agree to comply with and be bound by the following terms and conditions. If you do not agree to these terms, please do not use our services.<br><br>1. Acceptance of Terms:<br>By accessing or using our website hosting services, you agree to these Terms of Service and all applicable laws and regulations.<br><br>2. Hosting Services:<br>HNS Links provides hosting services for websites, and related content.<br><br>3. Content and Usage:<br>You are solely responsible for the content hosted on your website. You agree not to host any content that violates applicable laws or infringes on the rights of others. This includes anything usually classified as NSFW. HNS Links reserves the right to suspend or terminate services for any user found in violation of this provision.<br><br>4. Data Security:<br>We implement reasonable measures to protect the security of your data, but we cannot guarantee its absolute security. You should backup any critical data using your own backup solution.<br><br>5. Uptime and Downtime:<br>HNS Links strives to provide high uptime for its hosting services. However, downtime may occur for maintenance or other reasons. We will make reasonable efforts to notify you in advance of scheduled maintenance.<br><br>6. Support:<br>We offer customer support for technical issues related to our hosting services. Support availability and response times may vary. We can not guarantee support for issues unrelated to our hosting services.<br><br>7. Termination:<br>HNS Links reserves the right to terminate or suspend your site at any time for violation of these terms or for any other reason. You may also terminate your site by contacting us.<br><br>8. Changes to Terms:<br>HNS Links reserves the right to modify these Terms of Service at any time. Any changes will be effective immediately upon posting on our website. It is your responsibility to review these terms regularly.<br><br><br>By using our hosting services, you agree to these terms. If you have any questions or concerns, please contact us.<br>Thank you for choosing HNS Links!</p>
</div>
</div>
</div>
</div>
</section>
<footer class="bg-dark">
<div class="container py-4 py-lg-5">
<div class="row justify-content-center">
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">Services</h3>
<ul class="list-unstyled">
<li><a href="https://hnshosting.au" target="_blank">Wordpress Hosting</a></li>
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
</ul>
</div>
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li></li>
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><span>HNS Links</span></div>
<p class="text-muted">Easy to manage links page on Handshake domains</p>
</div>
</div>
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0">Copyright © 2024 HNS Links</p>
</div>
</div>
</footer>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>