mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
Authentication (previous phase, now landed together with its follow-ups): - Email+password+name accounts behind an AuthProvider interface; the OSS server ships BuiltinAuth only (file-backed auth.json: bcrypt password hashes + SHA-256 token digests, plaintext never stored; server-owned /auth/* pages; managed deployments can swap in another provider). - bdrive login: loopback-callback browser flow (sign-up on the page, the terminal finishes itself) with a device-code fallback for headless machines; long-lived revocable device tokens in settings.json. - Password reset via plain SMTP (stdlib) with a log-link fallback when no SMTP is configured. Move-proof projects: - .bdrive is now a directory; config.json carries a stable mount id. The volume store (~/.bdrive/volumes/<mount-id>/) and registry are keyed by that id — never the folder path — so renames/moves are free. - The daemon re-reads the project config each tick and exits cleanly (propagating nothing) when its folder vanishes; the registry self-heals and the next bdrive command at the new location resumes with zero spurious changes. bdrive init is the front door (mnt/umnt removed; bdrive stop pauses): - Interactive on a TTY (create new / connect existing project from the server's list; whole folder / shared subfolder via the include list), full flag bypass (--name/--project/--shared/--yes), never prompts without a TTY. Runs the login flow first when there is no session. Default server: beardrive.ai (config.DefaultServer). Web history (revert-ready): - Hubs now always require auth; journal ops carry the signed-in account (user/user_name) alongside the git/OS fallback author. - File-backed device registry: per-device name, OS, account, and the public IP the server observed, joined into history at read time. - GET /api/p/<id>/history?path=|prefix= (newest first) and GET /api/p/<id>/blob?sha= stream any exact version — blobs are retained forever, so the next phase's revert is re-putting an old blob. - UI: History button (file versions or project feed), per-folder history shortcut, view/download of any past version. Tests: auth flows (callback, device-code, reset single-use, persistence, gating), history API + device registry, folder-move survival, registry self-heal, ops-carry-account; docs (README/SKILL/CLAUDE) updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
46 lines
1.1 KiB
Go
46 lines
1.1 KiB
Go
package webapp
|
|
|
|
import (
|
|
"fmt"
|
|
"net/smtp"
|
|
"strings"
|
|
)
|
|
|
|
// Mailer sends plain-text mail over SMTP — the lowest-common-denominator
|
|
// transport a self-hoster can point at anything (Gmail app password, SES,
|
|
// Mailgun, a local relay). No SDK, stdlib only. A nil Mailer reports itself
|
|
// as unconfigured so callers can fall back to logging the message.
|
|
type Mailer struct {
|
|
Host string // e.g. smtp.gmail.com
|
|
Port int // e.g. 587 (STARTTLS)
|
|
User string
|
|
Pass string
|
|
From string // e.g. drive@example.com
|
|
}
|
|
|
|
func (m *Mailer) Send(to, subject, body string) error {
|
|
if m == nil || m.Host == "" {
|
|
return fmt.Errorf("no smtp configured")
|
|
}
|
|
from := m.From
|
|
if from == "" {
|
|
from = m.User
|
|
}
|
|
msg := strings.Join([]string{
|
|
"From: " + from,
|
|
"To: " + to,
|
|
"Subject: " + subject,
|
|
"MIME-Version: 1.0",
|
|
"Content-Type: text/plain; charset=utf-8",
|
|
"",
|
|
body,
|
|
}, "\r\n")
|
|
addr := fmt.Sprintf("%s:%d", m.Host, m.Port)
|
|
var auth smtp.Auth
|
|
if m.User != "" {
|
|
auth = smtp.PlainAuth("", m.User, m.Pass, m.Host)
|
|
}
|
|
// smtp.SendMail negotiates STARTTLS when the server offers it.
|
|
return smtp.SendMail(addr, auth, from, []string{to}, []byte(msg))
|
|
}
|