dev vouch dev on at. thats about it atvouch.dev
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

add initial cli

Luna 267f1478 088dfe24

+508
+3
.gitignore
··· 30 30 # Editor/IDE 31 31 # .idea/ 32 32 # .vscode/ 33 + 34 + atvouch 35 + .claude/
+2
Makefile
··· 1 + atvouch-cli: 2 + cd ./cli && go build -o ../atvouch
+29
cli/go.mod
··· 1 + module l4.pm/atvouch-cli 2 + 3 + go 1.25.7 4 + 5 + require ( 6 + github.com/beorn7/perks v1.0.1 // indirect 7 + github.com/bluesky-social/indigo v0.0.0-20260308004230-c55a189a51a9 // indirect 8 + github.com/cespare/xxhash/v2 v2.2.0 // indirect 9 + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 10 + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 11 + github.com/google/go-querystring v1.1.0 // indirect 12 + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 13 + github.com/inconshreveable/mousetrap v1.1.0 // indirect 14 + github.com/mattn/go-sqlite3 v1.14.34 // indirect 15 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 16 + github.com/mr-tron/base58 v1.2.0 // indirect 17 + github.com/prometheus/client_golang v1.17.0 // indirect 18 + github.com/prometheus/client_model v0.5.0 // indirect 19 + github.com/prometheus/common v0.45.0 // indirect 20 + github.com/prometheus/procfs v0.12.0 // indirect 21 + github.com/spf13/cobra v1.10.2 // indirect 22 + github.com/spf13/pflag v1.0.9 // indirect 23 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 24 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 25 + golang.org/x/crypto v0.21.0 // indirect 26 + golang.org/x/sys v0.22.0 // indirect 27 + golang.org/x/time v0.3.0 // indirect 28 + google.golang.org/protobuf v1.33.0 // indirect 29 + )
+54
cli/go.sum
··· 1 + github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 + github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 + github.com/bluesky-social/indigo v0.0.0-20260308004230-c55a189a51a9 h1:nzW5kShDFLv2CRWGUg5HDWfh8ctnyAAeasXKVTKZy8I= 4 + github.com/bluesky-social/indigo v0.0.0-20260308004230-c55a189a51a9/go.mod h1:VG/LeqLGNI3Ew7lsYixajnZGFfWPv144qbUddh+Oyag= 5 + github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 + github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 + github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 8 + github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 9 + github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 10 + github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 11 + github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 12 + github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 13 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 15 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 16 + github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 17 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 18 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 19 + github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 20 + github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 21 + github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= 22 + github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 23 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 24 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 25 + github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 26 + github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 27 + github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 28 + github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 29 + github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 30 + github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 31 + github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 32 + github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 33 + github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 34 + github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 35 + github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 36 + github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= 37 + github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 38 + github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 39 + github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 40 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 41 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 42 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 43 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 44 + go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 45 + golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 46 + golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 47 + golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 48 + golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 49 + golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 50 + golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 51 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 + google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 53 + google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 54 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+184
cli/main.go
··· 1 + package main 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "errors" 8 + "fmt" 9 + "log" 10 + "net" 11 + "net/http" 12 + "net/url" 13 + "os" 14 + "os/exec" 15 + "runtime" 16 + "strings" 17 + 18 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 19 + "github.com/spf13/cobra" 20 + ) 21 + 22 + func main() { 23 + rootCmd := &cobra.Command{ 24 + Use: "atvouch", 25 + Short: "AT Protocol vouching tool", 26 + } 27 + 28 + loginCmd := &cobra.Command{ 29 + Use: "login <handle>", 30 + Short: "Authenticate with your PDS via OAuth", 31 + Args: cobra.ExactArgs(1), 32 + RunE: func(cmd *cobra.Command, args []string) error { 33 + return login(cmd.Context(), args[0]) 34 + }, 35 + } 36 + 37 + meCmd := &cobra.Command{ 38 + Use: "me", 39 + Short: "Show current authenticated session info", 40 + Args: cobra.NoArgs, 41 + RunE: func(cmd *cobra.Command, args []string) error { 42 + return me(cmd.Context()) 43 + }, 44 + } 45 + 46 + rootCmd.AddCommand(loginCmd, meCmd) 47 + 48 + if err := rootCmd.ExecuteContext(context.Background()); err != nil { 49 + os.Exit(1) 50 + } 51 + } 52 + 53 + func newStore() (*Store, error) { 54 + return NewStore() 55 + } 56 + 57 + func newOAuthClient(store *Store, callbackURL string) *oauth.ClientApp { 58 + config := oauth.NewLocalhostConfig(callbackURL, []string{"atproto"}) 59 + return oauth.NewClientApp(&config, store) 60 + } 61 + 62 + func login(ctx context.Context, handle string) error { 63 + store, err := newStore() 64 + if err != nil { 65 + return err 66 + } 67 + 68 + // Start the callback server on a random available port 69 + callbackCh := make(chan url.Values, 1) 70 + port, server, err := listenForCallback(ctx, callbackCh) 71 + if err != nil { 72 + return err 73 + } 74 + defer server.Close() 75 + 76 + callbackURL := fmt.Sprintf("http://127.0.0.1:%d/callback", port) 77 + oauthClient := newOAuthClient(store, callbackURL) 78 + 79 + // Start the OAuth flow 80 + fmt.Printf("Logging in as %s...\n", handle) 81 + authURL, err := oauthClient.StartAuthFlow(ctx, handle) 82 + if err != nil { 83 + return fmt.Errorf("starting auth flow: %w", err) 84 + } 85 + 86 + // Open the browser to the authorization URL 87 + fmt.Printf("Opening browser...\n") 88 + if !strings.HasPrefix(authURL, "https://") { 89 + return fmt.Errorf("unexpected non-https auth URL") 90 + } 91 + if err := openBrowser(authURL); err != nil { 92 + fmt.Printf("Could not open browser automatically.\nPlease visit: %s\n", authURL) 93 + } 94 + 95 + // Wait for the OAuth callback 96 + fmt.Println("Waiting for authorization...") 97 + params := <-callbackCh 98 + 99 + // Exchange the authorization code for a session 100 + sessData, err := oauthClient.ProcessCallback(ctx, params) 101 + if err != nil { 102 + return fmt.Errorf("processing callback: %w", err) 103 + } 104 + 105 + // Mark this as the active session 106 + if err := store.SetActive(sessData.AccountDID, sessData.SessionID); err != nil { 107 + return fmt.Errorf("saving active session: %w", err) 108 + } 109 + 110 + fmt.Printf("Logged in as %s (%s)\n", handle, sessData.AccountDID) 111 + return nil 112 + } 113 + 114 + func me(ctx context.Context) error { 115 + store, err := newStore() 116 + if err != nil { 117 + return err 118 + } 119 + 120 + active, err := store.GetActive() 121 + if err != nil { 122 + return err 123 + } 124 + 125 + // We need a callback URL to construct the client config, but we won't 126 + // actually start any auth flow here. Use a dummy port. 127 + callbackURL := "http://127.0.0.1:0/callback" 128 + oauthClient := newOAuthClient(store, callbackURL) 129 + 130 + session, err := oauthClient.ResumeSession(ctx, active.DID, active.SessionID) 131 + if err != nil { 132 + return fmt.Errorf("resuming session: %w", err) 133 + } 134 + 135 + client := session.APIClient() 136 + var resp json.RawMessage 137 + if err := client.Get(ctx, "com.atproto.server.getSession", nil, &resp); err != nil { 138 + return fmt.Errorf("fetching session: %w", err) 139 + } 140 + 141 + // Pretty-print the response 142 + var pretty bytes.Buffer 143 + json.Indent(&pretty, resp, "", " ") 144 + fmt.Println(pretty.String()) 145 + 146 + return nil 147 + } 148 + 149 + func listenForCallback(ctx context.Context, res chan url.Values) (int, *http.Server, error) { 150 + listener, err := net.Listen("tcp", "127.0.0.1:0") 151 + if err != nil { 152 + return 0, nil, err 153 + } 154 + 155 + mux := http.NewServeMux() 156 + server := &http.Server{Handler: mux} 157 + 158 + mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { 159 + res <- r.URL.Query() 160 + w.Header().Set("Content-Type", "text/html") 161 + w.WriteHeader(200) 162 + w.Write([]byte("<h1>Authorized! You can close this tab.</h1>")) 163 + go server.Shutdown(ctx) 164 + }) 165 + 166 + go func() { 167 + if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) { 168 + log.Fatal(err) 169 + } 170 + }() 171 + 172 + return listener.Addr().(*net.TCPAddr).Port, server, nil 173 + } 174 + 175 + func openBrowser(url string) error { 176 + switch runtime.GOOS { 177 + case "darwin": 178 + return exec.Command("open", url).Run() 179 + case "windows": 180 + return exec.Command("cmd", "/c", "start", url).Run() 181 + default: 182 + return exec.Command("xdg-open", url).Run() 183 + } 184 + }
+236
cli/store.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "encoding/json" 7 + "fmt" 8 + "os" 9 + "path/filepath" 10 + 11 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 12 + "github.com/bluesky-social/indigo/atproto/syntax" 13 + _ "github.com/mattn/go-sqlite3" 14 + ) 15 + 16 + type Store struct { 17 + db *sql.DB 18 + } 19 + 20 + var _ oauth.ClientAuthStore = &Store{} 21 + 22 + func NewStore() (*Store, error) { 23 + configDir, err := os.UserConfigDir() 24 + if err != nil { 25 + return nil, fmt.Errorf("getting config dir: %w", err) 26 + } 27 + dir := filepath.Join(configDir, "atvouch") 28 + if err := os.MkdirAll(dir, 0700); err != nil { 29 + return nil, err 30 + } 31 + 32 + db, err := sql.Open("sqlite3", filepath.Join(dir, "state.db")) 33 + if err != nil { 34 + return nil, err 35 + } 36 + 37 + for _, pragma := range []string{ 38 + "PRAGMA journal_mode = WAL", 39 + "PRAGMA busy_timeout = 5000", 40 + "PRAGMA synchronous = NORMAL", 41 + "PRAGMA cache_size = -6000", 42 + "PRAGMA foreign_keys = true", 43 + "PRAGMA temp_store = memory", 44 + } { 45 + if _, err := db.Exec(pragma); err != nil { 46 + db.Close() 47 + return nil, fmt.Errorf("setting pragma: %w", err) 48 + } 49 + } 50 + 51 + if err := migrate(db); err != nil { 52 + db.Close() 53 + return nil, err 54 + } 55 + 56 + return &Store{db: db}, nil 57 + } 58 + 59 + var migrations = []struct { 60 + version int 61 + sql string 62 + }{ 63 + {1, ` 64 + CREATE TABLE sessions ( 65 + did TEXT NOT NULL, 66 + session_id TEXT NOT NULL, 67 + data TEXT NOT NULL, 68 + PRIMARY KEY (did, session_id) 69 + ) STRICT; 70 + 71 + CREATE TABLE auth_requests ( 72 + state TEXT NOT NULL PRIMARY KEY, 73 + data TEXT NOT NULL 74 + ) STRICT; 75 + 76 + CREATE TABLE active_session ( 77 + id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), 78 + did TEXT NOT NULL, 79 + session_id TEXT NOT NULL 80 + ) STRICT; 81 + `}, 82 + } 83 + 84 + func migrate(db *sql.DB) error { 85 + _, err := db.Exec(` 86 + CREATE TABLE IF NOT EXISTS migration_log ( 87 + version INTEGER NOT NULL PRIMARY KEY, 88 + applied_at TEXT NOT NULL DEFAULT (datetime('now')) 89 + ) STRICT; 90 + `) 91 + if err != nil { 92 + return fmt.Errorf("creating migration_log: %w", err) 93 + } 94 + 95 + for _, m := range migrations { 96 + var exists int 97 + err := db.QueryRow("SELECT 1 FROM migration_log WHERE version = ?", m.version).Scan(&exists) 98 + if err == nil { 99 + continue 100 + } 101 + if err != sql.ErrNoRows { 102 + return fmt.Errorf("checking migration %d: %w", m.version, err) 103 + } 104 + 105 + tx, err := db.Begin() 106 + if err != nil { 107 + return err 108 + } 109 + if _, err := tx.Exec(m.sql); err != nil { 110 + tx.Rollback() 111 + return fmt.Errorf("migration %d: %w", m.version, err) 112 + } 113 + if _, err := tx.Exec("INSERT INTO migration_log (version) VALUES (?)", m.version); err != nil { 114 + tx.Rollback() 115 + return fmt.Errorf("recording migration %d: %w", m.version, err) 116 + } 117 + if err := tx.Commit(); err != nil { 118 + return fmt.Errorf("committing migration %d: %w", m.version, err) 119 + } 120 + } 121 + 122 + return nil 123 + } 124 + 125 + func (s *Store) Close() error { 126 + return s.db.Close() 127 + } 128 + 129 + func (s *Store) SetActive(did syntax.DID, sessionID string) error { 130 + _, err := s.db.Exec( 131 + `INSERT INTO active_session (id, did, session_id) VALUES (1, ?, ?) 132 + ON CONFLICT (id) DO UPDATE SET did = excluded.did, session_id = excluded.session_id`, 133 + did.String(), sessionID, 134 + ) 135 + return err 136 + } 137 + 138 + type activeSession struct { 139 + DID syntax.DID 140 + SessionID string 141 + } 142 + 143 + func (s *Store) GetActive() (*activeSession, error) { 144 + var didStr, sessionID string 145 + err := s.db.QueryRow("SELECT did, session_id FROM active_session WHERE id = 1").Scan(&didStr, &sessionID) 146 + if err == sql.ErrNoRows { 147 + return nil, fmt.Errorf("no active session (run 'atvouch login' first)") 148 + } 149 + if err != nil { 150 + return nil, err 151 + } 152 + did, err := syntax.ParseDID(didStr) 153 + if err != nil { 154 + return nil, err 155 + } 156 + return &activeSession{DID: did, SessionID: sessionID}, nil 157 + } 158 + 159 + func (s *Store) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*oauth.ClientSessionData, error) { 160 + var data string 161 + err := s.db.QueryRowContext(ctx, 162 + "SELECT data FROM sessions WHERE did = ? AND session_id = ?", 163 + did.String(), sessionID, 164 + ).Scan(&data) 165 + if err == sql.ErrNoRows { 166 + return nil, fmt.Errorf("session not found for %s", did) 167 + } 168 + if err != nil { 169 + return nil, err 170 + } 171 + var sess oauth.ClientSessionData 172 + if err := json.Unmarshal([]byte(data), &sess); err != nil { 173 + return nil, err 174 + } 175 + return &sess, nil 176 + } 177 + 178 + func (s *Store) SaveSession(ctx context.Context, sess oauth.ClientSessionData) error { 179 + data, err := json.Marshal(sess) 180 + if err != nil { 181 + return err 182 + } 183 + _, err = s.db.ExecContext(ctx, 184 + `INSERT INTO sessions (did, session_id, data) VALUES (?, ?, ?) 185 + ON CONFLICT (did, session_id) DO UPDATE SET data = excluded.data`, 186 + sess.AccountDID.String(), sess.SessionID, string(data), 187 + ) 188 + return err 189 + } 190 + 191 + func (s *Store) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 192 + _, err := s.db.ExecContext(ctx, 193 + "DELETE FROM sessions WHERE did = ? AND session_id = ?", 194 + did.String(), sessionID, 195 + ) 196 + return err 197 + } 198 + 199 + func (s *Store) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) { 200 + var data string 201 + err := s.db.QueryRowContext(ctx, 202 + "SELECT data FROM auth_requests WHERE state = ?", 203 + state, 204 + ).Scan(&data) 205 + if err == sql.ErrNoRows { 206 + return nil, fmt.Errorf("request info not found: %s", state) 207 + } 208 + if err != nil { 209 + return nil, err 210 + } 211 + var req oauth.AuthRequestData 212 + if err := json.Unmarshal([]byte(data), &req); err != nil { 213 + return nil, err 214 + } 215 + return &req, nil 216 + } 217 + 218 + func (s *Store) SaveAuthRequestInfo(ctx context.Context, info oauth.AuthRequestData) error { 219 + data, err := json.Marshal(info) 220 + if err != nil { 221 + return err 222 + } 223 + _, err = s.db.ExecContext(ctx, 224 + "INSERT INTO auth_requests (state, data) VALUES (?, ?)", 225 + info.State, string(data), 226 + ) 227 + return err 228 + } 229 + 230 + func (s *Store) DeleteAuthRequestInfo(ctx context.Context, state string) error { 231 + _, err := s.db.ExecContext(ctx, 232 + "DELETE FROM auth_requests WHERE state = ?", 233 + state, 234 + ) 235 + return err 236 + }