actual initial thoughts
This commit is contained in:
parent
9330ca0e2d
commit
dce1006228
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
config.json
|
49
finger.go
Normal file
49
finger.go
Normal 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
|
||||
}
|
4
go.mod
4
go.mod
@ -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
2
go.sum
Normal 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
44
handlers.go
Normal 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
29
main.go
Normal 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
68
profile.go
Normal 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
30
signature.go
Normal 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
38
streams.go
Normal 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()
|
Loading…
Reference in New Issue
Block a user