summaryrefslogtreecommitdiff
path: root/modules/auth-config.el
blob: 52032c2a605588ec1b4316a413673dbf3a8d2728 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
;; auth-config.el --- Configuration for Authentication Utilities -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; Configuration for Emacs authentication and GPG integration:

;; • auth-source
;;   – Forces use of your default authinfo file
;;   – Disable external GPG agent in favor of Emacs's own prompt
;;   – Enable auth-source debug messages

;; • Easy PG Assistant (epa)
;;   – Force using the 'gpg2' executable for encryption/decryption operations

;; • oauth2-auto cache fix (via advice)
;;   – oauth2-auto version 20250624.1919 has caching bug on line 206
;;   – Function oauth2-auto--plstore-read has `or nil` disabling cache
;;   – This caused GPG passphrase prompts every ~15 minutes during gcal-sync
;;   – Fix: Advice to enable hash-table cache without modifying package
;;   – Works across package updates
;;   – Fixed 2025-11-11

;;; Code:

(eval-when-compile (require 'user-constants)) ;; defines authinfo-file location

;; -------------------------------- Auth Sources -------------------------------
;; auth sources settings

(use-package auth-source
  :ensure nil                           ;; built in
  :demand t                             ;; load this package immediately
  :config
  ;; USE gpg-agent for passphrase caching (400-day cache from gpg-agent.conf)
  ;; (setenv "GPG_AGENT_INFO" nil)      ;; DISABLED: was preventing gpg-agent cache
  (setq auth-sources `(,authinfo-file))  ;; use authinfo.gpg (see user-constants.el)
  (setq auth-source-debug t)             ;; echo debug info to Messages
  (setq auth-source-cache-expiry 86400)) ;; cache decrypted credentials for 24 hours

;; ----------------------------- Easy PG Assistant -----------------------------
;; Key management, cryptographic operations on regions and files, dired
;; integration, and automatic encryption/decryption of *.gpg files.

(use-package epa
  :ensure nil ;; built-in
  :demand t
  :config
  (epa-file-enable)
  ;; (setq epa-pinentry-mode 'loopback)  ;; emacs request passwords in minibuffer
  (setq epg-gpg-program "gpg2")  ;; force use gpg2 (not gpg v.1)

  ;; Update gpg-agent with current DISPLAY environment
  ;; This ensures pinentry can open GUI windows when Emacs starts
  (call-process "gpg-connect-agent" nil nil nil "updatestartuptty" "/bye"))

;; ---------------------------------- Plstore ----------------------------------
;; Encrypted storage used by oauth2-auto for Google Calendar tokens.
;; CRITICAL: Enable passphrase caching to prevent password prompts every 10 min.

(use-package plstore
  :ensure nil ;; built-in
  :demand t
  :config
  ;; Cache passphrase indefinitely (relies on gpg-agent for actual caching)
  (setq plstore-cache-passphrase-for-symmetric-encryption t)
  ;; Allow gpg-agent to cache the passphrase (400 days per gpg-agent.conf)
  (setq plstore-encrypt-to nil)) ;; Use symmetric encryption, not key-based

;; ----------------------------- oauth2-auto Cache Fix -----------------------------
;; Fix oauth2-auto caching bug that causes repeated GPG passphrase prompts.
;; The package has `or nil` on line 206 that disables its internal cache.
;; This advice overrides the buggy function to enable caching properly.

(defun cj/oauth2-auto--plstore-read-fixed (username provider)
  "Fixed version of oauth2-auto--plstore-read that enables caching.

This is a workaround for oauth2-auto.el bug where line 206 has:
  (or nil ;(gethash id oauth2-auto--plstore-cache)
which completely disables the internal hash-table cache.

This function re-implements the intended behavior with cache enabled."
  (require 'oauth2-auto)  ; Ensure package is loaded
  (let ((id (oauth2-auto--compute-id username provider)))
    ;; Check cache FIRST (this is what the original should do)
    (or (gethash id oauth2-auto--plstore-cache)
        ;; Cache miss - read from plstore and cache the result
        (let ((plstore (plstore-open oauth2-auto-plstore)))
          (unwind-protect
              (puthash id
                       (cdr (plstore-get plstore id))
                       oauth2-auto--plstore-cache)
            (plstore-close plstore))))))

;; Apply the fix via advice (survives package updates)
(with-eval-after-load 'oauth2-auto
  (advice-add 'oauth2-auto--plstore-read :override #'cj/oauth2-auto--plstore-read-fixed)
  (message "✓ oauth2-auto cache fix applied via advice"))

;; ------------------------ Authentication Reset Utility -----------------------

(defun cj/reset-auth-cache (&optional include-gpg-agent)
  "Reset authentication caches when wrong password was entered.

By default, only clears Emacs-side caches (auth-source, EPA file
handler) and leaves gpg-agent's long-term cache intact.  This preserves
your 400-day cache for GPG and SSH passphrases.

With prefix argument INCLUDE-GPG-AGENT (\\[universal-argument]), also
clears gpg-agent's password cache.  Use this when gpg-agent itself has
cached an incorrect password.

Clears:
1. auth-source cache (Emacs-level credential cache)
2. EPA file handler cache (encrypted file cache)
3. gpg-agent cache (only if INCLUDE-GPG-AGENT is non-nil)

Use this when you see errors like:
  - \"Bad session key\"
  - \"Decryption failed\"
  - GPG repeatedly using wrong cached password"
  (interactive "P")
  (message "Resetting authentication caches...")

  ;; Clear auth-source cache (Emacs credential cache)
  (auth-source-forget-all-cached)

  ;; Clear EPA file handler cache
  (when (fboundp 'epa-file-clear-cache)
    (epa-file-clear-cache))

  ;; Only clear gpg-agent cache if explicitly requested
  (if include-gpg-agent
      (let ((result (shell-command "echo RELOADAGENT | gpg-connect-agent")))
        (if (zerop result)
            (message "✓ Emacs and gpg-agent caches cleared. Next access will prompt for password.")
          (message "⚠ Warning: Failed to clear gpg-agent cache")))
    (message "✓ Emacs caches cleared. GPG/SSH passphrases preserved for session.")))

(defun cj/kill-gpg-agent ()
  "Force kill gpg-agent (it will restart automatically on next use).

This is a more aggressive reset than `cj/reset-auth-cache'.  Use this
when gpg-agent is stuck or behaving incorrectly.

The gpg-agent will automatically restart on the next GPG operation."
  (interactive)
  (let ((result (shell-command "gpgconf --kill gpg-agent")))
    (if (zerop result)
        (message "✓ gpg-agent killed. It will restart automatically on next use.")
      (message "⚠ Warning: Failed to kill gpg-agent"))))

(defun cj/clear-oauth2-auto-cache ()
  "Clear the oauth2-auto in-memory token cache.

This forces oauth2-auto to re-read tokens from oauth2-auto.plist on next
access.  Useful when OAuth tokens have been manually updated or after
re-authentication.

Note: This only clears Emacs's in-memory cache.  The oauth2-auto.plist
file on disk is not modified."
  (interactive)
  (if (boundp 'oauth2-auto--plstore-cache)
      (progn
        (clrhash oauth2-auto--plstore-cache)
        (message "✓ oauth2-auto token cache cleared"))
    (message "⚠ oauth2-auto not loaded yet")))

;; Keybindings
(with-eval-after-load 'keybindings
  (keymap-set cj/custom-keymap "A" #'cj/reset-auth-cache))

(with-eval-after-load 'which-key
  (which-key-add-key-based-replacements
    "C-; A" "reset auth cache"))

(provide 'auth-config)
;;; auth-config.el ends here.