mirror of
				https://github.com/h44z/wg-portal.git
				synced 2025-11-03 23:56:18 +00:00 
			
		
		
		
	API - CRUD for peers, interfaces and users (#340)
Public REST API implementation to handle peers, interfaces and users. It also includes some simple provisioning endpoints. The Swagger API documentation is available under /api/v1/doc.html
This commit is contained in:
		@@ -3,6 +3,7 @@ package domain
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
@@ -28,6 +29,7 @@ func (u *ContextUserInfo) UserId() string {
 | 
			
		||||
	return string(u.Id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultContextUserInfo returns a default context user info.
 | 
			
		||||
func DefaultContextUserInfo() *ContextUserInfo {
 | 
			
		||||
	return &ContextUserInfo{
 | 
			
		||||
		Id:      CtxUnknownUserId,
 | 
			
		||||
@@ -35,6 +37,7 @@ func DefaultContextUserInfo() *ContextUserInfo {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SystemAdminContextUserInfo returns a context user info for the system admin.
 | 
			
		||||
func SystemAdminContextUserInfo() *ContextUserInfo {
 | 
			
		||||
	return &ContextUserInfo{
 | 
			
		||||
		Id:      CtxSystemAdminId,
 | 
			
		||||
@@ -42,6 +45,7 @@ func SystemAdminContextUserInfo() *ContextUserInfo {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetUserInfoFromGin sets the user info from the gin context to the request context.
 | 
			
		||||
func SetUserInfoFromGin(c *gin.Context) context.Context {
 | 
			
		||||
	ginUserInfo, exists := c.Get(CtxUserInfo)
 | 
			
		||||
 | 
			
		||||
@@ -56,11 +60,13 @@ func SetUserInfoFromGin(c *gin.Context) context.Context {
 | 
			
		||||
	return ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetUserInfo sets the user info in the context.
 | 
			
		||||
func SetUserInfo(ctx context.Context, info *ContextUserInfo) context.Context {
 | 
			
		||||
	ctx = context.WithValue(ctx, CtxUserInfo, info)
 | 
			
		||||
	return ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserInfo returns the user info from the context.
 | 
			
		||||
func GetUserInfo(ctx context.Context) *ContextUserInfo {
 | 
			
		||||
	rawInfo := ctx.Value(CtxUserInfo)
 | 
			
		||||
	if rawInfo == nil {
 | 
			
		||||
@@ -74,6 +80,8 @@ func GetUserInfo(ctx context.Context) *ContextUserInfo {
 | 
			
		||||
	return DefaultContextUserInfo()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateUserAccessRights checks if the current user has access rights to the requested user.
 | 
			
		||||
// If the user is an admin, access is granted.
 | 
			
		||||
func ValidateUserAccessRights(ctx context.Context, requiredUser UserIdentifier) error {
 | 
			
		||||
	sessionUser := GetUserInfo(ctx)
 | 
			
		||||
 | 
			
		||||
@@ -86,9 +94,10 @@ func ValidateUserAccessRights(ctx context.Context, requiredUser UserIdentifier)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Warnf("insufficient permissions for %s (want %s), stack: %s", sessionUser.Id, requiredUser, GetStackTrace())
 | 
			
		||||
	return fmt.Errorf("insufficient permissions")
 | 
			
		||||
	return ErrNoPermission
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateAdminAccessRights checks if the current user has admin access rights.
 | 
			
		||||
func ValidateAdminAccessRights(ctx context.Context) error {
 | 
			
		||||
	sessionUser := GetUserInfo(ctx)
 | 
			
		||||
 | 
			
		||||
@@ -97,5 +106,5 @@ func ValidateAdminAccessRights(ctx context.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Warnf("insufficient admin permissions for %s, stack: %s", sessionUser.Id, GetStackTrace())
 | 
			
		||||
	return fmt.Errorf("insufficient permissions")
 | 
			
		||||
	return ErrNoPermission
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,9 @@ import (
 | 
			
		||||
 | 
			
		||||
var ErrNotFound = errors.New("record not found")
 | 
			
		||||
var ErrNotUnique = errors.New("record not unique")
 | 
			
		||||
var ErrNoPermission = errors.New("no permission")
 | 
			
		||||
var ErrDuplicateEntry = errors.New("duplicate entry")
 | 
			
		||||
var ErrInvalidData = errors.New("invalid data")
 | 
			
		||||
 | 
			
		||||
// GetStackTrace returns a stack trace of the current goroutine. The stack trace has at most 1024 bytes.
 | 
			
		||||
func GetStackTrace() string {
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,13 @@ func (p *Peer) ApplyInterfaceDefaults(in *Interface) {
 | 
			
		||||
	p.Interface.PostDown.TrySetValue(in.PeerDefPostDown)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Peer) GenerateDisplayName(prefix string) {
 | 
			
		||||
	if prefix != "" {
 | 
			
		||||
		prefix = fmt.Sprintf("%s ", strings.TrimSpace(prefix)) // add a space after the prefix
 | 
			
		||||
	}
 | 
			
		||||
	p.DisplayName = fmt.Sprintf("%sPeer %s", prefix, internal.TruncateString(string(p.Identifier), 8))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PeerInterfaceConfig struct {
 | 
			
		||||
	KeyPair // private/public Key of the peer
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package domain
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/subtle"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -42,6 +43,10 @@ type User struct {
 | 
			
		||||
	Locked         *time.Time    `gorm:"index;column:locked"` // if this field is set, the user is locked and can no longer login (WireGuard peers still can connect)
 | 
			
		||||
	LockedReason   string        // the reason why the user has been locked
 | 
			
		||||
 | 
			
		||||
	// API token for REST API access
 | 
			
		||||
	ApiToken        string `form:"api_token" binding:"omitempty"`
 | 
			
		||||
	ApiTokenCreated *time.Time
 | 
			
		||||
 | 
			
		||||
	LinkedPeerCount int `gorm:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +61,14 @@ func (u *User) IsLocked() bool {
 | 
			
		||||
	return u.Locked != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) IsApiEnabled() bool {
 | 
			
		||||
	if u.ApiToken != "" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) CanChangePassword() error {
 | 
			
		||||
	if u.Source == UserSourceDatabase {
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -115,6 +128,18 @@ func (u *User) CheckPassword(password string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) CheckApiToken(token string) error {
 | 
			
		||||
	if !u.IsApiEnabled() {
 | 
			
		||||
		return errors.New("api access disabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res := subtle.ConstantTimeCompare([]byte(u.ApiToken), []byte(token)); res != 1 {
 | 
			
		||||
		return errors.New("wrong token")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) HashPassword() error {
 | 
			
		||||
	if u.Password == "" {
 | 
			
		||||
		return nil // nothing to hash
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user