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 Conf struct { | type ConfMailer struct { | ||||||
|   Port uint `yaml:"port,omitempty"` |   Url string `yaml:"url,omitempty"` | ||||||
|   Mailer struct { |  | ||||||
|   ApiKey string `yaml:"api_key,omitempty"` |   ApiKey string `yaml:"api_key,omitempty"` | ||||||
|   From string `yaml:"from,omitempty"` |   From string `yaml:"from,omitempty"` | ||||||
|   } | } | ||||||
|  | type Conf struct { | ||||||
|  |   Port uint `yaml:"port,omitempty"` | ||||||
|  |   Mailer ConfMailer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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 |           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"); |     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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user