mirror of
https://github.com/lone-cloud/prism
synced 2026-06-03 08:43:10 -07:00
new eye icon to show/hide proton mail password during initial entry, fix proton re-linking, new release
This commit is contained in:
parent
d5509bb231
commit
4ad7255fc2
9 changed files with 100 additions and 6 deletions
20
Makefile
20
Makefile
|
|
@ -47,7 +47,25 @@ install-tools:
|
||||||
echo "signal-cli installed successfully to /usr/local/bin/signal-cli"
|
echo "signal-cli installed successfully to /usr/local/bin/signal-cli"
|
||||||
|
|
||||||
check-updates:
|
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:
|
release:
|
||||||
@if [ ! -f VERSION ]; then \
|
@if [ ! -f VERSION ]; then \
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.1.0
|
1.1.1
|
||||||
|
|
@ -503,6 +503,44 @@ details[open] > .integration-header::before {
|
||||||
font-size: 1rem;
|
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 */
|
/* Buttons */
|
||||||
.btn-primary,
|
.btn-primary,
|
||||||
.btn-secondary,
|
.btn-secondary,
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
else if (action === 'delete-telegram') deleteTelegram(btn);
|
else if (action === 'delete-telegram') deleteTelegram(btn);
|
||||||
else if (action === 'delete-proton') deleteProton(btn);
|
else if (action === 'delete-proton') deleteProton(btn);
|
||||||
else if (action === 'reload') reloadIntegrations();
|
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() {
|
function reloadIntegrations() {
|
||||||
const integrations = document.getElementById('integrations');
|
const integrations = document.getElementById('integrations');
|
||||||
if (integrations) {
|
if (integrations) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ func (m *Monitor) authenticateAndSetup(credStore *credentials.Store) error {
|
||||||
c := &protonmail.Client{
|
c := &protonmail.Client{
|
||||||
RootURL: protonAPIURL,
|
RootURL: protonAPIURL,
|
||||||
AppVersion: protonAppVersion,
|
AppVersion: protonAppVersion,
|
||||||
|
Debug: m.cfg != nil && m.cfg.VerboseLogging,
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth *protonmail.Auth
|
var auth *protonmail.Auth
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func (p *Integration) Start(ctx context.Context, logger *slog.Logger) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ type Monitor struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
consecutiveErrs int
|
consecutiveErrs int
|
||||||
lastConnected time.Time
|
lastConnected time.Time
|
||||||
|
cancelPoll context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(cfg *config.Config, logger *slog.Logger) *Monitor {
|
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 {
|
func (m *Monitor) Start(ctx context.Context, credStore *credentials.Store, publisher *delivery.Publisher) error {
|
||||||
|
m.Stop()
|
||||||
|
|
||||||
m.dispatcher = publisher
|
m.dispatcher = publisher
|
||||||
if err := m.authenticateAndSetup(credStore); err != nil {
|
if err := m.authenticateAndSetup(credStore); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -54,7 +65,9 @@ func (m *Monitor) Start(ctx context.Context, credStore *credentials.Store, publi
|
||||||
m.lastConnected = time.Now()
|
m.lastConnected = time.Now()
|
||||||
m.unseenMessageIDs = make(map[string]time.Time)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"prism/service/config"
|
||||||
"prism/service/credentials"
|
"prism/service/credentials"
|
||||||
"prism/service/util"
|
"prism/service/util"
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ type authHandler struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
integration *Integration
|
integration *Integration
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type protonAuthRequest struct {
|
type protonAuthRequest struct {
|
||||||
|
|
@ -46,6 +48,7 @@ func (h *authHandler) handleAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
c := &protonmail.Client{
|
c := &protonmail.Client{
|
||||||
RootURL: protonAPIURL,
|
RootURL: protonAPIURL,
|
||||||
AppVersion: protonAppVersion,
|
AppVersion: protonAppVersion,
|
||||||
|
Debug: h.cfg != nil && h.cfg.VerboseLogging,
|
||||||
}
|
}
|
||||||
authInfo, err := c.AuthInfo(req.Email)
|
authInfo, err := c.AuthInfo(req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -141,12 +144,16 @@ func (h *authHandler) handleDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.integration != nil {
|
||||||
|
h.integration.monitor.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
util.SetToast(w, "Proton Mail unlinked", "success")
|
util.SetToast(w, "Proton Mail unlinked", "success")
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "deleted"})
|
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 {
|
if handlers == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +166,7 @@ func RegisterRoutes(router *chi.Mux, handlers *Handlers, auth func(http.Handler)
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
integration: integration,
|
integration: integration,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
router.With(auth).Get("/fragment/proton", handlers.HandleFragment)
|
router.With(auth).Get("/fragment/proton", handlers.HandleFragment)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="proton-password">Password:</label>
|
<label for="proton-password">Password:</label>
|
||||||
<input type="password" id="proton-password" name="password" placeholder="Your Proton password" required>
|
<div class="password-wrapper">
|
||||||
|
<input type="password" id="proton-password" name="password" placeholder="Your Proton password" required>
|
||||||
|
<button type="button" class="password-toggle" data-action="toggle-password" aria-label="Show password">
|
||||||
|
<svg class="eye-icon eye-show" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
|
<svg class="eye-icon eye-hide" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-10-7-10-7a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 10 7 10 7a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="proton-totp">2FA Code (optional):</label>
|
<label for="proton-totp">2FA Code (optional):</label>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue