email auth works

This commit is contained in:
AJ ONeal 2018-07-29 23:16:09 -06:00
parent 434f72fa69
commit e6021d6ae7
1 changed files with 134 additions and 18 deletions

View File

@ -5,25 +5,32 @@ package main
import ( import (
"bufio" "bufio"
"crypto/rand"
"encoding/base64"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http"
"net/url"
"os" "os"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"gopkg.in/yaml.v2" "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 { type Conf struct {
Port uint `yaml:"port,omitempty"` Port uint `yaml:"port,omitempty"`
Mailer struct { Mailer ConfMailer
ApiKey string `yaml:"api_key,omitempty"`
From string `yaml:"from,omitempty"`
}
} }
type bufferedConn struct { type bufferedConn struct {
@ -55,10 +62,12 @@ type myMsg struct {
sender net.Conn sender net.Conn
bytes []byte bytes []byte
receivedAt time.Time receivedAt time.Time
channel string
} }
var firstMsgs chan myMsg 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 myUnsortedConns map[net.Conn]bool
var myRawConns map[net.Conn]bool var myRawConns map[net.Conn]bool
var newConns chan net.Conn var newConns chan net.Conn
@ -71,6 +80,18 @@ func usage() {
os.Exit(1) 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) { func handleRaw(conn bufferedConn) {
// TODO // TODO
// What happens if this is being read from range // What happens if this is being read from range
@ -78,29 +99,72 @@ func handleRaw(conn bufferedConn) {
// Should I use a channel here instead? // Should I use a channel here instead?
// TODO see https://jameshfisher.com/2017/04/18/golang-tcp-server.html // 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 // Handle all subsequent packets
buf := make([]byte, 1024) buffer := make([]byte, 1024)
for { for {
fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n"); fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n");
count, err := conn.Read(buf) count, err := conn.Read(buffer)
if nil != err { if nil != err {
if io.EOF != err { if io.EOF != err {
fmt.Fprintf(os.Stderr, "Non-EOF socket error: %s\n", err) fmt.Fprintf(os.Stderr, "Non-EOF socket error: %s\n", err)
} }
fmt.Fprintf(os.Stdout, "Ending socket\n") fmt.Fprintf(os.Stdout, "Ending socket\n")
// TODO put this in a channel to prevent data races
conn.Close();
delete(myRawConns, conn)
break break
} }
buf := buffer[:count]
// Fun fact: if the buffer's current length (not capacity) is 0 // Fun fact: if the buffer's current length (not capacity) is 0
// then the Read returns 0 without error // then the Read returns 0 without error
if 0 == count { if 0 == count {
fmt.Fprintf(os.Stdout, "Weird") 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 continue
} }
fmt.Fprintf(os.Stdout, "Queing message...\n"); fmt.Fprintf(os.Stdout, "Queing message...\n");
myMsgs <- myMsg{ myChans["general"] <- myMsg{
receivedAt: time.Now(), receivedAt: time.Now(),
sender: conn, sender: conn,
bytes: buf[0:count], bytes: buf[0:count],
channel: "general",
} }
} }
} }
@ -117,6 +181,7 @@ func handleSorted(conn bufferedConn) {
receivedAt: time.Now(), receivedAt: time.Now(),
sender: conn, sender: conn,
bytes: firstMsg, bytes: firstMsg,
channel: "general",
} }
// TODO // TODO
@ -142,28 +207,29 @@ func handleSorted(conn bufferedConn) {
// fmt.Fprintf(os.Stdout, "Weird") // fmt.Fprintf(os.Stdout, "Weird")
continue continue
} }
myMsgs <- myMsg{ myChans["general"] <- myMsg{
receivedAt: time.Now(), receivedAt: time.Now(),
sender: conn, sender: conn,
bytes: buf[0:count], bytes: buf[0:count],
channel: "general",
} }
} }
} }
// TODO https://github.com/polvi/sni // TODO https://github.com/polvi/sni
func handleConnection(conn net.Conn) { func handleConnection(netConn net.Conn) {
fmt.Fprintf(os.Stdout, "Accepting socket\n") fmt.Fprintf(os.Stdout, "Accepting socket\n")
m := sync.Mutex{} m := sync.Mutex{}
virgin := true virgin := true
myUnsortedConns[conn] = true
// Why don't these work? // Why don't these work?
//buf := make([]byte, 0, 1024) //buf := make([]byte, 0, 1024)
//buf := []byte{} //buf := []byte{}
// But this does // But this does
bufConn := newBufferedConn(conn) bufConn := newBufferedConn(netConn)
myUnsortedConns[bufConn] = true
go func() { go func() {
// Handle First Packet // Handle First Packet
fmsg, err := bufConn.Peek(1) fmsg, err := bufConn.Peek(1)
@ -177,6 +243,8 @@ func handleConnection(conn net.Conn) {
virgin = false virgin = false
go handleSorted(bufConn) go handleSorted(bufConn)
} else { } else {
// TODO probably needs to go into a channel
myRawConns[bufConn] = true
go handleRaw(bufConn) go handleRaw(bufConn)
} }
m.Unlock(); m.Unlock();
@ -188,22 +256,65 @@ func handleConnection(conn net.Conn) {
m.Lock() m.Lock()
if virgin { if virgin {
virgin = false virgin = false
// TODO probably needs to go into a channel
myRawConns[conn] = true
// don't block for this // don't block for this
// let it be handled after the unlock // 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() 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() { func main() {
flag.Usage = usage flag.Usage = usage
port := flag.Uint("telnet-port", 0, "tcp telnet chat port") port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
confname := flag.String("conf", "./config.yml", "yaml config file") confname := flag.String("conf", "./config.yml", "yaml config file")
flag.Parse() flag.Parse()
var config Conf
confstr, err := ioutil.ReadFile(*confname) confstr, err := ioutil.ReadFile(*confname)
fmt.Fprintf(os.Stdout, "-conf=%s\n", *confname) fmt.Fprintf(os.Stdout, "-conf=%s\n", *confname)
if nil != err { if nil != err {
@ -216,11 +327,16 @@ func main() {
} }
firstMsgs = make(chan myMsg, 128) 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) newConns = make(chan net.Conn, 128)
myRawConns = make(map[net.Conn]bool) myRawConns = make(map[net.Conn]bool)
myUnsortedConns = 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 var addr string
if 0 != int(*port) { if 0 != int(*port) {
addr = ":" + strconv.Itoa(int(*port)) addr = ":" + strconv.Itoa(int(*port))
@ -254,7 +370,7 @@ func main() {
ts := time.Now() ts := time.Now()
fmt.Fprintf(os.Stdout, "[Handle New Connection] [Timestamp] %s\n", ts) fmt.Fprintf(os.Stdout, "[Handle New Connection] [Timestamp] %s\n", ts)
go handleConnection(conn) go handleConnection(conn)
case msg := <- myMsgs: case msg := <- myChans["general"]:
ts, err := msg.receivedAt.MarshalJSON() ts, err := msg.receivedAt.MarshalJSON()
if nil != err { if nil != err {
fmt.Fprintf(os.Stderr, "[Error] %s\n", err) fmt.Fprintf(os.Stderr, "[Error] %s\n", err)