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 (
|
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)
|
||||||
|
|
Loading…
Reference in New Issue