From 3f55ce6dfc6b5e31cbf28c77a228afc715ea80a9 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 9 Jul 2023 12:30:36 +1000 Subject: [PATCH] [v3] Add provider methods, provide default login window, update readme for `oauth` plugin --- v3/examples/oauth/main.go | 20 +-- v3/plugins/oauth/README.md | 162 ++++++++++++++++- v3/plugins/oauth/plugin.go | 359 ++++++++++++++++++++++++++++++++++++- 3 files changed, 508 insertions(+), 33 deletions(-) diff --git a/v3/examples/oauth/main.go b/v3/examples/oauth/main.go index caf882b8a..3d2ac2c69 100644 --- a/v3/examples/oauth/main.go +++ b/v3/examples/oauth/main.go @@ -41,15 +41,6 @@ func main() { }, }) - oAuthWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ - Title: "Login", - Width: 600, - Height: 850, - Hidden: true, - DisableResize: true, - URL: "http://localhost:9876/auth/github", - }) - app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ Title: "OAuth Demo", DevToolsEnabled: true, @@ -61,17 +52,8 @@ func main() { }, }) - // Custom event handling - app.Events.On(oauth.Success, func(e *application.WailsEvent) { - oAuthWindow.Hide() - }) - - app.Events.On(oauth.Error, func(e *application.WailsEvent) { - oAuthWindow.Hide() - }) app.Events.On("github-login", func(e *application.WailsEvent) { - oAuthPlugin.Start() - oAuthWindow.Show() + oAuthPlugin.Github() }) err := app.Run() diff --git a/v3/plugins/oauth/README.md b/v3/plugins/oauth/README.md index b39a2e79b..e435b7797 100644 --- a/v3/plugins/oauth/README.md +++ b/v3/plugins/oauth/README.md @@ -1,10 +1,71 @@ # oauth Plugin -This plugin provides the ability to initiate an OAuth authentication flow. +This plugin provides the ability to initiate an OAuth authentication flow with a wide range of OAuth providers: + + - Amazon + - Apple + - Auth0 + - AzureAD + - BattleNet + - Bitbucket + - Box + - Dailymotion + - Deezer + - DigitalOcean + - Discord + - Dropbox + - EveOnline + - Facebook + - Fitbit + - Gitea + - Gitlab + - Github + - Google + - GooglePlus + - Heroku + - Intercom + - Instagram + - Kakao + - LastFM + - LinkedIn + - Line + - Mastodon + - Meetup + - MicrosoftOnline + - Naver + - NextCloud + - Okta + - Onedrive + - OpenIDConnect + - Patreon + - PayPal + - SalesForce + - SeaTalk + - Shopify + - Slack + - SoundCloud + - Spotify + - Steam + - Strava + - Stripe + - TikTok + - Twitter + - TwitterV2 + - Typetalk + - Twitch + - Uber + - VK + - WeCom + - Wepay + - Xero + - Yahoo + - Yammer + - Yandex + - Zoom ## Installation -Add the plugin to the `Plugins` option in the Applications options: +Add the plugin to the `Plugins` option in the Applications options. This example we are using the github provider: ```go package main @@ -34,20 +95,109 @@ func main() { }) ``` +### Configuration + +The plugin takes a `Config` struct as a parameter. This struct has the following fields: + +```go +type Config struct { + + // Address to bind the temporary webserver to + // Defaults to localhost:9876 + Address string + + // SessionSecret is the secret used to encrypt the session store. + SessionSecret string + + // MaxAge is the maximum age of the session in seconds. + MaxAge int + + // Providers is a list of goth providers to use. + Providers []goth.Provider + + // WindowConfig is the configuration for the window that will be opened + // to perform the OAuth login. + WindowConfig *application.WebviewWindowOptions +} +``` + +If you don't specify a `WindowConfig`, the plugin will use the default window configuration: + +```go + &application.WebviewWindowOptions{ + Title: "OAuth Login", + Width: 600, + Height: 850, + Hidden: true, + } +``` + ## Usage ### Go -You can start the flow by calling `Start()` on the plugin instance: +You can start the flow by calling one of the provider methods: + +```go + err := oAuthPlugin.Github() +``` + +In this example, we send an event from the frontend to start the process, so we listen for the event in the backend: ```go app.Events.On("github-login", func(e *application.WailsEvent) { - oAuthPlugin.Start() - oAuthWindow.Show() + err := oAuthPlugin.Github() + if err != nil { + // process error + } }) ``` -There is a working example of github auth in the `v3/examples` directory. +### JavaScript + +You can start the flow by calling one of the provider methods: + +```javascript + await wails.Plugin("oauth","Github") +``` + +### Handling Success & Failure + +When the OAuth flow completes, the plugin will send one of 2 events: + + - `wails:oauth:success` - The OAuth flow completed successfully. The event data will contain the user information. + - `wails:oauth:error` - The OAuth flow failed. The event data will contain the error message. + +In Javascript, we can listen for these events like so: + +```javascript + window.wails.Events.On("wails:oauth:success", (event) => { + document.getElementById("main").style.display = "none"; + document.getElementById("name").innerText = event.data.Name; + document.getElementById("logo").src = event.data.AvatarURL; + document.body.style.backgroundColor = "#000"; + document.body.style.color = "#FFF"; + }); +``` + +If you want to handle them in Go, you can do so like this: + +```go + app.Events.On("wails:oauth:success", func(e *application.WailsEvent) { + // Do something with the user data + }) +``` + +Both these events are constants in the plugin: + +```go + const ( + Success = "wails:oauth:success" + Error = "wails:oauth:error" + ) +``` + +There is a working example of GitHub auth in the `v3/examples/oauth` directory. ## Support diff --git a/v3/plugins/oauth/plugin.go b/v3/plugins/oauth/plugin.go index d46c2d067..18f9057e1 100644 --- a/v3/plugins/oauth/plugin.go +++ b/v3/plugins/oauth/plugin.go @@ -1,6 +1,7 @@ package oauth import ( + "fmt" "github.com/gorilla/pat" "github.com/gorilla/sessions" "github.com/markbates/goth" @@ -40,6 +41,10 @@ type Config struct { // Providers is a list of goth providers to use. Providers []goth.Provider + + // WindowConfig is the configuration for the window that will be opened + // to perform the OAuth login. + WindowConfig *application.WebviewWindowOptions } func NewPlugin(config Config) *Plugin { @@ -52,6 +57,14 @@ func NewPlugin(config Config) *Plugin { if result.config.Address == "" { result.config.Address = "localhost:9876" } + if result.config.WindowConfig == nil { + result.config.WindowConfig = &application.WebviewWindowOptions{ + Title: "OAuth Login", + Width: 600, + Height: 850, + Hidden: true, + } + } return result } @@ -109,7 +122,66 @@ func (p *Plugin) Init(_ *application.App) error { func (p *Plugin) CallableByJS() []string { return []string{ - "Start", + "Amazon", + "Apple", + "Auth0", + "AzureAD", + "BattleNet", + "Bitbucket", + "Box", + "Dailymotion", + "Deezer", + "DigitalOcean", + "Discord", + "Dropbox", + "EveOnline", + "Facebook", + "Fitbit", + "Gitea", + "Gitlab", + "Github", + "Google", + "GooglePlus", + "Heroku", + "Intercom", + "Instagram", + "Kakao", + "LastFM", + "LinkedIn", + "Line", + "Mastodon", + "Meetup", + "MicrosoftOnline", + "Naver", + "NextCloud", + "Okta", + "Onedrive", + "OpenIDConnect", + "Patreon", + "PayPal", + "SalesForce", + "SeaTalk", + "Shopify", + "Slack", + "SoundCloud", + "Spotify", + "Steam", + "Strava", + "Stripe", + "TikTok", + "Twitter", + "TwitterV2", + "Typetalk", + "Twitch", + "Uber", + "VK", + "WeCom", + "Wepay", + "Xero", + "Yahoo", + "Yammer", + "Yandex", + "Zoom", } } @@ -117,19 +189,290 @@ func (p *Plugin) InjectJS() string { return "" } -// ---------------- Plugin Methods ---------------- - -func (p *Plugin) Start() { +func (p *Plugin) start(provider string) error { if p.server != nil { - println("Already listening") - return + return fmt.Errorf("server already processing request. Please wait for the current login to complete") } p.server = &http.Server{ Addr: p.config.Address, Handler: p.router, } - println("Starting server") go p.server.ListenAndServe() - time.Sleep(1 * time.Second) + // Keep trying to connect until we succeed + var keepTrying = true + var connected = false + + go func() { + time.Sleep(3 * time.Second) + keepTrying = false + }() + + for keepTrying { + _, err := http.Get("http://" + p.config.Address) + if err == nil { + connected = true + break + } + } + + if !connected { + return fmt.Errorf("server failed to start") + } + + // create a window + p.config.WindowConfig.URL = "http://" + p.config.Address + "/auth/" + provider + window := application.Get().NewWebviewWindowWithOptions(*p.config.WindowConfig) + window.Show() + + application.Get().Events.On(Success, func(event *application.WailsEvent) { + window.Close() + }) + application.Get().Events.On(Error, func(event *application.WailsEvent) { + window.Close() + }) + + return nil +} + +// ---------------- Plugin Methods ---------------- + +func (p *Plugin) Amazon() error { + return p.start("amazon") +} + +func (p *Plugin) Apple() error { + return p.start("apple") +} + +func (p *Plugin) Auth0() error { + return p.start("auth0") +} + +func (p *Plugin) AzureAD() error { + return p.start("azuread") +} + +func (p *Plugin) BattleNet() error { + return p.start("battlenet") +} + +func (p *Plugin) Bitbucket() error { + return p.start("bitbucket") +} + +func (p *Plugin) Box() error { + return p.start("box") +} + +func (p *Plugin) Dailymotion() error { + return p.start("dailymotion") +} + +func (p *Plugin) Deezer() error { + return p.start("deezer") +} + +func (p *Plugin) DigitalOcean() error { + return p.start("digitalocean") +} + +func (p *Plugin) Discord() error { + return p.start("discord") +} + +func (p *Plugin) Dropbox() error { + return p.start("dropbox") +} + +func (p *Plugin) EveOnline() error { + return p.start("eveonline") +} + +func (p *Plugin) Facebook() error { + return p.start("facebook") +} + +func (p *Plugin) Fitbit() error { + return p.start("fitbit") +} + +func (p *Plugin) Gitea() error { + return p.start("gitea") +} + +func (p *Plugin) Gitlab() error { + return p.start("gitlab") +} + +func (p *Plugin) Github() error { + return p.start("github") +} + +func (p *Plugin) Google() error { + return p.start("google") +} + +func (p *Plugin) GooglePlus() error { + return p.start("gplus") +} + +func (p *Plugin) Heroku() error { + return p.start("heroku") +} + +func (p *Plugin) Intercom() error { + return p.start("intercom") +} + +func (p *Plugin) Instagram() error { + return p.start("instagram") +} + +func (p *Plugin) Kakao() error { + return p.start("kakao") +} + +func (p *Plugin) LastFM() error { + return p.start("lastfm") +} + +func (p *Plugin) LinkedIn() error { + return p.start("linkedin") +} + +func (p *Plugin) Line() error { + return p.start("line") +} + +func (p *Plugin) Mastodon() error { + return p.start("mastodon") +} + +func (p *Plugin) Meetup() error { + return p.start("meetup") +} + +func (p *Plugin) MicrosoftOnline() error { + return p.start("microsoftonline") +} + +func (p *Plugin) Naver() error { + return p.start("naver") +} + +func (p *Plugin) NextCloud() error { + return p.start("nextcloud") +} + +func (p *Plugin) Okta() error { + return p.start("okta") +} + +func (p *Plugin) Onedrive() error { + return p.start("onedrive") +} + +func (p *Plugin) OpenIDConnect() error { + return p.start("openid-connect") +} + +func (p *Plugin) Patreon() error { + return p.start("patreon") +} + +func (p *Plugin) PayPal() error { + return p.start("paypal") +} + +func (p *Plugin) SalesForce() error { + return p.start("salesforce") +} + +func (p *Plugin) SeaTalk() error { + return p.start("seatalk") +} + +func (p *Plugin) Shopify() error { + return p.start("shopify") +} + +func (p *Plugin) Slack() error { + return p.start("slack") +} + +func (p *Plugin) SoundCloud() error { + return p.start("soundcloud") +} + +func (p *Plugin) Spotify() error { + return p.start("spotify") +} + +func (p *Plugin) Steam() error { + return p.start("steam") +} + +func (p *Plugin) Strava() error { + return p.start("strava") +} + +func (p *Plugin) Stripe() error { + return p.start("stripe") +} + +func (p *Plugin) TikTok() error { + return p.start("tiktok") +} + +func (p *Plugin) Twitter() error { + return p.start("twitter") +} + +func (p *Plugin) TwitterV2() error { + return p.start("twitterv2") +} + +func (p *Plugin) Typetalk() error { + return p.start("typetalk") +} + +func (p *Plugin) Twitch() error { + return p.start("twitch") +} + +func (p *Plugin) Uber() error { + return p.start("uber") +} + +func (p *Plugin) VK() error { + return p.start("vk") +} + +func (p *Plugin) WeCom() error { + return p.start("wecom") +} + +func (p *Plugin) Wepay() error { + return p.start("wepay") +} + +func (p *Plugin) Xero() error { + return p.start("xero") +} + +func (p *Plugin) Yahoo() error { + return p.start("yahoo") +} + +func (p *Plugin) Yammer() error { + return p.start("yammer") +} + +func (p *Plugin) Yandex() error { + return p.start("yandex") +} + +func (p *Plugin) Zoom() error { + return p.start("zoom") }