mirror of
https://github.com/davidallendj/opaal.git
synced 2026-02-04 00:36:26 -07:00
Compare commits
8 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0a8d43421 | ||
|
|
a7e0e73e45 | ||
|
|
8c01ba897f | ||
|
|
a0cca97e7d | ||
|
|
b304361ce9 | ||
|
|
e929fac09e | ||
|
|
7022801fe9 | ||
|
|
cbb3e6f851 |
7 changed files with 79 additions and 60 deletions
|
|
@ -51,6 +51,9 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -109,12 +109,14 @@ 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: %s", err)
|
return nil, fmt.Errorf("failed to get ID token: %v", 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()
|
||||||
|
|
||||||
// domain, _ := url.Parse("http://127.0.0.1")
|
return b, nil
|
||||||
// client.Jar.SetCookies(domain, res.Cookies())
|
|
||||||
|
|
||||||
return io.ReadAll(res.Body)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,6 @@ 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ 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"
|
||||||
|
|
@ -22,6 +21,22 @@ 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
|
||||||
|
|
||||||
|
|
@ -29,19 +44,13 @@ 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 + "/oauth/authorize",
|
Authorization: "http://" + s.Addr + "/oauth2/authorize",
|
||||||
Token: "http://" + s.Addr + "/oauth/token",
|
Token: "http://" + s.Addr + "/oauth2/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)
|
||||||
|
|
@ -138,21 +147,18 @@ 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")
|
||||||
|
|
||||||
// example username and password so do simplified authorization code flow
|
if len(s.Issuer.Clients) <= 0 {
|
||||||
if username == "ochami" && password == "ochami" {
|
fmt.Printf("no registered clients found with identity provider (add them in config)\n")
|
||||||
client := oauth.Client{
|
return
|
||||||
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)},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// example username and password so do simplified authorization code flow
|
||||||
|
if username == "openchami" && password == "openchami" {
|
||||||
|
client := s.Issuer.Clients[0]
|
||||||
|
|
||||||
// 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")
|
fmt.Printf("no redirect URIs found for client %s (ID: %s)\n", client.Name, client.Id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, url := range client.RedirectUris {
|
for _, url := range client.RedirectUris {
|
||||||
|
|
@ -260,9 +266,13 @@ func (s *Server) StartIdentityProvider() error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that we're using the default registered client
|
// find a valid client
|
||||||
if clientId != "ochami" {
|
index := slices.IndexFunc(s.Issuer.Clients, func(c RegisteredClient) bool {
|
||||||
fmt.Printf("invalid client\n")
|
fmt.Printf("%s ? %s\n", c.Id, clientId)
|
||||||
|
return c.Id == clientId
|
||||||
|
})
|
||||||
|
if index < 0 {
|
||||||
|
fmt.Printf("no valid client found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,6 @@ 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
|
||||||
|
|
@ -63,7 +57,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 external identity providers: \n")
|
fmt.Printf("Login with an identity provider: \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)
|
||||||
|
|
@ -80,8 +74,7 @@ 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)
|
||||||
url := client.BuildAuthorizationUrl(s.State)
|
fmt.Printf("\t%s: /login?sso=%s\n", client.Name, client.Id)
|
||||||
fmt.Printf("\t%s\n", url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var code string
|
var code string
|
||||||
|
|
@ -121,7 +114,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -146,38 +141,47 @@ 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
|
|
||||||
if p.Endpoints.JwksUri != "" {
|
fetchAndMarshal := func() (err error) {
|
||||||
err := p.FetchJwks()
|
err = p.FetchJwks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to fetch keys using JWKS url...trying to fetch config and try again...\n")
|
fmt.Printf("failed to fetch keys: %v\n", err)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
} 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)
|
|
||||||
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = p.FetchJwks()
|
|
||||||
if err != nil {
|
// try and get the JWKS from param first
|
||||||
fmt.Printf("failed to fetch JWKS after fetching server config: %v\n", err)
|
if p.Endpoints.JwksUri != "" {
|
||||||
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
if err := fetchAndMarshal(); err != nil {
|
||||||
|
w.Write(jwks)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// forward the JWKS from the authorization server
|
// otherwise or if fetching the JWKS failed, try and fetch the whole config first and try again
|
||||||
if jwks == nil {
|
if p.Endpoints.Config != "" {
|
||||||
fmt.Printf("no JWKS was fetched from authorization server\n")
|
if err := p.FetchServerConfig(); err != nil {
|
||||||
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
fmt.Printf("failed to fetch server config: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), 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 {
|
||||||
|
fmt.Printf("failed to fetch and marshal JWKS after config update: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -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 'ochami' for both username and password)</label>
|
<label>(hint: try 'openchami' for both username and password)</label>
|
||||||
</form>
|
</form>
|
||||||
</div><!--end log form -->
|
</div><!--end log form -->
|
||||||
</html>
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue