feat(files): web file browser at files.<host> with webmail-password login

Adds a browser-based file manager so members can use their files without an
SSH key. Served on a loopback HTTP server (AGENTBBS_FILES_WEB_ADDR, default
127.0.0.1:8092) that Caddy fronts at files.<host>. Members sign in with their
webmail username + password, verified against the Mailu IMAP backend
(mailbox.VerifyLogin), and browse the same virtual /me + /public areas as SFTP
— no home directory is ever exposed. Upload/download/mkdir/delete with the
private-area quota enforced; reuses internal/files confinement (fs.go).
setup.sh renders the files.<DOMAIN> Caddy site + env knob. Unit tests cover
the auth gate and an upload/list/download/delete round trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-23 14:09:18 +00:00
parent 1dbb2c70e2
commit cddd9819cc
7 changed files with 835 additions and 1 deletions

View file

@ -58,6 +58,27 @@ func NewIMAPTransport(cfg IMAPConfig) (Transport, error) {
return &imapTransport{cfg: cfg, c: c}, nil
}
// VerifyLogin checks a username/password against the IMAP backend by logging in
// and immediately logging out. It returns nil only when the credentials are
// accepted. The web file browser uses this to authenticate members with their
// webmail (Mailu/Dovecot) password — the same credential Roundcube uses.
func VerifyLogin(addr, user, pass string, plaintext bool) error {
dial := imapclient.DialTLS
if plaintext {
dial = imapclient.DialInsecure
}
c, err := dial(addr, nil)
if err != nil {
return fmt.Errorf("imap dial %s: %w", addr, err)
}
defer func() { _ = c.Close() }()
if err := c.Login(user, pass).Wait(); err != nil {
return fmt.Errorf("imap login: %w", err)
}
_ = c.Logout().Wait()
return nil
}
func (t *imapTransport) Close() error {
t.mu.Lock()
defer t.mu.Unlock()