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 (
"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)