From d0f8c9087d548a720669a434cc167f8e810f5a13 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 19 Mar 2024 11:21:05 -0600 Subject: [PATCH] Added token refresh flag --- internal/config.go | 4 ++-- internal/flows/jwt_bearer.go | 18 ++++++++++++++---- internal/login.go | 17 ++++++++++------- internal/oauth/trusted.go | 12 ++++++++---- internal/server/server.go | 11 ++++++++++- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/internal/config.go b/internal/config.go index 55d457a..23c1910 100644 --- a/internal/config.go +++ b/internal/config.go @@ -24,7 +24,6 @@ type Options struct { FlowType string `yaml:"flow"` CachePath string `yaml:"cache"` CacheOnly bool `yaml:"cache-only"` - Refresh bool `yaml:"refresh"` Verbose bool `yaml:"verbose"` } @@ -53,6 +52,7 @@ type Authorization struct { KeyPath string `yaml:"key-path"` TokenDuration time.Duration `yaml:"token-duration"` TokenForwarding bool `yaml:"token-forwarding"` + TokenRefresh bool `yaml:"refresh"` } type Config struct { @@ -77,7 +77,6 @@ func NewConfig() Config { CachePath: "opaal.db", FlowType: "authorization_code", CacheOnly: false, - Refresh: true, Verbose: false, }, Authentication: Authentication{ @@ -87,6 +86,7 @@ func NewConfig() Config { KeyPath: "./keys", TokenForwarding: false, TokenDuration: 1 * time.Hour, + TokenRefresh: true, }, } } diff --git a/internal/flows/jwt_bearer.go b/internal/flows/jwt_bearer.go index 59e60ef..72c878d 100644 --- a/internal/flows/jwt_bearer.go +++ b/internal/flows/jwt_bearer.go @@ -85,7 +85,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin b := cryptox.MarshalRSAPrivateKey(privateKey) err = os.WriteFile(keyPath, b, os.ModePerm) if err != nil { - fmt.Printf("failed to write private key to file: %v", err) + fmt.Printf("failed to write private key to file: %v\n", err) } } else { privateKey, err := cryptox.GenerateRSAPrivateKey(rawPrivateKey) @@ -140,9 +140,18 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin // include the offline_access scope if refresh tokens are enabled if params.Refresh { - scope := payload["scp"].([]string) - scope = append(scope, "offline_access") - payload["scp"] = scope + v, ok := payload["scope"] + if !ok { + payload["scope"] = []string{"offline_access"} + } else { + // FIXME: probably should not assume scope is []string even though it should be + scope := v.([]string) + scope = append(scope, "offline_access") + payload["scope"] = scope + } + + // also include offline_access in client to make request + client.Scope = append(client.Scope, "offline_access") } payloadJson, err := json.Marshal(payload) if err != nil { @@ -201,6 +210,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin // 6. send JWT to authorization server and receive a access token if eps.Token != "" { fmt.Printf("Fetching access token from authorization server...\n") + fmt.Printf("jwt: %s\n", string(newJwt)) res, err := client.PerformTokenGrant(eps.Token, string(newJwt)) if err != nil { return "", fmt.Errorf("failed to fetch access token: %v", err) diff --git a/internal/login.go b/internal/login.go index 9ef564f..1641e13 100644 --- a/internal/login.go +++ b/internal/login.go @@ -5,6 +5,7 @@ import ( "davidallendj/opaal/internal/flows" "davidallendj/opaal/internal/oauth" "davidallendj/opaal/internal/oidc" + "davidallendj/opaal/internal/server" "errors" "fmt" "net/http" @@ -31,15 +32,15 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider // print the authorization URL for sharing var authorizationUrl = client.BuildAuthorizationUrl(provider.Endpoints.Authorization, state) - server := NewServerWithConfig(config) + s := NewServerWithConfig(config) fmt.Printf("Login with identity provider:\n\n %s/login\n %s\n\n", - server.GetListenAddr(), authorizationUrl, + s.GetListenAddr(), authorizationUrl, ) var button = MakeButton(authorizationUrl, "Login with "+client.Name) // authorize oauth client and listen for callback from provider - fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", server.GetListenAddr()) + fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", s.GetListenAddr()) params := server.ServerParams{ AuthProvider: &oidc.IdentityProvider{ Issuer: config.Authorization.Endpoints.Issuer, @@ -49,24 +50,26 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider }, }, Verbose: config.Options.Verbose, - JwtBearerFlowEndpoints: flows.JwtBearerEndpoints{ + JwtBearerEndpoints: flows.JwtBearerEndpoints{ Token: config.Authorization.Endpoints.Token, TrustedIssuers: config.Authorization.Endpoints.TrustedIssuers, Register: config.Authorization.Endpoints.Register, }, - JwtBearerFlowParams: flows.JwtBearerFlowParams{ + JwtBearerParams: flows.JwtBearerFlowParams{ Client: oauth.NewClient(), IdentityProvider: provider, TrustedIssuer: &oauth.TrustedIssuer{ AllowAnySubject: false, - Issuer: server.Addr, + Issuer: s.Addr, Subject: "opaal", ExpiresAt: time.Now().Add(config.Authorization.TokenDuration), + Scope: []string{}, }, Verbose: config.Options.Verbose, + Refresh: config.Authorization.TokenRefresh, }, } - err = server.Login(button, provider, client, params) + err = s.Login(button, provider, client, params) if errors.Is(err, http.ErrServerClosed) { fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n") } else if err != nil { diff --git a/internal/oauth/trusted.go b/internal/oauth/trusted.go index be8647f..6c90dfc 100644 --- a/internal/oauth/trusted.go +++ b/internal/oauth/trusted.go @@ -62,8 +62,12 @@ func (client *Client) ListTrustedIssuers(url string) ([]TrustedIssuer, error) { func (client *Client) AddTrustedIssuer(url string, ti *TrustedIssuer) ([]byte, error) { // hydra endpoint: POST /admin/trust/grants/jwt-bearer/issuers - quotedScopes := make([]string, len(client.Scope)) - for i, s := range client.Scope { + if ti == nil { + return nil, fmt.Errorf("no valid trusted issuer provided") + } + + quotedScopes := make([]string, len(ti.Scope)) + for i, s := range ti.Scope { quotedScopes[i] = fmt.Sprintf("\"%s\"", s) } @@ -73,13 +77,13 @@ func (client *Client) AddTrustedIssuer(url string, ti *TrustedIssuer) ([]byte, e "issuer": ti.Issuer, "expires_at": ti.ExpiresAt, "jwk": ti.PublicKey, - "scope": client.Scope, + "scope": ti.Scope, } if !ti.AllowAnySubject { body["subject"] = ti.Subject } b, err := json.Marshal(body) - fmt.Printf("request: %v\n", string(b)) + // fmt.Printf("request: %v\n", string(b)) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %v", err) } diff --git a/internal/server/server.go b/internal/server/server.go index 5e80cc6..afe3af8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -93,10 +93,14 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client * err := p.FetchServerConfig() if err != nil { fmt.Printf("failed to fetch server config: %v\n", err) + http.Redirect(w, r, "/error", http.StatusInternalServerError) + return } err = p.FetchJwks() if err != nil { fmt.Printf("failed to fetch JWKS after fetching server config: %v\n", err) + http.Redirect(w, r, "/error", http.StatusInternalServerError) + return } } @@ -113,6 +117,11 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client * r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) { // use refresh token provided to do a refresh token grant refreshToken := r.URL.Query().Get("refresh-token") + if refreshToken == "" { + fmt.Printf("no refresh token provided") + http.Redirect(w, r, "/error", http.StatusBadRequest) + return + } _, err := client.PerformRefreshTokenGrant(provider.Endpoints.Token, refreshToken) if err != nil { fmt.Printf("failed to perform refresh token grant: %v\n", err) @@ -214,7 +223,7 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client * if params.Verbose { fmt.Printf("Serving error page.") } - template, err := gonja.FromFile("pages/success.html") + template, err := gonja.FromFile("pages/error.html") if err != nil { panic(err) }