Files
wg-portal/internal/app/api/v1/handlers/web_authentication.go
T
h44zandGitHub 207d9ae1c3
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
Merge commit from fork
2026-07-23 23:01:13 +02:00

115 lines
3.2 KiB
Go

package handlers
import (
"context"
"net/http"
"github.com/h44z/wg-portal/internal/app/api/core/respond"
"github.com/h44z/wg-portal/internal/app/api/v0/model"
"github.com/h44z/wg-portal/internal/domain"
)
type Scope string
const (
ScopeAdmin Scope = "ADMIN" // Admin scope contains all other scopes
)
type UserAuthenticator interface {
IsUserValid(ctx context.Context, id domain.UserIdentifier) bool
}
type UserRepository interface {
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
}
type AuthenticationHandler struct {
authenticator UserAuthenticator
userRepo UserRepository
}
func NewAuthenticationHandler(authenticator UserAuthenticator, userRepo UserRepository) AuthenticationHandler {
return AuthenticationHandler{
authenticator: authenticator,
userRepo: userRepo,
}
}
// LoggedIn checks if a user is logged in. If scopes are given, they are validated as well.
func (h AuthenticationHandler) LoggedIn(scopes ...Scope) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username == "" || password == "" {
// Abort the request with the appropriate error code
respond.JSON(w, http.StatusUnauthorized,
model.Error{Code: http.StatusUnauthorized, Message: "missing credentials"})
return
}
// check if user exists in DB
ctx := domain.SetUserInfo(r.Context(), domain.SystemAdminContextUserInfo())
user, err := h.userRepo.GetUser(ctx, domain.UserIdentifier(username))
if err != nil {
// Abort the request with the appropriate error code
respond.JSON(w, http.StatusUnauthorized,
model.Error{Code: http.StatusUnauthorized, Message: "invalid credentials"})
return
}
// validate API token
if err := user.CheckApiToken(password); err != nil {
// Abort the request with the appropriate error code
respond.JSON(w, http.StatusUnauthorized,
model.Error{Code: http.StatusUnauthorized, Message: "invalid credentials"})
return
}
// ensure that user is still valid
if valid := h.authenticator.IsUserValid(r.Context(), domain.UserIdentifier(user.Identifier)); !valid {
respond.JSON(w, http.StatusForbidden,
model.Error{Code: http.StatusForbidden, Message: "account disabled or locked"})
return
}
if !UserHasScopes(user, scopes...) {
// Abort the request with the appropriate error code
respond.JSON(w, http.StatusForbidden,
model.Error{Code: http.StatusForbidden, Message: "not enough permissions"})
return
}
ctx = context.WithValue(r.Context(), domain.CtxUserInfo, &domain.ContextUserInfo{
Id: user.Identifier,
IsAdmin: user.IsAdmin,
})
r = r.WithContext(ctx)
// Continue down the chain to Handler etc
next.ServeHTTP(w, r)
})
}
}
func UserHasScopes(user *domain.User, scopes ...Scope) bool {
// No scopes give, so the check should succeed
if len(scopes) == 0 {
return true
}
// check if user has admin scope
if user.IsAdmin {
return true
}
// Check if admin scope is required
for _, scope := range scopes {
if scope == ScopeAdmin {
return false
}
}
return true
}