|
@ -7,6 +7,7 @@ import ( |
|
|
"bufio" |
|
|
"bufio" |
|
|
"bytes" |
|
|
"bytes" |
|
|
"crypto/rand" |
|
|
"crypto/rand" |
|
|
|
|
|
"crypto/subtle" |
|
|
"encoding/base64" |
|
|
"encoding/base64" |
|
|
"flag" |
|
|
"flag" |
|
|
"fmt" |
|
|
"fmt" |
|
@ -95,6 +96,9 @@ var delTcpChat chan bufferedConn |
|
|
var newHttpChat chan bufferedConn |
|
|
var newHttpChat chan bufferedConn |
|
|
var newHttpClient chan bufferedConn |
|
|
var newHttpClient chan bufferedConn |
|
|
var delHttpChat chan bufferedConn |
|
|
var delHttpChat chan bufferedConn |
|
|
|
|
|
var newAuthReqs chan authReq |
|
|
|
|
|
var valAuthReqs chan authReq |
|
|
|
|
|
var delAuthReqs chan authReq |
|
|
|
|
|
|
|
|
func usage() { |
|
|
func usage() { |
|
|
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n") |
|
|
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n") |
|
@ -228,6 +232,7 @@ func handleRaw(bufConn bufferedConn) { |
|
|
// prevent data race on len(myRawConns)
|
|
|
// prevent data race on len(myRawConns)
|
|
|
// XXX (there can't be a race between these two lines, right?)
|
|
|
// XXX (there can't be a race between these two lines, right?)
|
|
|
count := <-u.userCount |
|
|
count := <-u.userCount |
|
|
|
|
|
close(u.userCount) |
|
|
u.userCount = nil |
|
|
u.userCount = nil |
|
|
time.Sleep(50 * time.Millisecond) |
|
|
time.Sleep(50 * time.Millisecond) |
|
|
fmt.Fprintf(bufConn, "\n") |
|
|
fmt.Fprintf(bufConn, "\n") |
|
@ -483,10 +488,137 @@ func serveStatic(req *restful.Request, resp *restful.Response) { |
|
|
req.Request, |
|
|
req.Request, |
|
|
actual) |
|
|
actual) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func serveHello(req *restful.Request, resp *restful.Response) { |
|
|
func serveHello(req *restful.Request, resp *restful.Response) { |
|
|
fmt.Fprintf(resp, "{\"msg\":\"hello\"}") |
|
|
fmt.Fprintf(resp, "{\"msg\":\"hello\"}") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type authReq struct { |
|
|
|
|
|
Cid string `json:"cid"` |
|
|
|
|
|
ChallengedAt time.Time `json:"-"` |
|
|
|
|
|
Chan chan authReq `json:"-"` |
|
|
|
|
|
Code string `json:"code"` |
|
|
|
|
|
CreatedAt time.Time `json:"-"` |
|
|
|
|
|
DidAuth bool `json:"-"` |
|
|
|
|
|
Email string `json:"email"` |
|
|
|
|
|
VerifiedAt time.Time `json:"-"` |
|
|
|
|
|
Tries int `json:"-"` |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func requestAuth(req *restful.Request, resp *restful.Response) { |
|
|
|
|
|
ar := authReq{ |
|
|
|
|
|
CreatedAt: time.Now(), |
|
|
|
|
|
Tries: 0, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
err := req.ReadEntity(&ar) |
|
|
|
|
|
// Looks like restful handles JSON automatically?
|
|
|
|
|
|
/* |
|
|
|
|
|
err := json.NewDecoder(req.Body).Decode(&ar) |
|
|
|
|
|
*/ |
|
|
|
|
|
if nil != err { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad json in request body\"} }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
email := strings.TrimSpace(ar.Email) |
|
|
|
|
|
emailParts := strings.Split(email, "@") |
|
|
|
|
|
if 2 != len(emailParts) { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad email address '"+email+"'\"} }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var code string |
|
|
|
|
|
if "" != config.Mailer.ApiKey { |
|
|
|
|
|
code, err = sendAuthCode(config.Mailer, email) |
|
|
|
|
|
if nil != err { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error sending auth code via mailgun\" } }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if "" == code { |
|
|
|
|
|
code, err = genAuthCode() |
|
|
|
|
|
if nil != err { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error generating random number (code)\"} }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stdout, "\n== AUTHORIZATION ==\n[cheat code for %s]: %s\n", ar.Email, ar.Code) |
|
|
|
|
|
|
|
|
|
|
|
cid, _ := genAuthCode() |
|
|
|
|
|
if "" == cid { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error generating random number (cid)\"} }") |
|
|
|
|
|
} |
|
|
|
|
|
ar.Cid = cid |
|
|
|
|
|
ar.DidAuth = false |
|
|
|
|
|
ar.Email = email |
|
|
|
|
|
ar.Code = code |
|
|
|
|
|
|
|
|
|
|
|
newAuthReqs <- ar |
|
|
|
|
|
|
|
|
|
|
|
fmt.Fprintf(resp, "{ \"success\": true, \"cid\": \""+ar.Cid+"\" }") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func issueToken(req *restful.Request, resp *restful.Response) { |
|
|
|
|
|
ar := authReq{} |
|
|
|
|
|
cid := req.PathParameter("cid") |
|
|
|
|
|
|
|
|
|
|
|
if "" == cid { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad cid in request url params\"} }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//err := json.NewDecoder(r.Body).Decode(&ar)
|
|
|
|
|
|
err := req.ReadEntity(&ar) |
|
|
|
|
|
if nil != err { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad json in request body\"} }") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ar.Cid = cid |
|
|
|
|
|
ar.Chan = make(chan authReq) |
|
|
|
|
|
valAuthReqs <- ar |
|
|
|
|
|
av := <-ar.Chan |
|
|
|
|
|
close(ar.Chan) |
|
|
|
|
|
ar.Chan = nil |
|
|
|
|
|
av.Tries += 1 |
|
|
|
|
|
av.ChallengedAt = time.Now() |
|
|
|
|
|
|
|
|
|
|
|
// TODO security checks
|
|
|
|
|
|
// * ChallengedAt was at least 1 second ago
|
|
|
|
|
|
// * Tries does not exceed 5
|
|
|
|
|
|
// * CreatedAt is not more than 15 minutes old
|
|
|
|
|
|
// Probably also need to make sure than not more than n emails are sent per y minutes
|
|
|
|
|
|
|
|
|
|
|
|
// Not that this would even matter with the above, just a habit
|
|
|
|
|
|
if 1 != subtle.ConstantTimeCompare([]byte(av.Code), []byte(ar.Code)) { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"invalid authorization code\"} }") |
|
|
|
|
|
// I'm not sure if this is necessary, but I think it is
|
|
|
|
|
|
// to overwrite the original with the updated
|
|
|
|
|
|
// (these are copies, not pointers, IIRC)
|
|
|
|
|
|
// of course, should use a DB anyway...
|
|
|
|
|
|
newAuthReqs <- av |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
av.DidAuth = true |
|
|
|
|
|
ar.VerifiedAt = time.Now() |
|
|
|
|
|
newAuthReqs <- av |
|
|
|
|
|
|
|
|
|
|
|
fmt.Fprintf(resp, "{ \"success\": true, \"token\": \""+ar.Cid+"\" }") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func requireToken(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { |
|
|
|
|
|
//req.SetAttribute("go", "there")
|
|
|
|
|
|
//req.Attribute("go") // "there"
|
|
|
|
|
|
chain.ProcessFilter(req, resp) |
|
|
|
|
|
} |
|
|
|
|
|
func listMsgs(req *restful.Request, resp *restful.Response) { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_NO_IMPL\", \"message\": \"invalid authorization code\"} }") |
|
|
|
|
|
} |
|
|
|
|
|
func postMsg(req *restful.Request, resp *restful.Response) { |
|
|
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_NO_IMPL\", \"message\": \"invalid authorization code\"} }") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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") |
|
@ -509,10 +641,14 @@ func main() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
myRawConns := make(map[bufferedConn]bool) |
|
|
myRawConns := make(map[bufferedConn]bool) |
|
|
|
|
|
myAuthReqs := make(map[string]authReq) |
|
|
firstMsgs = make(chan myMsg, 128) |
|
|
firstMsgs = make(chan myMsg, 128) |
|
|
//myRooms = make(map[string](chan myMsg))
|
|
|
//myRooms = make(map[string](chan myMsg))
|
|
|
newConns = make(chan net.Conn, 128) |
|
|
newConns = make(chan net.Conn, 128) |
|
|
authTcpChat = make(chan tcpUser, 128) |
|
|
authTcpChat = make(chan tcpUser, 128) |
|
|
|
|
|
newAuthReqs = make(chan authReq, 128) |
|
|
|
|
|
valAuthReqs = make(chan authReq, 128) |
|
|
|
|
|
delAuthReqs = make(chan authReq, 128) |
|
|
newTcpChat = make(chan bufferedConn, 128) |
|
|
newTcpChat = make(chan bufferedConn, 128) |
|
|
newHttpChat = make(chan bufferedConn, 128) |
|
|
newHttpChat = make(chan bufferedConn, 128) |
|
|
newHttpClient = make(chan bufferedConn, 128) |
|
|
newHttpClient = make(chan bufferedConn, 128) |
|
@ -563,14 +699,14 @@ func main() { |
|
|
wsStatic.Route(wsStatic.GET("/{subpath:*}").To(serveStatic)) |
|
|
wsStatic.Route(wsStatic.GET("/{subpath:*}").To(serveStatic)) |
|
|
container.Add(wsStatic) |
|
|
container.Add(wsStatic) |
|
|
|
|
|
|
|
|
|
|
|
cors := restful.CrossOriginResourceSharing{ExposeHeaders: []string{"Authorization"}, CookiesAllowed: false, Container: container} |
|
|
wsApi := new(restful.WebService) |
|
|
wsApi := new(restful.WebService) |
|
|
wsApi.Path("/api") |
|
|
wsApi.Path("/api").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON).Filter(cors.Filter) |
|
|
wsApi.Route(wsApi.GET("/api/hello").To(serveHello)) |
|
|
wsApi.Route(wsApi.GET("/hello").To(serveHello)) |
|
|
/* |
|
|
wsApi.Route(wsApi.POST("/sessions").To(requestAuth)) |
|
|
ws.Route(ws.POST("/api/authn").To(createAuth)) |
|
|
wsApi.Route(wsApi.POST("/sessions/{cid}").To(issueToken)) |
|
|
ws.Route(ws.POST("/api/authn/{email}").To(createAuth)) |
|
|
wsApi.Route(wsApi.GET("/rooms/general").Filter(requireToken).To(listMsgs)) |
|
|
ws.Route(ws.GET("/api").Filter(basicAuthenticate).To(hello2)) |
|
|
wsApi.Route(wsApi.POST("/rooms/general").Filter(requireToken).To(postMsg)) |
|
|
*/ |
|
|
|
|
|
container.Add(wsApi) |
|
|
container.Add(wsApi) |
|
|
|
|
|
|
|
|
server := &http.Server{ |
|
|
server := &http.Server{ |
|
@ -602,6 +738,14 @@ func main() { |
|
|
channel: "general", |
|
|
channel: "general", |
|
|
email: "system", |
|
|
email: "system", |
|
|
} |
|
|
} |
|
|
|
|
|
case ar := <-newAuthReqs: |
|
|
|
|
|
myAuthReqs[ar.Cid] = ar |
|
|
|
|
|
case av := <-valAuthReqs: |
|
|
|
|
|
// TODO In this case it's probably more conventional (and efficient) to
|
|
|
|
|
|
// use struct with a mutex and the authReqs map than a chan chan
|
|
|
|
|
|
av.Chan <- myAuthReqs[av.Cid] |
|
|
|
|
|
case ar := <-delAuthReqs: |
|
|
|
|
|
delete(myAuthReqs, ar.Cid) |
|
|
case bufConn := <-newTcpChat: |
|
|
case bufConn := <-newTcpChat: |
|
|
go handleRaw(bufConn) |
|
|
go handleRaw(bufConn) |
|
|
case bufConn := <-delTcpChat: |
|
|
case bufConn := <-delTcpChat: |
|
|