-
-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add oauth1 handler * add twitter as supported oauth provider * example for twitter * update documentation * fix oauth1 logout test
- Loading branch information
Showing
10 changed files
with
562 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/dghubble/oauth1" | ||
"github.com/dgrijalva/jwt-go" | ||
"github.com/go-pkgz/auth/logger" | ||
"github.com/go-pkgz/auth/token" | ||
"github.com/go-pkgz/rest" | ||
) | ||
|
||
// Oauth1Handler implements /login, /callback and /logout handlers for oauth1 flow | ||
type Oauth1Handler struct { | ||
Params | ||
name string | ||
infoURL string | ||
conf oauth1.Config | ||
mapUser func(UserData, []byte) token.User // map info from InfoURL to User | ||
} | ||
|
||
// Name returns provider name | ||
func (h Oauth1Handler) Name() string { return h.name } | ||
|
||
// LoginHandler - GET /login?from=redirect-back-url&site=siteID&session=1 | ||
func (h Oauth1Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { | ||
h.Logf("[DEBUG] login with %s", h.Name()) | ||
|
||
// setting RedirectURL to {rootURL}/{routingPath}/{provider}/callback | ||
// e.g. http://localhost:8080/auth/twitter/callback | ||
h.conf.CallbackURL = h.makeRedirURL(r.URL.Path) | ||
|
||
requestToken, requestSecret, err := h.conf.RequestToken() | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to get request token") | ||
return | ||
} | ||
|
||
// use requestSecret as a state in oauth2 | ||
cid, err := randToken() | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to make claim's id") | ||
return | ||
} | ||
|
||
claims := token.Claims{ | ||
Handshake: &token.Handshake{ | ||
State: requestSecret, | ||
From: r.URL.Query().Get("from"), | ||
}, | ||
SessionOnly: r.URL.Query().Get("session") != "" && r.URL.Query().Get("session") != "0", | ||
StandardClaims: jwt.StandardClaims{ | ||
Id: cid, | ||
Audience: r.URL.Query().Get("site"), | ||
ExpiresAt: time.Now().Add(30 * time.Minute).Unix(), | ||
NotBefore: time.Now().Add(-1 * time.Minute).Unix(), | ||
}, | ||
} | ||
|
||
if _, err = h.JwtService.Set(w, claims); err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to set token") | ||
return | ||
} | ||
|
||
authURL, err := h.conf.AuthorizationURL(requestToken) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to obtain oauth1 URL") | ||
return | ||
} | ||
|
||
http.Redirect(w, r, authURL.String(), http.StatusFound) | ||
} | ||
|
||
// AuthHandler fills user info and redirects to "from" url. This is callback url redirected locally by browser | ||
// GET /callback | ||
func (h Oauth1Handler) AuthHandler(w http.ResponseWriter, r *http.Request) { | ||
oauthClaims, _, err := h.JwtService.Get(r) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to get token") | ||
return | ||
} | ||
|
||
requestToken, verifier, err := oauth1.ParseAuthorizationCallback(r) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to parse response from oauth1 server") | ||
return | ||
} | ||
|
||
accessToken, accessSecret, err := h.conf.AccessToken(requestToken, oauthClaims.Handshake.State, verifier) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to get accessToken and accessSecret") | ||
return | ||
} | ||
|
||
tok := oauth1.NewToken(accessToken, accessSecret) | ||
client := h.conf.Client(context.Background(), tok) | ||
|
||
uinfo, err := client.Get(h.infoURL) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusServiceUnavailable, err, "failed to get client info") | ||
return | ||
} | ||
|
||
defer func() { | ||
if e := uinfo.Body.Close(); e != nil { | ||
h.Logf("[WARN] failed to close response body, %s", e) | ||
} | ||
}() | ||
|
||
data, err := ioutil.ReadAll(uinfo.Body) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to read user info") | ||
return | ||
} | ||
|
||
jData := map[string]interface{}{} | ||
if e := json.Unmarshal(data, &jData); e != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to unmarshal user info") | ||
return | ||
} | ||
h.Logf("[DEBUG] got raw user info %+v", jData) | ||
|
||
u := h.mapUser(jData, data) | ||
u, err = setAvatar(h.AvatarSaver, u) | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to save avatar to proxy") | ||
return | ||
} | ||
|
||
cid, err := randToken() | ||
if err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to make claim's id") | ||
return | ||
} | ||
claims := token.Claims{ | ||
User: &u, | ||
StandardClaims: jwt.StandardClaims{ | ||
Issuer: h.Issuer, | ||
Id: cid, | ||
Audience: oauthClaims.Audience, | ||
}, | ||
SessionOnly: oauthClaims.SessionOnly, | ||
} | ||
|
||
if _, err = h.JwtService.Set(w, claims); err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusInternalServerError, err, "failed to set token") | ||
return | ||
} | ||
|
||
h.Logf("[DEBUG] user info %+v", u) | ||
|
||
// redirect to back url if presented in login query params | ||
if oauthClaims.Handshake != nil && oauthClaims.Handshake.From != "" { | ||
http.Redirect(w, r, oauthClaims.Handshake.From, http.StatusTemporaryRedirect) | ||
return | ||
} | ||
rest.RenderJSON(w, r, &u) | ||
} | ||
|
||
// LogoutHandler - GET /logout | ||
func (h Oauth1Handler) LogoutHandler(w http.ResponseWriter, r *http.Request) { | ||
if _, _, err := h.JwtService.Get(r); err != nil { | ||
rest.SendErrorJSON(w, r, h.L, http.StatusForbidden, err, "logout not allowed") | ||
return | ||
} | ||
h.JwtService.Reset(w) | ||
} | ||
|
||
func (h Oauth1Handler) makeRedirURL(path string) string { | ||
elems := strings.Split(path, "/") | ||
newPath := strings.Join(elems[:len(elems)-1], "/") | ||
|
||
return strings.TrimRight(h.URL, "/") + strings.TrimRight(newPath, "/") + urlCallbackSuffix | ||
} | ||
|
||
// initOauth2Handler makes oauth1 handler for given provider | ||
func initOauth1Handler(p Params, service Oauth1Handler) Oauth1Handler { | ||
if p.L == nil { | ||
p.L = logger.NoOp | ||
} | ||
p.Logf("[INFO] init oauth1 service %s", service.name) | ||
service.Params = p | ||
service.conf.ConsumerKey = p.Cid | ||
service.conf.ConsumerSecret = p.Csecret | ||
|
||
p.Logf("[DEBUG] created %s oauth2, id=%s, redir=%s, endpoint=%s", | ||
service.name, service.Cid, service.makeRedirURL("/{route}/"+service.name+"/"), service.conf.Endpoint) | ||
return service | ||
} |
Oops, something went wrong.