Add two-factor login (TOTP) with recovery codes
Users can set up an authenticator app from their account page; login then asks for a code after the password, with one-time recovery codes as a fallback. Sensitive actions (tokens, user changes, 2FA resets) need a code from the last 5 minutes. totp.require (none/admins/all) forces setup, and admins can reset a user's 2FA from the UI, the jarl CLI or jarld on the server. The dashboard and jarl status flag users missing 2FA and a shared "admin" account still in use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b22ffee1bb
commit
8f9cdd0a17
35 files changed
+1965
-43
No files matched your search
@@ -266,6 +266,10 @@ func (s *Server) authed(h handler) http.HandlerFunc {
|
||||
// The CLI warns its user when it sees this.
|
||||
w.Header().Set(api.TokenExpiresHeader, p.Token.ExpiresAt.UTC().Format(time.RFC3339))
|
||||
}
|
||||
if s.setupOnly(p) && !setupOnlyAllowed(r) {
|
||||
s.writeErr(w, r, errSetupOnly, nil)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), keyPrincipal, p)
|
||||
if se != nil {
|
||||
ctx = context.WithValue(ctx, keySession, se)
|
||||
@@ -327,6 +331,12 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) error {
|
||||
s.eng.Audit(r.Context(), &auth.Principal{User: &model.User{Username: name}, IP: ip, Agent: r.UserAgent(), Method: "session"}, "auth.login_failed", "user", 0, name, "failure", reason)
|
||||
return &httpError{http.StatusUnauthorized, "login_failed", "wrong username or password"}
|
||||
}
|
||||
if u.HasTOTP() {
|
||||
// The password was right; the session comes with the code
|
||||
// (POST /auth/login/totp).
|
||||
s.loginChallenge(w, u)
|
||||
return nil
|
||||
}
|
||||
s.limiter.reset(userKey)
|
||||
id, csrf := auth.NewSessionID()
|
||||
now := time.Now()
|
||||
@@ -338,7 +348,7 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) error {
|
||||
http.SetCookie(w, &http.Cookie{Name: sessionCookie, Value: id, Path: "/", HttpOnly: true, Secure: s.cookieSecure(r), SameSite: http.SameSiteStrictMode, Expires: se.ExpiresAt})
|
||||
p := &auth.Principal{User: u, Method: "session", IP: ip, Agent: r.UserAgent()}
|
||||
s.eng.Audit(r.Context(), p, "auth.login", "user", u.ID, u.Username, "success", "")
|
||||
writeJSON(w, http.StatusOK, s.whoamiOf(p, se))
|
||||
writeJSON(w, http.StatusOK, s.whoamiOf(r.Context(), p, se))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user