feat: Add TXT login using HIP 13
All checks were successful
Build Docker / Build Docker (push) Successful in 27s

This commit is contained in:
Nathan Woodburn 2024-06-15 13:29:25 +10:00
parent f6009fc335
commit e66ae58494
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
3 changed files with 367 additions and 46 deletions

View File

@ -1,6 +1,6 @@
import time import time
from .varo_auth import flask_login as varo_auth_flask_login from .varo_auth import flask_login as varo_auth_flask_login
from flask import Blueprint, request, session, url_for from flask import Blueprint, request, session, url_for, make_response
from flask import render_template, redirect, jsonify, send_from_directory from flask import render_template, redirect, jsonify, send_from_directory
from werkzeug.security import gen_salt from werkzeug.security import gen_salt
from authlib.integrations.flask_oauth2 import current_token from authlib.integrations.flask_oauth2 import current_token
@ -40,6 +40,14 @@ def split_by_crlf(s):
@bp.route("/", methods=("GET", "POST")) @bp.route("/", methods=("GET", "POST"))
def home(): def home():
next_page = request.args.get("next") next_page = request.args.get("next")
# Check if session exists
if "uuid" not in session:
session["uuid"] = gen_salt(24)
session.permanent = True
uuid = session["uuid"]
if request.method == "POST": if request.method == "POST":
auth = varo_auth_flask_login(request) auth = varo_auth_flask_login(request)
if auth == False: if auth == False:
@ -83,9 +91,15 @@ def home():
if openseaInfo.status_code == 200: if openseaInfo.status_code == 200:
hnsid = openseaInfo.json() hnsid = openseaInfo.json()
domains = []
if 'domains' in session:
domains = session['domains']
return render_template("home.html", user=user, clients=clients, address=address, hnsid=hnsid, users=users) return render_template("home.html", user=user, clients=clients,
address=address, hnsid=hnsid, users=users,
uuid=uuid, next=next_page, domains=domains)
@bp.route("/hnsid", methods=["POST"]) @bp.route("/hnsid", methods=["POST"])
def hnsid(): def hnsid():
@ -142,6 +156,129 @@ def hnsid_domain(domain):
return jsonify({"success": False, "error": "Domain not found"}) return jsonify({"success": False, "error": "Domain not found"})
@bp.route("/txt", methods=["POST"])
def txtLogin():
# Get domain from form
domain = request.form.get("domain")
# Get uuid
uuid = session["uuid"]
query = dns.message.make_query(domain, dns.rdatatype.TXT)
dns_request = query.to_wire()
# Send the DNS query over HTTPS
response = requests.post('https://hnsdoh.com/dns-query', data=dns_request, headers={'Content-Type': 'application/dns-message'})
# Parse the DNS response
dns_response = dns.message.from_wire(response.content)
# Loop over TXT records and look for profile avatar
idns_records = []
for record in dns_response.answer:
if record.rdtype == dns.rdatatype.TXT:
for txt in record:
txt_value:str = txt.to_text().strip('"')
if txt_value.startswith("IDNS1"):
print(txt_value)
idns = txt_value.removeprefix("IDNS1 ")
idns = idns.split(" ")
for r in idns:
idns_records.append(r)
for record in idns_records:
print(record)
type = record.split(":")[0]
content = record.split(":")[1]
key = content.split("=")[0]
value = content.split("=")[1]
print(f"Type: {type}, Key: {key}, Value: {value}")
if type == "auth" and key == "login.hns.au":
if value == uuid:
# Add domain to user
user = User.query.filter_by(username=domain).first()
if not user:
# Create user with username and profile picture
user = User(username=domain)
db.session.add(user)
db.session.commit()
session["id"] = user.id
session.permanent = True
if "domains" not in session:
session["domains"] = []
if domain not in session["domains"]:
session["domains"].append(domain)
print("User logged in with TXT")
# Check if next page is specified
next_page = request.args.get("next")
print(next_page)
if next_page and next_page != "None":
return redirect(next_page)
return redirect("/")
@bp.route("/txt/<domain>")
def txtLoginDomain(domain):
# Get uuid
uuid = session["uuid"]
query = dns.message.make_query(domain, dns.rdatatype.TXT)
dns_request = query.to_wire()
# Send the DNS query over HTTPS
response = requests.post('https://hnsdoh.com/dns-query', data=dns_request, headers={'Content-Type': 'application/dns-message'})
# Parse the DNS response
dns_response = dns.message.from_wire(response.content)
# Loop over TXT records and look for profile avatar
idns_records = []
for record in dns_response.answer:
if record.rdtype == dns.rdatatype.TXT:
for txt in record:
txt_value:str = txt.to_text().strip('"')
if txt_value.startswith("IDNS1"):
print(txt_value)
idns = txt_value.removeprefix("IDNS1 ")
idns = idns.split(" ")
for r in idns:
idns_records.append(r)
for record in idns_records:
print(record)
type = record.split(":")[0]
content = record.split(":")[1]
key = content.split("=")[0]
value = content.split("=")[1]
print(f"Type: {type}, Key: {key}, Value: {value}")
if type == "auth" and key == "login.hns.au":
if value == uuid:
# Add domain to user
user = User.query.filter_by(username=domain).first()
if not user:
# Create user with username and profile picture
user = User(username=domain)
db.session.add(user)
db.session.commit()
session["id"] = user.id
session.permanent = True
if "domains" not in session:
session["domains"] = []
if domain not in session["domains"]:
session["domains"].append(domain)
print("User logged in with TXT")
# Check if next page is specified
next_page = request.args.get("next")
print(next_page)
if next_page and next_page != "None":
return redirect(next_page)
return redirect("/")
@bp.route("/logout") @bp.route("/logout")
def logout(): def logout():
del session["id"] del session["id"]
@ -355,11 +492,13 @@ def avatar(username):
return send_from_directory("templates", "favicon.png", mimetype="image/png") return send_from_directory("templates", "favicon.png", mimetype="image/png")
@bp.route("/favicon.png") @bp.route("/favicon.png")
def favicon(): def favicon():
return send_from_directory("templates", "favicon.png", mimetype="image/png") return send_from_directory("templates", "favicon.png", mimetype="image/png")
@bp.errorhandler(404)
@bp.app_errorhandler(404)
def page_not_found(e):
print(f'404 error: {e}')
return render_template("404.html"), 404

125
website/templates/404.html Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HNS Login</title>
<link rel="icon" href="favicon.png" type="image/png">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
/* Dark theme*/
background-color: #222;
color: #fff;
}
h1 {
margin: 0;
padding: 20px;
background-color: #333;
color: #fff;
text-align: center;
}
h2 {
margin: 0;
padding: 20px;
text-align: center;
}
p {
margin: 0;
padding: 20px;
text-align: center;
}
form {
text-align: center;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
}
a.button {
display: block;
width: 200px;
margin: 20px auto;
padding: 10px 20px;
font-size: 16px;
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
text-align: center;
text-decoration: none;
}
a {
color: white;
}
button.loginbutton {
/* Put in the centre of the screen */
margin-left: 50%;
margin-top: 20px;
transform: translateX(-50%);
}
.login-option {
margin-top: 20px;
}
select {
padding: 10px 20px;
font-size: 16px;
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
margin-right: 25px;
}
.centre {
display: block;
text-align: center;
}
input {
padding: 10px 20px;
font-size: 16px;
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
margin-right: 25px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
</head>
<body>
<h1>HNS Login</h1>
<h2>404 - Page Not Found</h2>
<a href="/" class="button">Return to Home</a>
<div style="position: fixed; bottom: 0; width: 100%; text-align: center; background-color: #333; padding: 10px;">
Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a>, <a href="https://hns.id/"
target="_blank">HNS.ID</a> and <a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a>
</div>
<div style="height: 5em;"></div>
</body>
</html>

View File

@ -62,6 +62,7 @@
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
} }
a { a {
color: white; color: white;
} }
@ -77,17 +78,33 @@
.login-option { .login-option {
margin-top: 20px; margin-top: 20px;
} }
select { select {
padding: 10px 20px; padding: 10px 20px;
font-size: 16px; font-size: 16px;
background-color: #333; background-color: #333;
color: #fff; color: #fff;
border: none; border: none;
cursor: pointer; cursor: pointer;
margin-right: 25px; margin-right: 25px;
} }
.centre {
display: block;
text-align: center;
}
input {
padding: 10px 20px;
font-size: 16px;
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
margin-right: 25px;
}
</style> </style>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
</head> </head>
<body> <body>
@ -118,16 +135,16 @@
{% endfor %} {% endfor %}
<br> <br>
{% if user.id == 1 %} {% if user.id == 1 %}
{% for user_tmp in users %} {% for user_tmp in users %}
<pre> <pre>
<strong>User Info</strong> <strong>User Info</strong>
{%- for key in user_tmp %} {%- for key in user_tmp %}
<strong>{{ key }}: </strong>{{ user_tmp[key] }} <strong>{{ key }}: </strong>{{ user_tmp[key] }}
{%- endfor %} {%- endfor %}
</pre> </pre>
<hr> <hr>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<br><br> <br><br>
@ -135,17 +152,49 @@
Contact Nathan.Woodburn/ on any social media platform</p> Contact Nathan.Woodburn/ on any social media platform</p>
{% else %} {% else %}
<h2>Login with your Handshake domain</h2> <h2>Login with your Handshake domain</h2>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<div class="login-option"> <div class="login-option">
<script type="text/javascript" src="https://auth.varo.domains/v1"></script> {% if domains %}
<script>var varo = new Varo();</script> <p>Login using a TXT record</p>
<button class="loginbutton" onclick='varo.auth().then(auth => {
<div style="text-align: center;margin-top: 25px; margin-bottom: 25px;">
<select id="TXTDomainDropdown">
{% for domain in domains %}
<option value="{{domain}}">{{domain}}</option>
{% endfor %}
</select>
<button onclick="TXTLoginSelect()">Login</button>
</div>
<script>
function TXTLoginSelect() {
var selectedNFT = document.getElementById("TXTDomainDropdown").value;
window.location.href = "/txt/" + selectedNFT + window.location.search;
}
</script>
<span class="centre">Login with a new domain?</span>
{% endif %}
<span class="centre">Add this TXT record to any domain or login with an existing domain in Varo Auth</span>
<div class="centre">
<pre style="display: inline;margin-right: 10px;">IDNS1 auth:login.hns.au={{uuid}}</pre>
<!-- Copy button -->
<button style="display: inline;" onclick="copyToClipboard('IDNS1 auth:login.hns.au={{uuid}}')">Copy</button>
</div>
<div class="centre" style="margin-top: 25px;">
<form action="/txt?next={{ next }}" method="post">
<input type="text" name="domain" placeholder="Enter your domain">
<button type="submit">Login</button>
</div>
</div>
<script type="text/javascript" src="https://auth.varo.domains/v1"></script>
<script>var varo = new Varo();</script>
<button class="loginbutton" onclick='varo.auth().then(auth => {
if (auth.success) { if (auth.success) {
// handle success by calling your api to update the users session // handle success by calling your api to update the users session
$.post("/", JSON.stringify(auth.data), (response) => { $.post("/", JSON.stringify(auth.data), (response) => {
@ -153,7 +202,7 @@
}); });
} }
});'>Login with Varo</button> });'>Login with Varo</button>
</div>
<div class="login-option"> <div class="login-option">
<!-- Login for HNS.ID domains --> <!-- Login for HNS.ID domains -->
@ -216,42 +265,50 @@
</script> </script>
{% if address %} {% if address %}
<h4 style="text-align: center;">Logged in as {{address}}</h4>
<h4 style="text-align: center;">Logged in with HNS.ID</h4>
<span style="text-align: center;display: block;">Select a HNS.ID domain to log in with</span><br> <span style="text-align: center;display: block;">Select a HNS.ID domain to log in with</span><br>
<div style="text-align: center;"> <div style="text-align: center;">
<select id="nftDropdown"> <select id="nftDropdown">
{% for nft in hnsid.nfts %} {% for nft in hnsid.nfts %}
<option value="{{nft.name}}">{{nft.name}}</option> <option value="{{nft.name}}">{{nft.name}}</option>
{% endfor %} {% endfor %}
</select> </select>
<button onclick="HNSIDLoginSelect()">Login</button> <button onclick="HNSIDLoginSelect()">Login</button>
</div> </div>
<script> <script>
function HNSIDLoginSelect() { function HNSIDLoginSelect() {
var selectedNFT = document.getElementById("nftDropdown").value; var selectedNFT = document.getElementById("nftDropdown").value;
window.location.href = "/hnsid/" + selectedNFT + window.location.search; window.location.href = "/hnsid/" + selectedNFT + window.location.search;
} }
</script> </script>
<button class="loginbutton" onclick='javascript:loginETH();'>Login with another ETH address</button> <button class="loginbutton" onclick='javascript:loginETH();'>Login with another ETH address</button>
{% else %} {% else %}
<button class="loginbutton" onclick='javascript:loginETH();'>Login with HNS.ID</button> <button class="loginbutton" onclick='javascript:loginETH();'>Login with HNS.ID</button>
{% endif %} {% endif %}
</div> </div>
<script>
function copyToClipboard(text) {
var dummy = document.createElement("textarea");
document.body.appendChild(dummy);
dummy.value = text;
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
}
</script>
{% endif %} {% endif %}
<div style="position: fixed; bottom: 0; width: 100%; text-align: center; background-color: #333; padding: 10px;"> <div style="position: fixed; bottom: 0; width: 100%; text-align: center; background-color: #333; padding: 10px;">
Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a>, <a href="https://hns.id/" target="_blank">HNS.ID</a> and <a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a> Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a>, <a href="https://hns.id/"
target="_blank">HNS.ID</a> and <a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a>
</div> </div>
<div style="height: 5em;"></div> <div style="height: 5em;"></div>
</body> </body>