@@ -47,6 +47,11 @@ var SignatureHeaders = []string{
4747 "Gap-Auth",
4848}
4949
50+var (
51+ // ErrNeedsLogin means the user should be redirected to the login page
52+ ErrNeedsLogin = errors.New("redirect to login page")
53+)
54+
5055// OAuthProxy is the main authentication proxy
5156type OAuthProxy struct {
5257 CookieSeed string
@@ -477,20 +482,19 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
477482}
478483
479484// IsWhitelistedRequest is used to check if auth should be skipped for this request
480−func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
485+func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
481486 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
482487 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
483488}
484489
485490// IsWhitelistedPath is used to check if the request path is allowed without auth
486−func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
491+func (p *OAuthProxy) IsWhitelistedPath(path string) bool {
487492 for _, u := range p.compiledRegex {
488− ok = u.MatchString(path)
489− if ok {
490− return
493+ if u.MatchString(path) {
494+ return true
491495 }
492496 }
493− return
497+ return false
494498}
495499
496500func getRemoteAddr(req *http.Request) (s string) {
@@ -641,36 +645,54 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
641645
642646// AuthenticateOnly checks whether the user is currently logged in
643647func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
644− status := p.Authenticate(rw, req)
645− if status == http.StatusAccepted {
646− rw.WriteHeader(http.StatusAccepted)
647− } else {
648+ session, err := p.getAuthenticatedSession(rw, req)
649+ if err != nil {
648650 http.Error(rw, "unauthorized request", http.StatusUnauthorized)
651+ return
649652 }
653+
654+ // we are authenticated
655+ p.addHeadersForProxying(rw, req, session)
656+ rw.WriteHeader(http.StatusAccepted)
650657}
651658
652659// Proxy proxies the user request if the user is authenticated else it prompts
653660// them to authenticate
654661func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
655− status := p.Authenticate(rw, req)
656− if status == http.StatusInternalServerError {
657− p.ErrorPage(rw, http.StatusInternalServerError,
658− "Internal Error", "Internal Error")
659− } else if status == http.StatusForbidden {
662+ session, err := p.getAuthenticatedSession(rw, req)
663+ switch err {
664+ case nil:
665+ // we are authenticated
666+ p.addHeadersForProxying(rw, req, session)
667+ p.serveMux.ServeHTTP(rw, req)
668+
669+ case ErrNeedsLogin:
670+ // we need to send the user to a login screen
671+ if isAjax(req) {
672+ // no point redirecting an AJAX request
673+ p.ErrorJSON(rw, http.StatusUnauthorized)
674+ return
675+ }
676+
660677 if p.SkipProviderButton {
661678 p.OAuthStart(rw, req)
662679 } else {
663680 p.SignInPage(rw, req, http.StatusForbidden)
664681 }
665− } else if status == http.StatusUnauthorized {
666− p.ErrorJSON(rw, status)
667− } else {
668− p.serveMux.ServeHTTP(rw, req)
682+
683+ default:
684+ // unknown error
685+ logger.Printf("Unexpected internal error: %s", err)
686+ p.ErrorPage(rw, http.StatusInternalServerError,
687+ "Internal Error", "Internal Error")
669688 }
689+
670690}
671691
672−// Authenticate checks whether a user is authenticated
673−func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
692+// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
693+// Returns nil, ErrNeedsLogin if user needs to login.
694+// Set-Cookie headers may be set on the response as a side-effect of calling this method.
695+func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) {
674696 var saveSession, clearSession, revalidated bool
675697 remoteAddr := getRemoteAddr(req)
676698
@@ -720,7 +742,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
720742 err = p.SaveSession(rw, req, session)
721743 if err != nil {
722744 logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
723− return http.StatusInternalServerError
745+ return nil, err
724746 }
725747 }
726748
@@ -736,15 +758,14 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
736758 }
737759
738760 if session == nil {
739− // Check if is an ajax request and return unauthorized to avoid a redirect
740− // to the login page
741− if p.isAjax(req) {
742− return http.StatusUnauthorized
743− }
744− return http.StatusForbidden
761+ return nil, ErrNeedsLogin
745762 }
746763
747− // At this point, the user is authenticated. proxy normally
764+ return session, nil
765+}
766+
767+// addHeadersForProxying adds the appropriate headers the request / response for proxying
768+func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
748769 if p.PassBasicAuth {
749770 req.SetBasicAuth(session.User, p.BasicAuthPassword)
750771 req.Header["X-Forwarded-User"] = []string{session.User}
@@ -781,7 +802,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
781802 } else {
782803 rw.Header().Set("GAP-Auth", session.Email)
783804 }
784− return http.StatusAccepted
785805}
786806
787807// CheckBasicAuth checks the requests Authorization header for basic auth
@@ -815,7 +835,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionStat
815835}
816836
817837// isAjax checks if a request is an ajax request
818−func (p *OAuthProxy) isAjax(req *http.Request) bool {
838+func isAjax(req *http.Request) bool {
819839 acceptValues, ok := req.Header["accept"]
820840 if !ok {
821841 acceptValues = req.Header["Accept"]