package main import ( "biukop/sfm/loan" "database/sql" "encoding/json" "fmt" "github.com/brianvoe/gofakeit/v6" log "github.com/sirupsen/logrus" "net/http" "net/http/httputil" "strconv" "strings" "time" ) const apiV1Prefix = "/api/v1/" type apiV1HandlerMap struct { Method string Path string //regex Handler func(http.ResponseWriter, *http.Request, *loan.Session) } var apiV1Handler = []apiV1HandlerMap{ {"POST", "login", apiV1Login}, {"GET", "login", apiV1DumpRequest}, } //apiV1Main version 1 main entry for all REST API // func apiV1Main(w http.ResponseWriter, r *http.Request) { logRequestDebug(httputil.DumpRequest(r, true)) w.Header().Set("Content-Type", "application/json;charset=UTF-8") //track browser, and take session from cookie session := loan.Session{} apiV1InitSessionByBrowserId(w, r, &session) //try session login first, if not an empty session will be created e := apiV1InitSessionByHttpHeader(r, &session) if e != nil { log.Warnf("Fail to InitSession %+v", session) apiV1Client403Error(w, r) return } session.RenewIfExpireSoon() session.SetRemote(r) //make sure they are using latest remote //we have a session now, either guest or valid user //search through handler path := r.URL.Path[len(apiV1Prefix):] //strip API prefix for _, node := range apiV1Handler { if r.Method == node.Method && path == node.Path { node.Handler(w, r, &session) e = session.Write() //finish this session to DB if e != nil { log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error()) } return } } //Catch for all e = session.Write() //finish this session to DB apiV1DumpRequest(w, r, &session) } func apiV1InitSessionByBrowserId(w http.ResponseWriter, r *http.Request, session *loan.Session) { var mid string inCookie, e := r.Cookie("mid") if e == nil { mid = inCookie.Value } else { mid = strconv.Itoa(int(time.Now().Unix())) + "-" + gofakeit.UUID() } var sid string inCookie, e = r.Cookie("session") if e == nil { sid = inCookie.Value if sid != "" { e = session.Read(sid) if e == nil { if mid != session.Get("mid") { session.MarkEmpty() } } } else { //create a new session session.InitGuest(time.Now().Add(loan.DefaultSessionDuration)) session.Add("mid", mid) } } //add tracking cookie expiration := time.Now().Add(365 * 24 * time.Hour) cookie := http.Cookie{Name: "session", Value: session.Id, Expires: expiration} http.SetCookie(w, &cookie) cookie = http.Cookie{Name: "mid", Value: mid, Expires: expiration} http.SetCookie(w, &cookie) } func apiV1InitSessionByHttpHeader(r *http.Request, ss *loan.Session) (e error) { sid := r.Header.Get("Biukop-Session") //make sure session id is given if sid != "" { //Try to retrieve a copy of DB session trial := loan.Session{} e = trial.Retrieve(r) if e == nil { //we got existing session from DB e = trial.ValidateRequest(r) if e != nil { // db session does not match request log.Warnf("failed session login %+v, %s", ss, time.Now().Format(time.RFC1123)) ss.InitGuest(time.Now().Add(loan.DefaultSessionDuration)) e = nil } else { //else, we have logged this user *ss = trial //overwrite the incoming session } } else if e == sql.ErrNoRows { //db does not have required session. log.Warn("DB has no corresponding session ", sid) } else { // retrieve has error log.Warnf("Retrieve Session %s encountered error %s", sid, e.Error()) } } //if there is not session incoming if ss.IsEmpty() { //cookie may have initialized a session ss.InitGuest(time.Now().Add(loan.DefaultSessionDuration)) e = nil //we try to init an empty one } return } func apiV1ErrorCheck(e error) { if nil != e { panic(e.Error()) //TODO: detailed error check, truck all caller } } func apiV1Server500Error(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) fmt.Fprintf(w, "Server Internal Error "+time.Now().Format(time.RFC1123)) //write log dump := logRequestDebug(httputil.DumpRequest(r, true)) dump = strings.TrimSpace(dump) log.Warnf("Unhandled Protocol = %s path= %s", r.Method, r.URL.Path) } func apiV1Client403Error(w http.ResponseWriter, r *http.Request) { w.WriteHeader(403) type struct403 struct { Error int ErrorMsg string } e403 := struct403{Error: 403, ErrorMsg: "Not Authorized " + time.Now().Format(time.RFC1123)} msg403, _ := json.Marshal(e403) fmt.Fprintln(w, string(msg403)) //write log dump := logRequestDebug(httputil.DumpRequest(r, true)) dump = strings.TrimSpace(dump) log.Warnf("Not authorized http(%s) path= %s, %s", r.Method, r.URL.Path, dump) }