all repos — felt @ main

virtual tabletop for dungeons and dragons (and similar) using Go, MongoDB, and websockets

register/register.go (raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package register

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/hex"
	"html/template"
	"net/http"
	"strconv"
	"time"

	"hacklab.nilfm.cc/quartzgun/auth"
	"hacklab.nilfm.cc/quartzgun/renderer"
	"hacklab.nilfm.cc/quartzgun/router"
	"hacklab.nilfm.cc/quartzgun/util"
)

type SymmetricCrypto interface {
	Encode(b []byte) string
	Decode(s string) []byte
	Encrypt(text string) (string, error)
	Decrypt(text string) (string, error)
	IsValid(text string) bool
}

type SymmetricCrypt struct {
	Iv     []byte
	Secret string
}

func (self *SymmetricCrypt) IsValid(cipher string) bool {
	stringTimestamp, err := self.Decrypt(cipher)
	if err != nil {
		return false
	}
	int64Timestamp, err := strconv.ParseInt(stringTimestamp, 10, 64)
	if err != nil {
		return false
	}
	then := time.UnixMicro(int64Timestamp)
	return time.Since(then).Minutes() <= 15
}

func (self *SymmetricCrypt) Encode(b []byte) string {
	return hex.EncodeToString(b)
}

func (self *SymmetricCrypt) Decode(s string) []byte {
	data, err := hex.DecodeString(s)
	if err != nil {
		panic(err)
	}
	return data
}

func (self *SymmetricCrypt) Encrypt(text string) (string, error) {
	block, err := aes.NewCipher([]byte(self.Secret))
	if err != nil {
		return "", err
	}
	plainText := []byte(text)
	cfb := cipher.NewCFBEncrypter(block, self.Iv)
	cipherText := make([]byte, len(plainText))
	cfb.XORKeyStream(cipherText, plainText)
	return self.Encode(cipherText), nil
}

func (self *SymmetricCrypt) Decrypt(text string) (string, error) {
	block, err := aes.NewCipher([]byte(self.Secret))
	if err != nil {
		return "", err
	}
	cipherText := self.Decode(text)
	cfb := cipher.NewCFBDecrypter(block, self.Iv)
	plainText := make([]byte, len(cipherText))
	cfb.XORKeyStream(plainText, cipherText)
	return string(plainText), nil
}

func WithCrypto(next http.Handler, crypto SymmetricCrypto) http.Handler {
	handlerFunc := func(w http.ResponseWriter, req *http.Request) {
		util.AddContextValue(req, "crypto", crypto)
		next.ServeHTTP(w, req)
	}

	return http.HandlerFunc(handlerFunc)
}

func WithUserStoreAndCrypto(next http.Handler, udb auth.UserStore, crypto SymmetricCrypto) http.Handler {
	handlerFunc := func(w http.ResponseWriter, req *http.Request) {
		urlParams := req.Context().Value("params").(map[string]string)
		success := false
		cipher := urlParams["cipher"]
		username := req.FormValue("username")
		password := req.FormValue("password")
		if crypto.IsValid(cipher) && len(username) > 0 && len(password) > 0 {
			success = udb.AddUser(username, password) == nil
		}
		util.AddContextValue(req, "success", success)
		next.ServeHTTP(w, req)
	}

	return http.HandlerFunc(handlerFunc)
}

func CreateRegistrationInterface(udb auth.UserStore, crypto SymmetricCrypto) http.Handler {
	rtr := &router.Router{Fallback: *template.Must(template.ParseFiles("templates/error.html"))}

	rtr.Get(`/(?P<cipher>\S+)`, WithCrypto(renderer.Template("templates/register.html"), crypto))
	rtr.Post(`/(?P<cipher>\S+)`, WithUserStoreAndCrypto(renderer.Template("templates/registered.html"), udb, crypto))

	return http.HandlerFunc(rtr.ServeHTTP)
}