diff --git a/go.mod b/go.mod index 6e942d2..719f216 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/google/subcommands v1.2.0 github.com/google/uuid v1.4.0 github.com/julienschmidt/httprouter v1.3.0 + github.com/rs/cors v1.10.1 github.com/stretchr/testify v1.8.4 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d ) diff --git a/go.sum b/go.sum index b92086c..3356947 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/server/flow.go b/server/flow.go index c1c6ae2..8030f06 100644 --- a/server/flow.go +++ b/server/flow.go @@ -3,18 +3,13 @@ package server import ( "context" _ "embed" - "encoding/json" "fmt" "github.com/1f349/lavender/issuer" "github.com/1f349/lavender/server/pages" - "github.com/1f349/mjwt/auth" - "github.com/1f349/mjwt/claims" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/julienschmidt/httprouter" "golang.org/x/oauth2" "net/http" - "net/mail" "net/url" "strings" "time" @@ -138,98 +133,14 @@ func (h *HttpServer) flowCallback(rw http.ResponseWriter, req *http.Request, _ h http.Error(rw, "Failed to exchange code", http.StatusInternalServerError) return } - v2, err := testOa2UserInfo(v.sso, req.Context(), exchange) - if err != nil { - fmt.Println("Failed to get userinfo:", err) - http.Error(rw, "Failed to get userinfo", http.StatusInternalServerError) - return - } - defer v2.Body.Close() - if v2.StatusCode != http.StatusOK { - http.Error(rw, "Failed to get userinfo: unexpected status code", http.StatusInternalServerError) - return - } - var v3 map[string]any - if err = json.NewDecoder(v2.Body).Decode(&v3); err != nil { - fmt.Println("Failed to decode userinfo:", err) - http.Error(rw, "Failed to decode userinfo", http.StatusInternalServerError) - return - } - - sub, ok := v3["sub"].(string) - if !ok { - http.Error(rw, "Invalid subject in userinfo", http.StatusInternalServerError) - return - } - aud, ok := v3["aud"].(string) - if !ok { - http.Error(rw, "Invalid audience in userinfo", http.StatusInternalServerError) - return - } - - var needsMailFlag, needsDomains bool - - ps := claims.NewPermStorage() - for _, i := range v.target.Permissions { - if strings.HasPrefix(i, "dynamic:") { - switch i { - case "dynamic:mail-inbox": - needsMailFlag = true - case "dynamic:domain-owns": - needsDomains = true - } - } else { - ps.Set(i) - } - } - - if needsMailFlag { - if verified, ok := v3["email_verified"].(bool); ok && verified { - if mailAddress, ok := v3["email"].(string); ok { - address, err := mail.ParseAddress(mailAddress) - if err != nil { - http.Error(rw, "Invalid email in userinfo", http.StatusInternalServerError) - return - } - n := strings.IndexByte(address.Address, '@') - if n != -1 { - if address.Address[n+1:] == v.sso.Config.Namespace { - ps.Set("mail:inbox=" + address.Address) - } - } - } - } - } - - if needsDomains { - a := h.conf.Load().Users.AllDomains(sub + "@" + v.sso.Config.Namespace) - for _, i := range a { - ps.Set("domain:owns=" + i) - } - } - - nsSub := sub + "@" + v.sso.Config.Namespace - ati := uuidNewStringAti() - accessToken, err := h.signer.GenerateJwt(nsSub, ati, jwt.ClaimStrings{aud}, 15*time.Minute, auth.AccessTokenClaims{ - Perms: ps, - }) - if err != nil { - http.Error(rw, "Error generating access token", http.StatusInternalServerError) - return - } - - refreshToken, err := h.signer.GenerateJwt(nsSub, uuidNewStringRti(), jwt.ClaimStrings{aud}, 15*time.Minute, auth.RefreshTokenClaims{AccessTokenId: ati}) - if err != nil { - http.Error(rw, "Error generating refresh token", http.StatusInternalServerError) - return - } - - pages.RenderPageTemplate(rw, "flow-callback", map[string]any{ - "ServiceName": h.conf.Load().ServiceName, - "TargetOrigin": v.target.Url.String(), - "TargetMessage": v3, - "AccessToken": accessToken, - "RefreshToken": refreshToken, + h.finishTokenGenerateFlow(rw, req, v, exchange, func(accessToken, refreshToken string, v3 map[string]any) { + pages.RenderPageTemplate(rw, "flow-callback", map[string]any{ + "ServiceName": h.conf.Load().ServiceName, + "TargetOrigin": v.target.Url.String(), + "TargetMessage": v3, + "AccessToken": accessToken, + "RefreshToken": refreshToken, + }) }) } diff --git a/server/flow_test.go b/server/flow_test.go index 2a0d84c..b2af5d5 100644 --- a/server/flow_test.go +++ b/server/flow_test.go @@ -356,9 +356,9 @@ func TestFlowCallback(t *testing.T) { - + function openWindow(url, winnm, options) { + var wTop = firstAvailableValue([window.screen.availTop, window.screenY, window.screenTop, 0]); + var wLeft = firstAvailableValue([window.screen.availLeft, window.screenX, window.screenLeft, 0]); + var top = 0, left = 0; + var result; + if ((result = /top=(\d+)/g.exec(options))) top = parseInt(result[1]); + if ((result = /left=(\d+)/g.exec(options))) left = parseInt(result[1]); + if (options) { + options = options.replace("top=" + top, "top=" + (parseInt(top) + wTop)); + options = options.replace("left=" + left, "left=" + (parseInt(left) + wLeft)); + w = window.open(url, winnm, options); + } else w = window.open(url, winnm); + return w; + } + + function firstAvailableValue(arr) { + for (var i = 0; i < arr.length; i++) + if (typeof arr[i] != 'undefined') + return arr[i]; + } + + function doThisThing() { + if (currentLoginPopup) currentLoginPopup.close(); + currentLoginPopup = popupCenterScreen(ssoService + '/popup?origin=' + encodeURIComponent(location.origin), 'Login with Lavender', 500, 500, false); + } + + async function refreshAllTokens() { + let req = await fetch(ssoService + '/refresh', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'include', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({"token": currentTokens.refresh}), + }); + let reqJson = await req.json(); + updateTokenInfo(reqJson); + } + +
-

Test Client

+

Test Client

+
+ + +
+
- +
+ +
+
+ +
-
-
- -
-
-

Permissions:

-
    -
    +
    +

    Permissions:

    +
      +