Compare commits

..

No commits in common. "main" and "v0.3.7" have entirely different histories.
main ... v0.3.7

7 changed files with 59 additions and 78 deletions

View file

@ -51,9 +51,6 @@ func NewJwtBearerFlow(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) (s
if client == nil { if client == nil {
return "", fmt.Errorf("invalid client (client is nil)") return "", fmt.Errorf("invalid client (client is nil)")
} }
if verbose {
fmt.Printf("ID token (IDP): %s\n access token (IDP): %s", accessToken, idToken)
}
if accessToken != "" { if accessToken != "" {
_, err := jws.Verify([]byte(accessToken), jws.WithKeySet(client.Provider.KeySet), jws.WithValidateKey(true)) _, err := jws.Verify([]byte(accessToken), jws.WithKeySet(client.Provider.KeySet), jws.WithValidateKey(true))
if err != nil { if err != nil {

View file

@ -94,7 +94,6 @@ func NewServerWithConfig(conf *Config) *server.Server {
Host: conf.Server.Issuer.Host, Host: conf.Server.Issuer.Host,
Port: conf.Server.Issuer.Port, Port: conf.Server.Issuer.Port,
Endpoints: conf.Server.Issuer.Endpoints, Endpoints: conf.Server.Issuer.Endpoints,
Clients: conf.Server.Issuer.Clients,
}, },
} }
return server return server

View file

@ -109,14 +109,12 @@ func (client *Client) FetchTokenFromAuthenticationServer(code string, state stri
} }
res, err := http.PostForm(client.Provider.Endpoints.Token, body) res, err := http.PostForm(client.Provider.Endpoints.Token, body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get ID token: %v", err) return nil, fmt.Errorf("failed to get ID token: %s", err)
} }
b, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %v", err)
}
fmt.Printf("%s\n", string(b))
defer res.Body.Close() defer res.Body.Close()
return b, nil // domain, _ := url.Parse("http://127.0.0.1")
// client.Jar.SetCookies(domain, res.Cookies())
return io.ReadAll(res.Body)
} }

View file

@ -175,6 +175,7 @@ func UpdateEndpoints(eps *Endpoints, other *Endpoints) {
if ep != nil { if ep != nil {
if *ep == "" { if *ep == "" {
*ep = s *ep = s
fmt.Printf("updated %s\n", s)
} }
} }
} }

View file

@ -3,6 +3,7 @@ package server
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"davidallendj/opaal/internal/oauth"
"davidallendj/opaal/internal/oidc" "davidallendj/opaal/internal/oidc"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -21,22 +22,6 @@ import (
"github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/jwt"
) )
// TODO: make this a completely separate server
type IdentityProviderServer struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Endpoints oidc.Endpoints `yaml:"endpoints"`
Clients []RegisteredClient `yaml:"clients"`
}
// NOTE: could we use a oauth.Client here instead??
type RegisteredClient struct {
Id string `yaml:"id"`
Secret string `yaml:"secret"`
Name string `yaml:"name"`
RedirectUris []string `yaml:"redirect-uris"`
}
func (s *Server) StartIdentityProvider() error { func (s *Server) StartIdentityProvider() error {
// NOTE: this example does NOT implement CSRF tokens nor use them // NOTE: this example does NOT implement CSRF tokens nor use them
@ -44,13 +29,19 @@ func (s *Server) StartIdentityProvider() error {
var ( var (
r = chi.NewRouter() r = chi.NewRouter()
// clients = []oauth.Client{} // clients = []oauth.Client{}
callback = ""
activeCodes = []string{} activeCodes = []string{}
) )
// check if callback is set
if s.Callback == "" {
callback = "/oidc/callback"
}
// update endpoints that have values set // update endpoints that have values set
defaultEps := oidc.Endpoints{ defaultEps := oidc.Endpoints{
Authorization: "http://" + s.Addr + "/oauth2/authorize", Authorization: "http://" + s.Addr + "/oauth/authorize",
Token: "http://" + s.Addr + "/oauth2/token", Token: "http://" + s.Addr + "/oauth/token",
JwksUri: "http://" + s.Addr + "/.well-known/jwks.json", JwksUri: "http://" + s.Addr + "/.well-known/jwks.json",
} }
oidc.UpdateEndpoints(&s.Issuer.Endpoints, &defaultEps) oidc.UpdateEndpoints(&s.Issuer.Endpoints, &defaultEps)
@ -147,18 +138,21 @@ func (s *Server) StartIdentityProvider() error {
username := r.Form.Get("username") username := r.Form.Get("username")
password := r.Form.Get("password") password := r.Form.Get("password")
if len(s.Issuer.Clients) <= 0 {
fmt.Printf("no registered clients found with identity provider (add them in config)\n")
return
}
// example username and password so do simplified authorization code flow // example username and password so do simplified authorization code flow
if username == "openchami" && password == "openchami" { if username == "ochami" && password == "ochami" {
client := s.Issuer.Clients[0] client := oauth.Client{
Id: "ochami",
Secret: "ochami",
Name: "ochami",
Provider: oidc.IdentityProvider{
Issuer: "http://127.0.0.1:3333",
},
RedirectUris: []string{fmt.Sprintf("http://%s:%d%s", s.Host, s.Port, callback)},
}
// check if there are any redirect URIs supplied // check if there are any redirect URIs supplied
if len(client.RedirectUris) <= 0 { if len(client.RedirectUris) <= 0 {
fmt.Printf("no redirect URIs found for client %s (ID: %s)\n", client.Name, client.Id) fmt.Printf("no redirect URIs found")
return return
} }
for _, url := range client.RedirectUris { for _, url := range client.RedirectUris {
@ -266,13 +260,9 @@ func (s *Server) StartIdentityProvider() error {
return return
} }
// find a valid client // check that we're using the default registered client
index := slices.IndexFunc(s.Issuer.Clients, func(c RegisteredClient) bool { if clientId != "ochami" {
fmt.Printf("%s ? %s\n", c.Id, clientId) fmt.Printf("invalid client\n")
return c.Id == clientId
})
if index < 0 {
fmt.Printf("no valid client found")
return return
} }

View file

@ -25,6 +25,12 @@ type Server struct {
Issuer IdentityProviderServer `yaml:"issuer"` Issuer IdentityProviderServer `yaml:"issuer"`
} }
type IdentityProviderServer struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Endpoints oidc.Endpoints `yaml:"endpoints"`
}
type ServerParams struct { type ServerParams struct {
AuthProvider *oidc.IdentityProvider AuthProvider *oidc.IdentityProvider
Verbose bool Verbose bool
@ -57,7 +63,7 @@ func (s *Server) StartLogin(clients []oauth.Client, params ServerParams) error {
// make the login page SSO buttons and authorization URLs to write to stdout // make the login page SSO buttons and authorization URLs to write to stdout
buttons := "" buttons := ""
fmt.Printf("Login with an identity provider: \n") fmt.Printf("Login with external identity providers: \n")
for i, client := range clients { for i, client := range clients {
// fetch provider configuration before adding button // fetch provider configuration before adding button
p, err := oidc.FetchServerConfig(client.Provider.Issuer) p, err := oidc.FetchServerConfig(client.Provider.Issuer)
@ -74,7 +80,8 @@ func (s *Server) StartLogin(clients []oauth.Client, params ServerParams) error {
clients[i].Provider = *p clients[i].Provider = *p
buttons += makeButton(fmt.Sprintf("/login?sso=%s", client.Id), client.Name) buttons += makeButton(fmt.Sprintf("/login?sso=%s", client.Id), client.Name)
fmt.Printf("\t%s: /login?sso=%s\n", client.Name, client.Id) url := client.BuildAuthorizationUrl(s.State)
fmt.Printf("\t%s\n", url)
} }
var code string var code string
@ -114,9 +121,7 @@ func (s *Server) StartLogin(clients []oauth.Client, params ServerParams) error {
client = &clients[index] client = &clients[index]
url := client.BuildAuthorizationUrl(s.State) url := client.BuildAuthorizationUrl(s.State)
if params.Verbose { fmt.Printf("Redirect URL: %s\n", url)
fmt.Printf("Redirect URL: %s\n", url)
}
http.Redirect(w, r, url, http.StatusFound) http.Redirect(w, r, url, http.StatusFound)
return return
} }
@ -141,47 +146,38 @@ func (s *Server) StartLogin(clients []oauth.Client, params ServerParams) error {
p = params.AuthProvider p = params.AuthProvider
jwks []byte jwks []byte
) )
// try and get the JWKS from param first
fetchAndMarshal := func() (err error) { if p.Endpoints.JwksUri != "" {
err = p.FetchJwks() err := p.FetchJwks()
if err != nil { if err != nil {
fmt.Printf("failed to fetch keys: %v\n", err) fmt.Printf("failed to fetch keys using JWKS url...trying to fetch config and try again...\n")
return
} }
jwks, err = json.Marshal(p.KeySet) jwks, err = json.Marshal(p.KeySet)
if err != nil { if err != nil {
fmt.Printf("failed to marshal JWKS: %v\n", err) fmt.Printf("failed to marshal JWKS: %v\n", err)
} }
return } else if p.Endpoints.Config != "" && jwks == nil {
} // otherwise, try and fetch the whole config and try again
err := p.FetchServerConfig()
// try and get the JWKS from param first if err != nil {
if p.Endpoints.JwksUri != "" {
if err := fetchAndMarshal(); err != nil {
w.Write(jwks)
return
}
}
// otherwise or if fetching the JWKS failed, try and fetch the whole config first and try again
if p.Endpoints.Config != "" {
if err := p.FetchServerConfig(); err != nil {
fmt.Printf("failed to fetch server config: %v\n", err) fmt.Printf("failed to fetch server config: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 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 return
} }
} else {
fmt.Printf("getting JWKS from param failed and endpoints config unavailable\n")
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
} }
if err := fetchAndMarshal(); err != nil { // forward the JWKS from the authorization server
fmt.Printf("failed to fetch and marshal JWKS after config update: %v\n", err) if jwks == nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) fmt.Printf("no JWKS was fetched from authorization server\n")
http.Redirect(w, r, "/error", http.StatusInternalServerError)
return return
} }
w.Write(jwks) w.Write(jwks)
}) })
r.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { r.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {

View file

@ -7,7 +7,7 @@
<input type="password" id="password" name="password" title="password" placeholder="Enter your password..." /><br/> <input type="password" id="password" name="password" title="password" placeholder="Enter your password..." /><br/>
<button type="submit" class="btn">Login</button><br/> <button type="submit" class="btn">Login</button><br/>
<a class="forgot" href="#">Forgot Username?</a><br/> <a class="forgot" href="#">Forgot Username?</a><br/>
<label>(hint: try 'openchami' for both username and password)</label> <label>(hint: try 'ochami' for both username and password)</label>
</form> </form>
</div><!--end log form --> </div><!--end log form -->
</html> </html>