actual initial thoughts

This commit is contained in:
David 2022-11-11 14:07:50 +07:00
parent 9330ca0e2d
commit dce1006228
10 changed files with 264 additions and 1 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
config.json

49
finger.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"fmt"
"strings"
)
type Resource struct {
Subject string `json:"subject"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
Links []Links `json:"links,omitempty"`
}
type Links struct {
Rel string `json:"rel"`
Type string `json:"type,omitempty"`
Href string `json:"href,omitempty"`
}
type Webfinger struct {
domain string
username string
accountName string
}
func NewWebfinger(username string, domain string, aliases ...string) (*Webfinger, error) {
return &Webfinger{
username: username,
domain: domain,
accountName: fmt.Sprintf("acct:%s@%s", username, domain),
}, nil
}
func (w *Webfinger) FingerResource(name string) (Resource, error) {
if !strings.EqualFold(w.accountName, name) {
return Resource{}, fmt.Errorf("account not found")
}
return Resource{
Subject: w.accountName,
Links: []Links{
{
Rel: "self",
Type: "application/activity+json",
Href: fmt.Sprintf("https://%s/users/%s", w.domain, w.username),
},
},
}, nil
}

BIN
friend Executable file

Binary file not shown.

4
go.mod
View File

@ -1,3 +1,5 @@
module git.yetaga.in/alazyreader/friend
go 1.18
go 1.19
require github.com/go-chi/chi/v5 v5.0.7

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=

44
handlers.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
)
func WebFingerHandler(wf *Webfinger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
requestedResource := r.URL.Query().Get("resource")
res, err := wf.FingerResource(requestedResource)
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"error": "account not found"}`))
return
}
b, err := json.Marshal(res)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(b)
}
}
func ProfileHandler(pp *ProfileProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "user")
res, err := pp.Get(userID)
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"error": "account not found"}`))
return
}
b, err := json.Marshal(res)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(b)
}
}

29
main.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"log"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
wf, err := NewWebfinger("dma", "hello.yetaga.in")
if err != nil {
log.Fatalln(err)
}
pp, err := NewProfileProvider("hello.yetaga.in")
if err != nil {
log.Fatalln(err)
}
r := chi.NewRouter()
r.Get("/.well-known/webfinger", WebFingerHandler(wf))
r.Get("/users/{user}", ProfileHandler(pp))
log.Println("listening on http://0.0.0.0:8008")
err = http.ListenAndServe(":8008", r)
if err != nil {
log.Fatalln(err)
}
}

68
profile.go Normal file
View File

@ -0,0 +1,68 @@
package main
import "fmt"
type Actor struct {
ID string `json:"id"`
Type string `json:"type"`
URL string `json:"url"`
Name string `json:"name"`
PreferredUsername string `json:"preferredUsername,omitempty"`
Summary string `json:"summary,omitempty"`
PublicKey PublicKey `json:"publicKey,omitempty"`
Inbox string `json:"inbox,omitempty"`
Outbox string `json:"outbo,omitempty"`
Followers string `json:"followers,omitempty"`
Following string `json:"following,omitempty"`
Icon *Link `json:"icon,omitempty"`
Image *Link `json:"image,omitempty"`
Endpoints map[string]string `json:"endpoints,omitempty"`
}
type Link struct {
Type string `json:"type"`
MediaType string `json:"mediaType"`
URL string `json:"url"`
}
type PublicKey struct {
ID string `json:"id"`
Owner string `json:"owner"`
PublicKeyPem string `json:"publicKeyPem"`
}
type ProfileProvider struct {
domain string
}
func NewProfileProvider(domain string) (*ProfileProvider, error) {
return &ProfileProvider{
domain: domain,
}, nil
}
func (pp *ProfileProvider) Get(userID string) (*Actor, error) {
userRoot := fmt.Sprintf("https://%s/users/%s", pp.domain, userID)
return &Actor{
ID: userRoot,
Type: "Person",
URL: fmt.Sprintf("https://%s/@%s", pp.domain, userID),
Name: userID,
Inbox: userRoot + "/inbox",
Outbox: userRoot + "/outbox",
Followers: userRoot + "/followers",
Following: userRoot + "/following",
Endpoints: map[string]string{
"sharedInbox": fmt.Sprintf("https://%s/inbox", pp.domain),
},
PublicKey: PublicKey{
ID: userRoot + "#main-key",
Owner: userRoot,
PublicKeyPem: "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
},
}, nil
}

30
signature.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"crypto"
"crypto/rand"
"fmt"
"net/http"
"strings"
"time"
)
type Signer struct {
key crypto.Signer
keyID string
}
// Sign modifies the request, computing and setting the Signature header, and setting the Date header to the provided time.
func (s Signer) Sign(r *http.Request, date time.Time) error {
host := r.URL.Host
path := r.URL.Path
method := strings.ToLower(r.Method)
stringToSign := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", method, path, host, date.Format(time.RFC1123))
sig, err := s.key.Sign(rand.Reader, []byte(stringToSign), nil)
if err != nil {
return err
}
r.Header.Set("Date", date.Format(time.RFC1123))
r.Header.Set("Signature", fmt.Sprintf(`keyId="%s",headers="(request-target) host date",signature="%s"`, s.keyID, sig))
return nil
}

38
streams.go Normal file
View File

@ -0,0 +1,38 @@
package main
import "time"
type Stream struct {
Context string `json:"@context"`
ID string `json:"id"`
Type string `json:"type"`
TotalItems int `json:"totalItems"`
Items []struct{} `json:"items"`
}
type Activity struct {
Context string `json:"@context"`
Summary string `json:"summary"`
Type string `json:"type"`
Published time.Time `json:"published"`
Actor Actor `json:"actor"`
Object *Object `json:"object,omitempty"`
Target *Object `json:"target,omitempty"`
}
type Object struct {
ID string `json:"id"`
Type string `json:"type"`
URL string `json:"url"`
Name string `json:"name"`
}
type Streams struct{}
func (s *Streams) Inbox()
func (s *Streams) Outbox()
func (s *Streams) Followers()
func (s *Streams) Followed()