diff options
| -rw-r--r-- | client/client.go | 50 | ||||
| -rw-r--r-- | docs/src/api/functiondocs.md | 23 | ||||
| -rw-r--r-- | docs/src/api/statemachine.md | 2 | ||||
| -rw-r--r-- | exports/exports.go | 7 | ||||
| -rw-r--r-- | internal/oauth/oauth.go | 30 | ||||
| -rw-r--r-- | internal/server/server.go | 8 |
6 files changed, 98 insertions, 22 deletions
diff --git a/client/client.go b/client/client.go index 64e01bd..2afb1a9 100644 --- a/client/client.go +++ b/client/client.go @@ -112,6 +112,22 @@ type Client struct { mu sync.Mutex } +func (c *Client) NeedsMobileRedirect() bool { + splitted := strings.Split(c.Name, ".") + last := splitted[len(splitted)-1] + return last == "android" || last == "ios" +} + +func (c *Client) MobileRedirect() string { + vals := map[string]string{ + "org.letsconnect-vpn.app.ios": "org.letsconnect-vpn.app.ios:/api/callback", + "org.letsconnect-vpn.app.android": "org.letsconnect-vpn.app:/api/callback", + "org.eduvpn.app.ios": "org.eduvpn.app.ios:/api/callback", + "org.eduvpn.app.android": "org.eduvpn.app:/api/callback", + } + return vals[c.Name] +} + func (c *Client) updateTokens(srv server.Server) error { if c.TokenGetter == nil { return errors.New("no token getter defined") @@ -360,15 +376,39 @@ func (c *Client) locationCallback(ck *cookie.Cookie) error { } func (c *Client) loginCallback(ck *cookie.Cookie, srv server.Server) error { - url, err := server.OAuthURL(srv, c.Name) - if err != nil { - return err + // get a custom redirect + cr := "" + if c.NeedsMobileRedirect() { + cr = c.MobileRedirect() } - err = c.FSM.GoTransitionRequired(StateOAuthStarted, url) + url, err := server.OAuthURL(srv, c.Name, cr) if err != nil { return err } - err = server.OAuthExchange(ck.Context(), srv) + authCodeURI := "" + if c.NeedsMobileRedirect() { + errChan := make(chan error) + go func() { + err := c.FSM.GoTransitionRequired(StateOAuthStarted, &srvtypes.RequiredAskTransition{ + C: ck, + Data: url, + }) + if err != nil { + errChan <- err + } + }() + g, err := ck.Receive(errChan) + if err != nil { + return err + } + authCodeURI = g + } else { + err = c.FSM.GoTransitionRequired(StateOAuthStarted, url) + if err != nil { + return err + } + } + err = server.OAuthExchange(ck.Context(), srv, authCodeURI) if err != nil { return err } diff --git a/docs/src/api/functiondocs.md b/docs/src/api/functiondocs.md index 1f31871..7937196 100644 --- a/docs/src/api/functiondocs.md +++ b/docs/src/api/functiondocs.md @@ -86,7 +86,15 @@ The following state callbacks are mandatory to handle: - OAUTH_STARTED: This indicates that the OAuth procedure has been started, it returns the URL as the data. The client should open the webbrowser - with this URL and continue the authorization process. + with this URL and continue the authorization process. Note: For mobile + platforms this returns a Cookie and data (json: {"cookie": x, "data": + url}). This `url` should also be opened in the browser like desktop + platforms. But these platforms also need to reply to the library + to give back the full authorization code URI with CookieReply(x, + uri) that the apps get back when the user clicks approve. For this, + apps need to register an app url or sorts. For the valid values + for app URLs, see the redirect URIs for mobile platforms here + https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php Example Input (3=custom server): ```AddServer(mycookie, 3, "https://demo.eduvpn.nl", 0)``` @@ -483,8 +491,17 @@ So a client would: ### OAUTH_STARTED -This indicates that the OAuth procedure has been started, it returns the URL -as the data. + - OAUTH_STARTED: This indicates that the OAuth procedure has been started, + it returns the URL as the data. The client should open the webbrowser + with this URL and continue the authorization process. Note: For mobile + platforms this returns a Cookie and data (json: {"cookie": x, "data": + url}). This `url` should also be opened in the browser like desktop + platforms. But these platforms also need to reply to the library + to give back the full authorization code URI with CookieReply(x, + uri) that the apps get back when the user clicks approve. For this, + apps need to register an app url or sorts. For the valid values + for app URLs, see the redirect URIs for mobile platforms here + https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php The client should open the webbrowser with this URL and continue the authorization process. This is only called if authorization needs to be diff --git a/docs/src/api/statemachine.md b/docs/src/api/statemachine.md index 29c0549..2c86d93 100644 --- a/docs/src/api/statemachine.md +++ b/docs/src/api/statemachine.md @@ -171,7 +171,7 @@ For the explanation of what all the different states mean, see the [client docum In eduvpn-common, there are certain states that require attention from the client. -- OAuth Started: A state that must be handled by the client. How a client can 'handle' this state, we will see in the next section. In this state, the client must open the webbrowser with the authorization URL to complete to OAuth process +- OAuth Started: A state that must be handled by the client. How a client can 'handle' this state, we will see in the next section. In this state, the client must open the webbrowser with the authorization URL to complete to OAuth process. Note that on mobile platforms, you also need to reply with the authorization URI as these platforms do not support a local callback server using 127.0.0.1 - Ask Profile: The state that asks for a profile selection to the client. Reply to this state by using a "cookie" and the CookieReply function. What this means will be discussed in the Python client example too - Ask Location: Same for ask profile but for selecting a secure internet location. Only called if one must be chosen, e.g. due to a selection that is no longer valid diff --git a/exports/exports.go b/exports/exports.go index 8e3ab1c..d9ae7f3 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -290,7 +290,8 @@ func Deregister() *C.char { // The following state callbacks are mandatory to handle: // // - OAUTH_STARTED: This indicates that the OAuth procedure has been started, it returns the URL as the data. -// The client should open the webbrowser with this URL and continue the authorization process. +// The client should open the webbrowser with this URL and continue the authorization process. Note: For mobile platforms this returns a Cookie and data (json: {"cookie": x, "data": url}). +// This `url` should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php // // Example Input (3=custom server): // ```AddServer(mycookie, 3, "https://demo.eduvpn.nl", 0)``` @@ -542,7 +543,9 @@ func ServerList() (*C.char, *C.char) { // // ### OAUTH_STARTED // -// This indicates that the OAuth procedure has been started, it returns the URL as the data. +// - OAUTH_STARTED: This indicates that the OAuth procedure has been started, it returns the URL as the data. +// The client should open the webbrowser with this URL and continue the authorization process. Note: For mobile platforms this returns a Cookie and data (json: {"cookie": x, "data": url}). +// This `url` should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php // // The client should open the webbrowser with this URL and continue the authorization process. // This is only called if authorization needs to be retriggered diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go index 4a873f1..6de3ac7 100644 --- a/internal/oauth/oauth.go +++ b/internal/oauth/oauth.go @@ -446,7 +446,7 @@ func (oauth *OAuth) ListenerPort() (int, error) { } // AuthURL gets the authorization url to start the OAuth procedure. -func (oauth *OAuth) AuthURL(name string, postProcessAuth func(string) string) (string, error) { +func (oauth *OAuth) AuthURL(name string, postProcessAuth func(string) string, cr string) (string, error) { // Update the client ID oauth.ClientID = name @@ -478,10 +478,14 @@ func (oauth *OAuth) AuthURL(name string, postProcessAuth func(string) string) (s return "", errors.WrapPrefix(err, "oauth.setupListener error", 0) } - // Get the listener port - port, err := oauth.ListenerPort() - if err != nil { - return "", errors.WrapPrefix(err, "oauth.ListenerPort error", 0) + red := cr + if cr == "" { + // Get the listener port + port, err := oauth.ListenerPort() + if err != nil { + return "", errors.WrapPrefix(err, "oauth.ListenerPort error", 0) + } + red = fmt.Sprintf("http://127.0.0.1:%d/callback", port) } params := map[string]string{ @@ -491,7 +495,7 @@ func (oauth *OAuth) AuthURL(name string, postProcessAuth func(string) string) (s "response_type": "code", "scope": "config", "state": state, - "redirect_uri": fmt.Sprintf("http://127.0.0.1:%d/callback", port), + "redirect_uri": red, } p, err := url.Parse(oauth.BaseAuthorizationURL) @@ -510,13 +514,25 @@ func (oauth *OAuth) AuthURL(name string, postProcessAuth func(string) string) (s return postProcessAuth(u), nil } +func (oauth *OAuth) tokensWithURI(ctx context.Context, uri string) error { + // parse URI + p, err := url.Parse(uri) + if err != nil { + return err + } + return oauth.tokenHandler(ctx, p) +} + // Exchange starts the OAuth exchange by getting the tokens with the redirect callback // If it was unsuccessful it returns an error. -func (oauth *OAuth) Exchange(ctx context.Context) error { +func (oauth *OAuth) Exchange(ctx context.Context, uri string) error { // If there is no HTTP client defined, create a new one if oauth.httpClient == nil { oauth.httpClient = httpw.NewClient() } + if uri != "" { + return oauth.tokensWithURI(ctx, uri) + } return oauth.tokensWithCallback(ctx) } diff --git a/internal/server/server.go b/internal/server/server.go index b6f3b30..1bdef28 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -49,12 +49,12 @@ func UpdateTokens(srv Server, t oauth.Token) { srv.OAuth().UpdateTokens(t) } -func OAuthURL(srv Server, name string) (string, error) { - return srv.OAuth().AuthURL(name, srv.TemplateAuth()) +func OAuthURL(srv Server, name string, cr string) (string, error) { + return srv.OAuth().AuthURL(name, srv.TemplateAuth(), cr) } -func OAuthExchange(ctx context.Context, srv Server) error { - return srv.OAuth().Exchange(ctx) +func OAuthExchange(ctx context.Context, srv Server, uri string) error { + return srv.OAuth().Exchange(ctx, uri) } func HeaderToken(ctx context.Context, srv Server) (string, error) { |
