mirror of
https://github.com/davidallendj/opaal.git
synced 2025-12-20 03:27:02 -07:00
Added more to refresh token flow implementation
This commit is contained in:
parent
555d172ba6
commit
c25e3e2e1e
4 changed files with 127 additions and 46 deletions
|
|
@ -5,12 +5,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"davidallendj/opaal/internal/server"
|
"davidallendj/opaal/internal/server"
|
||||||
|
|
||||||
goutil "github.com/davidallendj/go-utils/util"
|
goutil "github.com/davidallendj/go-utils/util"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FlowOptions map[string]string
|
type FlowOptions map[string]string
|
||||||
|
|
@ -23,12 +24,14 @@ type Options struct {
|
||||||
FlowType string `yaml:"flow"`
|
FlowType string `yaml:"flow"`
|
||||||
CachePath string `yaml:"cache"`
|
CachePath string `yaml:"cache"`
|
||||||
CacheOnly bool `yaml:"cache-only"`
|
CacheOnly bool `yaml:"cache-only"`
|
||||||
TokenForwarding bool `yaml:"token-forwarding"`
|
|
||||||
Refresh bool `yaml:"refresh"`
|
Refresh bool `yaml:"refresh"`
|
||||||
Verbose bool `yaml:"verbose"`
|
Verbose bool `yaml:"verbose"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Endpoints struct {
|
type Endpoints struct {
|
||||||
|
Issuer string `yaml:"issuer"`
|
||||||
|
Config string `yaml:"config"`
|
||||||
|
JwksUri string `yaml:"jwks"`
|
||||||
Identities string `yaml:"identities"`
|
Identities string `yaml:"identities"`
|
||||||
TrustedIssuers string `yaml:"trusted-issuers"`
|
TrustedIssuers string `yaml:"trusted-issuers"`
|
||||||
Login string `yaml:"login"`
|
Login string `yaml:"login"`
|
||||||
|
|
@ -48,6 +51,8 @@ type Authentication struct {
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
Endpoints Endpoints `yaml:"endpoints"`
|
Endpoints Endpoints `yaml:"endpoints"`
|
||||||
KeyPath string `yaml:"key-path"`
|
KeyPath string `yaml:"key-path"`
|
||||||
|
TokenDuration time.Duration `yaml:"token-duration"`
|
||||||
|
TokenForwarding bool `yaml:"token-forwarding"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
@ -72,7 +77,6 @@ func NewConfig() Config {
|
||||||
CachePath: "opaal.db",
|
CachePath: "opaal.db",
|
||||||
FlowType: "authorization_code",
|
FlowType: "authorization_code",
|
||||||
CacheOnly: false,
|
CacheOnly: false,
|
||||||
TokenForwarding: false,
|
|
||||||
Refresh: true,
|
Refresh: true,
|
||||||
Verbose: false,
|
Verbose: false,
|
||||||
},
|
},
|
||||||
|
|
@ -81,6 +85,8 @@ func NewConfig() Config {
|
||||||
},
|
},
|
||||||
Authorization: Authorization{
|
Authorization: Authorization{
|
||||||
KeyPath: "./keys",
|
KeyPath: "./keys",
|
||||||
|
TokenForwarding: false,
|
||||||
|
TokenDuration: 1 * time.Hour,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,23 +40,33 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
||||||
|
|
||||||
// authorize oauth client and listen for callback from provider
|
// 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", server.GetListenAddr())
|
||||||
eps := flows.JwtBearerEndpoints{
|
params := server.ServerParams{
|
||||||
|
AuthProvider: &oidc.IdentityProvider{
|
||||||
|
Issuer: config.Authorization.Endpoints.Issuer,
|
||||||
|
Endpoints: oidc.Endpoints{
|
||||||
|
Config: config.Authorization.Endpoints.Config,
|
||||||
|
JwksUri: config.Authorization.Endpoints.JwksUri,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Verbose: config.Options.Verbose,
|
||||||
|
JwtBearerFlowEndpoints: flows.JwtBearerEndpoints{
|
||||||
Token: config.Authorization.Endpoints.Token,
|
Token: config.Authorization.Endpoints.Token,
|
||||||
TrustedIssuers: config.Authorization.Endpoints.TrustedIssuers,
|
TrustedIssuers: config.Authorization.Endpoints.TrustedIssuers,
|
||||||
Register: config.Authorization.Endpoints.Register,
|
Register: config.Authorization.Endpoints.Register,
|
||||||
}
|
},
|
||||||
params := flows.JwtBearerFlowParams{
|
JwtBearerFlowParams: flows.JwtBearerFlowParams{
|
||||||
Client: oauth.NewClient(),
|
Client: oauth.NewClient(),
|
||||||
IdentityProvider: provider,
|
IdentityProvider: provider,
|
||||||
TrustedIssuer: &oauth.TrustedIssuer{
|
TrustedIssuer: &oauth.TrustedIssuer{
|
||||||
AllowAnySubject: false,
|
AllowAnySubject: false,
|
||||||
Issuer: server.Addr,
|
Issuer: server.Addr,
|
||||||
Subject: "opaal",
|
Subject: "opaal",
|
||||||
ExpiresAt: time.Now().Add(time.Second * 3600),
|
ExpiresAt: time.Now().Add(config.Authorization.TokenDuration),
|
||||||
},
|
},
|
||||||
Verbose: config.Options.Verbose,
|
Verbose: config.Options.Verbose,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err = server.Login(button, provider, client, eps, params)
|
err = server.Login(button, provider, client, params)
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n")
|
fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type IdentityProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Endpoints struct {
|
type Endpoints struct {
|
||||||
|
Config string `db:"config_endpoint" json:"config_endpoint" yaml:"config"`
|
||||||
Authorization string `db:"authorization_endpoint" json:"authorization_endpoint" yaml:"authorization"`
|
Authorization string `db:"authorization_endpoint" json:"authorization_endpoint" yaml:"authorization"`
|
||||||
Token string `db:"token_endpoint" json:"token_endpoint" yaml:"token"`
|
Token string `db:"token_endpoint" json:"token_endpoint" yaml:"token"`
|
||||||
Revocation string `db:"revocation_endpoint" json:"revocation_endpoint" yaml:"revocation"`
|
Revocation string `db:"revocation_endpoint" json:"revocation_endpoint" yaml:"revocation"`
|
||||||
|
|
@ -109,6 +110,30 @@ func (p *IdentityProvider) LoadServerConfig(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *IdentityProvider) FetchServerConfig() error {
|
||||||
|
// make a request to a server's openid-configuration
|
||||||
|
req, err := http.NewRequest(http.MethodGet, p.Issuer+"/.well-known/openid-configuration", bytes.NewBuffer([]byte{}))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a new request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{} // temp client to get info and not used in flow
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to do request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read response body: %v", err)
|
||||||
|
}
|
||||||
|
err = p.ParseServerConfig(body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse server config: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func FetchServerConfig(issuer string) (*IdentityProvider, error) {
|
func FetchServerConfig(issuer string) (*IdentityProvider, error) {
|
||||||
// make a request to a server's openid-configuration
|
// make a request to a server's openid-configuration
|
||||||
req, err := http.NewRequest(http.MethodGet, issuer+"/.well-known/openid-configuration", bytes.NewBuffer([]byte{}))
|
req, err := http.NewRequest(http.MethodGet, issuer+"/.well-known/openid-configuration", bytes.NewBuffer([]byte{}))
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,13 @@ type Server struct {
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerParams struct {
|
||||||
|
AuthProvider *oidc.IdentityProvider
|
||||||
|
Verbose bool
|
||||||
|
JwtBearerEndpoints flows.JwtBearerEndpoints
|
||||||
|
JwtBearerParams flows.JwtBearerFlowParams
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) SetListenAddr(host string, port int) {
|
func (s *Server) SetListenAddr(host string, port int) {
|
||||||
s.Addr = s.GetListenAddr()
|
s.Addr = s.GetListenAddr()
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +38,7 @@ func (s *Server) GetListenAddr() string {
|
||||||
return fmt.Sprintf("%s:%d", s.Host, s.Port)
|
return fmt.Sprintf("%s:%d", s.Host, s.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *oauth.Client, eps flows.JwtBearerEndpoints, params flows.JwtBearerFlowParams) error {
|
func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *oauth.Client, params ServerParams) error {
|
||||||
var target = ""
|
var target = ""
|
||||||
|
|
||||||
// check if callback is set
|
// check if callback is set
|
||||||
|
|
@ -66,7 +73,41 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
r.HandleFunc("/key", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/keys", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
p = params.AuthProvider
|
||||||
|
jwks []byte
|
||||||
|
)
|
||||||
|
// try and get the JWKS from param first
|
||||||
|
if p.Endpoints.JwksUri != "" {
|
||||||
|
err := p.FetchJwks()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to fetch keys using JWKS url...trying to fetch config and try again...\n")
|
||||||
|
}
|
||||||
|
jwks, err = json.Marshal(p.KeySet)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to marshal JWKS: %v\n", err)
|
||||||
|
}
|
||||||
|
} else if p.Endpoints.Config != "" && jwks == nil {
|
||||||
|
// otherwise, try and fetch the whole config and try again
|
||||||
|
err := p.FetchServerConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to fetch server config: %v\n", err)
|
||||||
|
}
|
||||||
|
err = p.FetchJwks()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to fetch JWKS after fetching server config: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward the JWKS from the authorization server
|
||||||
|
if jwks == nil {
|
||||||
|
fmt.Printf("no JWKS was fetched from authorization server\n")
|
||||||
|
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(jwks)
|
||||||
|
|
||||||
})
|
})
|
||||||
r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -81,12 +122,13 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
||||||
|
|
||||||
// return token to target if set or the sending client
|
// return token to target if set or the sending client
|
||||||
returnTarget := r.URL.Query().Get("target")
|
returnTarget := r.URL.Query().Get("target")
|
||||||
if returnTarget != "" {
|
if returnTarget == "" {
|
||||||
|
returnTarget = r.URL.Host
|
||||||
} else {
|
}
|
||||||
host := r.URL.Host
|
_, _, err = httpx.MakeHttpRequest(returnTarget, http.MethodPost, httpx.Body{}, httpx.Headers{})
|
||||||
httpx.MakeHttpRequest(host, http.MethodPost, httpx.Body{}, httpx.Headers{})
|
if err != nil {
|
||||||
|
fmt.Printf("failed to make request")
|
||||||
|
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
r.HandleFunc(s.Callback, func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc(s.Callback, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -127,12 +169,10 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: extract scopes from ID token and add to trusted issuer
|
|
||||||
|
|
||||||
// complete JWT bearer flow to receive access token from authorization server
|
// complete JWT bearer flow to receive access token from authorization server
|
||||||
// fmt.Printf("bearer: %v\n", string(bearerToken))
|
// fmt.Printf("bearer: %v\n", string(bearerToken))
|
||||||
params.IdToken = data["id_token"].(string)
|
params.JwtBearerParams.IdToken = data["id_token"].(string)
|
||||||
accessToken, err = flows.NewJwtBearerFlow(eps, params)
|
accessToken, err = flows.NewJwtBearerFlow(params.JwtBearerEndpoints, params.JwtBearerParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to complete JWT bearer flow: %v\n", err)
|
fmt.Printf("failed to complete JWT bearer flow: %v\n", err)
|
||||||
w.Header().Add("Content-type", "text/html")
|
w.Header().Add("Content-type", "text/html")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue