package cmd import ( "fmt" "net/http" "net/url" "os" "strings" "github.com/spf13/cobra" "guardianproject.dev/ops/nix-cache-login/internal/config" "guardianproject.dev/ops/nix-cache-login/internal/netrc" ) var logoutCmd = &cobra.Command{ Use: "logout", Short: "Remove tokens and revoke session", Long: `Removes the access token from the netrc file, deletes the stored refresh token, and revokes the refresh token at Keycloak.`, RunE: runLogout, } func init() { rootCmd.AddCommand(logoutCmd) } func runLogout(cmd *cobra.Command, args []string) error { // Read and revoke refresh token if it exists rtPath := config.RefreshTokenPath() if rtData, err := os.ReadFile(rtPath); err == nil && len(rtData) > 0 { refreshToken := string(rtData) // Revoke at Keycloak revokeURL := strings.TrimSuffix(cfg.Issuer, "/") + "/protocol/openid-connect/revoke" data := url.Values{ "token": {refreshToken}, "token_type_hint": {"refresh_token"}, "client_id": {cfg.ClientID}, } resp, err := http.PostForm(revokeURL, data) if err != nil { fmt.Fprintf(os.Stderr, "Warning: could not revoke token at Keycloak: %v\n", err) } else { resp.Body.Close() if resp.StatusCode >= 400 { fmt.Fprintf(os.Stderr, "Warning: token revocation returned status %d\n", resp.StatusCode) } } // Delete refresh token file os.Remove(rtPath) } // Remove token from netrc if err := netrc.Remove(cfg.NetrcPath, cfg.CacheHost); err != nil { return fmt.Errorf("removing netrc entry: %w", err) } fmt.Fprintln(os.Stderr, "Logged out.") return nil }