diff --git a/cmd/eksterd/auth.go b/cmd/eksterd/auth.go index 7b102bf..848ace6 100644 --- a/cmd/eksterd/auth.go +++ b/cmd/eksterd/auth.go @@ -26,20 +26,12 @@ import ( "time" "github.com/gomodule/redigo/redis" + "p83.nl/go/ekster/pkg/auth" ) -// TokenResponse is the information that we get back from the token endpoint of the user... -type TokenResponse struct { - Me string `json:"me"` - ClientID string `json:"client_id"` - Scope string `json:"scope"` - IssuedAt int64 `json:"issued_at"` - Nonce int64 `json:"nonce"` -} - var authHeaderRegex = regexp.MustCompile("^Bearer (.+)$") -func (h *microsubHandler) cachedCheckAuthToken(conn redis.Conn, header string, r *TokenResponse) bool { +func (b *memoryBackend) cachedCheckAuthToken(conn redis.Conn, header string, r *auth.TokenResponse) bool { log.Println("Cached checking Auth Token") tokens := authHeaderRegex.FindStringSubmatch(header) @@ -60,7 +52,7 @@ func (h *microsubHandler) cachedCheckAuthToken(conn redis.Conn, header string, r log.Printf("Error while HGETALL %v\n", err) } - authorized := h.checkAuthToken(header, r) + authorized := b.checkAuthToken(header, r) authorized = true if authorized { @@ -85,10 +77,10 @@ func (h *microsubHandler) cachedCheckAuthToken(conn redis.Conn, header string, r return authorized } -func (h *microsubHandler) checkAuthToken(header string, token *TokenResponse) bool { +func (b *memoryBackend) checkAuthToken(header string, token *auth.TokenResponse) bool { log.Println("Checking auth token") - tokenEndpoint := h.Backend.(*memoryBackend).TokenEndpoint + tokenEndpoint := b.TokenEndpoint req, err := http.NewRequest("GET", tokenEndpoint, nil) if err != nil { diff --git a/cmd/eksterd/http.go b/cmd/eksterd/http.go index f06e504..33e4dea 100644 --- a/cmd/eksterd/http.go +++ b/cmd/eksterd/http.go @@ -211,7 +211,7 @@ func isLoggedIn(backend *memoryBackend, sess *session) bool { return false } - if !auth { + if !authEnabled { return true } @@ -448,7 +448,7 @@ func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { scope = "create" } - auth := authRequest{ + authReq := authRequest{ Me: me, ClientID: clientID, RedirectURI: redirectURI, @@ -456,7 +456,7 @@ func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { State: state, } - _, err = conn.Do("HMSET", redis.Args{}.Add("state:"+state).AddFlat(&auth)...) + _, err = conn.Do("HMSET", redis.Args{}.Add("state:"+state).AddFlat(&authReq)...) if err != nil { log.Println(err) fmt.Fprintf(w, "ERROR: %q\n", err) diff --git a/cmd/eksterd/main.go b/cmd/eksterd/main.go index d1c829a..7f38e53 100644 --- a/cmd/eksterd/main.go +++ b/cmd/eksterd/main.go @@ -23,12 +23,13 @@ import ( "log" "net/http" "os" - "regexp" "time" "github.com/gomodule/redigo/redis" + "p83.nl/go/ekster/pkg/auth" "p83.nl/go/ekster/pkg/microsub" + "p83.nl/go/ekster/pkg/server" ) const ( @@ -38,16 +39,15 @@ const ( var ( pool *redis.Pool port int - auth bool + authEnabled bool redisServer = flag.String("redis", "redis:6379", "") - entryRegex = regexp.MustCompile("^entry\\[\\d+\\]$") ) func init() { log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime) flag.IntVar(&port, "port", 80, "port for serving api") - flag.BoolVar(&auth, "auth", true, "use auth") + flag.BoolVar(&authEnabled, "auth", true, "use auth") } func newPool(addr string) *redis.Pool { @@ -58,11 +58,33 @@ func newPool(addr string) *redis.Pool { } } +func WithAuth(handler http.Handler, b *memoryBackend) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authorization := r.Header.Get("Authorization") + + var token auth.TokenResponse + + if !b.AuthTokenAccepted(authorization, &token) { + log.Printf("Token could not be validated") + http.Error(w, "Can't validate token", 403) + return + } + + if token.Me != b.Me { + log.Printf("Missing \"me\" in token response: %#v\n", token) + http.Error(w, "Wrong me", 403) + return + } + + handler.ServeHTTP(w, r) + }) +} + func main() { log.Println("eksterd - microsub server") flag.Parse() - if auth { + if authEnabled { log.Println("Using auth") } else { log.Println("Authentication disabled") @@ -98,10 +120,13 @@ func main() { Backend: backend.(*memoryBackend), }) - http.Handle("/microsub", µsubHandler{ - Backend: backend, - HubIncomingBackend: &hubBackend, - }) + handler := server.NewMicrosubHandler(backend, pool) + if authEnabled { + handler = WithAuth(handler, backend.(*memoryBackend)) + } + + http.Handle("/microsub", handler) + http.Handle("/incoming/", &incomingHandler{ Backend: &hubBackend, }) diff --git a/cmd/eksterd/memory.go b/cmd/eksterd/memory.go index ffa6f0d..1777087 100644 --- a/cmd/eksterd/memory.go +++ b/cmd/eksterd/memory.go @@ -32,6 +32,7 @@ import ( "sync" "time" + "p83.nl/go/ekster/pkg/auth" "p83.nl/go/ekster/pkg/fetch" "p83.nl/go/ekster/pkg/microsub" "p83.nl/go/ekster/pkg/util" @@ -41,6 +42,8 @@ import ( ) type memoryBackend struct { + hubIncomingBackend + lock sync.RWMutex Channels map[string]microsub.Channel Feeds map[string][]microsub.Feed @@ -78,6 +81,12 @@ func (f *fetch2) Fetch(url string) (*http.Response, error) { return Fetch2(url) } +func (b *memoryBackend) AuthTokenAccepted(header string, r *auth.TokenResponse) bool { + conn := pool.Get() + defer conn.Close() + return b.cachedCheckAuthToken(conn, header, r) +} + func (b *memoryBackend) Debug() { b.lock.RLock() defer b.lock.RUnlock() @@ -412,6 +421,8 @@ func (b *memoryBackend) FollowURL(uid string, url string) (microsub.Feed, error) b.ProcessContent(uid, feed.URL, resp.Header.Get("Content-Type"), resp.Body) + b.CreateFeed(url, uid) + return feed, nil } @@ -498,12 +509,14 @@ func (b *memoryBackend) Search(query string) ([]microsub.Feed, error) { } defer feedResp.Body.Close() + // TODO: Combine FeedHeader and FeedItems so we can use it here parsedFeed, err := fetch.FeedHeader(&fetch2{}, fetchUrl.String(), feedResp.Header.Get("Content-Type"), feedResp.Body) if err != nil { log.Printf("Error in parse of %s - %v\n", fetchUrl, err) continue } + // TODO: Only include the feed if it contains some items feeds = append(feeds, parsedFeed) if alts, e := md.Rels["alternate"]; e { diff --git a/pkg/auth/types.go b/pkg/auth/types.go new file mode 100644 index 0000000..20aee69 --- /dev/null +++ b/pkg/auth/types.go @@ -0,0 +1,14 @@ +package auth + +type Auther interface { + AuthTokenAccepted(header string, r *TokenResponse) bool +} + +// TokenResponse is the information that we get back from the token endpoint of the user... +type TokenResponse struct { + Me string `json:"me"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` + IssuedAt int64 `json:"issued_at"` + Nonce int64 `json:"nonce"` +} diff --git a/pkg/jf2/simplify.go b/pkg/jf2/simplify.go index 9acdbaf..6b8c22e 100644 --- a/pkg/jf2/simplify.go +++ b/pkg/jf2/simplify.go @@ -20,6 +20,7 @@ package jf2 import ( "fmt" "log" + "reflect" "strings" "time" @@ -28,6 +29,54 @@ import ( "willnorris.com/go/microformats" ) +func ConvertItemProps(item interface{}, props map[string][]interface{}) { + sv := reflect.ValueOf(item).Elem() + st := reflect.TypeOf(item).Elem() + + for i := 0; i < st.NumField(); i++ { + ft := st.Field(i) + fv := sv.Field(i) + + if value, ok := ft.Tag.Lookup("mf2"); ok { + if value == "" { + continue + } + if s, e := props[value]; e { + if len(s) > 0 { + if str, ok := s[0].(string); ft.Type.Kind() == reflect.String && ok { + fv.SetString(str) + } else if ft.Type.Kind() == reflect.Slice { + for _, v := range s { + fv.Set(reflect.Append(fv, reflect.ValueOf(v))) + } + } else if card, ok := s[0].(map[string]interface{}); ok { + var hcard microsub.Card + if t, ok := card["type"].([]interface{}); ok { + hcard.Type = t[0].(string)[2:] + } + if properties, ok := card["properties"].(map[string]interface{}); ok { + ps := make(map[string][]interface{}) + for k, v := range properties { + ps[k] = v.([]interface{}) + } + ConvertItemProps(&hcard, ps) + } + fv.Set(reflect.ValueOf(&hcard)) + } + } + } + } + } +} + +func ConvertItem(item interface{}, md *microformats.Microformat) { + sv := reflect.ValueOf(item).Elem() + + sv.FieldByName("Type").SetString(md.Type[0][2:]) + + ConvertItemProps(item, md.Properties) +} + func simplify(itemType string, item map[string][]interface{}, author map[string]string) map[string]interface{} { feedItem := make(map[string]interface{}) @@ -200,6 +249,7 @@ func fetchValue(key string, values map[string]string) string { } return "" } + func MapToAuthor(result map[string]string) *microsub.Card { item := µsub.Card{} item.Type = "card" diff --git a/pkg/jf2/simplify_test.go b/pkg/jf2/simplify_test.go index 896b10a..fce677c 100644 --- a/pkg/jf2/simplify_test.go +++ b/pkg/jf2/simplify_test.go @@ -18,78 +18,197 @@ package jf2 import ( - "log" - "net/url" + "encoding/json" "os" "testing" + "p83.nl/go/ekster/pkg/microsub" "willnorris.com/go/microformats" ) -func TestInReplyTo(t *testing.T) { +// func TestInReplyTo(t *testing.T) { +// +// f, err := os.Open("./tests/tantek-in-reply-to.html") +// if err != nil { +// log.Fatal(err) +// } +// defer f.Close() +// +// u, err := url.Parse("http://tantek.com/2018/115/t1/") +// if err != nil { +// log.Fatal(err) +// } +// +// data := microformats.Parse(f, u) +// results := SimplifyMicroformatData(data) +// +// if results[0]["type"] != "entry" { +// t.Fatalf("not an h-entry, but %s", results[0]["type"]) +// } +// if results[0]["in-reply-to"] != "https://github.com/w3c/csswg-drafts/issues/2589" { +// t.Fatalf("not in-reply-to, but %s", results[0]["in-reply-to"]) +// } +// if results[0]["syndication"] != "https://github.com/w3c/csswg-drafts/issues/2589#thumbs_up-by-tantek" { +// t.Fatalf("not in-reply-to, but %s", results[0]["syndication"]) +// } +// if results[0]["published"] != "2018-04-25 11:14-0700" { +// t.Fatalf("not published, but %s", results[0]["published"]) +// } +// if results[0]["updated"] != "2018-04-25 11:14-0700" { +// t.Fatalf("not updated, but %s", results[0]["updated"]) +// } +// if results[0]["url"] != "http://tantek.com/2018/115/t1/" { +// t.Fatalf("not url, but %s", results[0]["url"]) +// } +// if results[0]["uid"] != "http://tantek.com/2018/115/t1/" { +// t.Fatalf("not uid, but %s", results[0]["url"]) +// } +// +// if authorValue, e := results[0]["author"]; e { +// if author, ok := authorValue.(map[string]string); ok { +// if author["name"] != "Tantek ร‡elik" { +// t.Fatalf("name is not expected name, but %q", author["name"]) +// } +// if author["photo"] != "http://tantek.com/logo.jpg" { +// t.Fatalf("photo is not expected photo, but %q", author["photo"]) +// } +// if author["url"] != "http://tantek.com/" { +// t.Fatalf("url is not expected url, but %q", author["url"]) +// } +// } else { +// t.Fatal("author not a map") +// } +// } else { +// t.Fatal("author missing") +// } +// +// if contentValue, e := results[0]["content"]; e { +// if content, ok := contentValue.(map[string]string); ok { +// if content["text"] != "๐Ÿ‘" { +// t.Fatal("text content missing") +// } +// if content["html"] != "๐Ÿ‘" { +// t.Fatal("html content missing") +// } +// } +// } +// } - f, err := os.Open("./tests/tantek-in-reply-to.html") - if err != nil { - log.Fatal(err) - } - defer f.Close() +func TestMapToAuthor(t *testing.T) { + cardmap := make(map[string]string) - u, err := url.Parse("http://tantek.com/2018/115/t1/") - if err != nil { - log.Fatal(err) - } + cardmap["name"] = "Peter" + cardmap["url"] = "https://p83.nl/" + cardmap["photo"] = "https://peterstuifzand.nl/img/profile.jpg" - data := microformats.Parse(f, u) - results := SimplifyMicroformatData(data) + card := MapToAuthor(cardmap) - if results[0]["type"] != "entry" { - t.Fatalf("not an h-entry, but %s", results[0]["type"]) + if card.Type != "card" { + t.Error("mapped author type is not card") } - if results[0]["in-reply-to"] != "https://github.com/w3c/csswg-drafts/issues/2589" { - t.Fatalf("not in-reply-to, but %s", results[0]["in-reply-to"]) + if card.Name != cardmap["name"] { + t.Errorf("%q is not equal to %q", card.Name, "Peter") } - if results[0]["syndication"] != "https://github.com/w3c/csswg-drafts/issues/2589#thumbs_up-by-tantek" { - t.Fatalf("not in-reply-to, but %s", results[0]["syndication"]) + if card.URL != cardmap["url"] { + t.Errorf("%q is not equal to %q", card.URL, cardmap["url"]) } - if results[0]["published"] != "2018-04-25 11:14-0700" { - t.Fatalf("not published, but %s", results[0]["published"]) - } - if results[0]["updated"] != "2018-04-25 11:14-0700" { - t.Fatalf("not updated, but %s", results[0]["updated"]) - } - if results[0]["url"] != "http://tantek.com/2018/115/t1/" { - t.Fatalf("not url, but %s", results[0]["url"]) - } - if results[0]["uid"] != "http://tantek.com/2018/115/t1/" { - t.Fatalf("not uid, but %s", results[0]["url"]) - } - - if authorValue, e := results[0]["author"]; e { - if author, ok := authorValue.(map[string]string); ok { - if author["name"] != "Tantek ร‡elik" { - t.Fatalf("name is not expected name, but %q", author["name"]) - } - if author["photo"] != "http://tantek.com/logo.jpg" { - t.Fatalf("photo is not expected photo, but %q", author["photo"]) - } - if author["url"] != "http://tantek.com/" { - t.Fatalf("url is not expected url, but %q", author["url"]) - } - } else { - t.Fatal("author not a map") - } - } else { - t.Fatal("author missing") - } - - if contentValue, e := results[0]["content"]; e { - if content, ok := contentValue.(map[string]string); ok { - if content["text"] != "๐Ÿ‘" { - t.Fatal("text content missing") - } - if content["html"] != "๐Ÿ‘" { - t.Fatal("html content missing") - } - } + if card.Photo != cardmap["photo"] { + t.Errorf("%q is not equal to %q", card.Photo, cardmap["photo"]) + } +} + +func TestMapToItem(t *testing.T) { + itemmap := make(map[string]interface{}) + itemmap["type"] = "entry" + itemmap["name"] = "Title" + c := make(map[string]interface{}) + c["text"] = "Simple content" + c["html"] = "

Simple content

" + itemmap["content"] = c + itemmap["like-of"] = []string{ + "https://p83.nl/", + "https://p83.nl/test.html", + } + item := MapToItem(itemmap) + if item.Type != "entry" { + t.Errorf("Expected Type entry, was %q", item.Type) + } + if item.Name != "Title" { + t.Errorf("Expected Name == %q, was actually %q", "Title", item.Name) + } + if item.Content.Text != "Simple content" { + t.Errorf("Expected Content.Text == %q, was actually %q", "Simple content", item.Content.Text) + } + if item.Content.HTML != "

Simple content

" { + t.Errorf("Expected Content.HTML == %q, was actually %q", "

Simple content

", item.Content.HTML) + } + // if val := item.LikeOf[0]; val != "https://p83.nl/" { + // t.Errorf("Expected LikeOf[0] == %q, was actually %q", "https://p83.nl/", val) + // } + // if val := item.LikeOf[1]; val != "https://p83.nl/test.html" { + // t.Errorf("Expected LikeOf[1] == %q, was actually %q", "https://p83.nl/test.html", val) + // } +} + +func TestConvertItem0(t *testing.T) { + var item microsub.Item + var mdItem microformats.Microformat + f, err := os.Open("tests/test0.json") + if err != nil { + t.Fatalf("error while opening test0.json: %s", err) + } + json.NewDecoder(f).Decode(&mdItem) + ConvertItem(&item, &mdItem) + + if item.Type != "entry" { + t.Errorf("Expected Type entry, was %q", item.Type) + } + if item.Name != "name test" { + t.Errorf("Expected Name == %q, was %q", "name test", item.Name) + } +} + +func TestConvertItem1(t *testing.T) { + var item microsub.Item + var mdItem microformats.Microformat + f, err := os.Open("tests/test1.json") + if err != nil { + t.Fatalf("error while opening test1.json: %s", err) + } + json.NewDecoder(f).Decode(&mdItem) + ConvertItem(&item, &mdItem) + + if item.Type != "entry" { + t.Errorf("Expected Type entry, was %q", item.Type) + } + if item.Author.Type != "card" { + t.Errorf("Expected Author.Type card, was %q", item.Author.Type) + } + if item.Author.Name != "Peter" { + t.Errorf("Expected Author.Name == %q, was %q", "Peter", item.Author.Name) + } +} + +func TestConvertItem2(t *testing.T) { + var item microsub.Item + var mdItem microformats.Microformat + f, err := os.Open("tests/test2.json") + if err != nil { + t.Fatalf("error while opening test2.json: %s", err) + } + json.NewDecoder(f).Decode(&mdItem) + ConvertItem(&item, &mdItem) + + if item.Type != "entry" { + t.Errorf("Expected Type entry, was %q", item.Type) + } + if item.Photo[0] != "https://peterstuifzand.nl/img/profile.jpg" { + t.Errorf("Expected Photo[0], was %q", item.Type) + } + if item.Author.Type != "card" { + t.Errorf("Expected Author.Type card, was %q", item.Author.Type) + } + if item.Author.Name != "Peter" { + t.Errorf("Expected Author.Name == %q, was %q", "Peter", item.Author.Name) } } diff --git a/pkg/jf2/tests/test0.json b/pkg/jf2/tests/test0.json new file mode 100644 index 0000000..21bb27e --- /dev/null +++ b/pkg/jf2/tests/test0.json @@ -0,0 +1 @@ +{"type":["h-entry"],"properties":{"name":["name test"]}} \ No newline at end of file diff --git a/pkg/jf2/tests/test1.json b/pkg/jf2/tests/test1.json new file mode 100644 index 0000000..18d392d --- /dev/null +++ b/pkg/jf2/tests/test1.json @@ -0,0 +1 @@ +{"type":["h-entry"],"properties":{"name":["name test"],"author":[{"type":["h-card"],"properties":{"name":["Peter"]}}]}} \ No newline at end of file diff --git a/pkg/jf2/tests/test2.json b/pkg/jf2/tests/test2.json new file mode 100644 index 0000000..2e64d61 --- /dev/null +++ b/pkg/jf2/tests/test2.json @@ -0,0 +1,25 @@ +{ + "type": [ + "h-entry" + ], + "properties": { + "name": [ + "name test" + ], + "photo": [ + "https://peterstuifzand.nl/img/profile.jpg" + ], + "author": [ + { + "type": [ + "h-card" + ], + "properties": { + "name": [ + "Peter" + ] + } + } + ] + } +} \ No newline at end of file diff --git a/pkg/microsub/protocol.go b/pkg/microsub/protocol.go index 7536768..3000fed 100644 --- a/pkg/microsub/protocol.go +++ b/pkg/microsub/protocol.go @@ -40,40 +40,40 @@ type Channel struct { type Card struct { Filled bool `json:"-,omitempty"` Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - URL string `json:"url,omitempty"` - Photo string `json:"photo,omitempty"` - Locality string `json:"locality,omitempty"` - Region string `json:"region,omitempty"` - CountryName string `json:"country-name,omitempty"` - Longitude string `json:"longitude,omitempty"` - Latitude string `json:"latitude,omitempty"` + Name string `json:"name,omitempty" mf2:"name"` + URL string `json:"url,omitempty" mf2:"url"` + Photo string `json:"photo,omitempty" mf2:"photo"` + Locality string `json:"locality,omitempty" mf2:"locality"` + Region string `json:"region,omitempty" mf2:"region"` + CountryName string `json:"country-name,omitempty" mf2:"country-name"` + Longitude string `json:"longitude,omitempty" mf2:"longitude"` + Latitude string `json:"latitude,omitempty" mf2:"latitude"` } type Content struct { - Text string `json:"text,omitempty"` - HTML string `json:"html,omitempty"` + Text string `json:"text,omitempty" mf2:"value"` + HTML string `json:"html,omitempty" mf2:"html"` } // Item is a post object type Item struct { Type string `json:"type"` - Name string `json:"name,omitempty"` - Published string `json:"published,omitempty"` - Updated string `json:"updated,omitempty"` - URL string `json:"url,omitempty"` - UID string `json:"uid,omitempty"` - Author *Card `json:"author,omitempty"` - Category []string `json:"category,omitempty"` - Photo []string `json:"photo,omitempty"` - LikeOf []string `json:"like-of,omitempty"` - BookmarkOf []string `json:"bookmark-of,omitempty"` - RepostOf []string `json:"repost-of,omitempty"` - InReplyTo []string `json:"in-reply-to,omitempty"` - Content *Content `json:"content,omitempty"` - Latitude string `json:"latitude,omitempty"` - Longitude string `json:"longitude,omitempty"` - Checkin *Card `json:"checkin,omitempty"` + Name string `json:"name,omitempty" mf2:"name"` + Published string `json:"published,omitempty" mf2:"published"` + Updated string `json:"updated,omitempty" mf2:"updated"` + URL string `json:"url,omitempty" mf2:"url"` + UID string `json:"uid,omitempty" mf2:"uid"` + Author *Card `json:"author,omitempty" mf2:"author"` + Category []string `json:"category,omitempty" mf2:"category"` + Photo []string `json:"photo,omitempty" mf2:"photo"` + LikeOf []string `json:"like-of,omitempty" mf2:"like-of"` + BookmarkOf []string `json:"bookmark-of,omitempty" mf2:"bookmark-of"` + RepostOf []string `json:"repost-of,omitempty" mf2:"repost-of"` + InReplyTo []string `json:"in-reply-to,omitempty" mf2:"in-reply-to"` + Content *Content `json:"content,omitempty" mf2:"content"` + Latitude string `json:"latitude,omitempty" mf2:"latitude"` + Longitude string `json:"longitude,omitempty" mf2:"longitude"` + Checkin *Card `json:"checkin,omitempty" mf2:"checkin"` Refs map[string]Item `json:"refs,omitempty"` ID string `json:"_id,omitempty"` Read bool `json:"_is_read"` diff --git a/cmd/eksterd/events.go b/pkg/server/events.go similarity index 98% rename from cmd/eksterd/events.go rename to pkg/server/events.go index b6aaaa3..ad846d4 100644 --- a/cmd/eksterd/events.go +++ b/pkg/server/events.go @@ -1,4 +1,4 @@ -package main +package server import ( "encoding/json" diff --git a/cmd/eksterd/microsub.go b/pkg/server/microsub.go similarity index 82% rename from cmd/eksterd/microsub.go rename to pkg/server/microsub.go index 689cde3..d94a549 100644 --- a/cmd/eksterd/microsub.go +++ b/pkg/server/microsub.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package server import ( "encoding/json" @@ -23,20 +23,29 @@ import ( "log" "net/http" "os" + "regexp" "p83.nl/go/ekster/pkg/microsub" "github.com/gomodule/redigo/redis" ) +var ( + entryRegex = regexp.MustCompile("^entry\\[\\d+\\]$") +) + type microsubHandler struct { - Backend microsub.Microsub - HubIncomingBackend HubBackend + backend microsub.Microsub + pool *redis.Pool +} + +func NewMicrosubHandler(backend microsub.Microsub, pool *redis.Pool) http.Handler { + return µsubHandler{backend, pool} } func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var logger = log.New(os.Stdout, "logger: ", log.Lshortfile) - conn := redis.NewLoggingConn(pool.Get(), logger, "microsub") + conn := redis.NewLoggingConn(h.pool.Get(), logger, "microsub") defer conn.Close() r.ParseForm() @@ -51,30 +60,12 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if auth { - authorization := r.Header.Get("Authorization") - - var token TokenResponse - - if !h.cachedCheckAuthToken(conn, authorization, &token) { - log.Printf("Token could not be validated") - http.Error(w, "Can't validate token", 403) - return - } - - if token.Me != h.Backend.(*memoryBackend).Me { - log.Printf("Missing \"me\" in token response: %#v\n", token) - http.Error(w, "Wrong me", 403) - return - } - } - if r.Method == http.MethodGet { w.Header().Add("Access-Control-Allow-Origin", "*") values := r.URL.Query() action := values.Get("action") if action == "channels" { - channels, err := h.Backend.ChannelsGetList() + channels, err := h.backend.ChannelsGetList() if err != nil { http.Error(w, err.Error(), 500) return @@ -89,7 +80,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } else if action == "timeline" { - timeline, err := h.Backend.TimelineGet(values.Get("before"), values.Get("after"), values.Get("channel")) + timeline, err := h.backend.TimelineGet(values.Get("before"), values.Get("after"), values.Get("channel")) if err != nil { http.Error(w, err.Error(), 500) return @@ -104,7 +95,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } else if action == "preview" { - timeline, err := h.Backend.PreviewURL(values.Get("url")) + timeline, err := h.backend.PreviewURL(values.Get("url")) if err != nil { http.Error(w, err.Error(), 500) return @@ -119,7 +110,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } else if action == "follow" { channel := values.Get("channel") - following, err := h.Backend.FollowGetList(channel) + following, err := h.backend.FollowGetList(channel) if err != nil { http.Error(w, err.Error(), 500) return @@ -136,7 +127,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if action == "events" { conn, _, _ := w.(http.Hijacker).Hijack() cons := newConsumer(conn) - h.Backend.AddEventListener(cons) + h.backend.AddEventListener(cons) } else { http.Error(w, fmt.Sprintf("unknown action %s\n", action), 500) return @@ -152,20 +143,19 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { method := values.Get("method") uid := values.Get("channel") if method == "delete" { - err := h.Backend.ChannelsDelete(uid) + err := h.backend.ChannelsDelete(uid) if err != nil { http.Error(w, err.Error(), 500) return } w.Header().Add("Content-Type", "application/json") fmt.Fprintln(w, "[]") - h.Backend.(Debug).Debug() return } jw := json.NewEncoder(w) if uid == "" { - channel, err := h.Backend.ChannelsCreate(name) + channel, err := h.backend.ChannelsCreate(name) if err != nil { http.Error(w, err.Error(), 500) return @@ -177,7 +167,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } else { - channel, err := h.Backend.ChannelsUpdate(uid, name) + channel, err := h.backend.ChannelsUpdate(uid, name) if err != nil { http.Error(w, err.Error(), 500) return @@ -189,12 +179,11 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } - h.Backend.(Debug).Debug() } else if action == "follow" { uid := values.Get("channel") url := values.Get("url") - h.HubIncomingBackend.CreateFeed(url, uid) - feed, err := h.Backend.FollowURL(uid, url) + // h.HubIncomingBackend.CreateFeed(url, uid) + feed, err := h.backend.FollowURL(uid, url) if err != nil { http.Error(w, err.Error(), 500) return @@ -209,7 +198,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if action == "unfollow" { uid := values.Get("channel") url := values.Get("url") - err := h.Backend.UnfollowURL(uid, url) + err := h.backend.UnfollowURL(uid, url) if err != nil { http.Error(w, err.Error(), 500) return @@ -218,7 +207,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "[]") } else if action == "search" { query := values.Get("query") - feeds, err := h.Backend.Search(query) + feeds, err := h.backend.Search(query) if err != nil { http.Error(w, err.Error(), 500) return @@ -254,7 +243,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if len(markAsRead) > 0 { - err := h.Backend.MarkRead(channel, markAsRead) + err := h.backend.MarkRead(channel, markAsRead) if err != nil { http.Error(w, err.Error(), 500) return