Initial commit

This commit is contained in:
elisaveta9
2025-12-23 16:38:32 +03:00
commit b8f8e5607c
54 changed files with 2525 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Relay Admin Panel</title>
<style>
body {
font-family: sans-serif;
max-width: 600px;
margin: 40px auto;
}
h1 {
margin-bottom: 10px;
}
input, button {
padding: 6px;
font-size: 14px;
}
ul {
padding-left: 20px;
}
li {
margin: 6px 0;
}
.error {
color: red;
}
</style>
</head>
<body>
<h1>Relay Admin Panel</h1>
<label>
API Key:
<input id="apiKey" type="password" style="width: 100%" />
</label>
<hr>
<h3>Registered domains</h3>
<button onclick="loadDomains()">Refresh</button>
<ul id="domains"></ul>
<hr>
<h3>Add domain</h3>
<input id="newDomain" placeholder="example.phone.local" />
<button onclick="addDomain()">Add</button>
<p id="status" class="error"></p>
<script>
const apiBase = "/domains";
function headers() {
return {
"apikey": document.getElementById("apiKey").value
};
}
async function loadDomains() {
const res = await fetch(apiBase, { headers: headers() });
if (!res.ok) {
showError(res);
return;
}
const text = await res.text();
const list = document.getElementById("domains");
list.innerHTML = "";
text.trim().split("\n").forEach(domain => {
if (!domain) return;
const li = document.createElement("li");
li.textContent = domain + " ";
const btn = document.createElement("button");
btn.textContent = "Delete";
btn.onclick = () => deleteDomain(domain);
li.appendChild(btn);
list.appendChild(li);
});
}
async function addDomain() {
const domain = document.getElementById("newDomain").value;
if (!domain) return;
const res = await fetch(`${apiBase}?domain=${encodeURIComponent(domain)}`, {
method: "POST",
headers: headers()
});
if (!res.ok) {
showError(res);
return;
}
document.getElementById("newDomain").value = "";
loadDomains();
}
async function deleteDomain(domain) {
const res = await fetch(`${apiBase}?domain=${encodeURIComponent(domain)}`, {
method: "DELETE",
headers: headers()
});
if (!res.ok) {
showError(res);
return;
}
loadDomains();
}
function showError(res) {
document.getElementById("status").textContent =
`Error: ${res.status} ${res.statusText}`;
}
</script>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
package admin
import (
"log"
"os"
)
var adminLogger *log.Logger
func InitLogger() {
f, err := os.OpenFile(
"admin.log",
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
0600,
)
if err != nil {
log.Fatal("cannot open admin.log:", err)
}
adminLogger = log.New(f, "", log.LstdFlags|log.LUTC)
}
+3
View File
@@ -0,0 +1,3 @@
package admin
var ApiKey string
+58
View File
@@ -0,0 +1,58 @@
package admin
import (
"net/http"
"strings"
"relay/registry"
)
func domainsHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
registry.Global.Mu.Lock()
defer registry.Global.Mu.Unlock()
for d := range registry.Global.Domains {
w.Write([]byte(d + "\n"))
}
case "POST":
domain := strings.ToLower(r.URL.Query().Get("domain"))
if domain == "" {
http.Error(w, "domain required", http.StatusBadRequest)
return
}
registry.Global.Mu.Lock()
if _, exists := registry.Global.Domains[domain]; exists {
w.Write([]byte("already registered\n"))
} else {
registry.Global.Domains[domain] = nil
adminLogger.Printf(
"ADMIN ADD domain=%s ip=%s",
domain, r.RemoteAddr,
)
w.Write([]byte("registered\n"))
}
registry.Global.Mu.Unlock()
case "DELETE":
domain := strings.ToLower(r.URL.Query().Get("domain"))
registry.Global.Mu.Lock()
if _, exists := registry.Global.Domains[domain]; exists {
delete(registry.Global.Domains, domain)
adminLogger.Printf(
"ADMIN DELETE domain=%s ip=%s",
domain, r.RemoteAddr,
)
}
registry.Global.Mu.Unlock()
w.Write([]byte("deleted\n"))
default:
http.Error(w, "method not allowed", 405)
}
}
+26
View File
@@ -0,0 +1,26 @@
package admin
import (
"net/http"
)
func requireAPIKey(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("apikey")
ip := r.RemoteAddr
if key == "" {
adminLogger.Printf("ADMIN AUTH_MISSING ip=%s", ip)
http.Error(w, "missing api key", http.StatusUnauthorized)
return
}
if key != ApiKey {
adminLogger.Printf("ADMIN AUTH_FAIL ip=%s", ip)
http.Error(w, "invalid api key", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
+24
View File
@@ -0,0 +1,24 @@
package admin
import (
"log"
"net/http"
)
func Serve(addr string) {
mux := http.NewServeMux()
mux.Handle("/domains", requireAPIKey(http.HandlerFunc(domainsHandler)))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "admin.html")
})
log.Println("Admin API listening on", addr)
log.Fatal(http.ListenAndServeTLS(
addr,
"certs/admin.crt",
"certs/admin.key",
mux,
))
}