Skip to content

Commit

Permalink
Store user session in browser
Browse files Browse the repository at this point in the history
  • Loading branch information
feedmeapples committed Sep 17, 2022
1 parent 5d0a358 commit 5a5adf8
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 225 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ e2e/test-results
e2e/playwright/.cache
e2e/.env


# session filesystem store path
.tmp

# Output of code generation tools
third_party/OpenAPI/temporal

Expand Down
26 changes: 0 additions & 26 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"

"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"github.com/temporalio/ui-server/v2/plugins/forwardheaders"
"github.com/temporalio/ui-server/v2/plugins/fs_config_provider"
"github.com/temporalio/ui-server/v2/server"
Expand Down Expand Up @@ -103,23 +100,6 @@ func buildCLI() *cli.App {
}),
}

if cfg.Session.Filesystem.Path != "" {
fmt.Println("Using filesystem session store at: ", cfg.Session.Filesystem.Path)
if _, err := os.Stat(cfg.Session.Filesystem.Path); os.IsNotExist(err) {
fmt.Println("Creating session store directory: ", cfg.Session.Filesystem.Path)
os.Mkdir(cfg.Session.Filesystem.Path, 0755)
}

sessStore := sessions.NewFilesystemStore(cfg.Session.Filesystem.Path,
securecookie.GenerateRandomKey(32),
securecookie.GenerateRandomKey(32),
)
sessStore.MaxLength(64 * 1024)
opts = append(opts, server_options.WithSessionStore(sessStore))
} else {
fmt.Println("Using cookie session store")
}

s := server.NewServer(opts...)
defer s.Stop()
err = s.Start()
Expand All @@ -133,9 +113,3 @@ func buildCLI() *cli.App {
}
return app
}

func getDefaultFilesystemSessionPath() string {
dir, _ := os.Getwd()
sessDir := filepath.Join(dir, ".tmp")
return sessDir
}
28 changes: 8 additions & 20 deletions server/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (

"github.com/gogo/gateway"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -46,8 +45,8 @@ type Auth struct {
}

type CodecResponse struct {
Endpoint string
AccessToken string
Endpoint string
PassAccessToken bool
}

type SettingsResponse struct {
Expand Down Expand Up @@ -105,20 +104,6 @@ func GetSettings(cfgProvier *config.ConfigProviderWithRefresh) func(echo.Context
}
}

codec := &CodecResponse{
Endpoint: cfg.Codec.Endpoint,
}
if cfg.Codec.PassAccessToken {
sess, _ := session.Get(auth.AuthCookie, c)
if sess != nil {
token := sess.Values[auth.AccessTokenKey]
if token != nil {
codec.AccessToken = token.(string)
}

}
}

settings := &SettingsResponse{
Auth: &Auth{
Enabled: cfg.Auth.Enabled,
Expand All @@ -128,9 +113,12 @@ func GetSettings(cfgProvier *config.ConfigProviderWithRefresh) func(echo.Context
ShowTemporalSystemNamespace: cfg.ShowTemporalSystemNamespace,
FeedbackURL: cfg.FeedbackURL,
NotifyOnNewVersion: cfg.NotifyOnNewVersion,
Codec: codec,
Version: version.UIVersion,
DisableWriteActions: cfg.DisableWriteActions,
Codec: &CodecResponse{
Endpoint: cfg.Codec.Endpoint,
PassAccessToken: cfg.Codec.PassAccessToken,
},
Version: version.UIVersion,
DisableWriteActions: cfg.DisableWriteActions,
}

return c.JSON(http.StatusOK, settings)
Expand Down
163 changes: 15 additions & 148 deletions server/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,83 +25,39 @@
package auth

import (
"bytes"
"context"
"net/http"
"time"

"github.com/gorilla/sessions"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"github.com/temporalio/ui-server/v2/server/config"
"google.golang.org/grpc/metadata"
)

const (
AuthCookie = "auth"
AccessTokenKey = "access-token"
AuthExtrasCookie = "auth-extras"
IDTokenKey = "id-token"
EmailKey = "email"
NameKey = "name"
PictureKey = "picture"
AuthorizationExtrasHeader = "authorization-extras"
)

var (
sessOpts = &sessions.Options{
Path: "/",
MaxAge: 7 * 24 * int(time.Hour.Seconds()),
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
}
)

func GetCurrentUser(c echo.Context) error {
sess, _ := session.Get(AuthExtrasCookie, c)
email := sess.Values[EmailKey]
name := sess.Values[NameKey]
picture := sess.Values[PictureKey]

if email == nil {
return c.JSON(http.StatusOK, nil)
}
func SetUser(html []byte, user *User) []byte {
html = bytes.Replace(html, []byte("{{.AccessToken}}"), []byte(user.OAuth2Token.AccessToken), 1)
html = bytes.Replace(html, []byte("{{.IDToken}}"), []byte(user.IDToken.RawToken), 1)
html = bytes.Replace(html, []byte("{{.UserName}}"), []byte(user.IDToken.Claims.Name), 1)
html = bytes.Replace(html, []byte("{{.UserEmail}}"), []byte(user.IDToken.Claims.Email), 1)
html = bytes.Replace(html, []byte("{{.UserPicture}}"), []byte(user.IDToken.Claims.Picture), 1)

user := struct {
Email string
Name string
Picture string
}{email.(string), name.(string), picture.(string)}

return c.JSON(http.StatusOK, user)
return html
}

func SetUser(c echo.Context, user *User) error {
err := setAccessToken(c, user.OAuth2Token.AccessToken)
if err != nil {
return err
}
func SetReturnURL(html []byte, returnURL, publicPath string) []byte {
html = bytes.Replace(html, []byte("{{.ReturnUrl}}"), []byte(returnURL), 1)
html = bytes.Replace(html, []byte("{{.PublicPath}}"), []byte(publicPath), 1)

err = setIDToken(c, user.IDToken)
if err != nil {
return err
}

return nil
return html
}

func ClearUser(c echo.Context) error {
err := clearAccessToken(c)
if err != nil {
return err
}

err = clearIDToken(c)
if err != nil {
return err
}

// TODO logout user from IDP
return nil
}

Expand Down Expand Up @@ -145,98 +101,9 @@ func WithAuth(c echo.Context) runtime.ServeMuxOption {
}

func getAccessToken(c echo.Context) string {
rToken := c.Request().Header.Get(echo.HeaderAuthorization)
if rToken != "" {
return rToken
}

sess, _ := session.Get(AuthCookie, c)
if sess == nil {
return ""
}

cToken := sess.Values[AccessTokenKey]
if cToken == nil {
return ""
}

return "Bearer " + cToken.(string)
}

func setAccessToken(c echo.Context, token string) error {
sess, _ := session.Get(AuthCookie, c)
sess.Options = sessOpts
sess.Values[AccessTokenKey] = &token

err := sess.Save(c.Request(), c.Response())
if err != nil {
return err
}

return nil
}

func clearAccessToken(c echo.Context) error {
sess, _ := session.Get(AuthCookie, c)
sess.Options = &sessions.Options{
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
}
err := sess.Save(c.Request(), c.Response())
if err != nil {
return err
}

return nil
return c.Request().Header.Get(echo.HeaderAuthorization)
}

func getAuthorizationExtras(c echo.Context) string {
sess, _ := session.Get(AuthExtrasCookie, c)
if sess == nil {
return ""
}

extras := sess.Values[IDTokenKey]
if extras == nil {
return ""
}

return extras.(string)
}

func setIDToken(c echo.Context, idToken *IDToken) error {
sess, _ := session.Get(AuthExtrasCookie, c)
sess.Options = sessOpts

sess.Values[EmailKey] = &idToken.Claims.Email
sess.Values[PictureKey] = &idToken.Claims.Picture
sess.Values[NameKey] = &idToken.Claims.Name
sess.Values[IDTokenKey] = &idToken.RawToken

err := sess.Save(c.Request(), c.Response())
if err != nil {
return err
}

return nil
}

func clearIDToken(c echo.Context) error {
sess, _ := session.Get(AuthExtrasCookie, c)
sess.Options = &sessions.Options{
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
}
err := sess.Save(c.Request(), c.Response())
if err != nil {
return err
}

return nil
return c.Request().Header.Get(AuthorizationExtrasHeader)
}
73 changes: 73 additions & 0 deletions server/auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package auth_test

import (
_ "embed"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/temporalio/ui-server/v2/server/auth"
"golang.org/x/oauth2"
)

//go:embed auth_test.html
var html []byte

const (
metaT = "<meta name=\"%s\" content=\"%s\" />"
)

func TestSetUser(t *testing.T) {
user := auth.User{
OAuth2Token: &oauth2.Token{
AccessToken: "XXX.YYY.ZZZ",
},
IDToken: &auth.IDToken{
RawToken: "MMM.JJJ.NNN",
Claims: auth.Claims{
Email: "test@email.com",
EmailVerified: true,
Name: "test-name",
Picture: "test-picture",
},
},
}

html = auth.SetUser(html, &user)

assert.Contains(t, string(html), fmt.Sprintf(metaT, "access-token", user.OAuth2Token.AccessToken))
assert.Contains(t, string(html), fmt.Sprintf(metaT, "id-token", user.IDToken.RawToken))
assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-name", user.IDToken.Claims.Name))
assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-email", user.IDToken.Claims.Email))
assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-picture", user.IDToken.Claims.Picture))
}

func TestSetReturnURL(t *testing.T) {
html = auth.SetReturnURL(html, "http://localhost:8080/namespaces/default", "/custom-path")

metaT := "<meta name=\"%s\" content=\"%s\" />"
assert.Contains(t, string(html), fmt.Sprintf(metaT, "return-url", "http://localhost:8080/namespaces/default"))
assert.Contains(t, string(html), fmt.Sprintf(metaT, "public-path", "/custom-path"))
}
13 changes: 13 additions & 0 deletions server/auth/auth_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="access-token" content="{{.AccessToken}}" />
<meta name="id-token" content="{{.IDToken}}" />
<meta name="user-name" content="{{.UserName}}" />
<meta name="user-email" content="{{.UserEmail}}" />
<meta name="user-picture" content="{{.UserPicture}}" />
<meta name="return-url" content="{{.ReturnUrl}}" />
<meta name="public-path" content="{{.PublicPath}}" />
</head>
</html>
Loading

0 comments on commit 5a5adf8

Please sign in to comment.