WIP untested go restful APIs

This commit is contained in:
AJ ONeal 2018-07-31 20:35:40 -06:00
parent dd971fcc72
commit b3ef87cbdd
1 changed files with 151 additions and 7 deletions

View File

@ -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: