mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-10-01 19:43:49 +00:00
Serve the OpenAccess descriptor at /.well-known/openaccess.json (#127)
Spec: https://logicsrc.com/openaccess Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWRffW4ifQPUrGXJtgYWMd
This commit is contained in:
parent
d2e4b56ae7
commit
aad61eec17
5 changed files with 101 additions and 0 deletions
29
internal/files/openaccess.json
Normal file
29
internal/files/openaccess.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"openaccess": "0.1",
|
||||
"name": "AgentBBS",
|
||||
"url": "https://bbs.profullstack.com",
|
||||
"operator": "https://logicsrc.com/.well-known/openprofile.md",
|
||||
"redirect_uris": [
|
||||
"https://bbs.profullstack.com/api/v1/openaccess/callback"
|
||||
],
|
||||
"jwks": {
|
||||
"keys": [
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"kid": "Vg7JJd0byYl3",
|
||||
"x": "bck0ITevVM5tl-uMt-IAzuD_4DBuTpn9zlE0WKimdxw",
|
||||
"alg": "EdDSA",
|
||||
"use": "sig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scopes": {},
|
||||
"honours": [
|
||||
"profullstack.com/all-access"
|
||||
],
|
||||
"webhooks": "https://bbs.profullstack.com/api/v1/openaccess/events",
|
||||
"hubs": [
|
||||
"https://openaccess.logicsrc.com"
|
||||
]
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package files
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -71,9 +72,40 @@ func (s *Service) WebHandler(cfg WebConfig) http.Handler {
|
|||
mux.HandleFunc("/mkdir", h.handleMkdir)
|
||||
mux.HandleFunc("/delete", h.handleDelete)
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) })
|
||||
mux.Handle(OpenAccessPath, OpenAccessHandler())
|
||||
return mux
|
||||
}
|
||||
|
||||
// OpenAccessPath is the well-known location of the OpenAccess descriptor
|
||||
// (https://logicsrc.com/openaccess). Hubs such as openaccess.logicsrc.com fetch
|
||||
// it to list the BBS and to link accounts with OAuth 2.1 + PKCE.
|
||||
const OpenAccessPath = "/.well-known/openaccess.json"
|
||||
|
||||
// openAccessDescriptor is the static descriptor served verbatim. It names the
|
||||
// public signing key (JWKS), the redirect URI and the hubs the BBS trusts.
|
||||
//
|
||||
//go:embed openaccess.json
|
||||
var openAccessDescriptor []byte
|
||||
|
||||
// OpenAccessHandler serves the embedded OpenAccess descriptor as JSON with a
|
||||
// short public cache. It needs no session: hubs fetch it anonymously.
|
||||
func OpenAccessHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "public, max-age=300")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(openAccessDescriptor)))
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(openAccessDescriptor)
|
||||
})
|
||||
}
|
||||
|
||||
// --- session helpers --------------------------------------------------------
|
||||
|
||||
func (h *webSrv) lookup(r *http.Request) (string, bool) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package files
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
|
|
@ -394,3 +395,33 @@ func TestWebPublicReadOnlyByDefault(t *testing.T) {
|
|||
t.Fatalf("public upload: want redirect, got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebOpenAccessDescriptor(t *testing.T) {
|
||||
h, _ := webTestHandler(t)
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, OpenAccessPath, nil))
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status: want 200, got %d", rr.Code)
|
||||
}
|
||||
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
|
||||
t.Fatalf("content-type: got %q", ct)
|
||||
}
|
||||
if cc := rr.Header().Get("Cache-Control"); cc != "public, max-age=300" {
|
||||
t.Fatalf("cache-control: got %q", cc)
|
||||
}
|
||||
var doc struct {
|
||||
OpenAccess string `json:"openaccess"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &doc); err != nil {
|
||||
t.Fatalf("descriptor is not JSON: %v", err)
|
||||
}
|
||||
if doc.OpenAccess == "" || doc.URL == "" {
|
||||
t.Fatalf("descriptor missing openaccess/url: %s", rr.Body.String())
|
||||
}
|
||||
rr = httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodPost, OpenAccessPath, nil))
|
||||
if rr.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("POST: want 405, got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue