Check in
This commit is contained in:
commit
d6b0c343f7
3 changed files with 235 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.exe
|
||||
_test
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module git.fromouter.space/Hamcha/Aptweb
|
||||
|
||||
go 1.15
|
230
main.go
Normal file
230
main.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func mustGetEnv(env string) (val string) {
|
||||
val, ok := os.LookupEnv(env)
|
||||
if !ok {
|
||||
log.Fatalf("required env var %s not found", env)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var host string
|
||||
|
||||
type appDef struct {
|
||||
Name string
|
||||
WebhookURL string
|
||||
PublicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
var apps map[string]appDef
|
||||
|
||||
type profileLink struct {
|
||||
Rel string `json:"rel"`
|
||||
Type string `json:"type"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
type profileWebFingerData struct {
|
||||
Subject string `json:"subject"`
|
||||
Links []profileLink `json:"links"`
|
||||
}
|
||||
|
||||
type pubKeyBlock struct {
|
||||
ID string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
PEM string `json:"publicKeyPem"`
|
||||
}
|
||||
|
||||
type profilePage struct {
|
||||
Context []string `json:"@context"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Name string `json:"name"`
|
||||
Inbox string `json:"inbox"`
|
||||
Followers string `json:"followers"`
|
||||
PublicKey *pubKeyBlock `json:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
prefix := flag.String("prefix", "AW_", "Environment var prefix")
|
||||
proto := flag.String("proto", "https", "Protocol under which this server is going to be exposed")
|
||||
flag.Parse()
|
||||
|
||||
bind, ok := os.LookupEnv(*prefix + "BIND")
|
||||
if !ok {
|
||||
bind = ":8080" // Default to *:8080
|
||||
}
|
||||
|
||||
host = strings.ToLower(mustGetEnv(*prefix + "HOST"))
|
||||
fullhost := fmt.Sprintf("%s://%s", *proto, host)
|
||||
|
||||
// Find app definitions
|
||||
envs := os.Environ()
|
||||
envAppPrefix := *prefix + "APP_"
|
||||
envAppPrefixLen := len(envAppPrefix)
|
||||
apps = make(map[string]appDef)
|
||||
for _, envName := range envs {
|
||||
// Filter for APP_* vars
|
||||
if !strings.HasPrefix(envName, envAppPrefix) {
|
||||
continue
|
||||
}
|
||||
// Retrieve app name
|
||||
nextSep := strings.Index(envName[envAppPrefixLen:], "_")
|
||||
if nextSep < 0 {
|
||||
nextSep = 0
|
||||
}
|
||||
name := envName[envAppPrefixLen : envAppPrefixLen+nextSep]
|
||||
appName := strings.ToLower(name)
|
||||
// Skip if we processed this app already
|
||||
if _, ok := apps[appName]; ok {
|
||||
continue
|
||||
}
|
||||
// Retrieve info for app
|
||||
var pubkey *rsa.PublicKey = nil
|
||||
if key, ok := os.LookupEnv(envAppPrefix + name + "_PEM"); ok {
|
||||
key, _ := pem.Decode([]byte(key))
|
||||
pub, err := x509.ParsePKCS1PublicKey(key.Bytes)
|
||||
if err != nil {
|
||||
log.Fatalf("Error while parsing PEM/PKCS1 block for %s: %s", envAppPrefix+name+"_PEM", err.Error())
|
||||
}
|
||||
pubkey = pub
|
||||
}
|
||||
|
||||
apps[appName] = appDef{
|
||||
Name: mustGetEnv(envAppPrefix + name + "_NAME"),
|
||||
WebhookURL: mustGetEnv(envAppPrefix + name + "_URL"),
|
||||
PublicKey: pubkey,
|
||||
}
|
||||
|
||||
log.Printf("app registered: %s", appName)
|
||||
}
|
||||
|
||||
http.HandleFunc("/.well-known/host-meta", func(w http.ResponseWriter, r *http.Request) {
|
||||
headers := w.Header()
|
||||
headers.Add("Content-Type", "application/xrd+xml")
|
||||
headers.Add("Content-Type", "charset=utf-8")
|
||||
fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="https://%s/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>`, host)
|
||||
})
|
||||
|
||||
http.HandleFunc("/.well-known/webfinger", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := r.URL.Query()
|
||||
resource := params.Get("resource")
|
||||
if resource == "" {
|
||||
http.Error(w, "must include a valid resource", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(resource, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
http.Error(w, "invalid resource specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
switch parts[0] {
|
||||
case "acct":
|
||||
// Split user by host
|
||||
userParts := strings.SplitN(parts[1], "@", 2)
|
||||
if len(userParts) < 2 || strings.ToLower(userParts[1]) != host {
|
||||
http.Error(w, "Invalid user specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Check if user is a registered app
|
||||
_, ok := apps[userParts[0]]
|
||||
if !ok {
|
||||
http.Error(w, "user not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
headers := w.Header()
|
||||
headers.Add("Content-Type", "application/json")
|
||||
headers.Add("Content-Type", "charset=utf-8")
|
||||
|
||||
err := json.NewEncoder(w).Encode(profileWebFingerData{
|
||||
Subject: resource,
|
||||
Links: []profileLink{
|
||||
{
|
||||
Rel: "self",
|
||||
Type: "application/activity+json",
|
||||
Href: fmt.Sprintf("%s/u/%s", fullhost, userParts[0]),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error encoding webfinger: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/u/", func(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.SplitN(strings.Trim(r.URL.Path, " /"), "/", 2)
|
||||
if len(parts) < 2 {
|
||||
http.Error(w, "page not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
name := parts[1]
|
||||
app, ok := apps[name]
|
||||
if !ok {
|
||||
http.Error(w, "page not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
userURL := fmt.Sprintf("%s/u/%s", fullhost, name)
|
||||
|
||||
contextes := []string{
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
}
|
||||
|
||||
var pubkey *pubKeyBlock = nil
|
||||
if app.PublicKey != nil {
|
||||
pubKeyBytes := x509.MarshalPKCS1PublicKey(app.PublicKey)
|
||||
pubKeyPem := string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: pubKeyBytes,
|
||||
}))
|
||||
contextes = append(contextes, "https://w3id.org/security/v1")
|
||||
pubkey = &pubKeyBlock{
|
||||
ID: userURL + "#main-key",
|
||||
Owner: userURL,
|
||||
PEM: pubKeyPem,
|
||||
}
|
||||
}
|
||||
headers := w.Header()
|
||||
headers.Add("Content-Type", "application/activity+json")
|
||||
headers.Add("Content-Type", "charset=utf-8")
|
||||
err := json.NewEncoder(w).Encode(profilePage{
|
||||
Context: contextes,
|
||||
ID: userURL,
|
||||
Type: "Person",
|
||||
PreferredUsername: name,
|
||||
Name: app.Name,
|
||||
Inbox: fmt.Sprintf("%s/inbox", fullhost),
|
||||
Followers: fmt.Sprintf("%s/followers", userURL),
|
||||
PublicKey: pubkey,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Error encoding page: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/inbox", func(w http.ResponseWriter, r *http.Request) {
|
||||
byt, _ := ioutil.ReadAll(r.Body)
|
||||
fmt.Println(string(byt))
|
||||
})
|
||||
|
||||
if err := http.ListenAndServe(bind, nil); err != nil {
|
||||
log.Fatalf("error while listening: %f", err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue