email auth works
This commit is contained in:
parent
434f72fa69
commit
e6021d6ae7
152
chatserver.go
152
chatserver.go
|
@ -5,25 +5,32 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type ConfMailer struct {
|
||||
Url string `yaml:"url,omitempty"`
|
||||
ApiKey string `yaml:"api_key,omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
}
|
||||
type Conf struct {
|
||||
Port uint `yaml:"port,omitempty"`
|
||||
Mailer struct {
|
||||
ApiKey string `yaml:"api_key,omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
}
|
||||
Mailer ConfMailer
|
||||
}
|
||||
|
||||
type bufferedConn struct {
|
||||
|
@ -55,10 +62,12 @@ type myMsg struct {
|
|||
sender net.Conn
|
||||
bytes []byte
|
||||
receivedAt time.Time
|
||||
channel string
|
||||
}
|
||||
|
||||
var firstMsgs chan myMsg
|
||||
var myMsgs chan myMsg
|
||||
var myChans map[string](chan myMsg)
|
||||
//var myMsgs chan myMsg
|
||||
var myUnsortedConns map[net.Conn]bool
|
||||
var myRawConns map[net.Conn]bool
|
||||
var newConns chan net.Conn
|
||||
|
@ -71,6 +80,18 @@ func usage() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// https://blog.questionable.services/article/generating-secure-random-numbers-crypto-rand/
|
||||
func genAuthCode() (string, error) {
|
||||
n := 12
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func handleRaw(conn bufferedConn) {
|
||||
// TODO
|
||||
// What happens if this is being read from range
|
||||
|
@ -78,29 +99,72 @@ func handleRaw(conn bufferedConn) {
|
|||
// Should I use a channel here instead?
|
||||
// TODO see https://jameshfisher.com/2017/04/18/golang-tcp-server.html
|
||||
|
||||
var email string
|
||||
var code string
|
||||
var authn bool
|
||||
|
||||
// Handle all subsequent packets
|
||||
buf := make([]byte, 1024)
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n");
|
||||
count, err := conn.Read(buf)
|
||||
count, err := conn.Read(buffer)
|
||||
if nil != err {
|
||||
if io.EOF != err {
|
||||
fmt.Fprintf(os.Stderr, "Non-EOF socket error: %s\n", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Ending socket\n")
|
||||
|
||||
// TODO put this in a channel to prevent data races
|
||||
conn.Close();
|
||||
delete(myRawConns, conn)
|
||||
break
|
||||
}
|
||||
buf := buffer[:count]
|
||||
|
||||
// Fun fact: if the buffer's current length (not capacity) is 0
|
||||
// then the Read returns 0 without error
|
||||
if 0 == count {
|
||||
fmt.Fprintf(os.Stdout, "Weird")
|
||||
break
|
||||
}
|
||||
|
||||
if !authn {
|
||||
if "" == email {
|
||||
fmt.Fprintf(os.Stdout, "buf{%s}\n", buf[:count])
|
||||
// TODO use safer email testing
|
||||
email = strings.TrimSpace(string(buf[:count]))
|
||||
emailParts := strings.Split(email, "@")
|
||||
if 2 != len(emailParts) {
|
||||
fmt.Fprintf(conn, "Email: ")
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "email: '%v'\n", []byte(email))
|
||||
code, err = sendAuthCode(config.Mailer, strings.TrimSpace(email))
|
||||
if nil != err {
|
||||
// TODO handle better
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintf(conn, "Auth Code: ")
|
||||
continue
|
||||
}
|
||||
|
||||
if code != strings.TrimSpace(string(buf[:count])) {
|
||||
fmt.Fprintf(conn, "Incorrect Code\nAuth Code: ")
|
||||
} else {
|
||||
authn = true
|
||||
fmt.Fprintf(conn, "Welcome to #general! (TODO `/help' for list of commands)\n")
|
||||
// TODO number of users
|
||||
//fmt.Fprintf(conn, "Welcome to #general! TODO `/list' to see channels. `/join chname' to switch.\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Queing message...\n");
|
||||
myMsgs <- myMsg{
|
||||
myChans["general"] <- myMsg{
|
||||
receivedAt: time.Now(),
|
||||
sender: conn,
|
||||
bytes: buf[0:count],
|
||||
channel: "general",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +181,7 @@ func handleSorted(conn bufferedConn) {
|
|||
receivedAt: time.Now(),
|
||||
sender: conn,
|
||||
bytes: firstMsg,
|
||||
channel: "general",
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
@ -142,28 +207,29 @@ func handleSorted(conn bufferedConn) {
|
|||
// fmt.Fprintf(os.Stdout, "Weird")
|
||||
continue
|
||||
}
|
||||
myMsgs <- myMsg{
|
||||
myChans["general"] <- myMsg{
|
||||
receivedAt: time.Now(),
|
||||
sender: conn,
|
||||
bytes: buf[0:count],
|
||||
channel: "general",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO https://github.com/polvi/sni
|
||||
func handleConnection(conn net.Conn) {
|
||||
func handleConnection(netConn net.Conn) {
|
||||
fmt.Fprintf(os.Stdout, "Accepting socket\n")
|
||||
|
||||
m := sync.Mutex{}
|
||||
virgin := true
|
||||
myUnsortedConns[conn] = true
|
||||
|
||||
// Why don't these work?
|
||||
//buf := make([]byte, 0, 1024)
|
||||
//buf := []byte{}
|
||||
// But this does
|
||||
|
||||
bufConn := newBufferedConn(conn)
|
||||
bufConn := newBufferedConn(netConn)
|
||||
myUnsortedConns[bufConn] = true
|
||||
go func() {
|
||||
// Handle First Packet
|
||||
fmsg, err := bufConn.Peek(1)
|
||||
|
@ -177,6 +243,8 @@ func handleConnection(conn net.Conn) {
|
|||
virgin = false
|
||||
go handleSorted(bufConn)
|
||||
} else {
|
||||
// TODO probably needs to go into a channel
|
||||
myRawConns[bufConn] = true
|
||||
go handleRaw(bufConn)
|
||||
}
|
||||
m.Unlock();
|
||||
|
@ -188,22 +256,65 @@ func handleConnection(conn net.Conn) {
|
|||
m.Lock()
|
||||
if virgin {
|
||||
virgin = false
|
||||
// TODO probably needs to go into a channel
|
||||
myRawConns[conn] = true
|
||||
// don't block for this
|
||||
// let it be handled after the unlock
|
||||
defer fmt.Fprintf(conn, "Welcome! This is an open relay chat server. There is no security yet.\n")
|
||||
defer fmt.Fprintf(netConn, "Welcome to Sample Chat! You appear to be using Telnet.\nYou must authenticate via email to participate\nEmail: ")
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func sendAuthCode(cnf ConfMailer, to string) (string, error) {
|
||||
code, err := genAuthCode()
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO use go text templates with HTML escaping
|
||||
text := "Your authorization code:\n\n" + code
|
||||
html := "Your authorization code:<br><br>" + code
|
||||
|
||||
// https://stackoverflow.com/questions/24493116/how-to-send-a-post-request-in-go
|
||||
// https://stackoverflow.com/questions/16673766/basic-http-auth-in-go
|
||||
client := http.Client{}
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("from", cnf.From)
|
||||
form.Add("to", to)
|
||||
form.Add("subject", "Sample Chat Auth Code: " + code)
|
||||
form.Add("text", text)
|
||||
form.Add("html", html)
|
||||
|
||||
req, err := http.NewRequest("POST", cnf.Url, strings.NewReader(form.Encode()))
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
//req.PostForm = form
|
||||
req.Header.Add("User-Agent", "golang http.Client - Sample Chat App Authenticator")
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetBasicAuth("api", cnf.ApiKey)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Here's what Mailgun had to say about the event: %s\n", body)
|
||||
|
||||
return code, nil
|
||||
}
|
||||
|
||||
var config Conf
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
|
||||
confname := flag.String("conf", "./config.yml", "yaml config file")
|
||||
flag.Parse()
|
||||
|
||||
var config Conf
|
||||
confstr, err := ioutil.ReadFile(*confname)
|
||||
fmt.Fprintf(os.Stdout, "-conf=%s\n", *confname)
|
||||
if nil != err {
|
||||
|
@ -216,11 +327,16 @@ func main() {
|
|||
}
|
||||
|
||||
firstMsgs = make(chan myMsg, 128)
|
||||
myMsgs = make(chan myMsg, 128)
|
||||
//myMsgs = make(chan myMsg, 128)
|
||||
myChans = make(map[string](chan myMsg))
|
||||
newConns = make(chan net.Conn, 128)
|
||||
myRawConns = make(map[net.Conn]bool)
|
||||
myUnsortedConns = make(map[net.Conn]bool)
|
||||
|
||||
// TODO dynamically select on channels?
|
||||
// https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement
|
||||
myChans["general"] = make(chan myMsg, 128)
|
||||
|
||||
var addr string
|
||||
if 0 != int(*port) {
|
||||
addr = ":" + strconv.Itoa(int(*port))
|
||||
|
@ -254,7 +370,7 @@ func main() {
|
|||
ts := time.Now()
|
||||
fmt.Fprintf(os.Stdout, "[Handle New Connection] [Timestamp] %s\n", ts)
|
||||
go handleConnection(conn)
|
||||
case msg := <- myMsgs:
|
||||
case msg := <- myChans["general"]:
|
||||
ts, err := msg.receivedAt.MarshalJSON()
|
||||
if nil != err {
|
||||
fmt.Fprintf(os.Stderr, "[Error] %s\n", err)
|
||||
|
|
Loading…
Reference in New Issue