Inserted web templates, readme, license. Updated logo and favicon

This commit is contained in:
2019-07-06 15:55:06 +08:00
parent d606cc129b
commit 31796678ae
54 changed files with 1329 additions and 51 deletions

11
LICENSE Normal file
View 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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"
}

View 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
}
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)
}

View 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

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.

View File

@@ -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"
}
}

View File

@@ -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({

View File

@@ -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>

View File

@@ -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,

View File

@@ -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';

View File

@@ -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">

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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>

View 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();
});
});

View 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();
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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;
});
}
}

View File

@@ -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">

View File

@@ -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;
}

View File

@@ -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">&times;</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>

View File

@@ -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();
});
});

View File

@@ -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;
});
});
}
}
}

View 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();
});
});

View 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();
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB