package oauth2client import ( "crypto/tls" "errors" "gitea.inhome.blapointe.com/local/oauth2" "net/http" "net/url" "strconv" "strings" "time" ) type cached struct { access string exp time.Time } var cache = map[string]cached{} func Authenticate(server, scope string, w http.ResponseWriter, r *http.Request) error { oauth2server, err := url.Parse(server) if err != nil { return err } access, exists := findAccess(w, r) if !exists { return login(oauth2server, scope, w, r) } return verify(access, oauth2server, scope, w, r) } func findAccess(w http.ResponseWriter, r *http.Request) (string, bool) { fresh, exists := findAccessFresh(w, r) if exists { return fresh, true } stable, exists := findAccessStable(w, r) return stable, exists } func findAccessFresh(w http.ResponseWriter, r *http.Request) (string, bool) { access, found := findAccessFreshQueryParam(w, r) if !found { access, found = findAccessFreshCookie(w, r) } if found { setCookie(oauth2.COOKIE, access, "", w) } return access, found } func findAccessFreshQueryParam(w http.ResponseWriter, r *http.Request) (string, bool) { q := r.URL.Query() access := q.Get(oauth2.NEWCOOKIE) q.Del(oauth2.NEWCOOKIE) r.URL.RawQuery = q.Encode() if access == "" { return "", false } return access, true } func findAccessFreshCookie(w http.ResponseWriter, r *http.Request) (string, bool) { access, err := r.Cookie(oauth2.NEWCOOKIE) if err == http.ErrNoCookie { return "", false } host := r.Host if r.URL.Host != "" { host = r.URL.Host } host = strings.Split(host, ":")[0] hosts := strings.Split(host, ".") if len(host) > 1 { hosts = hosts[1:] } host = "." + strings.Join(hosts, ".") setCookie(oauth2.NEWCOOKIE, "", host, w) return access.Value, true } func findAccessStable(w http.ResponseWriter, r *http.Request) (string, bool) { access, err := r.Cookie(oauth2.COOKIE) if err == http.ErrNoCookie { return "", false } return access.Value, true } func login(oauth2server *url.URL, scope string, w http.ResponseWriter, r *http.Request) error { oauth2server.Path = "/users/log/" + scope url := *r.URL url.Host = r.Host if url.Scheme == "" { url.Scheme = oauth2server.Scheme } if url.Scheme == "" { url.Scheme = "https" } q := oauth2server.Query() q.Set(oauth2.REDIRECT, url.String()) oauth2server.RawQuery = q.Encode() http.Redirect(w, r, oauth2server.String(), http.StatusSeeOther) return errors.New("logging in") } func verify(access string, oauth2server *url.URL, scope string, w http.ResponseWriter, r *http.Request) error { if v, ok := cache[scope]; ok && v.access == access && time.Now().Before(v.exp) { return nil } oauth2server.Path = "/verify/" + scope data := url.Values{} data.Set("access", access) req, err := http.NewRequest("POST", oauth2server.String(), strings.NewReader(data.Encode())) if err != nil { return err } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) c := &http.Client{ Timeout: 5 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } resp, err := c.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return login(oauth2server, scope, w, r) } cache[scope] = cached{ access: access, exp: time.Now().Add(time.Minute), } return nil } func setCookie(key, value, domain string, w http.ResponseWriter) { cookie := &http.Cookie{ Name: key, Value: value, Path: "/", Domain: domain, } if value == "" { cookie.Expires = time.Now().Add(-1 * time.Hour) } http.SetCookie(w, cookie) }