From 03a9b20bab974ce05b0f89eec59c021247f39a7a Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Sat, 27 Dec 2025 23:04:58 -0600 Subject: [PATCH] large cleanup of unused code --- Makefile | 12 +++- cmd/blup/main.go | 46 +++++--------- internal/auth/oauth.go | 97 +++++++++++++++--------------- internal/auth/storage.go | 34 +++++------ internal/config/config.go | 53 ---------------- lexicons/blue.imgs.blup.image.json | 11 ++-- tnyshoot | 16 ----- 7 files changed, 96 insertions(+), 173 deletions(-) delete mode 100644 internal/config/config.go delete mode 100755 tnyshoot diff --git a/Makefile b/Makefile index 45d6f0a..ff2b3fa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,11 @@ +.PHONY: build install build-windows + +build: + go build -o ./blup ./cmd/blup + +install: + go install ./cmd/blup -PHONY: build-windows build-windows: - @GOOS=windows go build -o ./blup.exe ./cmd/cli/main.go - @osslsigncode sign -pkcs12 ../mycert.pfx -askpass -n "Blup" -i "https://blup.imgs.blue" -in ./blup.exe -out ./blup-signed.exe + GOOS=windows go build -o ./blup.exe ./cmd/blup + osslsigncode sign -pkcs12 ../mycert.pfx -askpass -n "Blup" -i "https://blup.imgs.blue" -in ./blup.exe -out ./blup-signed.exe diff --git a/cmd/blup/main.go b/cmd/blup/main.go index 588c957..84e8901 100644 --- a/cmd/blup/main.go +++ b/cmd/blup/main.go @@ -18,7 +18,6 @@ import ( "github.com/spf13/cobra" "tangled.sh/evan.jarrett.net/blup/internal/auth" "tangled.sh/evan.jarrett.net/blup/internal/clipboard" - "tangled.sh/evan.jarrett.net/blup/internal/config" "tangled.sh/evan.jarrett.net/blup/internal/screenshot" "tangled.sh/evan.jarrett.net/blup/internal/util" ) @@ -48,7 +47,7 @@ func main() { Short: "Log in with AT Protocol", RunE: runAuth, } - loginCmd.Flags().StringVar(&authHandle, "handle", "", "Bluesky (ATProto) handle") + loginCmd.Flags().StringVar(&authHandle, "handle", "", "ATProto handle") var statusCmd = &cobra.Command{ Use: "status", @@ -111,22 +110,16 @@ func main() { func runAuth(cmd *cobra.Command, args []string) error { var handle string if authHandle == "" { - fmt.Print("Enter your Bluesky handle: ") + fmt.Print("Enter your ATProto handle: ") fmt.Scanln(&handle) } else { - fmt.Printf("Using %s as bluesky handle\n", authHandle) + fmt.Printf("Using %s as ATProto handle\n", authHandle) handle = authHandle } - cfg, err := config.SaveConfig(&config.Config{ - AuthserverIss: "https://bsky.social", - Handle: handle, - }) - if err != nil { - return err - } + handle = strings.TrimPrefix(handle, "@") - if _, err := auth.RefreshTokens(cfg); err != nil { + if _, err := auth.RefreshTokens(handle); err != nil { return err } return nil @@ -149,14 +142,7 @@ func runStatus(cmd *cobra.Command, args []string) error { } func runLogout(cmd *cobra.Command, args []string) error { - cfg, err := config.LoadConfig() - if err != nil { - // No config, nothing to logout - fmt.Println("Not logged in") - return nil - } - - if err := auth.Logout(cfg); err != nil { + if err := auth.Logout(); err != nil { return err } fmt.Println("Logged out successfully") @@ -203,12 +189,6 @@ func copyToClipboard(text string) error { // uploadImage handles the core upload logic and returns the CDN URL func uploadImage(imagePath string) (string, error) { - // Load config - cfg, err := config.LoadConfig() - if err != nil { - return "", fmt.Errorf("not authenticated, run '%s auth' first: %w", Name, err) - } - ctx := context.Background() // Open file @@ -246,10 +226,10 @@ func uploadImage(imagePath string) (string, error) { return "", fmt.Errorf("error seeking file: %w", err) } - // Get authenticated session - sess, err := auth.RefreshTokens(cfg) + // Get authenticated session (will re-auth if needed using saved login identifier) + sess, err := auth.RefreshTokens("") if err != nil { - return "", err + return "", fmt.Errorf("not authenticated, run '%s login' first: %w", Name, err) } // Get API client from session @@ -283,6 +263,12 @@ func uploadImage(imagePath string) (string, error) { return "", fmt.Errorf("failed to decode upload response: %w", err) } + // Resolve handle from DID for CDN URL + handle, err := auth.ResolveHandle(ctx, sess.Data.AccountDID) + if err != nil { + return "", fmt.Errorf("failed to resolve handle: %w", err) + } + // Create record record := map[string]interface{}{ "$type": fmt.Sprintf("blue.imgs.%s.image", Name), @@ -317,7 +303,7 @@ func uploadImage(imagePath string) (string, error) { return "", err } - return fmt.Sprintf("%s/%s/%s", CDN, cfg.Handle, converted), nil + return fmt.Sprintf("%s/%s/%s", CDN, handle, converted), nil } func setupLogging() { diff --git a/internal/auth/oauth.go b/internal/auth/oauth.go index 42bc256..68174d3 100644 --- a/internal/auth/oauth.go +++ b/internal/auth/oauth.go @@ -12,20 +12,21 @@ import ( "time" "github.com/bluesky-social/indigo/atproto/auth/oauth" + "github.com/bluesky-social/indigo/atproto/identity" + "github.com/bluesky-social/indigo/atproto/syntax" "github.com/pkg/browser" - "tangled.sh/evan.jarrett.net/blup/internal/config" ) type OAuthFlow struct { - app *oauth.ClientApp - cfg *config.Config - store *KeyringAuthStore - authSuccess chan *oauth.ClientSessionData - authError chan error - savedState string + app *oauth.ClientApp + loginIdentifier string + store *KeyringAuthStore + authSuccess chan *oauth.ClientSessionData + authError chan error + savedState string } -func NewOAuthFlow(cfg *config.Config) (*OAuthFlow, error) { +func NewOAuthFlow(loginIdentifier string) (*OAuthFlow, error) { store := NewKeyringAuthStore() clientConfig := GetClientConfig() @@ -41,25 +42,24 @@ func NewOAuthFlow(cfg *config.Config) (*OAuthFlow, error) { app := oauth.NewClientApp(&clientConfig, store) return &OAuthFlow{ - app: app, - cfg: cfg, - store: store, - authSuccess: make(chan *oauth.ClientSessionData, 1), - authError: make(chan error, 1), + app: app, + loginIdentifier: loginIdentifier, + store: store, + authSuccess: make(chan *oauth.ClientSessionData, 1), + authError: make(chan error, 1), }, nil } func (f *OAuthFlow) Authenticate() (*oauth.ClientSessionData, error) { ctx := context.Background() - cfg := f.cfg - if cfg.Handle == "" { - return nil, fmt.Errorf("handle is required") + if f.loginIdentifier == "" { + return nil, fmt.Errorf("login identifier is required") } // Start the OAuth flow - this handles handle resolution, PAR, etc. // Note: StartAuthFlow internally calls SaveAuthRequestInfo which stores the state - redirectURL, err := f.app.StartAuthFlow(ctx, cfg.Handle) + redirectURL, err := f.app.StartAuthFlow(ctx, f.loginIdentifier) if err != nil { return nil, fmt.Errorf("failed to start auth flow: %w", err) } @@ -92,10 +92,10 @@ func (f *OAuthFlow) Authenticate() (*oauth.ClientSessionData, error) { return nil, fmt.Errorf("failed to save current session: %w", err) } - // Update config with PDS host - cfg.PDSHost = sess.HostURL - cfg.AuthserverIss = sess.AuthServerURL - config.SaveConfig(cfg) + // Save login identifier for re-authentication + if err := f.store.SetLoginIdentifier(f.loginIdentifier); err != nil { + return nil, fmt.Errorf("failed to save login identifier: %w", err) + } return sess, nil case err := <-f.authError: @@ -189,8 +189,18 @@ func (f *OAuthFlow) handleCallback(ctx context.Context, code, iss, state string) f.authSuccess <- sess } +// ResolveHandle resolves a handle from a DID using the identity directory +func ResolveHandle(ctx context.Context, did syntax.DID) (string, error) { + dir := identity.DefaultDirectory() + ident, err := dir.LookupDID(ctx, did) + if err != nil { + return "", fmt.Errorf("failed to resolve DID: %w", err) + } + return ident.Handle.String(), nil +} + // GetSession retrieves the current session, refreshing tokens if needed -func GetSession(cfg *config.Config) (*oauth.ClientSession, error) { +func GetSession() (*oauth.ClientSession, error) { ctx := context.Background() store := NewKeyringAuthStore() @@ -213,35 +223,20 @@ func GetSession(cfg *config.Config) (*oauth.ClientSession, error) { } // RefreshTokens refreshes the access token if needed and returns the session -func RefreshTokens(cfg *config.Config) (*oauth.ClientSession, error) { +// If loginIdentifier is provided, it will be used for initial auth if no session exists +// If empty, it will use the saved login identifier from keyring +func RefreshTokens(loginIdentifier string) (*oauth.ClientSession, error) { ctx := context.Background() store := NewKeyringAuthStore() - // Check for legacy tokens and migrate - if HasLegacyTokens() { - DeleteLegacyTokens() - fmt.Println("Your stored credentials are from an older version.") - fmt.Println("Please re-authenticate.") - // Trigger new auth flow - flow, err := NewOAuthFlow(cfg) - if err != nil { - return nil, err - } - sess, err := flow.Authenticate() - if err != nil { - return nil, err - } - // Resume the session to return a ClientSession - clientConfig := GetClientConfig() - app := oauth.NewClientApp(&clientConfig, store) - return app.ResumeSession(ctx, sess.AccountDID, sess.SessionID) - } - // Check for current session sessData, err := store.GetCurrentSession(ctx) if err != nil { // No session, need to authenticate - flow, err := NewOAuthFlow(cfg) + if loginIdentifier == "" { + return nil, fmt.Errorf("no active session and no login identifier provided") + } + flow, err := NewOAuthFlow(loginIdentifier) if err != nil { return nil, err } @@ -262,7 +257,14 @@ func RefreshTokens(cfg *config.Config) (*oauth.ClientSession, error) { sess, err := app.ResumeSession(ctx, sessData.AccountDID, sessData.SessionID) if err != nil { // Session invalid, need to re-authenticate - flow, err := NewOAuthFlow(cfg) + // Get login identifier from keyring if not provided + if loginIdentifier == "" { + loginIdentifier, err = store.GetLoginIdentifier() + if err != nil { + return nil, fmt.Errorf("session expired and no saved login identifier: %w", err) + } + } + flow, err := NewOAuthFlow(loginIdentifier) if err != nil { return nil, err } @@ -277,7 +279,7 @@ func RefreshTokens(cfg *config.Config) (*oauth.ClientSession, error) { } // Logout revokes tokens and clears the session -func Logout(cfg *config.Config) error { +func Logout() error { ctx := context.Background() store := NewKeyringAuthStore() @@ -296,6 +298,7 @@ func Logout(cfg *config.Config) error { fmt.Printf("Warning: failed to revoke tokens: %v\n", err) } - // Clear current session reference + // Clear current session reference and login identifier + store.ClearLoginIdentifier() return store.ClearCurrentSession() } diff --git a/internal/auth/storage.go b/internal/auth/storage.go index b97f6fd..3cfdd30 100644 --- a/internal/auth/storage.go +++ b/internal/auth/storage.go @@ -11,13 +11,12 @@ import ( ) const ( - keyringService = "blup" - currentSessionKey = "current-session" - sessionKeyPrefix = "session:" - authRequestPrefix = "auth-request:" - pendingAuthStateKey = "pending-auth-state" - legacyTokensKey = "oauth-tokens" - legacyJWKSKey = "oauth-jwks" + keyringService = "blup" + currentSessionKey = "current-session" + sessionKeyPrefix = "session:" + authRequestPrefix = "auth-request:" + pendingAuthStateKey = "pending-auth-state" + loginIdentifierKey = "login-identifier" ) // KeyringAuthStore implements oauth.ClientAuthStore using the system keyring @@ -159,16 +158,17 @@ func (s *KeyringAuthStore) ClearCurrentSession() error { return keyring.Delete(keyringService, currentSessionKey) } -// HasLegacyTokens checks if old-format tokens exist (for migration) -func HasLegacyTokens() bool { - _, err := keyring.Get(keyringService, legacyTokensKey) - return err == nil +// GetLoginIdentifier retrieves the stored login identifier (handle or PDS URL) +func (s *KeyringAuthStore) GetLoginIdentifier() (string, error) { + return keyring.Get(keyringService, loginIdentifierKey) } -// DeleteLegacyTokens removes old-format tokens and JWKS -func DeleteLegacyTokens() error { - // Ignore errors - keys might not exist - keyring.Delete(keyringService, legacyTokensKey) - keyring.Delete(keyringService, legacyJWKSKey) - return nil +// SetLoginIdentifier stores the login identifier for re-authentication +func (s *KeyringAuthStore) SetLoginIdentifier(id string) error { + return keyring.Set(keyringService, loginIdentifierKey, id) +} + +// ClearLoginIdentifier removes the stored login identifier +func (s *KeyringAuthStore) ClearLoginIdentifier() error { + return keyring.Delete(keyringService, loginIdentifierKey) } diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 053e03e..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,53 +0,0 @@ -package config - -import ( - "encoding/json" - "os" - "path/filepath" - "strings" -) - -type Config struct { - AuthserverIss string `json:"auth_server"` - PDSHost string `json:"pds_host"` - Handle string `json:"handle"` -} - -func LoadConfig() (*Config, error) { - configPath := GetConfigFile() - - // Default configuration - cfg := &Config{ - AuthserverIss: "https://bsky.social", - } - - data, err := os.ReadFile(configPath) - if err != nil { - return nil, err - } - - if err := json.Unmarshal(data, cfg); err != nil { - return nil, err - } - - return cfg, nil -} - -func SaveConfig(cfg *Config) (*Config, error) { - if cfg.Handle != "" { - cfg.Handle = strings.TrimPrefix(cfg.Handle, "@") - } - - configPath := GetConfigFile() - data, err := json.MarshalIndent(cfg, "", " ") - if err != nil { - return cfg, err - } - - return cfg, os.WriteFile(configPath, data, 0600) -} - -func GetConfigFile() string { - config, _ := os.UserConfigDir() - return filepath.Join(config, ".blup.json") -} diff --git a/lexicons/blue.imgs.blup.image.json b/lexicons/blue.imgs.blup.image.json index 3e98da6..6d289c5 100644 --- a/lexicons/blue.imgs.blup.image.json +++ b/lexicons/blue.imgs.blup.image.json @@ -33,19 +33,16 @@ }, "contentType": { "type": "string", - "description": "MIME type of the image" + "description": "MIME type of the image", + "maxLength": 128 }, "size": { "type": "integer", "description": "Size of the image in bytes" }, "metadata": { - "type": "object", - "description": "Additional metadata", - "properties": { - "type": "string", - "maxLength": 1000 - } + "type": "unknown", + "description": "Additional metadata" } } } diff --git a/tnyshoot b/tnyshoot deleted file mode 100755 index dc1c0b1..0000000 --- a/tnyshoot +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -function uploadImage { - ./blup upload $1 -} - -sleep 0.2 -img="/tmp/shot.png" -gnome-screenshot -af $img -clip=$(uploadImage $img) -#echo $clip | xclip -selection c -rm $img -# clip=https://imgs.blue/evan.jarrett.net/1TpTO2IcOkZLCV0ND6yxtza3jyrK39A1iOgOkzGvTorMD03w -# response=$(notify-send -a "TnyClick" "Image Uploaded" "$clip" -A default="View" -t 2000) -# if [[ $response == "default" ]]; then -# xdg-open "$clip" -# fi -- 2.51.2