2020-04-18 11:17:49 +02:00
package handler
import (
2020-04-19 05:46:43 +02:00
"encoding/json"
2020-04-20 19:26:49 +02:00
"fmt"
2020-05-21 10:51:24 +02:00
rice "github.com/GeertJohan/go.rice"
2020-04-18 11:17:49 +02:00
"net/http"
2020-04-25 11:58:14 +02:00
"strings"
2020-04-18 11:17:49 +02:00
"time"
2020-04-24 13:14:54 +02:00
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
2020-04-18 11:17:49 +02:00
"github.com/labstack/echo/v4"
2020-04-19 05:46:43 +02:00
"github.com/labstack/gommon/log"
2020-04-18 11:17:49 +02:00
"github.com/ngoduykhanh/wireguard-ui/model"
2020-04-18 16:42:53 +02:00
"github.com/ngoduykhanh/wireguard-ui/util"
2020-04-18 11:17:49 +02:00
"github.com/rs/xid"
2020-04-19 05:46:43 +02:00
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
2020-04-18 11:17:49 +02:00
)
2020-04-24 06:22:50 +02:00
// LoginPage handler
func LoginPage ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
return c . Render ( http . StatusOK , "login.html" , map [ string ] interface { } { } )
}
}
2020-04-24 13:14:54 +02:00
// Login for signing in handler
func Login ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
user := new ( model . User )
c . Bind ( user )
dbuser , err := util . GetUser ( )
if err != nil {
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot query user from DB" } )
}
if user . Username == dbuser . Username && user . Password == dbuser . Password {
// TODO: refresh the token
sess , _ := session . Get ( "session" , c )
sess . Options = & sessions . Options {
Path : "/" ,
MaxAge : 86400 ,
HttpOnly : true ,
}
// set session_token
tokenUID := xid . New ( ) . String ( )
sess . Values [ "username" ] = user . Username
sess . Values [ "session_token" ] = tokenUID
sess . Save ( c . Request ( ) , c . Response ( ) )
// set session_token in cookie
cookie := new ( http . Cookie )
cookie . Name = "session_token"
cookie . Value = tokenUID
cookie . Expires = time . Now ( ) . Add ( 24 * time . Hour )
c . SetCookie ( cookie )
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Logged in successfully" } )
}
return c . JSON ( http . StatusUnauthorized , jsonHTTPResponse { false , "Invalid credentials" } )
}
}
// Logout to log a user out
func Logout ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
clearSession ( c )
return c . Redirect ( http . StatusTemporaryRedirect , "/login" )
}
}
2020-04-19 10:50:59 +02:00
// WireGuardClients handler
func WireGuardClients ( ) echo . HandlerFunc {
2020-04-18 11:17:49 +02:00
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-23 13:01:40 +02:00
clientDataList , err := util . GetClients ( true )
2020-04-18 11:17:49 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , fmt . Sprintf ( "Cannot get client list: %v" , err ) } )
2020-04-18 11:17:49 +02:00
}
2020-04-19 10:50:59 +02:00
return c . Render ( http . StatusOK , "clients.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "" } ,
2020-04-24 13:14:54 +02:00
"username" : currentUser ( c ) ,
2020-04-18 16:42:53 +02:00
"clientDataList" : clientDataList ,
2020-04-18 11:17:49 +02:00
} )
}
}
// NewClient handler
func NewClient ( ) echo . HandlerFunc {
2020-04-19 05:46:43 +02:00
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-18 11:17:49 +02:00
client := new ( model . Client )
c . Bind ( client )
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-21 19:08:48 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
2020-04-21 19:08:48 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
}
// read server information
serverInterface := model . ServerInterface { }
if err := db . Read ( "server" , "interfaces" , & serverInterface ) ; err != nil {
log . Error ( "Cannot fetch server interface config from database: " , err )
}
2020-04-20 19:26:49 +02:00
// validate the input Allocation IPs
2020-04-21 19:08:48 +02:00
allocatedIPs , err := util . GetAllocatedIPs ( )
check , err := util . ValidateIPAllocation ( serverInterface . Addresses , allocatedIPs , client . AllocatedIPs )
if ! check {
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , fmt . Sprintf ( "%s" , err ) } )
2020-04-20 19:26:49 +02:00
}
2020-04-19 05:46:43 +02:00
// validate the input AllowedIPs
if util . ValidateAllowedIPs ( client . AllowedIPs ) == false {
2020-04-19 10:50:59 +02:00
log . Warnf ( "Invalid Allowed IPs input from user: %v" , client . AllowedIPs )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Allowed IPs must be in CIDR format" } )
}
2020-04-18 11:17:49 +02:00
// gen ID
guid := xid . New ( )
client . ID = guid . String ( )
2020-04-19 19:15:25 +02:00
// gen Wireguard key pair
2020-04-18 11:17:49 +02:00
key , err := wgtypes . GeneratePrivateKey ( )
if err != nil {
2020-04-19 19:15:25 +02:00
log . Error ( "Cannot generate wireguard key pair: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot generate Wireguard key pair" } )
2020-04-18 11:17:49 +02:00
}
client . PrivateKey = key . String ( )
client . PublicKey = key . PublicKey ( ) . String ( )
client . CreatedAt = time . Now ( ) . UTC ( )
client . UpdatedAt = client . CreatedAt
2020-04-19 05:46:43 +02:00
// write client to the database
2020-04-18 11:17:49 +02:00
db . Write ( "clients" , client . ID , client )
log . Infof ( "Created wireguard client: %v" , client )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusOK , client )
2020-04-18 11:17:49 +02:00
}
}
2020-04-22 12:11:28 +02:00
// SetClientStatus handler to enable / disable a client
func SetClientStatus ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-22 12:11:28 +02:00
data := make ( map [ string ] interface { } )
err := json . NewDecoder ( c . Request ( ) . Body ) . Decode ( & data )
if err != nil {
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Bad post data" } )
}
clientID := data [ "id" ] . ( string )
status := data [ "status" ] . ( bool )
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-22 12:11:28 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
2020-04-22 12:11:28 +02:00
}
client := model . Client { }
if err := db . Read ( "clients" , clientID , & client ) ; err != nil {
log . Error ( "Cannot fetch server interface config from database: " , err )
}
client . Enabled = status
db . Write ( "clients" , clientID , & client )
2020-04-22 16:39:35 +02:00
log . Infof ( "Changed client %s enabled status to %v" , client . ID , status )
2020-04-22 12:11:28 +02:00
2020-04-25 11:58:14 +02:00
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Changed client status successfully" } )
}
}
// DownloadClient handler
func DownloadClient ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
clientID := c . QueryParam ( "clientid" )
if clientID == "" {
return c . JSON ( http . StatusNotFound , jsonHTTPResponse { false , "Missing clientid parameter" } )
}
client , err := util . GetClientByID ( clientID )
if err != nil {
log . Errorf ( "Cannot generate client id %s config file for downloading: %v" , clientID , err )
return c . JSON ( http . StatusNotFound , jsonHTTPResponse { false , "Client not found" } )
}
// build config
server , _ := util . GetServer ( )
globalSettings , _ := util . GetGlobalSettings ( )
config := util . BuildClientConfig ( client , server , globalSettings )
// create io reader from string
reader := strings . NewReader ( config )
// set response header for downloading
c . Response ( ) . Header ( ) . Set ( echo . HeaderContentDisposition , "attachment; filename=wg0.conf" )
return c . Stream ( http . StatusOK , "text/plain" , reader )
2020-04-22 12:11:28 +02:00
}
}
2020-04-18 11:17:49 +02:00
// RemoveClient handler
func RemoveClient ( ) echo . HandlerFunc {
2020-04-19 05:46:43 +02:00
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-18 11:17:49 +02:00
client := new ( model . Client )
c . Bind ( client )
2020-04-19 05:46:43 +02:00
// delete client from database
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-18 11:17:49 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
2020-04-18 11:17:49 +02:00
}
2020-04-23 13:01:40 +02:00
2020-04-18 11:17:49 +02:00
if err := db . Delete ( "clients" , client . ID ) ; err != nil {
log . Error ( "Cannot delete wireguard client: " , err )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot delete client from database" } )
2020-04-18 11:17:49 +02:00
}
log . Infof ( "Removed wireguard client: %v" , client )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Client removed" } )
2020-04-18 11:17:49 +02:00
}
2020-04-19 05:46:43 +02:00
}
2020-04-19 10:50:59 +02:00
// WireGuardServer handler
func WireGuardServer ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-23 13:01:40 +02:00
server , err := util . GetServer ( )
2020-04-19 10:50:59 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot get server config: " , err )
2020-04-19 19:15:25 +02:00
}
2020-04-19 10:50:59 +02:00
return c . Render ( http . StatusOK , "server.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "wg-server" } ,
2020-04-24 13:14:54 +02:00
"username" : currentUser ( c ) ,
2020-04-23 13:01:40 +02:00
"serverInterface" : server . Interface ,
"serverKeyPair" : server . KeyPair ,
2020-04-19 10:50:59 +02:00
} )
}
}
// WireGuardServerInterfaces handler
func WireGuardServerInterfaces ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-19 10:50:59 +02:00
serverInterface := new ( model . ServerInterface )
c . Bind ( serverInterface )
// validate the input addresses
if util . ValidateServerAddresses ( serverInterface . Addresses ) == false {
log . Warnf ( "Invalid server interface addresses input from user: %v" , serverInterface . Addresses )
2020-04-19 19:19:00 +02:00
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Interface IP address must be in CIDR format" } )
2020-04-19 10:50:59 +02:00
}
serverInterface . UpdatedAt = time . Now ( ) . UTC ( )
// write config to the database
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-19 10:50:59 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
2020-04-19 10:50:59 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
}
2020-04-23 13:01:40 +02:00
2020-04-19 10:50:59 +02:00
db . Write ( "server" , "interfaces" , serverInterface )
log . Infof ( "Updated wireguard server interfaces settings: %v" , serverInterface )
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Updated interface addresses successfully" } )
}
}
2020-04-19 19:15:25 +02:00
// WireGuardServerKeyPair handler to generate private and public keys
func WireGuardServerKeyPair ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-19 19:15:25 +02:00
// gen Wireguard key pair
key , err := wgtypes . GeneratePrivateKey ( )
if err != nil {
log . Error ( "Cannot generate wireguard key pair: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot generate Wireguard key pair" } )
}
serverKeyPair := new ( model . ServerKeypair )
serverKeyPair . PrivateKey = key . String ( )
serverKeyPair . PublicKey = key . PublicKey ( ) . String ( )
serverKeyPair . UpdatedAt = time . Now ( ) . UTC ( )
// write config to the database
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-19 19:15:25 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
2020-04-19 19:15:25 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
}
2020-04-23 13:01:40 +02:00
2020-04-19 19:15:25 +02:00
db . Write ( "server" , "keypair" , serverKeyPair )
log . Infof ( "Updated wireguard server interfaces settings: %v" , serverKeyPair )
return c . JSON ( http . StatusOK , serverKeyPair )
}
}
2020-04-20 04:54:41 +02:00
// GlobalSettings handler
func GlobalSettings ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-23 13:01:40 +02:00
globalSettings , err := util . GetGlobalSettings ( )
2020-04-20 04:54:41 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot get global settings: " , err )
2020-04-20 04:54:41 +02:00
}
return c . Render ( http . StatusOK , "global_settings.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "global-settings" } ,
2020-04-24 13:14:54 +02:00
"username" : currentUser ( c ) ,
2020-04-20 04:54:41 +02:00
"globalSettings" : globalSettings ,
} )
}
}
// GlobalSettingSubmit handler to update the global settings
func GlobalSettingSubmit ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-20 04:54:41 +02:00
globalSettings := new ( model . GlobalSetting )
c . Bind ( globalSettings )
// validate the input dns server list
if util . ValidateIPAddressList ( globalSettings . DNSServers ) == false {
log . Warnf ( "Invalid DNS server list input from user: %v" , globalSettings . DNSServers )
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Invalid DNS server address" } )
}
globalSettings . UpdatedAt = time . Now ( ) . UTC ( )
// write config to the database
2020-04-23 13:01:40 +02:00
db , err := util . DBConn ( )
2020-04-20 04:54:41 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot initialize database: " , err )
2020-04-20 04:54:41 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
}
2020-04-23 13:01:40 +02:00
2020-04-20 04:54:41 +02:00
db . Write ( "server" , "global_settings" , globalSettings )
log . Infof ( "Updated global settings: %v" , globalSettings )
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Updated global settings successfully" } )
}
}
2020-04-20 11:50:50 +02:00
// MachineIPAddresses handler to get local interface ip addresses
func MachineIPAddresses ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-20 11:50:50 +02:00
// get private ip addresses
interfaceList , err := util . GetInterfaceIPs ( )
if err != nil {
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot get machine ip addresses" } )
}
// get public ip address
// TODO: Remove the go-external-ip dependency
publicInterface , err := util . GetPublicIP ( )
if err != nil {
log . Warn ( "Cannot get machine public ip address: " , err )
} else {
2020-04-23 18:40:44 +02:00
// prepend public ip to the list
interfaceList = append ( [ ] model . Interface { publicInterface } , interfaceList ... )
2020-04-20 11:50:50 +02:00
}
return c . JSON ( http . StatusOK , interfaceList )
}
}
2020-04-20 19:26:49 +02:00
// SuggestIPAllocation handler to get the list of ip address for client
func SuggestIPAllocation ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-23 13:01:40 +02:00
server , err := util . GetServer ( )
2020-04-20 19:26:49 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot fetch server config from database: " , err )
2020-04-20 19:26:49 +02:00
}
// return the list of suggestedIPs
// we take the first available ip address from
// each server's network addresses.
suggestedIPs := make ( [ ] string , 0 )
allocatedIPs , err := util . GetAllocatedIPs ( )
if err != nil {
log . Error ( "Cannot suggest ip allocation. Failed to get list of allocated ip addresses: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot suggest ip allocation: failed to get list of allocated ip addresses" } )
}
2020-04-23 13:01:40 +02:00
for _ , cidr := range server . Interface . Addresses {
2020-04-20 19:26:49 +02:00
ip , err := util . GetAvailableIP ( cidr , allocatedIPs )
if err != nil {
log . Error ( "Failed to get available ip from a CIDR: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , fmt . Sprintf ( "Cannot suggest ip allocation: failed to get available ip from network %s" , cidr ) } )
}
suggestedIPs = append ( suggestedIPs , fmt . Sprintf ( "%s/32" , ip ) )
}
return c . JSON ( http . StatusOK , suggestedIPs )
}
}
2020-04-23 04:29:44 +02:00
// ApplyServerConfig handler to write config file and restart Wireguard server
2020-05-21 10:51:24 +02:00
func ApplyServerConfig ( tmplBox * rice . Box ) echo . HandlerFunc {
2020-04-23 04:29:44 +02:00
return func ( c echo . Context ) error {
2020-04-24 13:14:54 +02:00
// access validation
validSession ( c )
2020-04-23 13:01:40 +02:00
server , err := util . GetServer ( )
2020-04-23 04:29:44 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot get server config: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot get server config" } )
2020-04-23 04:29:44 +02:00
}
2020-04-23 13:01:40 +02:00
clients , err := util . GetClients ( false )
2020-04-23 04:29:44 +02:00
if err != nil {
2020-04-23 13:01:40 +02:00
log . Error ( "Cannot get client config: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot get client config" } )
2020-04-23 04:29:44 +02:00
}
2020-04-23 13:01:40 +02:00
settings , err := util . GetGlobalSettings ( )
if err != nil {
log . Error ( "Cannot get global settings: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot get global settings" } )
2020-04-23 04:29:44 +02:00
}
// Write config file
2020-05-21 10:51:24 +02:00
err = util . WriteWireGuardServerConfig ( tmplBox , server , clients , settings )
2020-04-23 04:29:44 +02:00
if err != nil {
log . Error ( "Cannot apply server config: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , fmt . Sprintf ( "Cannot apply server config: %v" , err ) } )
}
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Applied server config successfully" } )
}
}