Inserted web templates, readme, license. Updated logo and favicon
This commit is contained in:
11
LICENSE
Normal file
11
LICENSE
Normal file
@@ -0,0 +1,11 @@
|
||||
Copyright 2019 Samuel Pua. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
29
README.md
Normal file
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Tap It - Text Phishing Framework
|
||||
|
||||
Tap It is a SMS phishing framework that allows handling of large SMS phishing campaigns. It automatically handles text template and allows basic handling of web template should phishing URLs be sent through the text.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Tap It is built on Go, and have all required libraries built-in the binary. To attempt to recompile the application, the following is required:
|
||||
* Go
|
||||
* Angular CLI
|
||||
* GORM
|
||||
* Gorilla Mux
|
||||
* Teabag XLSX Library
|
||||
|
||||
## Deployment
|
||||
|
||||
The entire application is designed to be Dockerised. To build the Docker environment with the pre-compiled binaries, simple run the following:
|
||||
```
|
||||
sudo docker-compose up
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
* **Samuel Pua** - *Initial work* - [GitHub](https://github.com/telboon)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD License - see the [LICENSE](LICENSE) file for details
|
||||
|
||||
|
||||
6
build.sh
6
build.sh
@@ -8,7 +8,13 @@ cd ./tapit-frontend
|
||||
ng build --optimization
|
||||
cd ..
|
||||
|
||||
# copy front-end
|
||||
cp -r ./tapit-frontend/dist/tapit-frontend/* ./tapit-build/static/
|
||||
# remove maps
|
||||
rm ./tapit-build/static/*.map
|
||||
|
||||
# copy back-end
|
||||
cp ./tapit-backend/tapit-backend ./tapit-build/tapit
|
||||
|
||||
# run server
|
||||
./tapit-build/tapit
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo docker rm postgres --force
|
||||
sudo docker run -d -e POSTGRES_USER="tapit" -e POSTGRES_PASSWORD="secret-tapit-password" -e POSTGRES_DB="tapit" --name postgres postgres
|
||||
sudo docker run -d -p5432:5432 -e POSTGRES_USER="tapit" -e POSTGRES_PASSWORD="secret-tapit-password" -e POSTGRES_DB="tapit" --name postgres postgres
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/jinzhu/gorm"
|
||||
"math/rand"
|
||||
"time"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -104,7 +103,7 @@ func (tapit *Tapit) register(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// checks if secret code is correct
|
||||
if userJson.SecretCode != tapit.globalSettings.secretRegistrationCode {
|
||||
if userJson.SecretCode != tapit.globalSettings.SecretRegistrationCode {
|
||||
messageOutput := NotificationJson{
|
||||
Text: "Your secret code is incorrect. Please try again.",
|
||||
ResultType: "failure",
|
||||
@@ -280,7 +279,6 @@ func (tapit *Tapit) deleteCookie() http.Cookie {
|
||||
|
||||
func generateToken() string {
|
||||
var tokenResult strings.Builder
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var r int
|
||||
tokenCharset := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
for i:=0; i<16; i++ {
|
||||
@@ -291,7 +289,7 @@ func generateToken() string {
|
||||
}
|
||||
|
||||
func (tapit *Tapit) hashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), tapit.globalSettings.bcryptCost)
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), tapit.globalSettings.BcryptCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ type Job struct {
|
||||
gorm.Model
|
||||
CampaignId uint
|
||||
CurrentStatus string // enum Failed, Queued, Sent, Delivered, Not Started
|
||||
WebStatus string // enum Not Visited, xx visits
|
||||
TimeSent time.Time
|
||||
ProviderTag string
|
||||
AccSID string
|
||||
@@ -63,14 +64,21 @@ type Job struct {
|
||||
ToNum string
|
||||
ResultStr string
|
||||
MessageSid string
|
||||
WebRoute string
|
||||
FullUrl string
|
||||
Visits []Visit
|
||||
}
|
||||
|
||||
type JobJson struct {
|
||||
Id uint `json:"id"`
|
||||
CurrentStatus string `json:"currentStatus"`
|
||||
WebStatus string `json:"webStatus"`
|
||||
TimeSent time.Time `json:"timeSent"`
|
||||
FromNum string `json:"fromNum"`
|
||||
ToNum string `json:"toNum"`
|
||||
WebRoute string `json:"webRoute"`
|
||||
FullUrl string `json:"fullUrl"`
|
||||
Visits []VisitJson `json:"visitJson"`
|
||||
}
|
||||
|
||||
type TwilioMessageJson struct {
|
||||
@@ -187,14 +195,24 @@ func (tapit *Tapit) createCampaign(w http.ResponseWriter, r *http.Request) {
|
||||
newCampaign.WebTemplateId = newCampaignJson.WebTemplateId
|
||||
newCampaign.ProviderTag = newCampaignJson.ProviderTag
|
||||
|
||||
// save campaign first
|
||||
tapit.db.NewRecord(&newCampaign)
|
||||
tapit.db.Create(&newCampaign)
|
||||
if newCampaign.ID == 0 {
|
||||
notifyPopup(w, r, "failure", "Failed to create campaign", nil)
|
||||
return
|
||||
}
|
||||
// update records
|
||||
for _, record := range newRecords {
|
||||
var newJob Job
|
||||
newJob.CurrentStatus = "Not Started"
|
||||
newJob.WebStatus = "Not Visited"
|
||||
newJob.ProviderTag = newCampaign.ProviderTag
|
||||
newJob.AccSID = newAccSID
|
||||
newJob.AuthToken = newAuthToken
|
||||
newJob.FromNum = newCampaign.FromNumber
|
||||
newJob.WebRoute = tapit.generateWebTemplateRoute()
|
||||
newJob.FullUrl = tapit.globalSettings.WebTemplatePrefix + newJob.WebRoute
|
||||
|
||||
// interpreting records
|
||||
var newBodyText string
|
||||
@@ -204,20 +222,17 @@ func (tapit *Tapit) createCampaign(w http.ResponseWriter, r *http.Request) {
|
||||
newBodyText = strings.Replace(newBodyText, "{lastName}", record.LastName, -1)
|
||||
newBodyText = strings.Replace(newBodyText, "{alias}", record.Alias, -1)
|
||||
newBodyText = strings.Replace(newBodyText, "{phoneNumber}", record.PhoneNumber, -1)
|
||||
newBodyText = strings.Replace(newBodyText, "{url}", newJob.FullUrl, -1)
|
||||
|
||||
newJob.BodyText = newBodyText
|
||||
|
||||
// saving it
|
||||
newCampaign.Jobs = append(newCampaign.Jobs, newJob)
|
||||
|
||||
// update campaign
|
||||
tapit.db.Save(&newCampaign)
|
||||
}
|
||||
|
||||
// update database
|
||||
tapit.db.NewRecord(&newCampaign)
|
||||
tapit.db.Create(&newCampaign)
|
||||
if newCampaign.ID == 0 {
|
||||
notifyPopup(w, r, "failure", "Failed to create campaign", nil)
|
||||
return
|
||||
}
|
||||
newCampaignJson.Id = newCampaign.ID
|
||||
newCampaignJson.CreateDate = newCampaign.CreatedAt
|
||||
newCampaignJson.Size = newCampaign.Size
|
||||
@@ -301,7 +316,11 @@ func campaignToJson(campaign Campaign) CampaignJson {
|
||||
// iterating jobs
|
||||
for _, job := range campaign.Jobs {
|
||||
var currJson JobJson
|
||||
currJson.Id = job.ID
|
||||
currJson.CurrentStatus = job.CurrentStatus
|
||||
currJson.WebStatus = job.WebStatus
|
||||
currJson.WebRoute = job.WebRoute
|
||||
currJson.FullUrl = job.FullUrl
|
||||
currJson.TimeSent = job.TimeSent
|
||||
currJson.FromNum = job.FromNum
|
||||
currJson.ToNum = job.ToNum
|
||||
@@ -425,7 +444,7 @@ func (tapit *Tapit) workerCampaign(campaign Campaign) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
jobChan = make(chan JobComms, 1)
|
||||
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
|
||||
for i:=0; i<tapit.globalSettings.ThreadsPerCampaign; i++ {
|
||||
wg.Add(1)
|
||||
go tapit.workerJob(jobChan, &wg)
|
||||
}
|
||||
@@ -436,7 +455,7 @@ func (tapit *Tapit) workerCampaign(campaign Campaign) {
|
||||
if campaignComms.Campaign.ID == campaign.ID {
|
||||
if campaignComms.Action == "stop" {
|
||||
// kill all
|
||||
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
|
||||
for i:=0; i<tapit.globalSettings.ThreadsPerCampaign; i++ {
|
||||
var stopComms JobComms
|
||||
stopComms.Action = "stop"
|
||||
jobChan <- stopComms
|
||||
@@ -468,7 +487,7 @@ func (tapit *Tapit) workerCampaign(campaign Campaign) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
|
||||
for i:=0; i<tapit.globalSettings.ThreadsPerCampaign; i++ {
|
||||
var stopComms JobComms
|
||||
stopComms.Action = "stop"
|
||||
jobChan <- stopComms
|
||||
@@ -512,6 +531,7 @@ func (tapit *Tapit) workerJob(jobChan chan JobComms, wg *sync.WaitGroup) {
|
||||
} else if twilioResult.Status == "delivered" {
|
||||
currentJob.Job.MessageSid = twilioResult.Sid
|
||||
currentJob.Job.CurrentStatus = "Delivered"
|
||||
currentJob.Job.TimeSent = time.Now()
|
||||
} else {
|
||||
currentJob.Job.CurrentStatus = "Failed"
|
||||
}
|
||||
|
||||
114
tapit-backend/global-settings.go
Normal file
114
tapit-backend/global-settings.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"strings"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// GlobalSettings contains basic common settings across the app
|
||||
type GlobalSettings struct {
|
||||
gorm.Model
|
||||
SecretRegistrationCode string
|
||||
ThreadsPerCampaign int
|
||||
BcryptCost int
|
||||
MaxRequestRetries int
|
||||
WaitBeforeRetry int
|
||||
WebTemplatePrefix string
|
||||
WebTemplateRoute string
|
||||
}
|
||||
|
||||
type GlobalSettingsJson struct {
|
||||
SecretRegistrationCode string `json:"secretRegistrationCode"`
|
||||
ThreadsPerCampaign int `json:"threadsPerCampaign"`
|
||||
BcryptCost int `json:"bcryptCost"`
|
||||
MaxRequestRetries int `json:"maxRequestRetries"`
|
||||
WaitBeforeRetry int `json:"waitBeforeRetry"`
|
||||
WebTemplatePrefix string `json:"webTemplatePrefix"`
|
||||
WebTemplateRoute string `json:"webTemplateRoute"`
|
||||
}
|
||||
|
||||
|
||||
func (tapit *Tapit) handleGlobalSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.ToUpper(r.Method) == "PUT" {
|
||||
tapit.updateGlobalSettings(w, r)
|
||||
} else if strings.ToUpper(r.Method) == "GET" {
|
||||
tapit.getGlobalSettings(w,r)
|
||||
} else {
|
||||
http.Error(w, "HTTP method not implemented", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) updateGlobalSettings(w http.ResponseWriter, r *http.Request) {
|
||||
requestBody, err:= ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
var globalSettingsJson GlobalSettingsJson
|
||||
var globalSettings GlobalSettings
|
||||
err = tapit.db.Last(&globalSettings).Error
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(requestBody, &globalSettingsJson)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
if globalSettingsJson.SecretRegistrationCode != "" && globalSettingsJson.ThreadsPerCampaign != 0 && globalSettingsJson.BcryptCost != 0 && globalSettingsJson.WebTemplatePrefix != "" && globalSettingsJson.WebTemplateRoute != "" {
|
||||
globalSettings.SecretRegistrationCode = globalSettingsJson.SecretRegistrationCode
|
||||
globalSettings.ThreadsPerCampaign = globalSettingsJson.ThreadsPerCampaign
|
||||
globalSettings.BcryptCost = globalSettingsJson.BcryptCost
|
||||
globalSettings.MaxRequestRetries = globalSettingsJson.MaxRequestRetries
|
||||
globalSettings.WaitBeforeRetry = globalSettingsJson.WaitBeforeRetry
|
||||
globalSettings.WebTemplatePrefix = globalSettingsJson.WebTemplatePrefix
|
||||
globalSettings.WebTemplateRoute = globalSettingsJson.WebTemplateRoute
|
||||
err = tapit.db.Save(&globalSettings).Error
|
||||
|
||||
if err != nil {
|
||||
notifyPopup(w, r, "failure", "Failed to update global settings", nil)
|
||||
return
|
||||
} else {
|
||||
tapit.globalSettings = globalSettings
|
||||
notifyPopup(w, r, "success", "Successfully updated global settings", globalSettingsJson)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
notifyPopup(w, r, "failure", "Failed to update global settings", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) getGlobalSettings(w http.ResponseWriter, r *http.Request) {
|
||||
var globalSettingsJson GlobalSettingsJson
|
||||
var globalSettings GlobalSettings
|
||||
err := tapit.db.Last(&globalSettings).Error
|
||||
|
||||
if err == nil {
|
||||
globalSettingsJson.SecretRegistrationCode = globalSettings.SecretRegistrationCode
|
||||
globalSettingsJson.ThreadsPerCampaign = globalSettings.ThreadsPerCampaign
|
||||
globalSettingsJson.BcryptCost = globalSettings.BcryptCost
|
||||
globalSettingsJson.MaxRequestRetries = globalSettings.MaxRequestRetries
|
||||
globalSettingsJson.WaitBeforeRetry = globalSettings.WaitBeforeRetry
|
||||
globalSettingsJson.WebTemplatePrefix = globalSettings.WebTemplatePrefix
|
||||
globalSettingsJson.WebTemplateRoute = globalSettings.WebTemplateRoute
|
||||
|
||||
jsonResults, err := json.Marshal(globalSettingsJson)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(jsonResults)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,18 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"path/filepath"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// Tapit is the general struct with shared objects
|
||||
type Tapit struct {
|
||||
db *gorm.DB
|
||||
globalSettings GlobalSettings
|
||||
campaignChan chan CampaignComms
|
||||
}
|
||||
|
||||
type GlobalSettings struct {
|
||||
secretRegistrationCode string
|
||||
threadsPerCampaign int
|
||||
bcryptCost int
|
||||
maxRequestRetries int
|
||||
waitBeforeRetry int
|
||||
}
|
||||
|
||||
func generateFileHandler(path string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Add("Cache-Control", "private, max-age=604800") // 7 days
|
||||
@@ -66,23 +61,43 @@ func main() {
|
||||
defer db.Close()
|
||||
|
||||
// DB Migrations
|
||||
db.AutoMigrate(&GlobalSettings{})
|
||||
db.AutoMigrate(&Session{})
|
||||
db.AutoMigrate(&User{})
|
||||
db.AutoMigrate(&TextTemplate{})
|
||||
db.AutoMigrate(&WebTemplate{})
|
||||
db.AutoMigrate(&TwilioProvider{})
|
||||
db.AutoMigrate(&Phonebook{})
|
||||
db.AutoMigrate(&PhoneRecord{})
|
||||
db.AutoMigrate(&Campaign{})
|
||||
db.AutoMigrate(&Job{})
|
||||
db.AutoMigrate(&Visit{})
|
||||
|
||||
// Setting up Tapit app
|
||||
var tapit Tapit
|
||||
tapit.db = db
|
||||
tapit.globalSettings.secretRegistrationCode = "Super-Secret-Code"
|
||||
tapit.globalSettings.threadsPerCampaign = 2
|
||||
tapit.globalSettings.bcryptCost = 12
|
||||
tapit.globalSettings.maxRequestRetries = 5
|
||||
tapit.globalSettings.waitBeforeRetry = 1000
|
||||
|
||||
var globalSettings GlobalSettings
|
||||
|
||||
// handle global settings
|
||||
err = tapit.db.Last(&globalSettings).Error
|
||||
if err != nil {
|
||||
globalSettings.SecretRegistrationCode = "Super-Secret-Code"
|
||||
globalSettings.ThreadsPerCampaign = 2
|
||||
globalSettings.BcryptCost = 12
|
||||
globalSettings.MaxRequestRetries = 5
|
||||
globalSettings.WaitBeforeRetry = 1000
|
||||
globalSettings.WebTemplatePrefix = "https://www.attacker.com/"
|
||||
globalSettings.WebTemplateRoute = "/"
|
||||
|
||||
tapit.db.NewRecord(&globalSettings)
|
||||
tapit.db.Create(&globalSettings)
|
||||
}
|
||||
|
||||
tapit.globalSettings = globalSettings
|
||||
|
||||
// Seeding random
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Clear running campaigns & starting background jobs
|
||||
tapit.clearRunningCampaigns()
|
||||
@@ -114,6 +129,10 @@ func main() {
|
||||
"/text-template",
|
||||
"/text-template/new",
|
||||
"/text-template/{id}/edit",
|
||||
"/web-template",
|
||||
"/web-template/new",
|
||||
"/web-template/{id}/edit",
|
||||
"/global-settings",
|
||||
"/provider",
|
||||
}
|
||||
indexPath := dir + "/static/index.html"
|
||||
@@ -127,6 +146,8 @@ func main() {
|
||||
|
||||
r.Handle("/api/text-template",tapit.authenticationHandler(tapit.handleTextTemplate))
|
||||
r.Handle("/api/text-template/{id}",tapit.authenticationHandler(tapit.handleSpecificTextTemplate))
|
||||
r.Handle("/api/web-template",tapit.authenticationHandler(tapit.handleWebTemplate))
|
||||
r.Handle("/api/web-template/{id}",tapit.authenticationHandler(tapit.handleSpecificWebTemplate))
|
||||
r.Handle("/api/provider/twilio",tapit.authenticationHandler(tapit.handleTwilioProvider))
|
||||
r.Handle("/api/phonebook",tapit.authenticationHandler(tapit.handlePhonebook))
|
||||
r.Handle("/api/phonebook/{id}",tapit.authenticationHandler(tapit.handleSpecificPhonebook))
|
||||
@@ -135,9 +156,21 @@ func main() {
|
||||
r.Handle("/api/campaign/{id}",tapit.authenticationHandler(tapit.handleSpecificCampaign))
|
||||
r.Handle("/api/campaign/{id}/start",tapit.authenticationHandler(tapit.handleStartCampaign))
|
||||
r.Handle("/api/campaign/{id}/pause",tapit.authenticationHandler(tapit.handleStopCampaign))
|
||||
r.Handle("/api/globalsettings",tapit.authenticationHandler(tapit.handleGlobalSettings))
|
||||
r.Handle("/api/jobs/{id}/visits",tapit.authenticationHandler(tapit.handleDownloadView))
|
||||
|
||||
// Starting management web server
|
||||
r.Handle("/", r)
|
||||
log.Println("Starting management web server on port 8000...")
|
||||
go http.ListenAndServe(":8000", r)
|
||||
|
||||
// Handle WebTemplate Routes
|
||||
webTemplateRouter := mux.NewRouter()
|
||||
webTemplateRouter.HandleFunc("/{route}", tapit.webTemplateRouteHandler)
|
||||
|
||||
// Starting victim route web server
|
||||
webTemplateRouter.Handle("/", webTemplateRouter)
|
||||
log.Println("Starting victim routes on port 8001...")
|
||||
http.ListenAndServe(":8001", webTemplateRouter)
|
||||
|
||||
// Starting web server
|
||||
http.Handle("/", r)
|
||||
log.Println("Starting web server...")
|
||||
http.ListenAndServe(":8000", nil)
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -11,12 +11,14 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// TextTemplate is the persistent object within Postgres
|
||||
type TextTemplate struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
TemplateStr string
|
||||
}
|
||||
|
||||
// TextTemplateJson is the temporary object for JSON data passing
|
||||
type TextTemplateJson struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -238,3 +240,4 @@ func textTemplateToJson(textTemplate TextTemplate) TextTemplateJson {
|
||||
result.CreateDate = textTemplate.CreatedAt
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -128,12 +128,12 @@ func (tapit *Tapit) twilioSend(accSid string, accToken string, bodyText string,
|
||||
|
||||
// sending request
|
||||
res, err := client.Do(newRequest1)
|
||||
retriesLeft := tapit.globalSettings.maxRequestRetries
|
||||
retriesLeft := tapit.globalSettings.MaxRequestRetries
|
||||
for err != nil && retriesLeft > 0 {
|
||||
log.Println("Error in sending request")
|
||||
res, err = client.Do(newRequest1)
|
||||
retriesLeft -= 1
|
||||
time.Sleep(time.Duration(tapit.globalSettings.waitBeforeRetry) * time.Millisecond)
|
||||
time.Sleep(time.Duration(tapit.globalSettings.WaitBeforeRetry) * time.Millisecond)
|
||||
}
|
||||
|
||||
// exit gracefully if can't
|
||||
@@ -160,12 +160,12 @@ func (tapit *Tapit) twilioCheck(accSid string, accToken string, messageSid strin
|
||||
|
||||
// sending request
|
||||
res, err := client.Do(newRequest1)
|
||||
retriesLeft := tapit.globalSettings.maxRequestRetries
|
||||
retriesLeft := tapit.globalSettings.MaxRequestRetries
|
||||
for err != nil && retriesLeft > 0 {
|
||||
log.Println("Error in sending request")
|
||||
res, err = client.Do(newRequest1)
|
||||
retriesLeft -= 1
|
||||
time.Sleep(time.Duration(tapit.globalSettings.waitBeforeRetry) * time.Millisecond)
|
||||
time.Sleep(time.Duration(tapit.globalSettings.WaitBeforeRetry) * time.Millisecond)
|
||||
}
|
||||
|
||||
// exit gracefully if can't
|
||||
@@ -207,6 +207,7 @@ func (tapit *Tapit) workerTwilioChecker() {
|
||||
} else if twilioResult.Status == "delivered" {
|
||||
job.MessageSid = twilioResult.Sid
|
||||
job.CurrentStatus = "Delivered"
|
||||
job.TimeSent = time.Now()
|
||||
}
|
||||
tapit.db.Save(&job)
|
||||
}
|
||||
|
||||
468
tapit-backend/web-template.go
Normal file
468
tapit-backend/web-template.go
Normal file
@@ -0,0 +1,468 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/gorilla/mux"
|
||||
"time"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"encoding/csv"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// WebTemplate is the persistent object within Postgres
|
||||
type WebTemplate struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
TemplateType string // enum redirect, harvester
|
||||
RedirectAgent string
|
||||
RedirectNegAgent string
|
||||
RedirectPlaceholderHtml string
|
||||
RedirectUrl string
|
||||
HarvesterBeforeHtml string
|
||||
HarvesterAfterHtml string
|
||||
}
|
||||
|
||||
// WebTemplateJson is the temporary object for JSON data passing
|
||||
type WebTemplateJson struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TemplateType string `json:"templateType"`
|
||||
RedirectAgent string `json:"redirectAgent"`
|
||||
RedirectNegAgent string `json:"redirectNegAgent"`
|
||||
RedirectPlaceholderHtml string `json:"redirectPlaceholderHtml"`
|
||||
RedirectUrl string `json:"redirectUrl"`
|
||||
HarvesterBeforeHtml string `json:"harvesterBeforeHtml"`
|
||||
HarvesterAfterHtml string `json:"harvesterAfterHtml"`
|
||||
CreateDate time.Time `json:"createDate"`
|
||||
}
|
||||
|
||||
type Visit struct {
|
||||
gorm.Model
|
||||
JobId uint
|
||||
SourceIp string
|
||||
UserAgent string
|
||||
Method string
|
||||
BodyContent string
|
||||
RawRequest string
|
||||
}
|
||||
|
||||
type VisitJson struct {
|
||||
Id uint `json:"id"`
|
||||
JobId uint `json:"jobId"`
|
||||
SourceIP string `json:"sourceIp"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
Method string `json:"method"`
|
||||
BodyContent string `json:"bodyContent"`
|
||||
RawRequest string `json:"rawRequest"`
|
||||
CreateDate time.Time `json:"createDate"`
|
||||
}
|
||||
|
||||
func (tapit *Tapit) handleWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.ToUpper(r.Method) == "GET" {
|
||||
tapit.getWebTemplates(w, r)
|
||||
} else if strings.ToUpper(r.Method) == "POST" {
|
||||
tapit.createWebTemplate(w, r)
|
||||
} else {
|
||||
http.Error(w, "HTTP method not implemented", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) getWebTemplates(w http.ResponseWriter, r *http.Request) {
|
||||
webTemplates := []WebTemplate{}
|
||||
tapit.db.Find(&webTemplates)
|
||||
jsonResults, err := json.Marshal(webTemplatesToJson(webTemplates))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(jsonResults)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func webTemplatesToJson(webTemplates []WebTemplate) []WebTemplateJson {
|
||||
webTemplateJson := make([]WebTemplateJson, 0)
|
||||
for _, webTemplate := range webTemplates {
|
||||
var currentWebTemplateJson WebTemplateJson
|
||||
currentWebTemplateJson.Id = int(webTemplate.ID)
|
||||
currentWebTemplateJson.Name = webTemplate.Name
|
||||
currentWebTemplateJson.TemplateType = webTemplate.TemplateType
|
||||
currentWebTemplateJson.RedirectAgent = webTemplate.RedirectAgent
|
||||
currentWebTemplateJson.RedirectNegAgent = webTemplate.RedirectNegAgent
|
||||
currentWebTemplateJson.RedirectPlaceholderHtml = webTemplate.RedirectPlaceholderHtml
|
||||
currentWebTemplateJson.RedirectUrl = webTemplate.RedirectUrl
|
||||
currentWebTemplateJson.HarvesterBeforeHtml = webTemplate.HarvesterBeforeHtml
|
||||
currentWebTemplateJson.HarvesterAfterHtml = webTemplate.HarvesterAfterHtml
|
||||
currentWebTemplateJson.CreateDate = webTemplate.CreatedAt
|
||||
|
||||
webTemplateJson = append(webTemplateJson, currentWebTemplateJson)
|
||||
}
|
||||
return webTemplateJson
|
||||
}
|
||||
|
||||
func (tapit *Tapit) createWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
// start doing work
|
||||
requestBody, err:= ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
newWebTemplateJson := WebTemplateJson{}
|
||||
err = json.Unmarshal(requestBody, &newWebTemplateJson)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
if newWebTemplateJson.Name != "" && newWebTemplateJson.TemplateType != "" {
|
||||
// check that not both user agents are filled
|
||||
if newWebTemplateJson.RedirectAgent != "" && newWebTemplateJson.RedirectNegAgent != "" {
|
||||
notifyPopup(w, r, "failure", "Please fill in only either positive or negative redirect user agent.", nil)
|
||||
return
|
||||
}
|
||||
newWebTemplate := jsonToWebTemplate(newWebTemplateJson)
|
||||
tapit.db.NewRecord(&newWebTemplate)
|
||||
tapit.db.Create(&newWebTemplate)
|
||||
if newWebTemplate.ID == 0 {
|
||||
notifyPopup(w, r, "failure", "Failed to create text template", nil)
|
||||
return
|
||||
}
|
||||
newWebTemplateJson.Id = int(newWebTemplate.ID)
|
||||
newWebTemplateJson.CreateDate = newWebTemplate.CreatedAt
|
||||
|
||||
notifyPopup(w, r, "success", "Successfully added new text template", newWebTemplateJson)
|
||||
return
|
||||
} else {
|
||||
notifyPopup(w, r, "failure", "Please fill in all details", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func jsonToWebTemplate(currentWebTemplateJson WebTemplateJson) WebTemplate {
|
||||
var webTemplate WebTemplate
|
||||
|
||||
webTemplate.Name = currentWebTemplateJson.Name
|
||||
webTemplate.TemplateType = currentWebTemplateJson.TemplateType
|
||||
webTemplate.RedirectAgent = currentWebTemplateJson.RedirectAgent
|
||||
webTemplate.RedirectNegAgent = currentWebTemplateJson.RedirectNegAgent
|
||||
webTemplate.RedirectPlaceholderHtml = currentWebTemplateJson.RedirectPlaceholderHtml
|
||||
webTemplate.RedirectUrl = currentWebTemplateJson.RedirectUrl
|
||||
webTemplate.HarvesterBeforeHtml = currentWebTemplateJson.HarvesterBeforeHtml
|
||||
webTemplate.HarvesterAfterHtml = currentWebTemplateJson.HarvesterAfterHtml
|
||||
|
||||
return webTemplate
|
||||
}
|
||||
|
||||
func (tapit *Tapit) handleSpecificWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.ToUpper(r.Method) == "PUT" {
|
||||
tapit.updateWebTemplate(w, r)
|
||||
} else if strings.ToUpper(r.Method) == "DELETE" {
|
||||
tapit.deleteWebTemplate(w,r)
|
||||
} else if strings.ToUpper(r.Method) == "GET" {
|
||||
tapit.getWebTemplate(w,r)
|
||||
} else {
|
||||
http.Error(w, "HTTP method not implemented", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) updateWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
requestBody, err:= ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
var newWebTemplateJson WebTemplateJson
|
||||
err = json.Unmarshal(requestBody, &newWebTemplateJson)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
if newWebTemplateJson.Name != "" && newWebTemplateJson.TemplateType != "" {
|
||||
var newWebTemplate WebTemplate
|
||||
|
||||
// get current phonebook
|
||||
var dbSearchWT WebTemplate
|
||||
dbSearchWT.ID = uint(newWebTemplateJson.Id)
|
||||
tapit.db.Where(&dbSearchWT).First(&newWebTemplate)
|
||||
|
||||
if newWebTemplate.ID == uint(newWebTemplateJson.Id) {
|
||||
// update name & template
|
||||
newWebTemplate.Name = newWebTemplateJson.Name
|
||||
newWebTemplate.TemplateType = newWebTemplateJson.TemplateType
|
||||
newWebTemplate.RedirectAgent = newWebTemplateJson.RedirectAgent
|
||||
newWebTemplate.RedirectNegAgent = newWebTemplateJson.RedirectNegAgent
|
||||
newWebTemplate.RedirectPlaceholderHtml = newWebTemplateJson.RedirectPlaceholderHtml
|
||||
newWebTemplate.RedirectUrl = newWebTemplateJson.RedirectUrl
|
||||
newWebTemplate.HarvesterBeforeHtml = newWebTemplateJson.HarvesterBeforeHtml
|
||||
newWebTemplate.HarvesterAfterHtml = newWebTemplateJson.HarvesterAfterHtml
|
||||
|
||||
// update database
|
||||
tapit.db.Save(&newWebTemplate)
|
||||
if newWebTemplate.ID == 0 {
|
||||
notifyPopup(w, r, "failure", "Failed to update phonebook", nil)
|
||||
return
|
||||
}
|
||||
newWebTemplateJson.Id = int(newWebTemplate.ID)
|
||||
newWebTemplateJson.CreateDate = newWebTemplate.CreatedAt
|
||||
|
||||
notifyPopup(w, r, "success", "Successfully updated web template", newWebTemplateJson)
|
||||
return
|
||||
} else {
|
||||
notifyPopup(w, r, "failure", "Failed to update web template", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
notifyPopup(w, r, "failure", "Please enter all details", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) deleteWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
tempID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
// start working
|
||||
var webTemplate WebTemplate
|
||||
|
||||
// get tt
|
||||
var dbSearchWT WebTemplate
|
||||
dbSearchWT.ID = uint(tempID)
|
||||
tapit.db.Where(dbSearchWT).First(&webTemplate)
|
||||
|
||||
if webTemplate.ID == uint(tempID) {
|
||||
// finally delete it
|
||||
tapit.db.Delete(&webTemplate)
|
||||
notifyPopup(w, r, "success", "Successfully deleted phonebook", nil)
|
||||
return
|
||||
} else {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) getWebTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
tempID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
// start working
|
||||
var webTemplate WebTemplate
|
||||
|
||||
// get tt
|
||||
var dbSearchWT WebTemplate
|
||||
dbSearchWT.ID = uint(tempID)
|
||||
tapit.db.Where(dbSearchWT).First(&webTemplate)
|
||||
|
||||
if webTemplate.ID == uint(tempID) {
|
||||
jsonResults, err := json.Marshal(webTemplateToJson(webTemplate))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(jsonResults)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (tapit *Tapit) generateWebTemplateRoute() string {
|
||||
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
// generate 5 char
|
||||
var newRoute string
|
||||
var successRoute bool
|
||||
successRoute = false
|
||||
|
||||
for !successRoute {
|
||||
newRoute = ""
|
||||
for i:=0; i<5; i++ {
|
||||
num := rand.Int() % len(charset)
|
||||
newRoute = newRoute + string(charset[num])
|
||||
|
||||
// search if route already exists
|
||||
var dbSearchJob Job
|
||||
var jobs []Job
|
||||
dbSearchJob.WebRoute = newRoute
|
||||
tapit.db.Where(&dbSearchJob).Find(&jobs)
|
||||
if len(jobs) == 0 {
|
||||
successRoute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return newRoute
|
||||
}
|
||||
|
||||
func webTemplateToJson(webTemplate WebTemplate) WebTemplateJson {
|
||||
var currentWebTemplateJson WebTemplateJson
|
||||
currentWebTemplateJson.Id = int(webTemplate.ID)
|
||||
currentWebTemplateJson.Name = webTemplate.Name
|
||||
currentWebTemplateJson.TemplateType = webTemplate.TemplateType
|
||||
currentWebTemplateJson.RedirectAgent = webTemplate.RedirectAgent
|
||||
currentWebTemplateJson.RedirectNegAgent = webTemplate.RedirectNegAgent
|
||||
currentWebTemplateJson.RedirectPlaceholderHtml = webTemplate.RedirectPlaceholderHtml
|
||||
currentWebTemplateJson.RedirectUrl = webTemplate.RedirectUrl
|
||||
currentWebTemplateJson.HarvesterBeforeHtml = webTemplate.HarvesterBeforeHtml
|
||||
currentWebTemplateJson.HarvesterAfterHtml = webTemplate.HarvesterAfterHtml
|
||||
currentWebTemplateJson.CreateDate = webTemplate.CreatedAt
|
||||
return currentWebTemplateJson
|
||||
}
|
||||
|
||||
func (tapit *Tapit) webTemplateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
vars := mux.Vars(r)
|
||||
currRoute := vars["route"]
|
||||
|
||||
currJob := Job{}
|
||||
err = tapit.db.Where(&Job{WebRoute:currRoute}).First(&currJob).Error
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
currCampaign := Campaign{}
|
||||
err = tapit.db.Where(&Campaign{Model: gorm.Model{ID:currJob.CampaignId}}).First(&currCampaign).Error
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
currWebTemplate := WebTemplate{}
|
||||
err = tapit.db.Where(&WebTemplate{Model: gorm.Model{ID:currCampaign.WebTemplateId}}).First(&currWebTemplate).Error
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
// check type for "redirect" or "harvester"
|
||||
if currWebTemplate.TemplateType == "redirect" {
|
||||
if currWebTemplate.RedirectAgent != "" {
|
||||
listOfUA := strings.Split(currWebTemplate.RedirectAgent, ",")
|
||||
currCheck := false
|
||||
for _, currUA := range listOfUA {
|
||||
// check if user agent matches
|
||||
if strings.Contains(r.UserAgent(), currUA) {
|
||||
currCheck = true
|
||||
}
|
||||
}
|
||||
|
||||
// if matches at least once, redirect, otherwise placeholder
|
||||
if currCheck == true {
|
||||
http.Redirect(w, r, currWebTemplate.RedirectUrl, 302)
|
||||
} else {
|
||||
w.Write([]byte(currWebTemplate.RedirectPlaceholderHtml))
|
||||
}
|
||||
} else {
|
||||
listOfUA := strings.Split(currWebTemplate.RedirectNegAgent, ",")
|
||||
currCheck := true
|
||||
for _, currUA := range listOfUA {
|
||||
// check if user agent matches
|
||||
if strings.Contains(r.UserAgent(), currUA) {
|
||||
currCheck = false
|
||||
}
|
||||
}
|
||||
|
||||
// if matches at least once, redirect, otherwise placeholder
|
||||
if currCheck == true {
|
||||
http.Redirect(w, r, currWebTemplate.RedirectUrl, 302)
|
||||
} else {
|
||||
w.Write([]byte(currWebTemplate.RedirectPlaceholderHtml))
|
||||
}
|
||||
}
|
||||
} else if currWebTemplate.TemplateType == "harvester" {
|
||||
// if get show before, if post show after
|
||||
if strings.ToUpper(r.Method) == "GET"{
|
||||
w.Write([]byte(currWebTemplate.HarvesterBeforeHtml))
|
||||
} else if strings.ToUpper(r.Method) == "POST"{
|
||||
w.Write([]byte(currWebTemplate.HarvesterAfterHtml))
|
||||
} else {
|
||||
http.Error(w, "Bad request", 400)
|
||||
}
|
||||
}
|
||||
|
||||
// saving records
|
||||
requestBody, err:= ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
var newVisit Visit
|
||||
|
||||
newVisit = Visit{}
|
||||
newVisit.JobId = currJob.ID
|
||||
if r.Header.Get("X-Forwarded-For") == "" {
|
||||
newVisit.SourceIp = r.RemoteAddr
|
||||
} else {
|
||||
newVisit.SourceIp = r.Header.Get("X-Forwarded-For")
|
||||
}
|
||||
newVisit.UserAgent = r.UserAgent()
|
||||
newVisit.Method = r.Method
|
||||
newVisit.BodyContent = string(requestBody)
|
||||
rawReqBytes, err := httputil.DumpRequest(r, true)
|
||||
if err == nil {
|
||||
newVisit.RawRequest = string(rawReqBytes)
|
||||
}
|
||||
|
||||
// Update visited status
|
||||
var visits []Visit
|
||||
tapit.db.Where(Visit{JobId: uint(currJob.ID)}).Find(&visits)
|
||||
currJob.WebStatus = strconv.Itoa(len(visits) + 1) + " visits"
|
||||
|
||||
tapit.db.Save(&currJob)
|
||||
|
||||
tapit.db.NewRecord(&newVisit)
|
||||
tapit.db.Create(&newVisit)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (tapit *Tapit) handleDownloadView(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.ToUpper(r.Method) == "GET" {
|
||||
var csvBuffer bytes.Buffer
|
||||
vars := mux.Vars(r)
|
||||
tempID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
http.Error(w, "Bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
var visits []Visit
|
||||
tapit.db.Where(Visit{JobId: uint(tempID)}).Find(&visits)
|
||||
|
||||
// generate csv
|
||||
csvWriter := csv.NewWriter(&csvBuffer)
|
||||
csvWriter.Write([]string{"ID", "Time", "Source IP", "User Agent", "Method", "Body Content", "Raw Request"})
|
||||
for _, visit := range visits {
|
||||
csvWriter.Write([]string{strconv.Itoa(int(visit.ID)), visit.CreatedAt.String(), visit.SourceIp, visit.UserAgent, visit.Method, visit.BodyContent, visit.RawRequest})
|
||||
}
|
||||
csvWriter.Flush()
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\"results.csv\"")
|
||||
w.Write(csvBuffer.Bytes())
|
||||
return
|
||||
} else {
|
||||
http.Error(w, "HTTP method not implemented", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.2 KiB |
BIN
tapit-build/static/logo.png
Normal file
BIN
tapit-build/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -20,7 +20,8 @@
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
"src/assets",
|
||||
"src/logo.png"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
@@ -133,4 +134,4 @@
|
||||
}
|
||||
},
|
||||
"defaultProject": "tapit-frontend"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import { TextTemplateComponent } from './text-template/text-template.component';
|
||||
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
import { WebTemplateComponent } from './web-template/web-template.component';
|
||||
import { WebTemplateNewComponent } from './web-template-new/web-template-new.component';
|
||||
import { GlobalSettingsComponent } from './global-settings/global-settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: MainComponent },
|
||||
@@ -28,6 +31,10 @@ const routes: Routes = [
|
||||
{ path: 'text-template/new', component: TextTemplateNewComponent },
|
||||
{ path: 'text-template/:id/edit', component: TextTemplateNewComponent },
|
||||
{ path: 'provider', component: ProviderComponent },
|
||||
{ path: 'web-template', component: WebTemplateComponent },
|
||||
{ path: 'web-template/new', component: WebTemplateNewComponent },
|
||||
{ path: 'web-template/:id/edit', component: WebTemplateNewComponent },
|
||||
{ path: 'global-settings', component: GlobalSettingsComponent },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" routerLink="/">Tap It!</a>
|
||||
<a class="navbar-brand" routerLink="/"><img src="logo.png" height="30px" alt="Tap It!"></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" routerLink="/profile">Profile</a>
|
||||
<a class="dropdown-item" routerLink="/provider">Providers</a>
|
||||
<a class="dropdown-item" routerLink="/globalsettings">Global Settings</a>
|
||||
<a class="dropdown-item" routerLink="/global-settings">Global Settings</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -18,6 +18,9 @@ import { RegisterComponent } from './register/register.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
import { WebTemplateComponent } from './web-template/web-template.component';
|
||||
import { WebTemplateNewComponent } from './web-template-new/web-template-new.component';
|
||||
import { GlobalSettingsComponent } from './global-settings/global-settings.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -34,7 +37,10 @@ import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
RegisterComponent,
|
||||
ProviderComponent,
|
||||
ProfileComponent,
|
||||
CampaignViewComponent
|
||||
CampaignViewComponent,
|
||||
WebTemplateComponent,
|
||||
WebTemplateNewComponent,
|
||||
GlobalSettingsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -23,7 +23,7 @@ export class UserNotification {
|
||||
})
|
||||
export class AuthService {
|
||||
currUser = new User();
|
||||
loggedin = false;
|
||||
loggedin = false; // change this for testing
|
||||
loginUrl = 'api/login';
|
||||
logoutUrl = 'api/logout';
|
||||
registerUrl = 'api/register';
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
<option *ngFor="let textTemplate of textTemplateService.textTemplates" [ngValue]="textTemplate.id">{{textTemplate.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="web-template-select">Web Template</label>
|
||||
<select class="form-control" (change)="updatePreviews()" [(ngModel)]="newCampaign.webTemplateId" id="web-template-select">
|
||||
<option [ngValue]="0"></option>
|
||||
<option *ngFor="let webTemplate of webTemplateService.webTemplates" [ngValue]="webTemplate.id">{{webTemplate.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 d-flex">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { ProviderService } from '../provider.service';
|
||||
import { PhonebookService } from '../phonebook.service';
|
||||
import { TextTemplateService } from '../text-template.service';
|
||||
import { WebTemplateService } from '../web-template.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-new',
|
||||
@@ -17,7 +18,9 @@ export class CampaignNewComponent implements OnInit {
|
||||
private router: Router,
|
||||
private providerService: ProviderService,
|
||||
private phonebookService: PhonebookService,
|
||||
private textTemplateService: TextTemplateService) { }
|
||||
private textTemplateService: TextTemplateService,
|
||||
private webTemplateService: WebTemplateService,
|
||||
) { }
|
||||
|
||||
newCampaign: Campaign = new Campaign();
|
||||
|
||||
@@ -43,6 +46,7 @@ export class CampaignNewComponent implements OnInit {
|
||||
tempStr = tempStr.replace('{lastName}', phonebook.records[0].lastName);
|
||||
tempStr = tempStr.replace('{alias}', phonebook.records[0].alias);
|
||||
tempStr = tempStr.replace('{phoneNumber}', phonebook.records[0].phoneNumber);
|
||||
tempStr = tempStr.replace('{url}', 'https://www.example.com/eR2c1');
|
||||
|
||||
this.previewStr = tempStr;
|
||||
});
|
||||
@@ -55,6 +59,7 @@ export class CampaignNewComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.newCampaign.textTemplateId = 0;
|
||||
this.newCampaign.webTemplateId = 0;
|
||||
this.newCampaign.phonebookId = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
.campaign-details:read-only {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.download-visits {
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-visits:hover {
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
<th scope="col">From</th>
|
||||
<th scope="col">To</th>
|
||||
<th scope="col">Currrent Status</th>
|
||||
<th scope="col">Web Status</th>
|
||||
<th scope="col">Web Route URL</th>
|
||||
<th scope="col">Time Sent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -47,6 +49,8 @@
|
||||
<td>{{ job.fromNum }}</td>
|
||||
<td>{{ job.toNum }}</td>
|
||||
<td>{{ job.currentStatus }}</td>
|
||||
<td><span class="download-visits" (click)="downloadVisits(job.id)">{{ job.webStatus }}</span></td>
|
||||
<td><a href="{{ job.fullUrl }}">{{ job.fullUrl }}</a></td>
|
||||
<td>{{ job.timeSent | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { CampaignService, Campaign, Job, CampaignNotification } from '../campaign.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { NotificationService } from '../notification.service';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-view',
|
||||
@@ -18,9 +19,28 @@ export class CampaignViewComponent implements OnInit {
|
||||
private campaignService: CampaignService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private notificationService: NotificationService
|
||||
private notificationService: NotificationService,
|
||||
private http: HttpClient
|
||||
) { }
|
||||
|
||||
downloadVisits(id: string) {
|
||||
fetch('/api/jobs/' + id + '/visits')
|
||||
.then(resp => resp.blob())
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
// the filename you want
|
||||
a.download = 'visits-' + id + '.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
this.notificationService.addNotification('success', 'Successfully retrived web visits');
|
||||
})
|
||||
.catch(() => this.notificationService.addNotification('failure', 'Failed to download web visits'));
|
||||
}
|
||||
|
||||
startCampaign() {
|
||||
this.campaignService.startCampaign(this.currCampaign).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
|
||||
@@ -21,9 +21,11 @@ export class Campaign {
|
||||
export class Job {
|
||||
id: number;
|
||||
currentStatus: string;
|
||||
webStatus: string;
|
||||
timeSent: Date;
|
||||
fromNum: string;
|
||||
toNum: string;
|
||||
fullUrl: string;
|
||||
}
|
||||
|
||||
export class CampaignNotification {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Web Status</th>
|
||||
<th scope="col">Target Size</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
@@ -20,6 +21,7 @@
|
||||
<tr routerLink="/campaign/{{ campaign.id }}/view">
|
||||
<td>{{ campaign.name }}</td>
|
||||
<td>{{ campaign.currentStatus }}</td>
|
||||
<td>{{ campaign.webStatus }}</td>
|
||||
<td>{{ campaign.size }}</td>
|
||||
<td>{{ campaign.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
|
||||
12
tapit-frontend/src/app/global-settings.service.spec.ts
Normal file
12
tapit-frontend/src/app/global-settings.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GlobalSettingsService } from './global-settings.service';
|
||||
|
||||
describe('GlobalSettingsService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: GlobalSettingsService = TestBed.get(GlobalSettingsService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
60
tapit-frontend/src/app/global-settings.service.ts
Normal file
60
tapit-frontend/src/app/global-settings.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export class GlobalSettings {
|
||||
secretRegistrationCode: string;
|
||||
threadsPerCampaign: number;
|
||||
bcryptCost: number;
|
||||
maxRequestRetries: number;
|
||||
waitBeforeRetry: number;
|
||||
webTemplatePrefix: string;
|
||||
webTemplateRoute: string;
|
||||
}
|
||||
|
||||
export class GlobalSettingsNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: GlobalSettings;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GlobalSettingsService {
|
||||
globalSettings = new GlobalSettings();
|
||||
|
||||
globalSettingsUrl = 'api/globalsettings';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
getGlobalSettings() {
|
||||
this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions).subscribe(globalSettings => {
|
||||
this.globalSettings = globalSettings;
|
||||
});
|
||||
}
|
||||
|
||||
getGlobalSettingsObs(): Observable<GlobalSettings> {
|
||||
return this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions);
|
||||
}
|
||||
|
||||
updateGlobalSettings(globalSettings: GlobalSettings) {
|
||||
this.http.put<GlobalSettingsNotification>(this.globalSettingsUrl, globalSettings, this.httpOptions).subscribe(settingsMessage => {
|
||||
this.notificationService.addNotification(settingsMessage.resultType, settingsMessage.text);
|
||||
this.globalSettings = settingsMessage.payload;
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in updating settings');
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.getGlobalSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<h4>Global TapIt Settings</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="registration-code" class="pr-2 mt-auto mb-auto">Secret Registration Code</label>
|
||||
<input type="text" class="flex-grow-1" id="registration-code" [(ngModel)]="displaySettings.secretRegistrationCode" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="threads" class="pr-2 mt-auto mb-auto">SMS Threads Per Campaign</label>
|
||||
<input type="text" class="flex-grow-1" id="threads" [(ngModel)]="displaySettings.threadsPerCampaign" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="bcrypt-cost" class="pr-2 mt-auto mb-auto">BCrypt Cost</label>
|
||||
<input type="text" class="flex-grow-1" id="bcrypt-cost" [(ngModel)]="displaySettings.bcryptCost" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="max-retries" class="pr-2 mt-auto mb-auto">Max Request Retries</label>
|
||||
<input type="text" class="flex-grow-1" id="max-retries" [(ngModel)]="displaySettings.maxRequestRetries" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="wait-time" class="pr-2 mt-auto mb-auto">Wait Before Retry (ms) </label>
|
||||
<input type="text" class="flex-grow-1" id="wait-time" [(ngModel)]="displaySettings.waitBeforeRetry" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="web-prefix" class="pr-2 mt-auto mb-auto">Web URL Prefix</label>
|
||||
<input type="text" class="flex-grow-1" id="web-prefix" [(ngModel)]="displaySettings.webTemplatePrefix" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="web-route" class="pr-2 mt-auto mb-auto">Web Route</label>
|
||||
<input type="text" class="flex-grow-1" id="web-route" [(ngModel)]="displaySettings.webTemplateRoute" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="updateGlobalSettings()" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GlobalSettingsComponent } from './global-settings.component';
|
||||
|
||||
describe('GlobalSettingsComponent', () => {
|
||||
let component: GlobalSettingsComponent;
|
||||
let fixture: ComponentFixture<GlobalSettingsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ GlobalSettingsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GlobalSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { GlobalSettings, GlobalSettingsService } from '../global-settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-settings',
|
||||
templateUrl: './global-settings.component.html',
|
||||
styleUrls: ['./global-settings.component.css']
|
||||
})
|
||||
export class GlobalSettingsComponent implements OnInit {
|
||||
|
||||
tempSettings = new GlobalSettings();
|
||||
displaySettings;
|
||||
|
||||
updateGlobalSettings() {
|
||||
this.tempSettings.secretRegistrationCode = this.displaySettings.secretRegistrationCode;
|
||||
this.tempSettings.webTemplatePrefix = this.displaySettings.webTemplatePrefix;
|
||||
this.tempSettings.webTemplateRoute = this.displaySettings.webTemplateRoute;
|
||||
this.tempSettings.threadsPerCampaign = parseInt(this.displaySettings.threadsPerCampaign, 10) + 0;
|
||||
this.tempSettings.bcryptCost = parseInt(this.displaySettings.bcryptCost, 10);
|
||||
this.tempSettings.maxRequestRetries = parseInt(this.displaySettings.maxRequestRetries, 10);
|
||||
this.tempSettings.waitBeforeRetry = parseInt(this.displaySettings.waitBeforeRetry, 10);
|
||||
|
||||
this.globalSettingsService.updateGlobalSettings(this.tempSettings);
|
||||
}
|
||||
|
||||
constructor(private globalSettingsService: GlobalSettingsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.globalSettingsService.getGlobalSettingsObs().subscribe(settings => {
|
||||
console.log(settings);
|
||||
this.displaySettings = settings;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
<li>{{ '{' }}lastName{{ '}' }}</li>
|
||||
<li>{{ '{' }}alias{{ '}' }}</li>
|
||||
<li>{{ '{' }}phoneNumber{{ '}' }}</li>
|
||||
<li>{{ '{' }}url{{ '}' }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,6 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
|
||||
@@ -28,6 +28,7 @@ export class TextTemplateNewComponent implements OnInit {
|
||||
tempStr = tempStr.replace('{lastName}', 'Smith');
|
||||
tempStr = tempStr.replace('{alias}', 'Johnny');
|
||||
tempStr = tempStr.replace('{phoneNumber}', '+6598765432');
|
||||
tempStr = tempStr.replace('{url}', 'https://www.example.com/eR2c1');
|
||||
|
||||
this.previewStr = tempStr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Web Template Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newWebTemplate.name" placeholder="Web Template Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="web-template-type">Web Template Type</label>
|
||||
<select class="form-control" [(ngModel)]="newWebTemplate.templateType" id="web-template-type">
|
||||
<option></option>
|
||||
<option *ngFor="let templateEnum of webTemplateService.wTemplateEnum" [ngValue]="templateEnum.tag">{{templateEnum.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- for redirect -->
|
||||
<ng-container *ngIf="newWebTemplate.templateType === 'redirect'">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="redirect-url" class="pr-2 mt-auto mb-auto">Redirect URL</label>
|
||||
<input type="text" class="flex-grow-1" id="redirect-url" [(ngModel)]="newWebTemplate.redirectUrl" placeholder="https://www.attacker.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="positive-redirect" class="pr-2 mt-auto mb-auto">Positive Redirect UA (Comma separated, leave blank if unused)</label>
|
||||
<input type="text" class="flex-grow-1" id="positive-redirect" [(ngModel)]="newWebTemplate.redirectAgent" placeholder="Windows NT">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="negative-redirect" class="pr-2 mt-auto mb-auto">Negative Redirect UA (Comma Seperated, leave blank if unused)</label>
|
||||
<input type="text" class="flex-grow-1" id="negative-redirect" [(ngModel)]="newWebTemplate.redirectNegAgent" placeholder="Android,iPhone">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex">
|
||||
<p><small><em>Use only either 'Positive' or 'Negative' redirect. DO NOT USE BOTH.</em></small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="placeholder-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label>
|
||||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.redirectPlaceholderHtml" id="placeholder-html" rows="6"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="newWebTemplate.templateType === 'harvester'">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="placeholder-harvest-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label>
|
||||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterBeforeHtml" id="placeholder-harvest-html" rows="6"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="after-html" class="pr-2 mt-auto mb-auto">After HTML</label>
|
||||
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterAfterHtml" id="after-html" rows="6"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewWebTemplate()" class="btn btn-primary ml-2">Save Web Template</button>
|
||||
<button type="button" *ngIf="router.url !== '/web-template/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ newWebTemplate.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the text template?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deleteWebTemplate()" data-dismiss="modal">Delete Web Template</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebTemplateNewComponent } from './web-template-new.component';
|
||||
|
||||
describe('WebTemplateNewComponent', () => {
|
||||
let component: WebTemplateNewComponent;
|
||||
let fixture: ComponentFixture<WebTemplateNewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ WebTemplateNewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(WebTemplateNewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WebTemplateService, WebTemplate } from '../web-template.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-web-template-new',
|
||||
templateUrl: './web-template-new.component.html',
|
||||
styleUrls: ['./web-template-new.component.css']
|
||||
})
|
||||
export class WebTemplateNewComponent implements OnInit {
|
||||
|
||||
newWebTemplate: WebTemplate = new WebTemplate();
|
||||
id = 0;
|
||||
|
||||
submitNewWebTemplate() {
|
||||
if (this.router.url === '/web-template/new') {
|
||||
this.webTemplateService.addWebTemplate(this.newWebTemplate);
|
||||
} else {
|
||||
this.editWebTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
deleteWebTemplate() {
|
||||
this.webTemplateService.deleteWebTemplate(this.newWebTemplate);
|
||||
}
|
||||
|
||||
editWebTemplate() {
|
||||
this.webTemplateService.editWebTemplate(this.newWebTemplate);
|
||||
}
|
||||
|
||||
constructor(private webTemplateService: WebTemplateService, private router: Router, private route: ActivatedRoute) { }
|
||||
|
||||
ngOnInit() {
|
||||
// if page is edit
|
||||
if (this.router.url !== '/web-template/new') {
|
||||
const idParam = 'id';
|
||||
this.route.params.subscribe( params => {
|
||||
this.id = parseInt(params[idParam], 10);
|
||||
this.webTemplateService.getWebTemplateObs(this.id).subscribe(currWT => {
|
||||
this.newWebTemplate = currWT;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/web-template.service.spec.ts
Normal file
12
tapit-frontend/src/app/web-template.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebTemplateService } from './web-template.service';
|
||||
|
||||
describe('WebTemplateService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: WebTemplateService = TestBed.get(WebTemplateService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
97
tapit-frontend/src/app/web-template.service.ts
Normal file
97
tapit-frontend/src/app/web-template.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class WebTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
templateType: string; // enum redirect, harvester
|
||||
redirectAgent: string;
|
||||
redirectNegAgent: string;
|
||||
redirectPlaceholderHtml: string;
|
||||
redirectUrl: string;
|
||||
harvesterBeforeHtml: string;
|
||||
harvesterAfterHtml: string;
|
||||
createDate: Date;
|
||||
}
|
||||
|
||||
export class WebTemplateNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: WebTemplate;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebTemplateService {
|
||||
|
||||
wTemplateEnum = [
|
||||
{name: 'Redirect Based On User Agent', tag: 'redirect'},
|
||||
{name: 'Credentials Harvesting', tag: 'harvester'},
|
||||
];
|
||||
|
||||
templateUrl = '/api/web-template';
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
webTemplates: WebTemplate[] = [];
|
||||
|
||||
getWebTemplates() {
|
||||
this.http.get<WebTemplate[]>(this.templateUrl).subscribe(templates => {
|
||||
if (templates === null) {
|
||||
this.webTemplates = [];
|
||||
} else {
|
||||
this.webTemplates = templates;
|
||||
}
|
||||
});
|
||||
}
|
||||
addWebTemplate(newWebTemplate: WebTemplate) {
|
||||
this.http.post<WebTemplateNotification>(this.templateUrl, newWebTemplate, this.httpOptions).subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
this.webTemplates.push(templateNotification.payload);
|
||||
if (templateNotification.payload !== null) {
|
||||
this.router.navigate(['/web-template']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
deleteWebTemplate(webTemplate: WebTemplate) {
|
||||
this.http.delete<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), this.httpOptions)
|
||||
.subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
this.router.navigate(['/web-template']);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in deleting web template');
|
||||
});
|
||||
}
|
||||
|
||||
editWebTemplate(webTemplate: WebTemplate) {
|
||||
this.http.put<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), webTemplate, this.httpOptions)
|
||||
.subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
if (templateNotification.payload !== null) {
|
||||
this.router.navigate(['/web-template']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in editing web template');
|
||||
});
|
||||
}
|
||||
|
||||
getWebTemplateObs(id: number) {
|
||||
return this.http.get<WebTemplate>(this.templateUrl + '/' + id.toString());
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.getWebTemplates();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" routerLink="/web-template/new">New Web Template</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let webTemplate of webTemplateService.webTemplates">
|
||||
<tr routerLink="/web-template/{{ webTemplate.id }}/edit">
|
||||
<td>{{ webTemplate.name }}</td>
|
||||
<td>{{ webTemplate.templateType }}</td>
|
||||
<td>{{ webTemplate.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<p *ngIf="webTemplateService.webTemplates.length === 0">No web template created yet. Create templates by clicking <a routerLink="/web-template/new">here</a></p>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebTemplateComponent } from './web-template.component';
|
||||
|
||||
describe('WebTemplateComponent', () => {
|
||||
let component: WebTemplateComponent;
|
||||
let fixture: ComponentFixture<WebTemplateComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ WebTemplateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(WebTemplateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WebTemplateService } from '../web-template.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-web-template',
|
||||
templateUrl: './web-template.component.html',
|
||||
styleUrls: ['./web-template.component.css']
|
||||
})
|
||||
export class WebTemplateComponent implements OnInit {
|
||||
|
||||
constructor(private webTemplateService: WebTemplateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.webTemplateService.getWebTemplates();
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.2 KiB |
BIN
tapit-frontend/src/logo.png
Normal file
BIN
tapit-frontend/src/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user