···25 "ref": "#intent",
26 "description": "Public access to or replay of account data as part of archiving and preservation efforts"
27 },
28- "syntheticContentGeneration": {
29 "type": "ref",
30 "ref": "#intent",
31 "description": "Inclusion of account data in bulk 'snapshot' datasets which are publicly redistributed, even if only for a fixed time period"
32 },
33- "syntheticContentGeneration": {
34 "type": "ref",
35 "ref": "#intent",
36 "description": "Bridging account data or interactions into distinct social web protocol ecosystems"
···25 "ref": "#intent",
26 "description": "Public access to or replay of account data as part of archiving and preservation efforts"
27 },
28+ "bulkDataset": {
29 "type": "ref",
30 "ref": "#intent",
31 "description": "Inclusion of account data in bulk 'snapshot' datasets which are publicly redistributed, even if only for a fixed time period"
32 },
33+ "protocolBridging": {
34 "type": "ref",
35 "ref": "#intent",
36 "description": "Bridging account data or interactions into distinct social web protocol ecosystems"
+88-80
main.go
···12 _ "github.com/joho/godotenv/autoload"
1314 "github.com/bluesky-social/indigo/atproto/auth/oauth"
15- "github.com/bluesky-social/indigo/atproto/crypto"
16 "github.com/bluesky-social/indigo/atproto/identity"
17 "github.com/bluesky-social/indigo/atproto/syntax"
18···37 Usage: "public host name for this client (if not localhost dev mode)",
38 EnvVars: []string{"CLIENT_HOSTNAME"},
39 },
40- &cli.StringFlag{
41- Name: "client-secret-key",
42- Usage: "confidential client secret key. should be P-256 private key in multibase encoding",
43- EnvVars: []string{"CLIENT_SECRET_KEY"},
44- },
45- &cli.StringFlag{
46- Name: "client-secret-key-id",
47- Usage: "key id for client-secret-key",
48- Value: "primary",
49- EnvVars: []string{"CLIENT_SECRET_KEY_ID"},
50- },
51 },
52 }
53 h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
···72var tmplLoginText string
73var tmplLogin = template.Must(template.Must(template.New("login.html").Parse(tmplBaseText)).Parse(tmplLoginText))
7475-//go:embed "post.html"
76-var tmplPostText string
77-var tmplPost = template.Must(template.Must(template.New("post.html").Parse(tmplBaseText)).Parse(tmplPostText))
7879-func (s *Server) Homepage(w http.ResponseWriter, r *http.Request) {
80- tmplHome.Execute(w, nil)
0081}
8283func runServer(cctx *cli.Context) error {
···101 )
102 }
103104- // If a client secret key is provided (as a multibase string), turn this in to a confidential client
105- if cctx.String("client-secret-key") != "" && hostname != "" {
106- priv, err := crypto.ParsePrivateMultibase(cctx.String("client-secret-key"))
107- if err != nil {
108- return err
109- }
110- config.AddClientSecret(priv, cctx.String("client-secret-key-id"))
111- slog.Info("configuring confidential OAuth client")
112- }
113-114 oauthClient := oauth.NewClientApp(&config, oauth.NewMemStore())
115116 srv := Server{
···125 http.HandleFunc("GET /oauth/login", srv.OAuthLogin)
126 http.HandleFunc("POST /oauth/login", srv.OAuthLogin)
127 http.HandleFunc("GET /oauth/callback", srv.OAuthCallback)
128- http.HandleFunc("GET /oauth/refresh", srv.OAuthRefresh)
129 http.HandleFunc("GET /oauth/logout", srv.OAuthLogout)
130- http.HandleFunc("GET /bsky/post", srv.Post)
131- http.HandleFunc("POST /bsky/post", srv.Post)
132133 slog.Info("starting http server", "bind", bind)
134 if err := http.ListenAndServe(bind, nil); err != nil {
···160161 scope := "atproto transition:generic"
162 meta := s.OAuth.Config.ClientMetadata(scope)
163- if s.OAuth.Config.IsConfidential() {
164- meta.JWKSUri = strPtr(fmt.Sprintf("https://%s/oauth/jwks.json", r.Host))
165- }
166- meta.ClientName = strPtr("indigo atp-oauth-demo")
167 meta.ClientURI = strPtr(fmt.Sprintf("https://%s", r.Host))
168169 // internal consistency check
···187 http.Error(w, err.Error(), http.StatusInternalServerError)
188 return
189 }
000000000190}
191192func (s *Server) OAuthLogin(w http.ResponseWriter, r *http.Request) {
···237 }
238239 slog.Info("login successful", "did", sessData.AccountDID.String())
240- http.Redirect(w, r, "/bsky/post", http.StatusFound)
241-}
242-243-func (s *Server) OAuthRefresh(w http.ResponseWriter, r *http.Request) {
244- ctx := r.Context()
245-246- did := s.currentSessionDID(r)
247- if did == nil {
248- // TODO: suppowed to set a WWW header; and could redirect?
249- http.Error(w, "not authenticated", http.StatusUnauthorized)
250- return
251- }
252-253- oauthSess, err := s.OAuth.ResumeSession(ctx, *did)
254- if err != nil {
255- http.Error(w, "not authenticated", http.StatusUnauthorized)
256- return
257- }
258-259- if err := oauthSess.RefreshTokens(ctx); err != nil {
260- http.Error(w, err.Error(), http.StatusBadRequest)
261- return
262- }
263- s.OAuth.Store.SaveSession(ctx, *oauthSess.Data)
264- slog.Info("refreshed tokens")
265- http.Redirect(w, r, "/", http.StatusFound)
266}
267268func (s *Server) OAuthLogout(w http.ResponseWriter, r *http.Request) {
···280 http.Redirect(w, r, "/", http.StatusFound)
281}
282283-func (s *Server) Post(w http.ResponseWriter, r *http.Request) {
284- ctx := r.Context()
285-286- slog.Info("in post handler")
00000000287288- if r.Method != "POST" {
289- tmplPost.Execute(w, nil)
290- return
291- }
292293 did := s.currentSessionDID(r)
294 if did == nil {
···303 return
304 }
305 c := oauthSess.APIClient()
00000000000000000000306307 if err := r.ParseForm(); err != nil {
308 http.Error(w, fmt.Errorf("parsing form data: %w", err).Error(), http.StatusBadRequest)
309 return
310 }
311- text := r.PostFormValue("post_text")
312313- body := map[string]any{
314- "repo": c.AccountDID.String(),
315- "collection": "app.bsky.feed.post",
316- "record": map[string]any{
317- "$type": "app.bsky.feed.post",
318- "text": text,
319- "createdAt": syntax.DatetimeNow(),
0000000000000320 },
321 }
322323- slog.Info("attempting post...", "text", text)
324- if err := c.Post(ctx, "com.atproto.repo.createRecord", body, nil); err != nil {
325- http.Error(w, fmt.Errorf("posting failed: %w", err).Error(), http.StatusBadRequest)
0000000000326 return
327 }
328329- http.Redirect(w, r, "/bsky/post", http.StatusFound)
330}