mirror of
https://github.com/davidallendj/opaal.git
synced 2025-12-20 03:27:02 -07:00
Added token refresh flag
This commit is contained in:
parent
aecfa30e2c
commit
d0f8c9087d
5 changed files with 44 additions and 18 deletions
|
|
@ -24,7 +24,6 @@ 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"`
|
||||||
Refresh bool `yaml:"refresh"`
|
|
||||||
Verbose bool `yaml:"verbose"`
|
Verbose bool `yaml:"verbose"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +52,7 @@ type Authorization struct {
|
||||||
KeyPath string `yaml:"key-path"`
|
KeyPath string `yaml:"key-path"`
|
||||||
TokenDuration time.Duration `yaml:"token-duration"`
|
TokenDuration time.Duration `yaml:"token-duration"`
|
||||||
TokenForwarding bool `yaml:"token-forwarding"`
|
TokenForwarding bool `yaml:"token-forwarding"`
|
||||||
|
TokenRefresh bool `yaml:"refresh"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
@ -77,7 +77,6 @@ func NewConfig() Config {
|
||||||
CachePath: "opaal.db",
|
CachePath: "opaal.db",
|
||||||
FlowType: "authorization_code",
|
FlowType: "authorization_code",
|
||||||
CacheOnly: false,
|
CacheOnly: false,
|
||||||
Refresh: true,
|
|
||||||
Verbose: false,
|
Verbose: false,
|
||||||
},
|
},
|
||||||
Authentication: Authentication{
|
Authentication: Authentication{
|
||||||
|
|
@ -87,6 +86,7 @@ func NewConfig() Config {
|
||||||
KeyPath: "./keys",
|
KeyPath: "./keys",
|
||||||
TokenForwarding: false,
|
TokenForwarding: false,
|
||||||
TokenDuration: 1 * time.Hour,
|
TokenDuration: 1 * time.Hour,
|
||||||
|
TokenRefresh: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin
|
||||||
b := cryptox.MarshalRSAPrivateKey(privateKey)
|
b := cryptox.MarshalRSAPrivateKey(privateKey)
|
||||||
err = os.WriteFile(keyPath, b, os.ModePerm)
|
err = os.WriteFile(keyPath, b, os.ModePerm)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
privateKey, err := cryptox.GenerateRSAPrivateKey(rawPrivateKey)
|
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
|
// include the offline_access scope if refresh tokens are enabled
|
||||||
if params.Refresh {
|
if params.Refresh {
|
||||||
scope := payload["scp"].([]string)
|
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")
|
scope = append(scope, "offline_access")
|
||||||
payload["scp"] = scope
|
payload["scope"] = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
// also include offline_access in client to make request
|
||||||
|
client.Scope = append(client.Scope, "offline_access")
|
||||||
}
|
}
|
||||||
payloadJson, err := json.Marshal(payload)
|
payloadJson, err := json.Marshal(payload)
|
||||||
if err != nil {
|
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
|
// 6. send JWT to authorization server and receive a access token
|
||||||
if eps.Token != "" {
|
if eps.Token != "" {
|
||||||
fmt.Printf("Fetching access token from authorization server...\n")
|
fmt.Printf("Fetching access token from authorization server...\n")
|
||||||
|
fmt.Printf("jwt: %s\n", string(newJwt))
|
||||||
res, err := client.PerformTokenGrant(eps.Token, string(newJwt))
|
res, err := client.PerformTokenGrant(eps.Token, string(newJwt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to fetch access token: %v", err)
|
return "", fmt.Errorf("failed to fetch access token: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"davidallendj/opaal/internal/flows"
|
"davidallendj/opaal/internal/flows"
|
||||||
"davidallendj/opaal/internal/oauth"
|
"davidallendj/opaal/internal/oauth"
|
||||||
"davidallendj/opaal/internal/oidc"
|
"davidallendj/opaal/internal/oidc"
|
||||||
|
"davidallendj/opaal/internal/server"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -31,15 +32,15 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
||||||
|
|
||||||
// print the authorization URL for sharing
|
// print the authorization URL for sharing
|
||||||
var authorizationUrl = client.BuildAuthorizationUrl(provider.Endpoints.Authorization, state)
|
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",
|
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)
|
var button = MakeButton(authorizationUrl, "Login with "+client.Name)
|
||||||
|
|
||||||
// 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", s.GetListenAddr())
|
||||||
params := server.ServerParams{
|
params := server.ServerParams{
|
||||||
AuthProvider: &oidc.IdentityProvider{
|
AuthProvider: &oidc.IdentityProvider{
|
||||||
Issuer: config.Authorization.Endpoints.Issuer,
|
Issuer: config.Authorization.Endpoints.Issuer,
|
||||||
|
|
@ -49,24 +50,26 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Verbose: config.Options.Verbose,
|
Verbose: config.Options.Verbose,
|
||||||
JwtBearerFlowEndpoints: flows.JwtBearerEndpoints{
|
JwtBearerEndpoints: 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,
|
||||||
},
|
},
|
||||||
JwtBearerFlowParams: flows.JwtBearerFlowParams{
|
JwtBearerParams: 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: s.Addr,
|
||||||
Subject: "opaal",
|
Subject: "opaal",
|
||||||
ExpiresAt: time.Now().Add(config.Authorization.TokenDuration),
|
ExpiresAt: time.Now().Add(config.Authorization.TokenDuration),
|
||||||
|
Scope: []string{},
|
||||||
},
|
},
|
||||||
Verbose: config.Options.Verbose,
|
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) {
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,12 @@ func (client *Client) ListTrustedIssuers(url string) ([]TrustedIssuer, error) {
|
||||||
|
|
||||||
func (client *Client) AddTrustedIssuer(url string, ti *TrustedIssuer) ([]byte, error) {
|
func (client *Client) AddTrustedIssuer(url string, ti *TrustedIssuer) ([]byte, error) {
|
||||||
// hydra endpoint: POST /admin/trust/grants/jwt-bearer/issuers
|
// hydra endpoint: POST /admin/trust/grants/jwt-bearer/issuers
|
||||||
quotedScopes := make([]string, len(client.Scope))
|
if ti == nil {
|
||||||
for i, s := range client.Scope {
|
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)
|
quotedScopes[i] = fmt.Sprintf("\"%s\"", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,13 +77,13 @@ func (client *Client) AddTrustedIssuer(url string, ti *TrustedIssuer) ([]byte, e
|
||||||
"issuer": ti.Issuer,
|
"issuer": ti.Issuer,
|
||||||
"expires_at": ti.ExpiresAt,
|
"expires_at": ti.ExpiresAt,
|
||||||
"jwk": ti.PublicKey,
|
"jwk": ti.PublicKey,
|
||||||
"scope": client.Scope,
|
"scope": ti.Scope,
|
||||||
}
|
}
|
||||||
if !ti.AllowAnySubject {
|
if !ti.AllowAnySubject {
|
||||||
body["subject"] = ti.Subject
|
body["subject"] = ti.Subject
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(body)
|
b, err := json.Marshal(body)
|
||||||
fmt.Printf("request: %v\n", string(b))
|
// fmt.Printf("request: %v\n", string(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal request body: %v", err)
|
return nil, fmt.Errorf("failed to marshal request body: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,14 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
||||||
err := p.FetchServerConfig()
|
err := p.FetchServerConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to fetch server config: %v\n", err)
|
fmt.Printf("failed to fetch server config: %v\n", err)
|
||||||
|
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
err = p.FetchJwks()
|
err = p.FetchJwks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to fetch JWKS after fetching server config: %v\n", err)
|
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) {
|
r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// use refresh token provided to do a refresh token grant
|
// use refresh token provided to do a refresh token grant
|
||||||
refreshToken := r.URL.Query().Get("refresh-token")
|
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)
|
_, err := client.PerformRefreshTokenGrant(provider.Endpoints.Token, refreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to perform refresh token grant: %v\n", err)
|
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 {
|
if params.Verbose {
|
||||||
fmt.Printf("Serving error page.")
|
fmt.Printf("Serving error page.")
|
||||||
}
|
}
|
||||||
template, err := gonja.FromFile("pages/success.html")
|
template, err := gonja.FromFile("pages/error.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue