// CallbackHandler handles OAuth2 redirection URI requests by parsing the auth // code and state, comparing with the state value from the ctx, and obtaining // an OAuth2 Token. func CallbackHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { authCode, state, err := parseCallback(req) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ownerState, err := StateFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } if state != ownerState || state == "" { ctx = gologin.WithError(ctx, ErrInvalidState) failure.ServeHTTP(ctx, w, req) return } // use the authorization code to get a Token token, err := config.Exchange(ctx, authCode) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithToken(ctx, token) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// EmptyTempHandler adds an empty request token secret to the ctx if none is // present to support OAuth1 providers which do not require temp secrets to // be kept between the login phase and callback phase. func EmptyTempHandler(success ctxh.ContextHandler) ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { _, _, err := RequestTokenFromContext(ctx) if err != nil { ctx = WithRequestToken(ctx, "", "") } success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// CallbackHandler handles OAuth1 callback requests by parsing the oauth token // and verifier, reading the request token secret from the ctx, then obtaining // an access token and adding it to the ctx. func CallbackHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, verifier, err := oauth1.ParseAuthorizationCallback(req) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } // upstream handler should add the request token secret from the login step _, requestSecret, err := RequestTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } accessToken, accessSecret, err := config.AccessToken(requestToken, requestSecret, verifier) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithAccessToken(ctx, accessToken, accessSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// googleHandler is a ContextHandler that gets the OAuth2 Token from the ctx // to get the corresponding Google Userinfoplus. If successful, the user info // is added to the ctx and the success handler is called. Otherwise, the // failure handler is called. func googleHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { token, err := oauth2Login.TokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, token) googleService, err := google.New(httpClient) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } userInfoPlus, err := googleService.Userinfo.Get().Do() err = validateResponse(userInfoPlus, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, userInfoPlus) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler handles OAuth1 login requests by obtaining a request token and // secret (temporary credentials) and adding it to the ctx. If successful, // handling delegates to the success handler, otherwise to the failure handler. // // Typically, the success handler is an AuthRedirectHandler or a handler which // stores the request token secret. func LoginHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, requestSecret, err := config.RequestToken() if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithRequestToken(ctx, requestToken, requestSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler handles OAuth2 login requests by reading the state value from // the ctx and redirecting requests to the AuthURL with that state value. func LoginHandler(config *oauth2.Config, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { state, err := StateFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } authURL := config.AuthCodeURL(state) http.Redirect(w, req, authURL, http.StatusFound) } return ctxh.ContextHandlerFunc(fn) }
// StateHandler checks for a state cookie. If found, the state value is read // and added to the ctx. Otherwise, a non-guessable value is added to the ctx // and to a (short-lived) state cookie issued to the requester. // // Implements OAuth 2 RFC 6749 10.12 CSRF Protection. If you wish to issue // state params differently, write a ContextHandler which sets the ctx state, // using oauth2 WithState(ctx, state) since it is required by LoginHandler // and CallbackHandler. func StateHandler(config gologin.CookieConfig, success ctxh.ContextHandler) ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { cookie, err := req.Cookie(config.Name) if err == nil { // add the cookie state to the ctx ctx = WithState(ctx, cookie.Value) } else { // add Cookie with a random state val := randomState() http.SetCookie(w, internal.NewCookie(config, val)) ctx = WithState(ctx, val) } success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// AuthRedirectHandler reads the request token from the ctx and redirects // to the authorization URL. func AuthRedirectHandler(config *oauth1.Config, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, _, err := RequestTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } authorizationURL, err := config.AuthorizationURL(requestToken) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } http.Redirect(w, req, authorizationURL.String(), http.StatusFound) } return ctxh.ContextHandlerFunc(fn) }
// tumblrHandler is a ContextHandler that gets the OAuth1 access token from // the ctx and obtains the Tumblr User. If successful, the User is added to // the ctx and the success handler is called. Otherwise, the failure handler // is called. func tumblrHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, oauth1.NewToken(accessToken, accessSecret)) tumblrClient := newClient(httpClient) user, resp, err := tumblrClient.UserInfo() err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// twitterHandler is a ContextHandler that gets the OAuth1 access token from // the ctx and calls Twitter verify_credentials to get the corresponding User. // If successful, the User is added to the ctx and the success handler is // called. Otherwise, the failure handler is called. func twitterHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, oauth1.NewToken(accessToken, accessSecret)) twitterClient := twitter.NewClient(httpClient) accountVerifyParams := &twitter.AccountVerifyParams{ IncludeEntities: twitter.Bool(false), SkipStatus: twitter.Bool(true), IncludeEmail: twitter.Bool(false), } user, resp, err := twitterClient.Accounts.VerifyCredentials(accountVerifyParams) err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// TokenHandler receives a Digits access token/secret and calls the Digits // accounts endpoint to get the corresponding Account. If successful, the // access token/secret and Account are added to the ctx and the success handler // is called. Otherwise, the failure handler is called. func TokenHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { success = digitsHandler(config, success, failure) if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { ctx = gologin.WithError(ctx, fmt.Errorf("Method not allowed")) failure.ServeHTTP(ctx, w, req) return } req.ParseForm() accessToken := req.PostForm.Get(accessTokenField) accessSecret := req.PostForm.Get(accessTokenSecretField) err := validateToken(accessToken, accessSecret) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = oauth1Login.WithAccessToken(ctx, accessToken, accessSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// facebookHandler is a ContextHandler that gets the OAuth2 Token from the ctx // to get the corresponding Facebook User. If successful, the user is added to // the ctx and the success handler is called. Otherwise, the failure handler // is called. func facebookHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { token, err := oauth2Login.TokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, token) facebookService := newClient(httpClient) user, resp, err := facebookService.Me() err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// getAccountViaEcho is a ContextHandler that gets the Digits Echo endpoint and // OAuth header from the ctx and calls the endpoint to get the corresponding // Digits Account. If successful, the Account is added to the ctx and the // success handler is called. Otherwise, the failure handler is called. func getAccountViaEcho(config *Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } client := config.Client if client == nil { client = http.DefaultClient } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { endpoint, header, err := EchoFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } // fetch the Digits Account account, resp, err := requestAccount(client, endpoint, header) // validate the Digits Account response err = validateResponse(account, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithAccount(ctx, account) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler receives a Digits OAuth Echo endpoint and OAuth header, // validates the echo, and calls the endpoint to get the corresponding Digits // Account. If successful, the Digits Account is added to the ctx and the // success handler is called. Otherwise, the failure handler is called. func LoginHandler(config *Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { success = getAccountViaEcho(config, success, failure) if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { ctx = gologin.WithError(ctx, fmt.Errorf("Method not allowed")) failure.ServeHTTP(ctx, w, req) return } req.ParseForm() accountEndpoint := req.PostForm.Get(accountEndpointField) accountRequestHeader := req.PostForm.Get(accountRequestHeaderField) // validate POST'ed Digits OAuth Echo data err := validateEcho(accountEndpoint, accountRequestHeader, config.ConsumerKey) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithEcho(ctx, accountEndpoint, accountRequestHeader) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// CookieTempHandler persists or retrieves the request token secret (temporary // credentials). If the request token can be read from the ctx (login phase), // the secret is set in a short-lived cookie to be read later. Otherwise // (callback phase) the cookie is read to retrieve the request token secret // and add it to the ctx. // If the ctx contains no request token and the request has no temp cookie, // the failure handler is called. // // Some OAuth1 providers (Twitter, Digits) do NOT require temp secrets to be // kept between the login phase and callback phase. To implement those // providers, use the EmptyTempHandler instead. func CookieTempHandler(config gologin.CookieConfig, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { _, requestSecret, err := RequestTokenFromContext(ctx) if err == nil { // add request secret to a short-lived cookie http.SetCookie(w, internal.NewCookie(config, requestSecret)) success.ServeHTTP(ctx, w, req) return } // read request secret from the short-lived cookie to add to ctx cookie, err := req.Cookie(config.Name) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithRequestToken(ctx, "", cookie.Value) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }