Former-commit-id: e4f9dfbdfe16c9ca1ad84ac5a844a5d060fb3b5e
This commit is contained in:
kataras 2017-06-07 22:14:13 +03:00
parent 6282a71a6f
commit 5e00c50c37
6 changed files with 432 additions and 5 deletions

View File

@ -163,7 +163,7 @@ $ rizla main.go
<details> <details>
<summary>Legends</summary> <summary>Legends</summary>
I'm sorry for taking this personally but I really need to thanks each one of them because they stood up [](https://github.com/kataras/iris#support) for me when others trying to "bullying" my personality in order to deflame Iris. I'm sorry for taking this personally but I really need to thank each one of them because they stood up [](https://github.com/kataras/iris#support) for me when others tried to "bullying" my personality in order to deflame Iris.
All of us should read and respect the official [golang](https://golang.org/conduct) and [iris](https://github.com/iris-contrib/community-board/blob/master/CODE-OF-CONDUCT.md) community **Code of Conduct**. This type of commitment and communication is the way of making Go great. All of us should read and respect the official [golang](https://golang.org/conduct) and [iris](https://github.com/iris-contrib/community-board/blob/master/CODE-OF-CONDUCT.md) community **Code of Conduct**. This type of commitment and communication is the way of making Go great.

View File

@ -44,6 +44,7 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [Request Logger](beginner/request-logger/main.go) * [Request Logger](beginner/request-logger/main.go)
* [Basic Authentication](beginner/basicauth/main.go) * [Basic Authentication](beginner/basicauth/main.go)
* [Level: Intermediate](intermediate) * [Level: Intermediate](intermediate)
* [OAUth2](intermediate/oauth2/main.go)
* [Transactions](intermediate/transactions/main.go) * [Transactions](intermediate/transactions/main.go)
* [HTTP Testing](intermediate/httptest/main_test.go) * [HTTP Testing](intermediate/httptest/main_test.go)
* [Watch & Compile Typescript source files](intermediate/typescript/main.go) * [Watch & Compile Typescript source files](intermediate/typescript/main.go)

View File

@ -0,0 +1,409 @@
package main
// Any OAuth2 (even the pure golang/x/net/oauth2) package
// can be used with Iris but at this example we will see the markbates' goth:
//
// $ go get github.com/markbates/goth/...
//
// This OAuth2 example works with sessions, so we will need
// to attach a session manager.
// Optionally: for even more secure session values,
// developers can use any third-party package to add a custom cookie encoder/decoder.
// At this example we will use the gorilla's securecookie:
//
// $ go get github.com/gorilla/securecookie
// Example of securecookie can be found at "sessions/securecookie" example folder.
// Notes:
// The whole example is converted by markbates/goth/example/main.go.
// It's tested with my own TWITTER application and it worked, even for localhost.
// I guess that everything else works as expected, all bugs reported by goth library's community
// are fixed in the time I wrote that example, have fun!
import (
"errors"
"os"
"sort"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/view"
"github.com/gorilla/securecookie" // optionally, used for session's encoder/decoder
"github.com/markbates/goth"
"github.com/markbates/goth/providers/amazon"
"github.com/markbates/goth/providers/auth0"
"github.com/markbates/goth/providers/bitbucket"
"github.com/markbates/goth/providers/box"
"github.com/markbates/goth/providers/dailymotion"
"github.com/markbates/goth/providers/deezer"
"github.com/markbates/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox"
"github.com/markbates/goth/providers/facebook"
"github.com/markbates/goth/providers/fitbit"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/gitlab"
"github.com/markbates/goth/providers/gplus"
"github.com/markbates/goth/providers/heroku"
"github.com/markbates/goth/providers/instagram"
"github.com/markbates/goth/providers/intercom"
"github.com/markbates/goth/providers/lastfm"
"github.com/markbates/goth/providers/linkedin"
"github.com/markbates/goth/providers/meetup"
"github.com/markbates/goth/providers/onedrive"
"github.com/markbates/goth/providers/openidConnect"
"github.com/markbates/goth/providers/paypal"
"github.com/markbates/goth/providers/salesforce"
"github.com/markbates/goth/providers/slack"
"github.com/markbates/goth/providers/soundcloud"
"github.com/markbates/goth/providers/spotify"
"github.com/markbates/goth/providers/steam"
"github.com/markbates/goth/providers/stripe"
"github.com/markbates/goth/providers/twitch"
"github.com/markbates/goth/providers/twitter"
"github.com/markbates/goth/providers/uber"
"github.com/markbates/goth/providers/wepay"
"github.com/markbates/goth/providers/xero"
"github.com/markbates/goth/providers/yahoo"
"github.com/markbates/goth/providers/yammer"
)
// These are some function helpers that you may use if you want
// GetProviderName is a function used to get the name of a provider
// for a given request. By default, this provider is fetched from
// the URL query string. If you provide it in a different way,
// assign your own function to this variable that returns the provider
// name for your request.
var GetProviderName = func(ctx context.Context) (string, error) {
// try to get it from the url param "provider"
if p := ctx.URLParam("provider"); p != "" {
return p, nil
}
// try to get it from the url PATH parameter "{provider} or :provider or {provider:string} or {provider:alphabetical}"
if p := ctx.Params().Get("provider"); p != "" {
return p, nil
}
// try to get it from context's per-request storage
if p := ctx.Values().GetString("provider"); p != "" {
return p, nil
}
// if not found then return an empty string with the corresponding error
return "", errors.New("you must select a provider")
}
/*
BeginAuthHandler is a convienence handler for starting the authentication process.
It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider".
BeginAuthHandler will redirect the user to the appropriate authentication end-point
for the requested provider.
See https://github.com/markbates/goth/examples/main.go to see this in action.
*/
func BeginAuthHandler(ctx context.Context) {
url, err := GetAuthURL(ctx)
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.Writef("%v", err)
return
}
ctx.Redirect(url, iris.StatusTemporaryRedirect)
}
/*
GetAuthURL starts the authentication process with the requested provided.
It will return a URL that should be used to send users to.
It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider" or from the context's value of "provider" key.
I would recommend using the BeginAuthHandler instead of doing all of these steps
yourself, but that's entirely up to you.
*/
func GetAuthURL(ctx context.Context) (string, error) {
providerName, err := GetProviderName(ctx)
if err != nil {
return "", err
}
provider, err := goth.GetProvider(providerName)
if err != nil {
return "", err
}
sess, err := provider.BeginAuth(SetState(ctx))
if err != nil {
return "", err
}
url, err := sess.GetAuthURL()
if err != nil {
return "", err
}
ctx.Session().Set(providerName, sess.Marshal())
return url, nil
}
// SetState sets the state string associated with the given request.
// If no state string is associated with the request, one will be generated.
// This state is sent to the provider and can be retrieved during the
// callback.
var SetState = func(ctx context.Context) string {
state := ctx.URLParam("state")
if len(state) > 0 {
return state
}
return "state"
}
// GetState gets the state returned by the provider during the callback.
// This is used to prevent CSRF attacks, see
// http://tools.ietf.org/html/rfc6749#section-10.12
var GetState = func(ctx context.Context) string {
return ctx.URLParam("state")
}
/*
CompleteUserAuth does what it says on the tin. It completes the authentication
process and fetches all of the basic information about the user from the provider.
It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider".
See https://github.com/markbates/goth/examples/main.go to see this in action.
*/
var CompleteUserAuth = func(ctx context.Context) (goth.User, error) {
providerName, err := GetProviderName(ctx)
if err != nil {
return goth.User{}, err
}
provider, err := goth.GetProvider(providerName)
if err != nil {
return goth.User{}, err
}
value := ctx.Session().GetString(providerName)
if value == "" {
return goth.User{}, errors.New("session value for " + providerName + " not found")
}
sess, err := provider.UnmarshalSession(value)
if err != nil {
return goth.User{}, err
}
user, err := provider.FetchUser(sess)
if err == nil {
// user can be found with existing session data
return user, err
}
// get new token and retry fetch
_, err = sess.Authorize(provider, ctx.Request().URL.Query())
if err != nil {
return goth.User{}, err
}
ctx.Session().Set(providerName, sess.Marshal())
return provider.FetchUser(sess)
}
// Logout invalidates a user session.
func Logout(ctx context.Context) error {
providerName, err := GetProviderName(ctx)
if err != nil {
return err
}
ctx.Session().Delete(providerName)
return nil
}
// End of the "some function helpers".
func main() {
goth.UseProviders(
twitter.New(os.Getenv("TWITTER_KEY"), os.Getenv("TWITTER_SECRET"), "http://localhost:3000/auth/twitter/callback"),
// If you'd like to use authenticate instead of authorize in Twitter provider, use this instead.
// twitter.NewAuthenticate(os.Getenv("TWITTER_KEY"), os.Getenv("TWITTER_SECRET"), "http://localhost:3000/auth/twitter/callback"),
facebook.New(os.Getenv("FACEBOOK_KEY"), os.Getenv("FACEBOOK_SECRET"), "http://localhost:3000/auth/facebook/callback"),
fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"),
gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"),
github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"),
spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"),
linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"),
lastfm.New(os.Getenv("LASTFM_KEY"), os.Getenv("LASTFM_SECRET"), "http://localhost:3000/auth/lastfm/callback"),
twitch.New(os.Getenv("TWITCH_KEY"), os.Getenv("TWITCH_SECRET"), "http://localhost:3000/auth/twitch/callback"),
dropbox.New(os.Getenv("DROPBOX_KEY"), os.Getenv("DROPBOX_SECRET"), "http://localhost:3000/auth/dropbox/callback"),
digitalocean.New(os.Getenv("DIGITALOCEAN_KEY"), os.Getenv("DIGITALOCEAN_SECRET"), "http://localhost:3000/auth/digitalocean/callback", "read"),
bitbucket.New(os.Getenv("BITBUCKET_KEY"), os.Getenv("BITBUCKET_SECRET"), "http://localhost:3000/auth/bitbucket/callback"),
instagram.New(os.Getenv("INSTAGRAM_KEY"), os.Getenv("INSTAGRAM_SECRET"), "http://localhost:3000/auth/instagram/callback"),
intercom.New(os.Getenv("INTERCOM_KEY"), os.Getenv("INTERCOM_SECRET"), "http://localhost:3000/auth/intercom/callback"),
box.New(os.Getenv("BOX_KEY"), os.Getenv("BOX_SECRET"), "http://localhost:3000/auth/box/callback"),
salesforce.New(os.Getenv("SALESFORCE_KEY"), os.Getenv("SALESFORCE_SECRET"), "http://localhost:3000/auth/salesforce/callback"),
amazon.New(os.Getenv("AMAZON_KEY"), os.Getenv("AMAZON_SECRET"), "http://localhost:3000/auth/amazon/callback"),
yammer.New(os.Getenv("YAMMER_KEY"), os.Getenv("YAMMER_SECRET"), "http://localhost:3000/auth/yammer/callback"),
onedrive.New(os.Getenv("ONEDRIVE_KEY"), os.Getenv("ONEDRIVE_SECRET"), "http://localhost:3000/auth/onedrive/callback"),
//Pointed localhost.com to http://localhost:3000/auth/yahoo/callback through proxy as yahoo
// does not allow to put custom ports in redirection uri
yahoo.New(os.Getenv("YAHOO_KEY"), os.Getenv("YAHOO_SECRET"), "http://localhost.com"),
slack.New(os.Getenv("SLACK_KEY"), os.Getenv("SLACK_SECRET"), "http://localhost:3000/auth/slack/callback"),
stripe.New(os.Getenv("STRIPE_KEY"), os.Getenv("STRIPE_SECRET"), "http://localhost:3000/auth/stripe/callback"),
wepay.New(os.Getenv("WEPAY_KEY"), os.Getenv("WEPAY_SECRET"), "http://localhost:3000/auth/wepay/callback", "view_user"),
//By default paypal production auth urls will be used, please set PAYPAL_ENV=sandbox as environment variable for testing
//in sandbox environment
paypal.New(os.Getenv("PAYPAL_KEY"), os.Getenv("PAYPAL_SECRET"), "http://localhost:3000/auth/paypal/callback"),
steam.New(os.Getenv("STEAM_KEY"), "http://localhost:3000/auth/steam/callback"),
heroku.New(os.Getenv("HEROKU_KEY"), os.Getenv("HEROKU_SECRET"), "http://localhost:3000/auth/heroku/callback"),
uber.New(os.Getenv("UBER_KEY"), os.Getenv("UBER_SECRET"), "http://localhost:3000/auth/uber/callback"),
soundcloud.New(os.Getenv("SOUNDCLOUD_KEY"), os.Getenv("SOUNDCLOUD_SECRET"), "http://localhost:3000/auth/soundcloud/callback"),
gitlab.New(os.Getenv("GITLAB_KEY"), os.Getenv("GITLAB_SECRET"), "http://localhost:3000/auth/gitlab/callback"),
dailymotion.New(os.Getenv("DAILYMOTION_KEY"), os.Getenv("DAILYMOTION_SECRET"), "http://localhost:3000/auth/dailymotion/callback", "email"),
deezer.New(os.Getenv("DEEZER_KEY"), os.Getenv("DEEZER_SECRET"), "http://localhost:3000/auth/deezer/callback", "email"),
discord.New(os.Getenv("DISCORD_KEY"), os.Getenv("DISCORD_SECRET"), "http://localhost:3000/auth/discord/callback", discord.ScopeIdentify, discord.ScopeEmail),
meetup.New(os.Getenv("MEETUP_KEY"), os.Getenv("MEETUP_SECRET"), "http://localhost:3000/auth/meetup/callback"),
//Auth0 allocates domain per customer, a domain must be provided for auth0 to work
auth0.New(os.Getenv("AUTH0_KEY"), os.Getenv("AUTH0_SECRET"), "http://localhost:3000/auth/auth0/callback", os.Getenv("AUTH0_DOMAIN")),
xero.New(os.Getenv("XERO_KEY"), os.Getenv("XERO_SECRET"), "http://localhost:3000/auth/xero/callback"),
)
// OpenID Connect is based on OpenID Connect Auto Discovery URL (https://openid.net/specs/openid-connect-discovery-1_0-17.html)
// because the OpenID Connect provider initialize it self in the New(), it can return an error which should be handled or ignored
// ignore the error for now
openidConnect, _ := openidConnect.New(os.Getenv("OPENID_CONNECT_KEY"), os.Getenv("OPENID_CONNECT_SECRET"), "http://localhost:3000/auth/openid-connect/callback", os.Getenv("OPENID_CONNECT_DISCOVERY_URL"))
if openidConnect != nil {
goth.UseProviders(openidConnect)
}
m := make(map[string]string)
m["amazon"] = "Amazon"
m["bitbucket"] = "Bitbucket"
m["box"] = "Box"
m["dailymotion"] = "Dailymotion"
m["deezer"] = "Deezer"
m["digitalocean"] = "Digital Ocean"
m["discord"] = "Discord"
m["dropbox"] = "Dropbox"
m["facebook"] = "Facebook"
m["fitbit"] = "Fitbit"
m["github"] = "Github"
m["gitlab"] = "Gitlab"
m["soundcloud"] = "SoundCloud"
m["spotify"] = "Spotify"
m["steam"] = "Steam"
m["stripe"] = "Stripe"
m["twitch"] = "Twitch"
m["uber"] = "Uber"
m["wepay"] = "Wepay"
m["yahoo"] = "Yahoo"
m["yammer"] = "Yammer"
m["gplus"] = "Google Plus"
m["heroku"] = "Heroku"
m["instagram"] = "Instagram"
m["intercom"] = "Intercom"
m["lastfm"] = "Last FM"
m["linkedin"] = "Linkedin"
m["onedrive"] = "Onedrive"
m["paypal"] = "Paypal"
m["twitter"] = "Twitter"
m["salesforce"] = "Salesforce"
m["slack"] = "Slack"
m["meetup"] = "Meetup.com"
m["auth0"] = "Auth0"
m["openid-connect"] = "OpenID Connect"
m["xero"] = "Xero"
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
providerIndex := &ProviderIndex{Providers: keys, ProvidersMap: m}
// create our app,
// set a view
// set sessions
// and setup the router for the showcase
app := iris.New()
// attach and build our templates
app.AttachView(view.HTML("./templates", ".html"))
// attach a session manager
cookieName := "mycustomsessionid"
// AES only supports key sizes of 16, 24 or 32 bytes.
// You either need to provide exactly that amount or you derive the key from what you type in.
hashKey := []byte("the-big-and-secret-fash-key-here")
blockKey := []byte("lot-secret-of-characters-big-too")
secureCookie := securecookie.New(hashKey, blockKey)
sessManager := sessions.New(sessions.Config{
Cookie: cookieName,
Encode: secureCookie.Encode,
Decode: secureCookie.Decode,
})
app.AttachSessionManager(sessManager)
// start of the router
app.Get("/auth/{provider}/callback", func(ctx context.Context) {
user, err := CompleteUserAuth(ctx)
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Writef("%v", err)
return
}
ctx.ViewData("", user)
if err := ctx.View("user.html"); err != nil {
ctx.Writef("%v", err)
}
})
app.Get("/logout/{provider}", func(ctx context.Context) {
Logout(ctx)
ctx.Redirect("/", iris.StatusTemporaryRedirect)
})
app.Get("/auth/{provider}", func(ctx context.Context) {
// try to get the user without re-authenticating
if gothUser, err := CompleteUserAuth(ctx); err == nil {
ctx.ViewData("", gothUser)
if err := ctx.View("user.html"); err != nil {
ctx.Writef("%v", err)
}
} else {
BeginAuthHandler(ctx)
}
})
app.Get("/", func(ctx context.Context) {
ctx.ViewData("", providerIndex)
if err := ctx.View("index.html"); err != nil {
ctx.Writef("%v", err)
}
})
// http://localhost:3000
app.Run(iris.Addr("localhost:3000"))
}
type ProviderIndex struct {
Providers []string
ProvidersMap map[string]string
}

View File

@ -0,0 +1,3 @@
{{range $key,$value:=.Providers}}
<p><a href="/auth/{{$value}}">Log in with {{index $.ProvidersMap $value}}</a></p>
{{end}}

View File

@ -0,0 +1,11 @@
<p><a href="/logout/{{.Provider}}">logout</a></p>
<p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
<p>Email: {{.Email}}</p>
<p>NickName: {{.NickName}}</p>
<p>Location: {{.Location}}</p>
<p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p>
<p>Description: {{.Description}}</p>
<p>UserID: {{.UserID}}</p>
<p>AccessToken: {{.AccessToken}}</p>
<p>ExpiresAt: {{.ExpiresAt}}</p>
<p>RefreshToken: {{.RefreshToken}}</p>

View File

@ -1,13 +1,16 @@
package main package main
import ( // developers can use any library to add a custom cookie encoder/decoder.
// developers can use any library to add a custom cookie encoder/decoder. // At this example we use the gorilla's securecookie package:
// At this example we use the gorilla's securecookie library: // $ go get github.com/gorilla/securecookie
"github.com/gorilla/securecookie" // $ go run main.go
import (
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/sessions" "github.com/kataras/iris/sessions"
"github.com/gorilla/securecookie"
) )
func main() { func main() {