feat: Add DNS management

This commit is contained in:
Nathan Woodburn 2024-01-25 23:15:59 +11:00
parent 123e4eea98
commit fd5d1cae2c
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
5 changed files with 273 additions and 7 deletions

View File

@ -201,6 +201,46 @@ def getDNS(domain: str):
return response['result']['records'] return response['result']['records']
def setDNS(account,domain,records):
account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if account_name == False:
return {
"error": "Invalid account"
}
records = json.loads(records)
newRecords = []
TXTRecords = []
for record in records:
if record['type'] == 'TXT':
TXTRecords.append(record['value'])
elif record['type'] == 'NS':
newRecords.append({
'type': 'NS',
'ns': record['value']
})
elif record['type'] in ['GLUE4','GLUE6',"SYNTH4","SYNTH6"]:
newRecords.append({
'type': record['type'],
'ns': str(record['value']).split(' ')[0],
'address': str(record['value']).split(' ')[1]
})
else:
newRecords.append(record)
if len(TXTRecords) > 0:
newRecords.append({
'type': 'TXT',
'txt': TXTRecords
})
data = '{"records":'+str(newRecords).replace("'","\"")+'}'
response = hsw.sendUPDATE(account_name,password,domain,data)
return response
def getNodeSync(): def getNodeSync():
response = hsd.getInfo() response = hsd.getInfo()
sync = response['chain']['progress']*100 sync = response['chain']['progress']*100

93
main.py
View File

@ -1,3 +1,4 @@
import json
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory,send_file from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory,send_file
import os import os
import dotenv import dotenv
@ -7,6 +8,7 @@ import render
import re import re
from flask_qrcode import QRcode from flask_qrcode import QRcode
import domainLookup import domainLookup
import urllib.parse
dotenv.load_dotenv() dotenv.load_dotenv()
@ -317,7 +319,7 @@ def search():
dns=dns, txs=txs) dns=dns, txs=txs)
@app.route('/manage/<domain>') @app.route('/manage/<domain>')
def manage(domain): def manage(domain: str):
# Check if the user is logged in # Check if the user is logged in
if request.cookies.get("account") is None: if request.cookies.get("account") is None:
return redirect("/login") return redirect("/login")
@ -341,15 +343,16 @@ def manage(domain):
expiry = domain_info['info']['stats']['daysUntilExpire'] expiry = domain_info['info']['stats']['daysUntilExpire']
dns = account_module.getDNS(domain) dns = account_module.getDNS(domain)
raw_dns = str(dns).replace("'",'"')
dns = render.dns(dns) dns = render.dns(dns)
return render_template("manage.html", account=account, sync=account_module.getNodeSync(), return render_template("manage.html", account=account, sync=account_module.getNodeSync(),
domain=domain,expiry=expiry, dns=dns) domain=domain,expiry=expiry, dns=dns,raw_dns=urllib.parse.quote(raw_dns))
@app.route('/manage/<domain>/renew') @app.route('/manage/<domain>/renew')
def renew(domain): def renew(domain: str):
# Check if the user is logged in # Check if the user is logged in
if request.cookies.get("account") is None: if request.cookies.get("account") is None:
return redirect("/login") return redirect("/login")
@ -363,6 +366,90 @@ def renew(domain):
return redirect("/success?tx=" + response['hash']) return redirect("/success?tx=" + response['hash'])
@app.route('/manage/<domain>/edit')
def editPage(domain: str):
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
domain = domain.lower()
own_domains = account_module.getDomains(account)
own_domains = [x['name'] for x in own_domains]
own_domains = [x.lower() for x in own_domains]
if domain not in own_domains:
return redirect("/search?q=" + domain)
user_edits = request.args.get("dns")
if user_edits != None:
dns = urllib.parse.unquote(user_edits)
else:
dns = account_module.getDNS(domain)
dns = json.loads(dns)
# Check if new records have been added
dnsType = request.args.get("type")
dnsValue = request.args.get("value")
if dnsType != None and dnsValue != None:
if dnsType != "DS":
dns.append({"type": dnsType, "value": dnsValue})
else:
# Verify the DS record
ds = dnsValue.split(" ")
if len(ds) != 4:
raw_dns = str(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
try:
ds[0] = int(ds[0])
ds[1] = int(ds[1])
ds[2] = int(ds[2])
except:
raw_dns = str(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
finally:
dns.append({"type": dnsType, "keyTag": ds[0], "algorithm": ds[1], "digestType": ds[2], "digest": ds[3]})
dns = json.dumps(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(dns))
raw_dns = str(dns).replace("'",'"')
dns = render.dns(dns,True)
errorMessage = request.args.get("error")
if errorMessage == None:
errorMessage = ""
return render_template("edit.html", account=account, sync=account_module.getNodeSync(),
domain=domain, error=errorMessage,
dns=dns,raw_dns=urllib.parse.quote(raw_dns))
@app.route('/manage/<domain>/edit/save')
def editSave(domain: str):
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
if not account_module.check_account(request.cookies.get("account")):
return redirect("/logout")
domain = domain.lower()
dns = request.args.get("dns")
raw_dns = dns
dns = urllib.parse.unquote(dns)
response = account_module.setDNS(request.cookies.get("account"),domain,dns)
if 'error' in response:
print(response)
return redirect("/manage/" + domain + "/edit?dns="+raw_dns+"&error=" + str(response['error']))
return redirect("/success?tx=" + response['hash'])
@app.route('/auction/<domain>') @app.route('/auction/<domain>')
def auction(domain): def auction(domain):

View File

@ -1,6 +1,6 @@
import datetime import datetime
import json import json
import urllib.parse
def domains(domains): def domains(domains):
html = '' html = ''
@ -71,9 +71,9 @@ def transactions(txs):
return html return html
def dns(data): def dns(data, edit=False):
html_output = "" html_output = ""
index = 0
for entry in data: for entry in data:
html_output += f"<tr><td>{entry['type']}</td>\n" html_output += f"<tr><td>{entry['type']}</td>\n"
@ -101,7 +101,14 @@ def dns(data):
value += str(val) + " " value += str(val) + " "
html_output += f"<td>{value}</td>\n" html_output += f"<td>{value}</td>\n"
if edit:
# Remove the current entry from the list
keptRecords = str(data[:index] + data[index+1:]).replace("'", '"')
keptRecords = urllib.parse.quote(keptRecords)
html_output += f"<td><a href='edit?dns={keptRecords}'>Remove</a></td>\n"
html_output += " </tr>\n" html_output += " </tr>\n"
index += 1
return html_output return html_output
def txs(data): def txs(data):

132
templates/edit.html Normal file
View File

@ -0,0 +1,132 @@
<!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>Manage - FireWallet</title>
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="900x768" 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=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
<link rel="stylesheet" href="/assets/fonts/material-icons.min.css">
<link rel="stylesheet" href="/assets/css/styles.min.css">
</head>
<body id="page-top">
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="/">
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><i class="material-icons">send</i><span>Send HNS</span></a></li>
<li class="nav-item"><a class="nav-link" href="receive"><i class="material-icons">call_received</i><span>Receive</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>
</nav>
<div class="d-flex flex-column" id="content-wrapper">
<div id="content">
<nav class="navbar navbar-expand bg-white shadow mb-4 topbar static-top navbar-light">
<div class="container-fluid"><button class="btn btn-link d-md-none rounded-circle me-3" id="sidebarToggleTop" type="button"><i class="fas fa-bars"></i></button>
<form class="d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 navbar-search" action="/search" method="get">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for domain" name="q" value="{{search_term}}"><button class="btn btn-primary py-0" type="submit"><i class="fas fa-search"></i></button></div>
</form><span>Sync: {{sync}}%</span>
<ul class="navbar-nav flex-nowrap ms-auto">
<li class="nav-item dropdown d-sm-none no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><i class="fas fa-search"></i></a>
<div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown">
<form class="me-auto navbar-search w-100">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for ...">
<div class="input-group-append"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
</div>
</form>
</div>
</li>
<li class="nav-item dropdown no-arrow">
<div class="nav-item dropdown no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><span class="d-none d-lg-inline me-2 text-gray-600 small">{{account}}</span><img class="border rounded-circle img-profile" src="/assets/img/HNS.png"></a>
<div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt fa-sm fa-fw me-2 text-gray-400"></i>&nbsp;Logout</a></div>
</div>
</li>
</ul>
</div>
</nav>
<h4 class="text-center" style="color: rgb(255,0,0);">{{error}}</h4>
<div class="container-fluid">
<div class="card">
<div class="card-body">
<h4 class="card-title">{{domain}}/</h4>
</div>
</div>
</div>
<div class="container-fluid" style="margin-top: 50px;">
<div class="card">
<div class="card-body">
<h4 class="card-title" style="display: inline-block;">DNS</h4><div class="table-responsive">
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{{dns | safe}}
</tbody>
</table>
</div>
<form style="width: 100%;">
<div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Type</th>
<th>Value</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><select class="form-select" name="type">
<option value="TXT" selected="">TXT</option>
<option value="NS">NS</option>
<option value="DS">DS</option>
<option value="GLUE4">GLUE4</option>
<option value="GLUE6">GLUE6</option>
<option value="SYNTH4">SYNTH4</option>
<option value="SYNTH6">SYNTH6</option>
</select></td>
<td><input class="form-control" type="text" name="value"></td>
<td><input class="btn btn-primary" type="submit" value="Add"></td>
</tr>
</tbody>
</table>
</div>
</div><input class="form-control" type="hidden" name="dns" value="{{raw_dns}}">
</form><a class="btn btn-primary" role="button" href="edit/save?dns={{raw_dns}}">Save DNS</a>
</div>
</div>
</div>
</div>
<footer class="bg-white sticky-footer">
<div class="container my-auto">
<div class="text-center my-auto copyright"><span>Copyright © FireWallet 2023</span></div>
</div>
</footer>
</div><a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a>
</div>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>

View File

@ -71,7 +71,7 @@
<div class="container-fluid" style="margin-top: 50px;"> <div class="container-fluid" style="margin-top: 50px;">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" style="display: inline-block;">DNS</h4><a class="btn btn-primary" role="button" style="position: absolute; right:16px;" href="/manage/{{domain}}/edit">Edit</a><div class="table-responsive"> <h4 class="card-title" style="display: inline-block;">DNS</h4><a class="btn btn-primary" role="button" style="position: absolute; right:16px;" href="/manage/{{domain}}/edit?dns={{raw_dns}}">Edit</a><div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>