accounts and email
This commit is contained in:
@@ -57,6 +57,11 @@ type changePasswordRequest struct {
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type passwordResetRequest struct {
|
||||
Token string `json:"token"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type deleteAccountRequest struct {
|
||||
CurrentPassword string `json:"currentPassword"`
|
||||
}
|
||||
@@ -79,14 +84,15 @@ func (s *Server) handleLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||
s.renderError(w, r, http.StatusInternalServerError, "load registration settings")
|
||||
return
|
||||
}
|
||||
s.renderTemplate(w, http.StatusOK, "login.gohtml", layoutData{
|
||||
s.renderTemplate(w, r, http.StatusOK, "login.gohtml", layoutData{
|
||||
Title: "Sign in",
|
||||
BodyClass: "page-auth",
|
||||
BodyTemplate: "login_content",
|
||||
Data: struct {
|
||||
Next string
|
||||
SignupsEnabled bool
|
||||
}{Next: nextPath, SignupsEnabled: settings.SignupsEnabled},
|
||||
DevMode bool
|
||||
}{Next: nextPath, SignupsEnabled: settings.SignupsEnabled, DevMode: s.config.DevMode},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -100,13 +106,24 @@ func (s *Server) handleSetupPage(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
s.renderTemplate(w, http.StatusOK, "setup.gohtml", layoutData{
|
||||
s.renderTemplate(w, r, http.StatusOK, "setup.gohtml", layoutData{
|
||||
Title: "Initial setup",
|
||||
BodyClass: "page-auth",
|
||||
BodyTemplate: "setup_content",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handlePasswordResetPage(w http.ResponseWriter, r *http.Request) {
|
||||
s.renderTemplate(w, r, http.StatusOK, "password_reset.gohtml", layoutData{
|
||||
Title: "Reset password",
|
||||
BodyClass: "page-auth",
|
||||
BodyTemplate: "password_reset_content",
|
||||
Data: struct {
|
||||
Token string
|
||||
}{Token: strings.TrimSpace(r.URL.Query().Get("token"))},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.allowAuthAttempt(w, r) {
|
||||
return
|
||||
@@ -135,7 +152,7 @@ func (s *Server) handleAccountPage(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
s.renderTemplate(w, http.StatusOK, "account.gohtml", layoutData{
|
||||
s.renderTemplate(w, r, http.StatusOK, "account.gohtml", layoutData{
|
||||
Title: "Account",
|
||||
BodyClass: "page-account",
|
||||
BodyTemplate: "account_content",
|
||||
@@ -143,6 +160,20 @@ func (s *Server) handleAccountPage(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleTokenCreatePage(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := requirePrincipal(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
s.renderTemplate(w, r, http.StatusOK, "token_create.gohtml", layoutData{
|
||||
Title: "Create access token",
|
||||
BodyClass: "page-account",
|
||||
BodyTemplate: "token_create_content",
|
||||
Data: principal,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleAuthMe(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := requirePrincipal(r)
|
||||
if !ok {
|
||||
@@ -193,9 +224,26 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
_ = s.auth.RevokeSession(r.Context(), principal.SessionID)
|
||||
}
|
||||
clearSessionCookie(w)
|
||||
if s.config.DevMode {
|
||||
setDevLogoutCookie(w, r)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "logged_out"})
|
||||
}
|
||||
|
||||
func (s *Server) handleDevLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.config.DevMode || s.devUserID == "" {
|
||||
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "dev login is not available"})
|
||||
return
|
||||
}
|
||||
principal, err := s.auth.PrincipalForUser(r.Context(), s.devUserID)
|
||||
if err != nil {
|
||||
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
clearDevLogoutCookie(w)
|
||||
writeJSON(w, http.StatusOK, map[string]any{"principal": principal})
|
||||
}
|
||||
|
||||
func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
principal, _ := requirePrincipal(r)
|
||||
if !requireSessionPrincipal(w, principal) {
|
||||
@@ -213,6 +261,22 @@ func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "password_changed"})
|
||||
}
|
||||
|
||||
func (s *Server) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.allowAuthAttempt(w, r) {
|
||||
return
|
||||
}
|
||||
var req passwordResetRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid json"})
|
||||
return
|
||||
}
|
||||
if err := s.auth.ResetPassword(r.Context(), req.Token, req.NewPassword); err != nil {
|
||||
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "password_reset"})
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
principal, _ := requirePrincipal(r)
|
||||
if !requireSessionPrincipal(w, principal) {
|
||||
@@ -425,7 +489,7 @@ func (s *Server) handleDeviceVerify(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) handleDeviceVerifyPage(w http.ResponseWriter, r *http.Request) {
|
||||
userCode := strings.TrimSpace(r.URL.Query().Get("user_code"))
|
||||
s.renderTemplate(w, http.StatusOK, "device_verify.gohtml", layoutData{
|
||||
s.renderTemplate(w, r, http.StatusOK, "device_verify.gohtml", layoutData{
|
||||
Title: "Approve device",
|
||||
BodyClass: "page-auth",
|
||||
BodyTemplate: "device_verify_content",
|
||||
@@ -464,6 +528,36 @@ func (s *Server) handleAdminAuthUsers(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"users": public})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminUpdateAuthUsers(w http.ResponseWriter, r *http.Request) {
|
||||
principal, _ := requirePrincipal(r)
|
||||
var req struct {
|
||||
Users []auth.UserAccessUpdate `json:"users"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid json"})
|
||||
return
|
||||
}
|
||||
users, err := s.auth.UpdateUserAccess(r.Context(), principal, req.Users)
|
||||
if err != nil {
|
||||
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
public := make([]map[string]any, 0, len(users))
|
||||
for _, user := range users {
|
||||
public = append(public, publicUser(user))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"users": public})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminSendPasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
principal, _ := requirePrincipal(r)
|
||||
if err := s.auth.SendPasswordReset(r.Context(), principal, chi.URLParam(r, "id")); err != nil {
|
||||
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "password_reset_sent"})
|
||||
}
|
||||
|
||||
func (s *Server) allowAuthAttempt(w http.ResponseWriter, r *http.Request) bool {
|
||||
if s.authLimiter.Allow(authRateLimitKey(r)) {
|
||||
return true
|
||||
@@ -473,6 +567,7 @@ func (s *Server) allowAuthAttempt(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
|
||||
func setSessionCookie(w http.ResponseWriter, r *http.Request, token string, expires time.Time) {
|
||||
clearDevLogoutCookie(w)
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: auth.SessionCookieName,
|
||||
Value: token,
|
||||
@@ -484,6 +579,29 @@ func setSessionCookie(w http.ResponseWriter, r *http.Request, token string, expi
|
||||
})
|
||||
}
|
||||
|
||||
func setDevLogoutCookie(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: devLogoutCookieName,
|
||||
Value: "1",
|
||||
Path: "/",
|
||||
MaxAge: int((24 * time.Hour).Seconds()),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Secure: isSecureRequest(r),
|
||||
})
|
||||
}
|
||||
|
||||
func clearDevLogoutCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: devLogoutCookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
}
|
||||
|
||||
func clearSessionCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: auth.SessionCookieName,
|
||||
@@ -501,6 +619,7 @@ func publicUser(user auth.User) map[string]any {
|
||||
"email": user.Email,
|
||||
"displayName": user.DisplayName,
|
||||
"role": user.Role,
|
||||
"disabled": user.Disabled,
|
||||
"createdAt": user.CreatedAt,
|
||||
"lastSeenAt": user.LastSeenAt,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user