From 4ad7255fc2569736f84410658e52e51e6c166c78 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 28 Mar 2026 14:25:55 -0700 Subject: [PATCH] new eye icon to show/hide proton mail password during initial entry, fix proton re-linking, new release --- Makefile | 20 +++++++++- VERSION | 2 +- public/index.css | 38 +++++++++++++++++++ public/integration.js | 10 +++++ service/integration/proton/auth.go | 1 + service/integration/proton/integration.go | 2 +- service/integration/proton/monitor.go | 15 +++++++- service/integration/proton/routes.go | 10 ++++- .../proton/templates/proton-content.html | 8 +++- 9 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ca14603..6c51f06 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,25 @@ install-tools: echo "signal-cli installed successfully to /usr/local/bin/signal-cli" check-updates: - @go list -u -m -f '{{if not .Indirect}}{{.Path}} {{.Version}}{{if .Update}} [{{.Update.Version}}]{{end}}{{end}}' all | grep "\[" || echo "All dependencies are up to date" + @echo "=== Go module updates ===" + @go list -u -m -f '{{if not .Indirect}}{{.Path}} {{.Version}}{{if .Update}} -> {{.Update.Version}}{{end}}{{end}}' all | grep " -> " || echo "All Go dependencies are up to date" + @echo "" + @echo "=== Dockerfile base image updates ===" + @for image in $$(grep -E '^FROM ' Dockerfile | awk '{print $$2}' | grep -v 'AS'); do \ + echo "Checking $$image..."; \ + current_digest=$$(docker inspect --format='{{index .RepoDigests 0}}' $$image 2>/dev/null | cut -d@ -f2); \ + docker pull -q $$image > /dev/null 2>&1; \ + latest_digest=$$(docker inspect --format='{{index .RepoDigests 0}}' $$image 2>/dev/null | cut -d@ -f2); \ + if [ -z "$$current_digest" ]; then \ + echo " $$image: pulled (no prior local image to compare)"; \ + elif [ "$$current_digest" = "$$latest_digest" ]; then \ + echo " $$image: up to date"; \ + else \ + echo " $$image: UPDATE AVAILABLE"; \ + echo " local: $$current_digest"; \ + echo " latest: $$latest_digest"; \ + fi; \ + done release: @if [ ! -f VERSION ]; then \ diff --git a/VERSION b/VERSION index 1cc5f65..8cfbc90 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.1 \ No newline at end of file diff --git a/public/index.css b/public/index.css index adebf7f..8406a02 100644 --- a/public/index.css +++ b/public/index.css @@ -503,6 +503,44 @@ details[open] > .integration-header::before { font-size: 1rem; } +/* Password Toggle */ +.password-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.password-wrapper input { + width: 100%; + padding-right: 2.5rem; +} + +.password-toggle { + position: absolute; + right: 0.5rem; + background: none; + border: none; + cursor: pointer; + padding: 0; + color: var(--text-secondary); + display: flex; + align-items: center; + transition: color 0.2s; +} + +.password-toggle:hover { + color: var(--text-primary); +} + +.eye-icon { + width: 1.25rem; + height: 1.25rem; +} + +.eye-hide { + display: none; /* overridden by inline style after first toggle */ +} + /* Buttons */ .btn-primary, .btn-secondary, diff --git a/public/integration.js b/public/integration.js index 30a1af2..e502519 100644 --- a/public/integration.js +++ b/public/integration.js @@ -18,9 +18,19 @@ document.addEventListener('DOMContentLoaded', () => { else if (action === 'delete-telegram') deleteTelegram(btn); else if (action === 'delete-proton') deleteProton(btn); else if (action === 'reload') reloadIntegrations(); + else if (action === 'toggle-password') togglePassword(btn); }); }); +function togglePassword(btn) { + const input = btn.closest('.password-wrapper').querySelector('input'); + const isHidden = input.type === 'password'; + input.type = isHidden ? 'text' : 'password'; + btn.querySelector('.eye-show').style.display = isHidden ? 'none' : 'block'; + btn.querySelector('.eye-hide').style.display = isHidden ? 'block' : 'none'; + btn.setAttribute('aria-label', isHidden ? 'Hide password' : 'Show password'); +} + function reloadIntegrations() { const integrations = document.getElementById('integrations'); if (integrations) { diff --git a/service/integration/proton/auth.go b/service/integration/proton/auth.go index 0d8c3ce..4f23ac8 100644 --- a/service/integration/proton/auth.go +++ b/service/integration/proton/auth.go @@ -21,6 +21,7 @@ func (m *Monitor) authenticateAndSetup(credStore *credentials.Store) error { c := &protonmail.Client{ RootURL: protonAPIURL, AppVersion: protonAppVersion, + Debug: m.cfg != nil && m.cfg.VerboseLogging, } var auth *protonmail.Auth diff --git a/service/integration/proton/integration.go b/service/integration/proton/integration.go index fca7752..58345ba 100644 --- a/service/integration/proton/integration.go +++ b/service/integration/proton/integration.go @@ -43,7 +43,7 @@ func (p *Integration) RegisterRoutes(router *chi.Mux, auth func(http.Handler) ht } } - RegisterRoutes(router, p.Handlers, auth, p.db, p.apiKey, logger, p) + RegisterRoutes(router, p.Handlers, auth, p.db, p.apiKey, logger, p, p.cfg) } func (p *Integration) Start(ctx context.Context, logger *slog.Logger) { diff --git a/service/integration/proton/monitor.go b/service/integration/proton/monitor.go index 532a2b8..71e6738 100644 --- a/service/integration/proton/monitor.go +++ b/service/integration/proton/monitor.go @@ -31,6 +31,7 @@ type Monitor struct { startTime time.Time consecutiveErrs int lastConnected time.Time + cancelPoll context.CancelFunc } func NewMonitor(cfg *config.Config, logger *slog.Logger) *Monitor { @@ -40,7 +41,17 @@ func NewMonitor(cfg *config.Config, logger *slog.Logger) *Monitor { } } +func (m *Monitor) Stop() { + if m.cancelPoll != nil { + m.cancelPoll() + m.cancelPoll = nil + } + m.client = nil +} + func (m *Monitor) Start(ctx context.Context, credStore *credentials.Store, publisher *delivery.Publisher) error { + m.Stop() + m.dispatcher = publisher if err := m.authenticateAndSetup(credStore); err != nil { return err @@ -54,7 +65,9 @@ func (m *Monitor) Start(ctx context.Context, credStore *credentials.Store, publi m.lastConnected = time.Now() m.unseenMessageIDs = make(map[string]time.Time) - go m.pollEvents(ctx) + pollCtx, cancel := context.WithCancel(ctx) + m.cancelPoll = cancel + go m.pollEvents(pollCtx) return nil } diff --git a/service/integration/proton/routes.go b/service/integration/proton/routes.go index 47e09b2..875914e 100644 --- a/service/integration/proton/routes.go +++ b/service/integration/proton/routes.go @@ -8,6 +8,7 @@ import ( "log/slog" "net/http" + "prism/service/config" "prism/service/credentials" "prism/service/util" @@ -23,6 +24,7 @@ type authHandler struct { apiKey string logger *slog.Logger integration *Integration + cfg *config.Config } type protonAuthRequest struct { @@ -46,6 +48,7 @@ func (h *authHandler) handleAuth(w http.ResponseWriter, r *http.Request) { c := &protonmail.Client{ RootURL: protonAPIURL, AppVersion: protonAppVersion, + Debug: h.cfg != nil && h.cfg.VerboseLogging, } authInfo, err := c.AuthInfo(req.Email) if err != nil { @@ -141,12 +144,16 @@ func (h *authHandler) handleDelete(w http.ResponseWriter, r *http.Request) { return } + if h.integration != nil { + h.integration.monitor.Stop() + } + util.SetToast(w, "Proton Mail unlinked", "success") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "deleted"}) } -func RegisterRoutes(router *chi.Mux, handlers *Handlers, auth func(http.Handler) http.Handler, db *sql.DB, apiKey string, logger *slog.Logger, integration *Integration) { +func RegisterRoutes(router *chi.Mux, handlers *Handlers, auth func(http.Handler) http.Handler, db *sql.DB, apiKey string, logger *slog.Logger, integration *Integration, cfg *config.Config) { if handlers == nil { return } @@ -159,6 +166,7 @@ func RegisterRoutes(router *chi.Mux, handlers *Handlers, auth func(http.Handler) apiKey: apiKey, logger: logger, integration: integration, + cfg: cfg, } router.With(auth).Get("/fragment/proton", handlers.HandleFragment) diff --git a/service/integration/proton/templates/proton-content.html b/service/integration/proton/templates/proton-content.html index 7477071..686566a 100644 --- a/service/integration/proton/templates/proton-content.html +++ b/service/integration/proton/templates/proton-content.html @@ -9,7 +9,13 @@
- +
+ + +