updated HWSAuth doc
466
HWSAuth.md
466
HWSAuth.md
@@ -36,17 +36,38 @@ This eliminates type assertions and provides compile-time type checking.
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. User Model
|
### 1. User Model (Bun ORM)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type User struct {
|
type User struct {
|
||||||
UserID int
|
bun.BaseModel `bun:"table:users,alias:u"`
|
||||||
Username string
|
|
||||||
Email string
|
ID int `bun:"id,pk,autoincrement"`
|
||||||
|
Username string `bun:"username,unique"`
|
||||||
|
PasswordHash string `bun:"password_hash"`
|
||||||
|
Email string `bun:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) ID() int {
|
// Required by HWSAuth
|
||||||
return u.UserID
|
func (u *User) GetID() int {
|
||||||
|
return u.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// User lookup function for HWSAuth
|
||||||
|
func GetUserByID(ctx context.Context, tx bun.Tx, id int) (*User, error) {
|
||||||
|
user := new(User)
|
||||||
|
err := tx.NewSelect().
|
||||||
|
Model(user).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Limit(1).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "sql: no rows in result set" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,42 +84,62 @@ HWSAUTH_REFRESH_TOKEN_EXPIRY=1440
|
|||||||
HWSAUTH_LANDING_PAGE=/dashboard
|
HWSAUTH_LANDING_PAGE=/dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
Load config:
|
Load config using `ConfigFromEnv()`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// ConfigFromEnv reads all HWSAUTH_* environment variables
|
||||||
|
// and returns a ready-to-use Config struct
|
||||||
cfg, err := hwsauth.ConfigFromEnv()
|
cfg, err := hwsauth.ConfigFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `ConfigFromEnv()` function automatically loads all configuration from environment variables and validates required fields (like `HWSAUTH_SECRET_KEY`). This is the recommended way to configure HWSAuth.
|
||||||
|
|
||||||
### 3. Create Authenticator
|
### 3. Create Authenticator
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
func setupAuth(
|
||||||
|
config *Config,
|
||||||
|
logger *hlog.Logger,
|
||||||
|
db *bun.DB,
|
||||||
|
server *hws.Server,
|
||||||
|
) (*hwsauth.Authenticator[*User, bun.Tx], error) {
|
||||||
|
// Define the BeginTX function
|
||||||
beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
||||||
return db.BeginTx(ctx, nil)
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
return tx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUser := func(ctx context.Context, tx *sql.Tx, id int) (User, error) {
|
// Create the authenticator
|
||||||
var user User
|
auth, err := hwsauth.NewAuthenticator(
|
||||||
err := tx.QueryRowContext(ctx,
|
config.HWSAuth,
|
||||||
"SELECT id, username, email FROM users WHERE id = $1", id).
|
GetUserByID,
|
||||||
Scan(&user.UserID, &user.Username, &user.Email)
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
auth, err := hwsauth.NewAuthenticator[User, *sql.Tx](
|
|
||||||
cfg,
|
|
||||||
loadUser,
|
|
||||||
server,
|
server,
|
||||||
beginTx,
|
beginTx,
|
||||||
logger,
|
logger,
|
||||||
errorPage,
|
errorPageHandler,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure ignored paths
|
||||||
|
auth.IgnorePaths("/", "/static/*", "/healthz")
|
||||||
|
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Apply Middleware
|
### 4. Apply Middleware
|
||||||
|
|
||||||
```go
|
```go
|
||||||
server.AddMiddleware(auth.Authenticate())
|
// Add middleware to server
|
||||||
auth.IgnorePaths("/", "/login", "/register")
|
err := server.AddMiddleware(auth.Authenticate())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Features
|
## Core Features
|
||||||
@@ -112,7 +153,7 @@ server.AddMiddleware(auth.Authenticate())
|
|||||||
|
|
||||||
**IgnorePaths()** - Exclude paths from authentication:
|
**IgnorePaths()** - Exclude paths from authentication:
|
||||||
```go
|
```go
|
||||||
auth.IgnorePaths("/public", "/health")
|
auth.IgnorePaths("/public", "/healthz")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Route Guards
|
### Route Guards
|
||||||
@@ -294,76 +335,342 @@ Function to create database transactions.
|
|||||||
7. **Rate limiting**: Protect authentication endpoints
|
7. **Rate limiting**: Protect authentication endpoints
|
||||||
8. **HTTPS only**: Never send tokens over HTTP
|
8. **HTTPS only**: Never send tokens over HTTP
|
||||||
|
|
||||||
## Common Patterns
|
## Complete Examples
|
||||||
|
|
||||||
### Protected Dashboard
|
### Route Setup with LoginReq, LogoutReq, and FreshReq
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func setupRoutes(server *hws.Server, auth *hwsauth.Authenticator[User, *sql.Tx]) {
|
func setupRoutes(
|
||||||
// Public routes
|
server *hws.Server,
|
||||||
server.AddRoute("GET", "/", homeHandler)
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
server.AddRoute("GET", "/login", auth.LogoutReq(loginPageHandler))
|
db *bun.DB,
|
||||||
server.AddRoute("POST", "/login", loginSubmitHandler)
|
) error {
|
||||||
|
routes := []hws.Route{
|
||||||
|
// Public routes - no authentication required
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Method: hws.MethodGET,
|
||||||
|
Handler: homeHandler,
|
||||||
|
},
|
||||||
|
|
||||||
// Protected routes
|
// LogoutReq - requires user to NOT be logged in
|
||||||
server.AddRoute("GET", "/dashboard",
|
// Redirects authenticated users away
|
||||||
auth.LoginReq(dashboardHandler))
|
{
|
||||||
server.AddRoute("GET", "/profile",
|
Path: "/login",
|
||||||
auth.LoginReq(profileHandler))
|
Method: hws.MethodGET,
|
||||||
|
Handler: auth.LogoutReq(loginPageHandler),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/login",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LogoutReq(loginSubmitHandler(server, auth, db)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/register",
|
||||||
|
Method: hws.MethodGET,
|
||||||
|
Handler: auth.LogoutReq(registerPageHandler),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/register",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LogoutReq(registerSubmitHandler(server, auth, db)),
|
||||||
|
},
|
||||||
|
|
||||||
// Sensitive operations
|
// Logout - accessible to anyone
|
||||||
server.AddRoute("POST", "/change-password",
|
{
|
||||||
auth.LoginReq(auth.FreshReq(changePasswordHandler)))
|
Path: "/logout",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: logoutHandler(server, auth, db),
|
||||||
|
},
|
||||||
|
|
||||||
|
// LoginReq - requires user to be authenticated
|
||||||
|
{
|
||||||
|
Path: "/dashboard",
|
||||||
|
Method: hws.MethodGET,
|
||||||
|
Handler: auth.LoginReq(dashboardHandler),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/profile",
|
||||||
|
Method: hws.MethodGET,
|
||||||
|
Handler: auth.LoginReq(profileHandler),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reauthentication endpoint for sensitive operations
|
||||||
|
{
|
||||||
|
Path: "/reauthenticate",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LoginReq(reauthenticateHandler(server, auth, db)),
|
||||||
|
},
|
||||||
|
|
||||||
|
// FreshReq - requires fresh authentication (recent login)
|
||||||
|
// Used for sensitive operations like changing password/username
|
||||||
|
{
|
||||||
|
Path: "/change-password",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LoginReq(auth.FreshReq(changePasswordHandler(server, auth, db))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/change-username",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LoginReq(auth.FreshReq(changeUsernameHandler(server, auth, db))),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Regular authenticated routes (no fresh token required)
|
||||||
|
{
|
||||||
|
Path: "/change-bio",
|
||||||
|
Method: hws.MethodPOST,
|
||||||
|
Handler: auth.LoginReq(changeBioHandler(server, auth, db)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.AddRoutes(routes...)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Login Handler
|
### Login Handler with Transaction Management
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
func loginSubmitHandler(
|
||||||
|
server *hws.Server,
|
||||||
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
|
db *bun.DB,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Start database transaction
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusServiceUnavailable,
|
||||||
|
Message: "Login failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Parse form data
|
||||||
|
r.ParseForm()
|
||||||
username := r.FormValue("username")
|
username := r.FormValue("username")
|
||||||
password := r.FormValue("password")
|
password := r.FormValue("password")
|
||||||
|
|
||||||
user, err := validateCredentials(username, password)
|
// Validate credentials
|
||||||
if err != nil {
|
user, err := getUserByUsername(ctx, tx, username)
|
||||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
if err != nil || user == nil {
|
||||||
|
renderLoginForm(w, r, "Invalid username or password")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rememberMe := r.FormValue("remember_me") == "on"
|
err = user.CheckPassword(ctx, tx, password)
|
||||||
|
if err != nil {
|
||||||
|
renderLoginForm(w, r, "Invalid username or password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if "remember me" is enabled
|
||||||
|
rememberMe := r.FormValue("remember-me") == "on"
|
||||||
|
|
||||||
|
// Login user - sets authentication cookies
|
||||||
err = auth.Login(w, r, user, rememberMe)
|
err = auth.Login(w, r, user, rememberMe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Login failed", http.StatusInternalServerError)
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Login failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
// Redirect to dashboard or previous page
|
||||||
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registration with Auto-Login
|
||||||
|
|
||||||
|
```go
|
||||||
|
func registerSubmitHandler(
|
||||||
|
server *hws.Server,
|
||||||
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
|
db *bun.DB,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusServiceUnavailable,
|
||||||
|
Message: "Registration failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
username := r.FormValue("username")
|
||||||
|
password := r.FormValue("password")
|
||||||
|
confirmPassword := r.FormValue("confirm-password")
|
||||||
|
|
||||||
|
// Validate passwords match
|
||||||
|
if password != confirmPassword {
|
||||||
|
renderRegisterForm(w, r, "Passwords do not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username is unique
|
||||||
|
exists, err := usernameExists(ctx, tx, username)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Registration failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
renderRegisterForm(w, r, "Username is taken")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
user, err := createUser(ctx, tx, username, password)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Registration failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-login after registration
|
||||||
|
rememberMe := r.FormValue("remember-me") == "on"
|
||||||
|
err = auth.Login(w, r, user, rememberMe)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Login failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logout Handler
|
### Logout Handler
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
func logoutHandler(
|
||||||
tx, _ := db.BeginTx(r.Context(), nil)
|
server *hws.Server,
|
||||||
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
|
db *bun.DB,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Logout failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
if err := auth.Logout(tx, w, r); err != nil {
|
// Logout - clears cookies and revokes tokens in database
|
||||||
http.Error(w, "Logout failed", http.StatusInternalServerError)
|
err = auth.Logout(tx, w, r)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Logout failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Access Current User
|
### Reauthentication for Fresh Token Requirement
|
||||||
|
|
||||||
|
```go
|
||||||
|
func reauthenticateHandler(
|
||||||
|
server *hws.Server,
|
||||||
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
|
db *bun.DB,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Reauthentication failed",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Get current user from context
|
||||||
|
user := auth.CurrentModel(r.Context())
|
||||||
|
|
||||||
|
// Validate password
|
||||||
|
r.ParseForm()
|
||||||
|
password := r.FormValue("password")
|
||||||
|
err = user.CheckPassword(ctx, tx, password)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
renderPasswordPrompt(w, r, "Incorrect password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh tokens to make them "fresh"
|
||||||
|
err = auth.RefreshAuthTokens(tx, w, r)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Failed to refresh tokens",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protected Handler - Accessing Current User
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get the authenticated user from context
|
||||||
user := auth.CurrentModel(r.Context())
|
user := auth.CurrentModel(r.Context())
|
||||||
if user.ID() == 0 {
|
|
||||||
|
// This check is optional since LoginReq already ensures authentication
|
||||||
|
if user.GetID() == 0 {
|
||||||
http.Error(w, "Not authenticated", http.StatusUnauthorized)
|
http.Error(w, "Not authenticated", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -373,7 +680,59 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTemplate(w, "dashboard", data)
|
renderTemplate(w, "dashboard.html", data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sensitive Operation with Fresh Token
|
||||||
|
|
||||||
|
```go
|
||||||
|
func changePasswordHandler(
|
||||||
|
server *hws.Server,
|
||||||
|
auth *hwsauth.Authenticator[*User, bun.Tx],
|
||||||
|
db *bun.DB,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusServiceUnavailable,
|
||||||
|
Message: "Failed to change password",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Get current user - guaranteed to exist due to LoginReq + FreshReq
|
||||||
|
user := auth.CurrentModel(r.Context())
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
newPassword := r.FormValue("new-password")
|
||||||
|
confirmPassword := r.FormValue("confirm-password")
|
||||||
|
|
||||||
|
if newPassword != confirmPassword {
|
||||||
|
renderChangePasswordForm(w, r, "Passwords do not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update password
|
||||||
|
err = user.UpdatePassword(ctx, tx, newPassword)
|
||||||
|
if err != nil {
|
||||||
|
server.ThrowError(w, r, hws.HWSError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Failed to change password",
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
http.Redirect(w, r, "/profile", http.StatusSeeOther)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -413,3 +772,4 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
- [Source Code](https://git.haelnorr.com/h/golib/hwsauth)
|
- [Source Code](https://git.haelnorr.com/h/golib/hwsauth)
|
||||||
- [Issue Tracker](https://git.haelnorr.com/h/golib/hwsauth/issues)
|
- [Issue Tracker](https://git.haelnorr.com/h/golib/hwsauth/issues)
|
||||||
- [Examples](https://git.haelnorr.com/h/golib/hwsauth/tree/master/examples)
|
- [Examples](https://git.haelnorr.com/h/golib/hwsauth/tree/master/examples)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user