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" | 	"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: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user