Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User can manage his peers on his own #82

Merged
merged 3 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions assets/tpl/user_create_client.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{ .Static.WebsiteTitle }} - Admin</title>
<meta name="description" content="{{ .Static.WebsiteTitle }}">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
<link rel="stylesheet" href="/css/custom.css">
</head>

<body id="page-top" class="d-flex flex-column min-vh-100">
{{template "prt_nav.html" .}}
<div class="container mt-5">
{{template "prt_flashes.html" .}}

<!-- server mode -->
<h1>Create a new client</h1>

<form method="post" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<input type="hidden" name="uid" value="{{.Peer.UID}}">
<div class="form-row">
<div class="form-group required col-md-12">
<label for="server_PublicKey">Public Key</label>
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Peer.PublicKey}}" required>
</div>
</div>

<button type="submit" class="btn btn-primary">Save</button>
<a href="/user/profile" class="btn btn-secondary">Cancel</a>
</form>
</div>
{{template "prt_footer.html" .}}
<script src="/js/jquery.min.js"></script>
<script src="/js/jquery.easing.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/bootstrap-confirmation.min.js"></script>
<script src="/js/custom.js"></script>
</body>

</html>
54 changes: 54 additions & 0 deletions assets/tpl/user_edit_client.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{ .Static.WebsiteTitle }} - Admin</title>
<meta name="description" content="{{ .Static.WebsiteTitle }}">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
<link rel="stylesheet" href="/css/custom.css">
</head>

<body id="page-top" class="d-flex flex-column min-vh-100">
{{template "prt_nav.html" .}}
<div class="container mt-5">
{{template "prt_flashes.html" .}}

<!-- server mode -->
<h1>Edit client: <strong>{{.Peer.Identifier}}</strong></h1>

<form method="post" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<input type="hidden" name="uid" value="{{.Peer.UID}}">
<div class="form-row">
<div class="form-group required col-md-12">
<label for="server_PublicKey">Public Key</label>
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Peer.PublicKey}}" required disabled="disabled">
</div>
</div>

<div class="form-row">
<div class="form-group col-md-12">
<div class="custom-control custom-switch">
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="server_Disabled" {{if .Peer.DeactivatedAt}}disabled="disabled"{{end}} {{if .Peer.DeactivatedAt}}checked{{end}}>
<label class="custom-control-label" for="server_Disabled">
Disabled
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a href="/user/profile" class="btn btn-secondary">Cancel</a>
</form>
</div>
{{template "prt_footer.html" .}}
<script src="/js/jquery.min.js"></script>
<script src="/js/jquery.easing.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/bootstrap-confirmation.min.js"></script>
<script src="/js/custom.js"></script>
</body>

</html>
19 changes: 18 additions & 1 deletion assets/tpl/user_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@
<div class="container mt-5">
<h1>WireGuard VPN User-Portal</h1>

<h2 class="mt-4">Your VPN Profiles</h2>
<div class="mt-4 row">
<div class="col-sm-8 col-12">
<h2 class="mt-2">Your VPN Profiles</h2>
</div>
<div class="col-sm-4 col-12 text-right">
{{if eq $.UserManagePeers true}}
<a href="/user/peer/create" title="Add a peer" class="btn btn-primary"><i class="fa fa-fw fa-plus"></i><i class="fa fa-fw fa-user"></i></a>
{{end}}
</div>
</div>
<div class="mt-2 table-responsive">
<table class="table table-sm" id="userTable">
<thead>
Expand All @@ -26,6 +35,9 @@ <h2 class="mt-4">Your VPN Profiles</h2>
<th scope="col"><a href="?sort=mail">E-Mail <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "mail"}}"></i></a></th>
<th scope="col"><a href="?sort=ip">IP's <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "ip"}}"></i></a></th>
<th scope="col"><a href="?sort=handshake">Handshake <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "handshake"}}"></i></a></th>
{{if eq $.UserManagePeers true}}
<th scope="col"></th>
{{end}}
</tr>
</thead>
<tbody>
Expand All @@ -42,6 +54,11 @@ <h2 class="mt-4">Your VPN Profiles</h2>
<td>{{$p.Email}}</td>
<td>{{$p.IPsStr}}</td>
<td><span data-toggle="tooltip" data-placement="left" title="" data-original-title="{{$p.LastHandshakeTime}}">{{$p.LastHandshake}}</span></td>
{{if eq $.UserManagePeers true}}
<td>
<a href="/user/peer/edit?pkey={{$p.PublicKey}}" title="Edit peer"><i class="fas fa-cog"></i></a>
</td>
{{end}}
</tr>
<tr class="hiddenRow">
<td colspan="6" class="hiddenCell" style="white-space:nowrap">
Expand Down
1 change: 1 addition & 0 deletions internal/server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func NewConfig() *Config {
cfg.WG.DefaultDeviceName = "wg0"
cfg.WG.ConfigDirectoryPath = "/etc/wireguard"
cfg.WG.ManageIPAddresses = true
cfg.WG.UserManagePeers = false
cfg.Email.Host = "127.0.0.1"
cfg.Email.Port = 25
cfg.Email.Encryption = common.MailEncryptionNone
Expand Down
19 changes: 10 additions & 9 deletions internal/server/handlers_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,16 @@ func (s *Server) GetUserIndex(c *gin.Context) {
peers := s.peers.GetSortedPeersForEmail(currentSession.SortedBy["userpeers"], currentSession.SortDirection["userpeers"], currentSession.Email)

c.HTML(http.StatusOK, "user_index.html", gin.H{
"Route": c.Request.URL.Path,
"Alerts": GetFlashes(c),
"Session": currentSession,
"Static": s.getStaticData(),
"Peers": peers,
"TotalPeers": len(peers),
"Users": []users.User{*s.users.GetUser(currentSession.Email)},
"Device": s.peers.GetDevice(currentSession.DeviceName),
"DeviceNames": s.GetDeviceNames(),
"Route": c.Request.URL.Path,
"Alerts": GetFlashes(c),
"Session": currentSession,
"Static": s.getStaticData(),
"Peers": peers,
"TotalPeers": len(peers),
"Users": []users.User{*s.users.GetUser(currentSession.Email)},
"Device": s.peers.GetDevice(currentSession.DeviceName),
"DeviceNames": s.GetDeviceNames(),
"UserManagePeers": s.config.WG.UserManagePeers,
})
}

Expand Down
114 changes: 114 additions & 0 deletions internal/server/handlers_peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,117 @@ func (s *Server) sendPeerConfigMail(peer wireguard.Peer) error {

return nil
}

func (s *Server) GetUserCreatePeer(c *gin.Context) {
currentSession, err := s.setNewPeerFormInSession(c)
if err != nil {
s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
return
}
c.HTML(http.StatusOK, "user_create_client.html", gin.H{
"Route": c.Request.URL.Path,
"Alerts": GetFlashes(c),
"Session": currentSession,
"Static": s.getStaticData(),
"Peer": currentSession.FormData.(wireguard.Peer),
"EditableKeys": s.config.Core.EditableKeys,
"Device": s.peers.GetDevice(currentSession.DeviceName),
"DeviceNames": s.GetDeviceNames(),
"AdminEmail": s.config.Core.AdminUser,
"Csrf": csrf.GetToken(c),
})
}

func (s *Server) PostUserCreatePeer(c *gin.Context) {
currentSession := GetSessionData(c)
var formPeer wireguard.Peer
if currentSession.FormData != nil {
formPeer = currentSession.FormData.(wireguard.Peer)
}

formPeer.Email = currentSession.Email;
formPeer.Identifier = currentSession.Email;
formPeer.DeviceType = wireguard.DeviceTypeServer;
formPeer.PrivateKey = "";

if err := c.ShouldBind(&formPeer); err != nil {
_ = s.updateFormInSession(c, formPeer)
SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
c.Redirect(http.StatusSeeOther, "/user/peer/create?formerr=bind")
return
}

disabled := c.PostForm("isdisabled") != ""
now := time.Now()
if disabled {
formPeer.DeactivatedAt = &now
}

if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil {
_ = s.updateFormInSession(c, formPeer)
SetFlashMessage(c, "failed to add user: "+err.Error(), "danger")
c.Redirect(http.StatusSeeOther, "/user/peer/create?formerr=create")
return
}

SetFlashMessage(c, "client created successfully", "success")
c.Redirect(http.StatusSeeOther, "/user/profile")
}

func (s *Server) GetUserEditPeer(c *gin.Context) {
peer := s.peers.GetPeerByKey(c.Query("pkey"))


currentSession, err := s.setFormInSession(c, peer)
if err != nil {
s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
return
}

if peer.Email != currentSession.Email {
s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
return;
}

c.HTML(http.StatusOK, "user_edit_client.html", gin.H{
"Route": c.Request.URL.Path,
"Alerts": GetFlashes(c),
"Session": currentSession,
"Static": s.getStaticData(),
"Peer": currentSession.FormData.(wireguard.Peer),
"EditableKeys": s.config.Core.EditableKeys,
"Device": s.peers.GetDevice(currentSession.DeviceName),
"DeviceNames": s.GetDeviceNames(),
"AdminEmail": s.config.Core.AdminUser,
"Csrf": csrf.GetToken(c),
})
}

func (s *Server) PostUserEditPeer(c *gin.Context) {
currentPeer := s.peers.GetPeerByKey(c.Query("pkey"))
urlEncodedKey := url.QueryEscape(c.Query("pkey"))

currentSession := GetSessionData(c)

if currentPeer.Email != currentSession.Email {
s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
return;
}

disabled := c.PostForm("isdisabled") != ""
now := time.Now()
if disabled && currentPeer.DeactivatedAt == nil {
currentPeer.DeactivatedAt = &now
}

// Update in database
if err := s.UpdatePeer(currentPeer, now); err != nil {
_ = s.updateFormInSession(c, currentPeer)
SetFlashMessage(c, "failed to update user: "+err.Error(), "danger")
c.Redirect(http.StatusSeeOther, "/user/peer/edit?pkey="+urlEncodedKey+"&formerr=update")
return
}

SetFlashMessage(c, "changes applied successfully", "success")
c.Redirect(http.StatusSeeOther, "/user/peer/edit?pkey="+urlEncodedKey)
}
7 changes: 7 additions & 0 deletions internal/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ func SetupRoutes(s *Server) {
user.GET("/download", s.GetPeerConfig)
user.GET("/email", s.GetPeerConfigMail)
user.GET("/status", s.GetPeerStatus)

if s.config.WG.UserManagePeers {
user.GET("/peer/create", s.GetUserCreatePeer)
user.POST("/peer/create", s.PostUserCreatePeer)
user.GET("/peer/edit", s.GetUserEditPeer)
user.POST("/peer/edit", s.PostUserEditPeer)
}
}

func SetupApiRoutes(s *Server) {
Expand Down
1 change: 1 addition & 0 deletions internal/wireguard/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Config struct {
DefaultDeviceName string `yaml:"defaultDevice" envconfig:"WG_DEFAULT_DEVICE"` // this device is used for auto-created peers, use GetDefaultDeviceName() to access this field
ConfigDirectoryPath string `yaml:"configDirectory" envconfig:"WG_CONFIG_PATH"` // optional, if set, updates will be written to this path, filename: <devicename>.conf
ManageIPAddresses bool `yaml:"manageIPAddresses" envconfig:"MANAGE_IPS"` // handle ip-address setup of interface
UserManagePeers bool `yaml:"userManagePeers" envconfig:"USER_MANAGE_PEERS"` // user can manage own peers
}

func (c Config) GetDefaultDeviceName() string {
Expand Down
3 changes: 2 additions & 1 deletion internal/wireguard/tpl/peer.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
[Interface]

# Core settings
PrivateKey = {{ .Peer.PrivateKey }}

PrivateKey = {{or .Peer.PrivateKey "<please-insert-your-private-key>" }}
Address = {{ .Peer.IPsStr }}

# Misc. settings (optional)
Expand Down