commit 8ccc3e303efaf16110100aa1aec9a10cd0d000c6 Author: Alvie Rahman Date: Thu May 26 02:08:50 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7800f0f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +redis diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6229964 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.16 + +WORKDIR /go/src/app +COPY . . + +RUN go get -d -v ./... +RUN go install -v ./... + +CMD ["sus"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..995c85b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + sus: + build: . + ports: [ "80:80" ] + environment: + - SECRET=secret + redis: + image: redis:7 + volumes: [ "./redis:/data" ] + ports: [ "6379:6379" ] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f55964d --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module git.alv.cx/alvierahman90/sus + +go 1.18 + +require ( + github.com/go-redis/redis/v8 v8.11.5 + github.com/gorilla/mux v1.8.0 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f +) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/onsi/gomega v1.19.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f663b3b --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5ece28e --- /dev/null +++ b/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "os" + + "github.com/go-redis/redis/v8" + "github.com/gorilla/mux" + "golang.org/x/net/context" +) + +var client = redis.NewClient(&redis.Options{ + Addr: "redis:6379", + Password: "", + DB: 0, +}) + +var SECRET string + +func main() { + r := mux.NewRouter() + r.HandleFunc("/{shortlink}", shortlinkHandler) + r.HandleFunc("/", indexHandler) + + listenAddress := "0.0.0.0:80" + + if p, ok := os.LookupEnv("LISTEN_ADDRESS"); ok { + listenAddress = p + } + + if p, ok := os.LookupEnv("SECRET"); ok { + SECRET = p + } + + log.Fatal(http.ListenAndServe(listenAddress, r)) +} + +func indexHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("indexHandler called") + if r.Method != "POST" { + http.Redirect(w, r, "http://alv.cx", 302) + return + } + + r.ParseForm() + + command := r.PostForm.Get("Command") + shortlink := r.PostForm.Get("Shortlink") + value := r.PostForm.Get("Value") + fmt.Println(shortlink) + fmt.Println(value) + + signature := r.Header.Get("Signature") + calculatedSignature := fmt.Sprintf( + "SUS-SIGNATURE-%v", + getSha256HMACSignature( + []byte(SECRET), + command+":"+shortlink+":"+value, + ), + ) + + if signature != calculatedSignature { + fmt.Println("signature do no match") + fmt.Println(signature) + fmt.Println(calculatedSignature) + w.WriteHeader(401) + w.Write([]byte("401 Unauthorized")) + return + } + + if command == "create" { + + ctx := context.Background() + _, err := client.Get(ctx, shortlink).Result() + if err == redis.Nil { + fmt.Printf("shortlink: %v, value: %v", shortlink, value) + err = client.Set(ctx, shortlink, value, 0).Err() + + if err != nil { + fmt.Println(err) + w.WriteHeader(500) + w.Write([]byte("500 Internal Server Error")) + return + } + + w.WriteHeader(200) + w.Write([]byte("200 Success")) + + return + + } else if err != nil { + fmt.Println(err) + w.WriteHeader(500) + w.Write([]byte("500 Internal Server Error")) + return + } + + fmt.Println(err) + w.WriteHeader(403) + w.Write([]byte("403 Forbidden")) + return + } + + if command == "delete" { + if value != "confirm" { + w.WriteHeader(400) + w.Write([]byte("400 Bad Request")) + } + + ctx := context.Background() + if err := client.Del(ctx, shortlink).Err(); err != nil { + w.WriteHeader(500) + w.Write([]byte("500 Internal Server Error")) + } + } +} + +func shortlinkHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("shortlinkHandler called") + shortlink := string(mux.Vars(r)["shortlink"]) + ctx := context.Background() + redirect, err := client.Get(ctx, shortlink).Result() + fmt.Printf("shortlink: %v, redirect: %v\n", shortlink, redirect) + if err == redis.Nil { + w.WriteHeader(404) + w.Write([]byte("404 Not Found")) + return + } else if err != nil { + fmt.Println(err) + w.WriteHeader(500) + w.Write([]byte("500 Internal Server Error")) + return + } + + http.Redirect(w, r, redirect, 302) + return +} + +func getSha256HMACSignature(secret []byte, data string) string { + h := hmac.New(sha256.New, secret) + io.WriteString(h, data) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/readme b/readme new file mode 100644 index 0000000..614061f --- /dev/null +++ b/readme @@ -0,0 +1,4 @@ +sus +=== + +simple URL shortener diff --git a/sustool.py b/sustool.py new file mode 100755 index 0000000..10238d1 --- /dev/null +++ b/sustool.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import sys +import requests +import hmac +import hashlib + + +def get_args(): + """ Get command line arguments """ + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('susserver') + parser.add_argument('command') + parser.add_argument('shortlink') + parser.add_argument('value') + parser.add_argument('--secret', default="secret") + parser.add_argument('--http', action='store_true') + return parser.parse_args() + + +def main(args): + """ Entry point for script """ + r = requests.post(f"{'http' if args.http else 'https'}://{args.susserver}", + data = { + 'Command': args.command, + 'Shortlink': args.shortlink, + 'Value': args.value, + }, + headers = { + 'Signature': 'SUS-SIGNATURE-' + hmac.new( + args.secret.encode("UTF-8"), + (args.command+":"+args.shortlink+":"+args.value).encode("UTF-8"), + hashlib.sha256 + ).hexdigest() + } + ) + print(r) + print(r.content) + return 0 + + +if __name__ == '__main__': + try: + sys.exit(main(get_args())) + except KeyboardInterrupt: + sys.exit(0)