mirror of
https://github.com/h44z/wg-portal
synced 2025-02-26 05:49:14 +00:00
user can manage own peers on default device (#82)
Co-authored-by: GitHubActionRunner <knm@knm.io>
This commit is contained in:
parent
b34d2e1174
commit
2f194884d3
44
assets/tpl/user_create_client.html
Normal file
44
assets/tpl/user_create_client.html
Normal file
@ -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
assets/tpl/user_edit_client.html
Normal file
54
assets/tpl/user_edit_client.html
Normal file
@ -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>
|
@ -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>
|
||||
@ -26,6 +35,9 @@
|
||||
<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>
|
||||
@ -42,6 +54,11 @@
|
||||
<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">
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user