WIP untested go restful APIs
This commit is contained in:
parent
dd971fcc72
commit
b3ef87cbdd
158
chatserver.go
158
chatserver.go
|
@ -7,6 +7,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -95,6 +96,9 @@ var delTcpChat chan bufferedConn
|
|||
var newHttpChat chan bufferedConn
|
||||
var newHttpClient chan bufferedConn
|
||||
var delHttpChat chan bufferedConn
|
||||
var newAuthReqs chan authReq
|
||||
var valAuthReqs chan authReq
|
||||
var delAuthReqs chan authReq
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n")
|
||||
|
@ -228,6 +232,7 @@ func handleRaw(bufConn bufferedConn) {
|
|||
// prevent data race on len(myRawConns)
|
||||
// XXX (there can't be a race between these two lines, right?)
|
||||
count := <-u.userCount
|
||||
close(u.userCount)
|
||||
u.userCount = nil
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "\n")
|
||||
|
@ -483,10 +488,137 @@ func serveStatic(req *restful.Request, resp *restful.Response) {
|
|||
req.Request,
|
||||
actual)
|
||||
}
|
||||
|
||||
func serveHello(req *restful.Request, resp *restful.Response) {
|
||||
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() {
|
||||
flag.Usage = usage
|
||||
port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
|
||||
|
@ -509,10 +641,14 @@ func main() {
|
|||
}
|
||||
|
||||
myRawConns := make(map[bufferedConn]bool)
|
||||
myAuthReqs := make(map[string]authReq)
|
||||
firstMsgs = make(chan myMsg, 128)
|
||||
//myRooms = make(map[string](chan myMsg))
|
||||
newConns = make(chan net.Conn, 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)
|
||||
newHttpChat = make(chan bufferedConn, 128)
|
||||
newHttpClient = make(chan bufferedConn, 128)
|
||||
|
@ -563,14 +699,14 @@ func main() {
|
|||
wsStatic.Route(wsStatic.GET("/{subpath:*}").To(serveStatic))
|
||||
container.Add(wsStatic)
|
||||
|
||||
cors := restful.CrossOriginResourceSharing{ExposeHeaders: []string{"Authorization"}, CookiesAllowed: false, Container: container}
|
||||
wsApi := new(restful.WebService)
|
||||
wsApi.Path("/api")
|
||||
wsApi.Route(wsApi.GET("/api/hello").To(serveHello))
|
||||
/*
|
||||
ws.Route(ws.POST("/api/authn").To(createAuth))
|
||||
ws.Route(ws.POST("/api/authn/{email}").To(createAuth))
|
||||
ws.Route(ws.GET("/api").Filter(basicAuthenticate).To(hello2))
|
||||
*/
|
||||
wsApi.Path("/api").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON).Filter(cors.Filter)
|
||||
wsApi.Route(wsApi.GET("/hello").To(serveHello))
|
||||
wsApi.Route(wsApi.POST("/sessions").To(requestAuth))
|
||||
wsApi.Route(wsApi.POST("/sessions/{cid}").To(issueToken))
|
||||
wsApi.Route(wsApi.GET("/rooms/general").Filter(requireToken).To(listMsgs))
|
||||
wsApi.Route(wsApi.POST("/rooms/general").Filter(requireToken).To(postMsg))
|
||||
container.Add(wsApi)
|
||||
|
||||
server := &http.Server{
|
||||
|
@ -602,6 +738,14 @@ func main() {
|
|||
channel: "general",
|
||||
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:
|
||||
go handleRaw(bufConn)
|
||||
case bufConn := <-delTcpChat:
|
||||
|
|
Loading…
Reference in New Issue