package opaal import ( "davidallendj/opaal/internal/oauth" "fmt" "net/http" "slices" "davidallendj/opaal/internal/flows" "davidallendj/opaal/internal/server" "github.com/davidallendj/go-utils/mathx" ) func NewClientWithConfig(config *Config) *oauth.Client { // make sure config is valid if config == nil { return nil } // make sure we have at least one client clients := config.Authentication.Clients if len(clients) <= 0 { return nil } // use the first client found by default return &oauth.Client{ Id: clients[0].Id, Secret: clients[0].Secret, Name: clients[0].Name, Provider: clients[0].Provider, Scope: clients[0].Scope, RedirectUris: clients[0].RedirectUris, } } func NewClientWithConfigByIndex(config *Config, index int) *oauth.Client { size := len(config.Authentication.Clients) index = mathx.Clamp(index, 0, size) return nil } func NewClientWithConfigByName(config *Config, name string) *oauth.Client { index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool { return c.Name == name }) if index >= 0 { return &config.Authentication.Clients[index] } return nil } func NewClientWithConfigByProvider(config *Config, issuer string) *oauth.Client { index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool { return c.Provider.Issuer == issuer }) if index >= 0 { return &config.Authentication.Clients[index] } return nil } func NewClientWithConfigById(config *Config, id string) *oauth.Client { index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool { return c.Id == id }) if index >= 0 { return &config.Authentication.Clients[index] } return nil } func NewClientCredentialsFlowWithConfig(config *Config, params flows.ClientCredentialsFlowParams) (string, error) { eps := flows.ClientCredentialsFlowEndpoints{ Clients: config.Authorization.Endpoints.Clients, Authorize: config.Authorization.Endpoints.Authorize, Token: config.Authorization.Endpoints.Token, } return flows.NewClientCredentialsFlow(eps, params) } func NewServerWithConfig(conf *Config) *server.Server { host := conf.Server.Host port := conf.Server.Port server := &server.Server{ Server: &http.Server{ Addr: fmt.Sprintf("%s:%d", host, port), }, Host: host, Port: port, Issuer: server.IdentityProviderServer{ Host: conf.Server.Issuer.Host, Port: conf.Server.Issuer.Port, }, } return server } // func NewAuthorizationCodeFlowWithConfig(config *Config, client *oauth.Client, idp *oidc.IdentityProvider) error { // // create new server and client to use for flow // server := NewServerWithConfig(config) // // build the authorization URL to redirect user for social sign-in // state := config.Authentication.Flows["authorization_code"]["state"] // var authorizationUrl = client.BuildAuthorizationUrl(idp.Endpoints.Authorization, state) // // print the authorization URL for sharing // fmt.Printf("Login with identity provider:\n\n %s/login\n %s\n\n", // server.GetListenAddr(), authorizationUrl, // ) // // automatically open browser to initiate login flow (only useful for testing and debugging) // if config.Options.OpenBrowser { // util.OpenUrl(authorizationUrl) // } // // authorize oauth client and listen for callback from provider // fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", server.GetListenAddr()) // err := server.Login(authorizationUrl, c) // if errors.Is(err, http.ErrServerClosed) { // fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n") // } else if err != nil { // return fmt.Errorf("failed to start server: %s", err) // } // // start up another server in background to listen for success or failures // d := StartListener(server) // // use code from response and exchange for bearer token (with ID token) // bearerToken, err := client.FetchTokenFromAuthenticationServer( // code, // idp.Endpoints.Token, // state, // ) // if err != nil { // return fmt.Errorf("failed to fetch token from issuer: %v", err) // } // // unmarshal data to get id_token and access_token // var data map[string]any // err = json.Unmarshal([]byte(bearerToken), &data) // if err != nil || data == nil { // return fmt.Errorf("failed to unmarshal token: %v", err) // } // // make sure we have an ID token // if data["id_token"] == nil { // return fmt.Errorf("no ID token found...aborting") // } // // extract ID token from bearer as JSON string for easy consumption // idToken := data["id_token"].(string) // idJwtSegments, err := util.DecodeJwt(idToken) // if err != nil { // fmt.Printf("failed to parse ID token: %v\n", err) // } else { // fmt.Printf("id_token: %v\n", idToken) // fmt.Println() // } // // extract the access token to get the scopes // accessToken := data["access_token"].(string) // accessJwtSegments, err := util.DecodeJwt(accessToken) // if err != nil || len(accessJwtSegments) <= 0 { // fmt.Printf("failed to parse access token: %v\n", err) // } else { // fmt.Printf("access_token (from identity provider): %v\n", accessToken) // fmt.Println() // } // if !config.Options.TokenForwarding { // // 1. verify that the JWT from the issuer is valid using all keys // _, err = jws.Verify([]byte(idToken), jws.WithKeySet(idp.KeySet), jws.WithValidateKey(true)) // if err != nil { // return fmt.Errorf("failed to verify JWT: %v", err) // } // // 2. Check if we are already registered as a trusted issuer with authorization server... // // 3.a if not, create a new JWKS (or just JWK) to be verified // var ( // keyPath string // privateJwk jwk.Key // publicJwk jwk.Key // ) // if config.Authorization.KeyPath != "" { // keyPath = config.Authorization.Endpoints.Authorize // } // privateKey, err := os.ReadFile(keyPath) // if err != nil { // privateJwk, publicJwk, err = cryptox.GenerateJwkKeyPair() // if err != nil { // return fmt.Errorf("failed to generate JWK pair: %v", err) // } // } else { // privateJwk, publicJwk, err = cryptox.GenerateJwkKeyPairFromPrivateKey(privateKey) // if err != nil { // return fmt.Errorf("failed to generate JWK pair from private key: %v", err) // } // } // privateJwk.Set("kid", uuid.New().String()) // publicJwk.Set("kid", uuid.New().String()) // // 3.b ...and then, add opaal's server host as a trusted issuer with JWK // fmt.Printf("Attempting to add issuer to authorization server...\n") // ti := oauth.NewTrustedIssuer() // ti.Issuer = server.Addr // ti.PublicKey = publicJwk // ti.Subject = "1" // ti.ExpiresAt = time.Now().Add(time.Second * 3600) // res, err := client.AddTrustedIssuer( // config.Authorization.Endpoints.TrustedIssuers, // ti, // ) // if err != nil { // return fmt.Errorf("failed to add trusted issuer: %v", err) // } // fmt.Printf("%v\n", string(res)) // // 4. create a new JWT based on the claims from the identity provider and sign // parsedIdToken, err := jwt.ParseString(idToken, jwt.WithKeySet(idp.KeySet)) // if err != nil { // return fmt.Errorf("failed to parse ID token: %v", err) // } // payload := parsedIdToken.PrivateClaims() // payload["iss"] = server.Addr // payload["aud"] = []string{config.Authorization.Endpoints.Token} // payload["iat"] = time.Now().Unix() // payload["nbf"] = time.Now().Unix() // payload["exp"] = time.Now().Add(time.Second * 3600).Unix() // payload["sub"] = "1" // payloadJson, err := json.Marshal(payload) // if err != nil { // return fmt.Errorf("failed to marshal payload: %v", err) // } // newToken, err := jws.Sign(payloadJson, jws.WithJSON(), jws.WithKey(jwa.RS256, privateJwk)) // if err != nil { // return fmt.Errorf("failed to sign token: %v", err) // } // // 5. dynamically register new OAuth client and authorize it to make jwt_bearer request // fmt.Printf("Registering new OAuth2 client with authorization server...\n") // res, err = client.RegisterOAuthClient(config.Authorization.Endpoints.Register, []string{}) // if err != nil { // return fmt.Errorf("failed to register client: %v", err) // } // fmt.Printf("%v\n", string(res)) // // extract the client info from response // var clientData map[string]any // err = json.Unmarshal(res, &clientData) // if err != nil { // return fmt.Errorf("failed to unmarshal client data: %v", err) // } else { // // check for error first // errJson := clientData["error"] // if errJson == nil { // client.Id = clientData["client_id"].(string) // client.Secret = clientData["client_secret"].(string) // } else { // // delete client and try to create again // fmt.Printf("Attempting to delete client...\n") // err := client.DeleteOAuthClient(config.Authorization.Endpoints.Clients) // if err != nil { // return fmt.Errorf("failed to delete OAuth client: %v", err) // } // fmt.Printf("Attempting to re-create client...\n") // res, err := client.CreateOAuthClient(config.Authorization.Endpoints.Clients, []string{}) // if err != nil { // return fmt.Errorf("failed to register client: %v", err) // } // fmt.Printf("%v\n", string(res)) // } // } // // authorize the client // // fmt.Printf("Attempting to authorize client...\n") // // res, err = client.AuthorizeOAuthClient(config.Authorization.RequestUrls.Authorize) // // if err != nil { // // return fmt.Errorf("failed to authorize client: %v", err) // // } // // fmt.Printf("%v\n", string(res)) // // 6. send JWT to authorization server and receive a access token // if config.Authorization.Endpoints.Token != "" { // fmt.Printf("Fetching access token from authorization server...\n") // res, err := client.PerformTokenGrant(config.Authorization.Endpoints.Token, string(newToken)) // if err != nil { // return fmt.Errorf("failed to fetch access token: %v", err) // } // fmt.Printf("%s\n", res) // } // } else { // // extract the scope from access token claims // // var scope []string // // var accessJsonPayload map[string]any // // var accessJwtPayload []byte = accessJwtSegments[1] // // if accessJsonPayload != nil { // // err := json.Unmarshal(accessJwtPayload, &accessJsonPayload) // // if err != nil { // // return fmt.Errorf("failed to unmarshal JWT: %v", err) // // } // // scope = idJsonPayload["scope"].([]string) // // } // // create a new identity with identity and session manager if url is provided // // if config.RequestUrls.Identities != "" { // // fmt.Printf("Attempting to create a new identity...\n") // // err := client.CreateIdentity(config.RequestUrls.Identities, idToken) // // if err != nil { // // return fmt.Errorf("failed to create new identity: %v", err) // // } // // _, err = client.FetchIdentities(config.RequestUrls.Identities) // // if err != nil { // // return fmt.Errorf("failed to fetch identities: %v", err) // // } // // fmt.Printf("Created new identity successfully.\n\n") // // } // // extract the subject from ID token claims // var subject string // var audience []string // var idJsonPayload map[string]any // var idJwtPayload []byte = idJwtSegments[1] // if idJwtPayload != nil { // err := json.Unmarshal(idJwtPayload, &idJsonPayload) // if err != nil { // return fmt.Errorf("failed to unmarshal JWT: %v", err) // } // subject = idJsonPayload["sub"].(string) // audType := reflect.ValueOf(idJsonPayload["aud"]) // switch audType.Kind() { // case reflect.String: // audience = append(audience, idJsonPayload["aud"].(string)) // case reflect.Array: // audience = idJsonPayload["aud"].([]string) // } // } else { // return fmt.Errorf("failed to extract subject from ID token claims") // } // // fetch JWKS and add issuer to authentication server to submit ID token // fmt.Printf("Fetching JWKS from authentication server for verification...\n") // err = idp.FetchJwks() // if err != nil { // return fmt.Errorf("failed to fetch JWK: %v", err) // } else { // fmt.Printf("Successfully retrieved JWK from authentication server.\n\n") // fmt.Printf("Attempting to add issuer to authorization server...\n") // res, err := client.AddTrustedIssuerWithIdentityProvider( // config.Authorization.Endpoints.TrustedIssuers, // idp, // subject, // time.Duration(1000), // ) // if err != nil { // return fmt.Errorf("failed to add trusted issuer: %v", err) // } // fmt.Printf("%v\n", string(res)) // } // // add client ID to audience // audience = append(audience, client.Id) // audience = append(audience, "http://127.0.0.1:4444/oauth2/token") // // try and register a new client with authorization server // fmt.Printf("Registering new OAuth2 client with authorization server...\n") // res, err := client.RegisterOAuthClient(config.Authorization.Endpoints.Register, audience) // if err != nil { // return fmt.Errorf("failed to register client: %v", err) // } // fmt.Printf("%v\n", string(res)) // // extract the client info from response // var clientData map[string]any // err = json.Unmarshal(res, &clientData) // if err != nil { // return fmt.Errorf("failed to unmarshal client data: %v", err) // } else { // // check for error first // errJson := clientData["error"] // if errJson == nil { // client.Id = clientData["client_id"].(string) // client.Secret = clientData["client_secret"].(string) // } else { // // delete client and create again // fmt.Printf("Attempting to delete client...\n") // err := client.DeleteOAuthClient(config.Authorization.Endpoints.Clients) // if err != nil { // return fmt.Errorf("failed to delete OAuth client: %v", err) // } // fmt.Printf("Attempting to re-create client...\n") // res, err := client.CreateOAuthClient(config.Authorization.Endpoints.Clients, audience) // if err != nil { // return fmt.Errorf("failed to register client: %v", err) // } // fmt.Printf("%v\n", string(res)) // } // } // // authorize the client // // fmt.Printf("Attempting to authorize client...\n") // // res, err = client.AuthorizeOAuthClient(config.Authorization.RequestUrls.Authorize) // // if err != nil { // // return fmt.Errorf("failed to authorize client: %v", err) // // } // // fmt.Printf("%v\n", string(res)) // // use ID token/user info to fetch access token from authentication server // if config.Authorization.Endpoints.Token != "" { // fmt.Printf("Fetching access token from authorization server...\n") // res, err := client.PerformTokenGrant(config.Authorization.Endpoints.Token, idToken) // if err != nil { // return fmt.Errorf("failed to fetch access token: %v", err) // } // fmt.Printf("%s\n", res) // } // } // var access_token []byte // d <- access_token // return nil // }