Telegram support, send config button
This commit is contained in:
		
							parent
							
								
									c691f0733d
								
							
						
					
					
						commit
						4c6080b3fa
					
				|  | @ -1,5 +1,15 @@ | ||||||
| function renderClientList(data) { | function renderClientList(data) { | ||||||
|     $.each(data, function(index, obj) { |     $.each(data, function(index, obj) { | ||||||
|  |         // render telegram button
 | ||||||
|  |         let telegramButton = '' | ||||||
|  |         if (obj.Client.telegram_userid) { | ||||||
|  |             telegramButton =    `<div class="btn-group">      
 | ||||||
|  |                                     <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||||
|  |                                         data-target="#modal_telegram_client" data-clientid="${obj.Client.id}" | ||||||
|  |                                         data-clientname="${obj.Client.name}">Telegram</button> | ||||||
|  |                                 </div>` | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // render client status css tag style
 |         // render client status css tag style
 | ||||||
|         let clientStatusHtml = '>' |         let clientStatusHtml = '>' | ||||||
|         if (obj.Client.enabled) { |         if (obj.Client.enabled) { | ||||||
|  | @ -43,7 +53,7 @@ function renderClientList(data) { | ||||||
|                                         data-target="#modal_email_client" data-clientid="${obj.Client.id}" |                                         data-target="#modal_email_client" data-clientid="${obj.Client.id}" | ||||||
|                                         data-clientname="${obj.Client.name}">Email</button> |                                         data-clientname="${obj.Client.name}">Email</button> | ||||||
|                                 </div> |                                 </div> | ||||||
| 
 |                                 ${telegramButton} | ||||||
|                                 <div class="btn-group"> |                                 <div class="btn-group"> | ||||||
|                                     <button type="button" class="btn btn-outline-danger btn-sm">More</button> |                                     <button type="button" class="btn btn-outline-danger btn-sm">More</button> | ||||||
|                                     <button type="button" class="btn btn-outline-danger btn-sm dropdown-toggle dropdown-icon"  |                                     <button type="button" class="btn btn-outline-danger btn-sm dropdown-toggle dropdown-icon"  | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -3,6 +3,7 @@ module github.com/ngoduykhanh/wireguard-ui | ||||||
| go 1.16 | go 1.16 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	github.com/NicoNex/echotron/v3 v3.27.0 | ||||||
| 	github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd | 	github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd | ||||||
| 	github.com/go-playground/universal-translator v0.17.0 // indirect | 	github.com/go-playground/universal-translator v0.17.0 // indirect | ||||||
| 	github.com/gorilla/sessions v1.2.0 | 	github.com/gorilla/sessions v1.2.0 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -1,4 +1,6 @@ | ||||||
| github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= | ||||||
|  | github.com/NicoNex/echotron/v3 v3.27.0 h1:iq4BLPO+Dz1JHjh2HPk0D0NldAZSYcAjaOicgYEhUzw= | ||||||
|  | github.com/NicoNex/echotron/v3 v3.27.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI= | ||||||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||||||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||||||
| github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= | github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= | ||||||
|  |  | ||||||
|  | @ -19,12 +19,14 @@ import ( | ||||||
| 	"github.com/labstack/echo/v4" | 	"github.com/labstack/echo/v4" | ||||||
| 	"github.com/labstack/gommon/log" | 	"github.com/labstack/gommon/log" | ||||||
| 	"github.com/rs/xid" | 	"github.com/rs/xid" | ||||||
|  | 	"github.com/skip2/go-qrcode" | ||||||
| 	"golang.zx2c4.com/wireguard/wgctrl" | 	"golang.zx2c4.com/wireguard/wgctrl" | ||||||
| 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||||||
| 
 | 
 | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/emailer" | 	"github.com/ngoduykhanh/wireguard-ui/emailer" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/model" | 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/util" | 	"github.com/ngoduykhanh/wireguard-ui/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -569,6 +571,56 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SendTelegramClient handler to send the configuration via Telegram
 | ||||||
|  | func SendTelegramClient(db store.IStore) echo.HandlerFunc { | ||||||
|  | 	type clientIdUseridPayload struct { | ||||||
|  | 		ID     string `json:"id"` | ||||||
|  | 		Userid string `json:"userid"` | ||||||
|  | 	} | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		var payload clientIdUseridPayload | ||||||
|  | 		c.Bind(&payload) | ||||||
|  | 
 | ||||||
|  | 		qrCodeSettings := model.QRCodeSettings{ | ||||||
|  | 			Enabled:    true, | ||||||
|  | 			IncludeDNS: true, | ||||||
|  | 			IncludeMTU: true, | ||||||
|  | 		} | ||||||
|  | 		clientData, err := db.GetClientByID(payload.ID, qrCodeSettings) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Errorf("Cannot generate client id %s config file for downloading: %v", payload.ID, err) | ||||||
|  | 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// build config
 | ||||||
|  | 		server, _ := db.GetServer() | ||||||
|  | 		globalSettings, _ := db.GetGlobalSettings() | ||||||
|  | 		config := util.BuildClientConfig(*clientData.Client, server, globalSettings) | ||||||
|  | 		configData := []byte(config) | ||||||
|  | 		var qrData []byte | ||||||
|  | 
 | ||||||
|  | 		if clientData.Client.PrivateKey != "" { | ||||||
|  | 			qrData, err = qrcode.Encode(config, qrcode.Medium, 512) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "qr gen: " + err.Error()}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		userid, err := strconv.ParseInt(clientData.Client.TgUserid, 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "userid: " + err.Error()}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = telegram.SendConfig(userid, clientData.Client.Name, configData, qrData) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Telegram message sent successfully"}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // UpdateClient handler to update client information
 | // UpdateClient handler to update client information
 | ||||||
| func UpdateClient(db store.IStore) echo.HandlerFunc { | func UpdateClient(db store.IStore) echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								main.go
								
								
								
								
							
							
						
						
									
										30
									
								
								main.go
								
								
								
								
							|  | @ -15,6 +15,7 @@ import ( | ||||||
| 	"github.com/labstack/echo/v4" | 	"github.com/labstack/echo/v4" | ||||||
| 	"github.com/labstack/gommon/log" | 	"github.com/labstack/gommon/log" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||||
| 
 | 
 | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/emailer" | 	"github.com/ngoduykhanh/wireguard-ui/emailer" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/handler" | 	"github.com/ngoduykhanh/wireguard-ui/handler" | ||||||
|  | @ -42,6 +43,9 @@ var ( | ||||||
| 	flagSendgridApiKey           string | 	flagSendgridApiKey           string | ||||||
| 	flagEmailFrom                string | 	flagEmailFrom                string | ||||||
| 	flagEmailFromName            string = "WireGuard UI" | 	flagEmailFromName            string = "WireGuard UI" | ||||||
|  | 	flagTelegramToken            string | ||||||
|  | 	flagTelegramAllowConfRequest bool   = false | ||||||
|  | 	flagTelegramFloodWait        int    = 60 | ||||||
| 	flagSessionSecret            string = util.RandomString(32) | 	flagSessionSecret            string = util.RandomString(32) | ||||||
| 	flagWgConfTemplate           string | 	flagWgConfTemplate           string | ||||||
| 	flagBasePath                 string | 	flagBasePath                 string | ||||||
|  | @ -80,6 +84,9 @@ func init() { | ||||||
| 	flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN or NONE.") | 	flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN or NONE.") | ||||||
| 	flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.") | 	flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.") | ||||||
| 	flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.") | 	flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.") | ||||||
|  | 	flag.StringVar(&flagTelegramToken, "telegram-token", util.LookupEnvOrString("TELEGRAM_TOKEN", flagTelegramToken), "Telegram bot token for distributing configs to clients.") | ||||||
|  | 	flag.BoolVar(&flagTelegramAllowConfRequest, "telegram-allow-conf-request", util.LookupEnvOrBool("TELEGRAM_ALLOW_CONF_REQUEST", flagTelegramAllowConfRequest), "Allow users to get configs from the bot by sending a message.") | ||||||
|  | 	flag.IntVar(&flagTelegramFloodWait, "telegram-flood-wait", util.LookupEnvOrInt("TELEGRAM_FLOOD_WAIT", flagTelegramFloodWait), "Time in minutes before the next conf request is processed.") | ||||||
| 	flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") | 	flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") | ||||||
| 	flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") | 	flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") | ||||||
| 	flag.StringVar(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.") | 	flag.StringVar(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.") | ||||||
|  | @ -131,8 +138,15 @@ func init() { | ||||||
| 	util.BasePath = util.ParseBasePath(flagBasePath) | 	util.BasePath = util.ParseBasePath(flagBasePath) | ||||||
| 	util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges) | 	util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges) | ||||||
| 
 | 
 | ||||||
|  | 	lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")) | ||||||
|  | 
 | ||||||
|  | 	telegram.TelegramToken = flagTelegramToken | ||||||
|  | 	telegram.TelegramAllowConfRequest = flagTelegramAllowConfRequest | ||||||
|  | 	telegram.TelegramFloodWait = flagTelegramFloodWait | ||||||
|  | 	telegram.LogLevel = lvl | ||||||
|  | 
 | ||||||
| 	// print only if log level is INFO or lower
 | 	// print only if log level is INFO or lower
 | ||||||
| 	if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO { | 	if lvl <= log.INFO { | ||||||
| 		// print app information
 | 		// print app information
 | ||||||
| 		fmt.Println("Wireguard UI") | 		fmt.Println("Wireguard UI") | ||||||
| 		fmt.Println("App Version\t:", appVersion) | 		fmt.Println("App Version\t:", appVersion) | ||||||
|  | @ -221,6 +235,7 @@ func main() { | ||||||
| 	app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson) | ||||||
|  | 	app.POST(util.BasePath+"/send-telegram-client", handler.SendTelegramClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) | 	app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) | ||||||
|  | @ -248,6 +263,8 @@ func main() { | ||||||
| 	// serves other static files
 | 	// serves other static files
 | ||||||
| 	app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) | 	app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) | ||||||
| 
 | 
 | ||||||
|  | 	initTelegram(db, util.BuildClientConfig) | ||||||
|  | 
 | ||||||
| 	if strings.HasPrefix(util.BindAddress, "unix://") { | 	if strings.HasPrefix(util.BindAddress, "unix://") { | ||||||
| 		// Listen on unix domain socket.
 | 		// Listen on unix domain socket.
 | ||||||
| 		// https://github.com/labstack/echo/issues/830
 | 		// https://github.com/labstack/echo/issues/830
 | ||||||
|  | @ -296,3 +313,14 @@ func initServerConfig(db store.IStore, tmplDir fs.FS) { | ||||||
| 		log.Fatalf("Cannot create server config: ", err) | 		log.Fatalf("Cannot create server config: ", err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func initTelegram(db store.IStore, buildClientConfig telegram.BuildClientConfig) { | ||||||
|  | 	go func() { | ||||||
|  | 		for { | ||||||
|  | 			err := telegram.Start(db, buildClientConfig) | ||||||
|  | 			if err == nil { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,116 @@ | ||||||
|  | package telegram | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/NicoNex/echotron/v3" | ||||||
|  | 	"github.com/labstack/gommon/log" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type BuildClientConfig func(client model.Client, server model.Server, setting model.GlobalSetting) string | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	TelegramToken            string | ||||||
|  | 	TelegramAllowConfRequest bool | ||||||
|  | 	TelegramFloodWait        int | ||||||
|  | 	LogLevel                 log.Lvl | ||||||
|  | 
 | ||||||
|  | 	TgBot      *echotron.API | ||||||
|  | 	TgBotMutex sync.RWMutex | ||||||
|  | 
 | ||||||
|  | 	floodWait = make(map[int64]int64, 0) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func Start(db store.IStore, buildClientConfig BuildClientConfig) (err error) { | ||||||
|  | 	defer func() { | ||||||
|  | 		TgBotMutex.Lock() | ||||||
|  | 		TgBot = nil | ||||||
|  | 		TgBotMutex.Unlock() | ||||||
|  | 		if r := recover(); r != nil { | ||||||
|  | 			err = fmt.Errorf("[PANIC] recovered from panic: %v", r) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	token := TelegramToken | ||||||
|  | 	if token == "" || len(token) < 30 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bot := echotron.NewAPI(token) | ||||||
|  | 
 | ||||||
|  | 	res, err := bot.GetMe() | ||||||
|  | 	if !res.Ok || err != nil { | ||||||
|  | 		log.Warnf("[Telegram] Unable to connect to bot.\n%v\n%v", res.Description, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	TgBotMutex.Lock() | ||||||
|  | 	TgBot = &bot | ||||||
|  | 	TgBotMutex.Unlock() | ||||||
|  | 
 | ||||||
|  | 	if LogLevel <= log.INFO { | ||||||
|  | 		fmt.Printf("[Telegram] Authorized as %s\n", res.Result.Username) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !TelegramAllowConfRequest { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ticker := time.NewTicker(time.Minute) | ||||||
|  | 	go func() { | ||||||
|  | 		for range ticker.C { | ||||||
|  | 			updateFloodWait() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	updatesChan := echotron.PollingUpdatesOptions(token, false, echotron.UpdateOptions{AllowedUpdates: []echotron.UpdateType{echotron.MessageUpdate}}) | ||||||
|  | 	for update := range updatesChan { | ||||||
|  | 		if update.Message != nil { | ||||||
|  | 			floodWait[update.Message.Chat.ID] = time.Now().Unix() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func SendConfig(userid int64, clientName string, confData, qrData []byte) error { | ||||||
|  | 	TgBotMutex.RLock() | ||||||
|  | 	defer TgBotMutex.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	if TgBot == nil { | ||||||
|  | 		return fmt.Errorf("telegram bot is not configured or not available") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, wait := floodWait[userid]; wait { | ||||||
|  | 		return fmt.Errorf("this client already got their config less than %d minutes ago", TelegramFloodWait) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	qrAttachment := echotron.NewInputFileBytes("qr.png", qrData) | ||||||
|  | 	_, err := TgBot.SendPhoto(qrAttachment, userid, &echotron.PhotoOptions{Caption: clientName}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(err) | ||||||
|  | 		return fmt.Errorf("unable to send qr picture") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	confAttachment := echotron.NewInputFileBytes(clientName+".conf", confData) | ||||||
|  | 	_, err = TgBot.SendDocument(confAttachment, userid, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(err) | ||||||
|  | 		return fmt.Errorf("unable to send conf file") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	floodWait[userid] = time.Now().Unix() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func updateFloodWait() { | ||||||
|  | 	thresholdTS := time.Now().Unix() - 60000*int64(TelegramFloodWait) | ||||||
|  | 	for userid, ts := range floodWait { | ||||||
|  | 		if ts < thresholdTS { | ||||||
|  | 			delete(floodWait, userid) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -80,6 +80,35 @@ Wireguard Clients | ||||||
| </div> | </div> | ||||||
| <!-- /.modal --> | <!-- /.modal --> | ||||||
| 
 | 
 | ||||||
|  | <div class="modal fade" id="modal_telegram_client"> | ||||||
|  |     <div class="modal-dialog"> | ||||||
|  |         <div class="modal-content"> | ||||||
|  |             <div class="modal-header"> | ||||||
|  |                 <h4 class="modal-title">Telegram Configuration</h4> | ||||||
|  |                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|  |                     <span aria-hidden="true">×</span> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |             <form name="frm_telegram_client" id="frm_telegram_client"> | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     <input type="hidden" id="tg_client_id" name="tg_client_id"> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <label for="tg_client_userid" class="control-label">Telegram userid</label> | ||||||
|  |                         <input type="text" class="form-control" id="tg_client_userid" name="tg_client_userid"> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-footer justify-content-between"> | ||||||
|  |                     <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||||||
|  |                     <button type="submit" class="btn btn-success">Send</button> | ||||||
|  |                 </div> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.modal-content --> | ||||||
|  |     </div> | ||||||
|  |     <!-- /.modal-dialog --> | ||||||
|  | </div> | ||||||
|  | <!-- /.modal --> | ||||||
|  | 
 | ||||||
| <div class="modal fade" id="modal_edit_client"> | <div class="modal fade" id="modal_edit_client"> | ||||||
|     <div class="modal-dialog"> |     <div class="modal-dialog"> | ||||||
|         <div class="modal-content"> |         <div class="modal-content"> | ||||||
|  | @ -683,6 +712,31 @@ Wireguard Clients | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         // submitTelegramClient function for sending a telegram message with the configuration to the client | ||||||
|  |         function submitTelegramClient() { | ||||||
|  |             const client_id = $("#tg_client_id").val(); | ||||||
|  |             const userid = $("#tg_client_userid").val(); | ||||||
|  |             const data = {"id": client_id, "userid": userid}; | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'POST', | ||||||
|  |                 url: '{{.basePath}}/send-telegram-client', | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 data: JSON.stringify(data), | ||||||
|  |                 success: function(resp) { | ||||||
|  |                     $("#modal_telegram_client").modal('hide'); | ||||||
|  |                     toastr.success('Sent config via telegram to client successfully'); | ||||||
|  |                     // Refresh the home page (clients page) after sending email successfully | ||||||
|  |                     location.reload(); | ||||||
|  |                 }, | ||||||
|  |                 error: function(jqXHR, exception) { | ||||||
|  |                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                     toastr.error(responseJson['message']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // submitEditClient function for updating an existing client |         // submitEditClient function for updating an existing client | ||||||
|         // This sends dialogue data to the back-end when user presses "Save" |         // This sends dialogue data to the back-end when user presses "Save" | ||||||
|         // See e.g. routes.go:UpdateClient for where data is processed/verified. |         // See e.g. routes.go:UpdateClient for where data is processed/verified. | ||||||
|  | @ -745,6 +799,8 @@ Wireguard Clients | ||||||
|                 submitEditClient(); |                 submitEditClient(); | ||||||
|             } else if (formId === "frm_email_client") { |             } else if (formId === "frm_email_client") { | ||||||
|                 submitEmailClient(); |                 submitEmailClient(); | ||||||
|  |             } else if (formId === "frm_telegram_client") { | ||||||
|  |                 submitTelegramClient(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -781,6 +837,30 @@ Wireguard Clients | ||||||
|             regenerateQRCode(); |             regenerateQRCode(); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |         $("#modal_telegram_client").on('show.bs.modal', function (event) { | ||||||
|  |             let modal = $(this); | ||||||
|  |             const button = $(event.relatedTarget); | ||||||
|  |             const client_id = button.data('clientid'); | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'GET', | ||||||
|  |                 url: '{{.basePath}}/api/client/' + client_id, | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 success: function (resp) { | ||||||
|  |                     const client = resp.Client; | ||||||
|  | 
 | ||||||
|  |                     modal.find(".modal-title").text("Send config to client " + client.name); | ||||||
|  |                     modal.find("#tg_client_id").val(client.id); | ||||||
|  |                     modal.find("#tg_client_userid").val(client.telegram_userid); | ||||||
|  |                 }, | ||||||
|  |                 error: function (jqXHR, exception) { | ||||||
|  |                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                     toastr.error(responseJson['message']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         $(document).ready(function () { |         $(document).ready(function () { | ||||||
|             $.validator.setDefaults({ |             $.validator.setDefaults({ | ||||||
|                 submitHandler: function (form) { |                 submitHandler: function (form) { | ||||||
|  | @ -836,6 +916,32 @@ Wireguard Clients | ||||||
|                     $(element).removeClass('is-invalid'); |                     $(element).removeClass('is-invalid'); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  |             // Telegram client form validation | ||||||
|  |             $("#frm_telegram_client").validate({ | ||||||
|  |                 rules: { | ||||||
|  |                     tg_client_userid: { | ||||||
|  |                         required: true, | ||||||
|  |                         number: true, | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |                 messages: { | ||||||
|  |                     tg_client_userid: { | ||||||
|  |                         required: "Please enter a telegram userid", | ||||||
|  |                         number: "Please enter a valid telegram userid" | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |                 errorElement: 'span', | ||||||
|  |                 errorPlacement: function (error, element) { | ||||||
|  |                     error.addClass('invalid-feedback'); | ||||||
|  |                     element.closest('.form-group').append(error); | ||||||
|  |                 }, | ||||||
|  |                 highlight: function (element, errorClass, validClass) { | ||||||
|  |                     $(element).addClass('is-invalid'); | ||||||
|  |                 }, | ||||||
|  |                 unhighlight: function (element, errorClass, validClass) { | ||||||
|  |                     $(element).removeClass('is-invalid'); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|             //  |             //  | ||||||
|         }); |         }); | ||||||
|     </script> |     </script> | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||||
| 	"golang.org/x/mod/sumdb/dirhash" | 	"golang.org/x/mod/sumdb/dirhash" | ||||||
| 
 | 
 | ||||||
| 	externalip "github.com/glendc/go-external-ip" | 	externalip "github.com/glendc/go-external-ip" | ||||||
|  | @ -28,7 +29,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // BuildClientConfig to create wireguard client config string
 | // BuildClientConfig to create wireguard client config string
 | ||||||
| func BuildClientConfig(client model.Client, server model.Server, setting model.GlobalSetting) string { | var BuildClientConfig telegram.BuildClientConfig = func(client model.Client, server model.Server, setting model.GlobalSetting) string { | ||||||
| 	// Interface section
 | 	// Interface section
 | ||||||
| 	clientAddress := fmt.Sprintf("Address = %s\n", strings.Join(client.AllocatedIPs, ",")) | 	clientAddress := fmt.Sprintf("Address = %s\n", strings.Join(client.AllocatedIPs, ",")) | ||||||
| 	clientPrivateKey := fmt.Sprintf("PrivateKey = %s\n", client.PrivateKey) | 	clientPrivateKey := fmt.Sprintf("PrivateKey = %s\n", client.PrivateKey) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue