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
68 lines
2.3 KiB
Go
68 lines
2.3 KiB
Go
package webapp
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Authentication is opt-in (`auth` in the server config) and sits behind the
|
|
// AuthProvider interface. The open-source server ships exactly one
|
|
// implementation, BuiltinAuth (email + password accounts in a file-backed
|
|
// registry, server-owned /auth/* pages). A managed deployment can swap in a
|
|
// different provider (e.g. PropelAuth-backed) without touching the CLI or
|
|
// the API: the CLI learns the login page from /api/config and the callback
|
|
// flow is provider-agnostic.
|
|
|
|
// User is an authenticated account as the rest of the server sees it.
|
|
type User struct {
|
|
ID string `json:"id"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// AuthProvider is the seam between the server and an identity system.
|
|
type AuthProvider interface {
|
|
// CLILoginPath is the page `bdrive login` opens in a browser. The CLI
|
|
// appends ?redirect=http://127.0.0.1:<port>/callback&state=<nonce>.
|
|
CLILoginPath() string
|
|
// Authenticate resolves the request's Bearer token or session cookie.
|
|
Authenticate(r *http.Request) (User, bool)
|
|
// Register mounts the provider's own pages and endpoints (/auth/*,
|
|
// /api/auth/*) on the server mux.
|
|
Register(mux *http.ServeMux)
|
|
}
|
|
|
|
// authGate wraps the API with authentication when a provider is configured.
|
|
// The static frontend and the provider's own surface stay reachable so a
|
|
// browser can get to the login page; everything else under /api/ needs a
|
|
// valid identity.
|
|
func (s *Server) authGate(next http.Handler) http.Handler {
|
|
if s.Auth == nil {
|
|
return next
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
p := r.URL.Path
|
|
open := strings.HasPrefix(p, "/auth/") ||
|
|
strings.HasPrefix(p, "/api/auth/") ||
|
|
p == "/api/config" ||
|
|
!strings.HasPrefix(p, "/api/") // static frontend; its API calls are gated
|
|
if !open {
|
|
if _, ok := s.Auth.Authenticate(r); !ok {
|
|
http.Error(w, "authentication required (bdrive login, or sign in at /auth/login)", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// requestUser returns the authenticated user, or a zero User when auth is
|
|
// disabled (everything then runs as an anonymous single user).
|
|
func (s *Server) requestUser(r *http.Request) User {
|
|
if s.Auth == nil {
|
|
return User{}
|
|
}
|
|
u, _ := s.Auth.Authenticate(r)
|
|
return u
|
|
}
|