Reorganized server and client internal code

This commit is contained in:
David Allen 2024-02-25 09:59:20 -07:00
parent 6d482cc60f
commit 1a4afd2d16
No known key found for this signature in database
GPG key ID: 1D2A29322FBB6FCB
3 changed files with 221 additions and 213 deletions

View file

@ -1,35 +1,5 @@
package opaal
import (
"bytes"
"davidallendj/opaal/internal/oidc"
"davidallendj/opaal/internal/util"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strings"
"time"
"golang.org/x/net/publicsuffix"
)
type Server struct {
http.Server
Host string `yaml:"host"`
Port int `yaml:"port"`
}
type Client struct {
http.Client
Id string `yaml:"id"`
Secret string `yaml:"secret"`
RedirectUris []string `yaml:"redirect-uris"`
}
type ActionUrls struct {
Identities string `yaml:"identities"`
TrustedIssuers string `yaml:"trusted-issuers"`
@ -38,189 +8,6 @@ type ActionUrls struct {
JwksUri string `yaml:"jwks_uri"`
}
func NewServerWithConfig(config *Config) *Server {
host := config.Server.Host
port := config.Server.Port
server := &Server{
Host: host,
Port: port,
}
server.Addr = fmt.Sprintf("%s:%d", host, port)
return server
}
func NewClientWithConfig(config *Config) *Client {
jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
return &Client{
Id: config.Client.Id,
Secret: config.Client.Secret,
RedirectUris: config.Client.RedirectUris,
Client: http.Client{Jar: jar},
}
}
func (s *Server) SetListenAddr(host string, port int) {
s.Host = host
s.Port = port
s.Addr = s.GetListenAddr()
}
func (s *Server) GetListenAddr() string {
return fmt.Sprintf("%s:%d", s.Host, s.Port)
}
func (s *Server) WaitForAuthorizationCode(loginUrl string) (string, error) {
var code string
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/login", http.StatusSeeOther)
})
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
// show login page with notice to redirect
loginPage, err := os.ReadFile("pages/index.html")
if err != nil {
fmt.Printf("failed to load login page: %v\n", err)
}
loginPage = []byte(strings.ReplaceAll(string(loginPage), "{{loginUrl}}", loginUrl))
w.WriteHeader(http.StatusSeeOther)
w.Write(loginPage)
})
http.HandleFunc("/oidc/callback", func(w http.ResponseWriter, r *http.Request) {
// get the code from the OIDC provider
if r != nil {
code = r.URL.Query().Get("code")
fmt.Printf("Authorization code: %v\n", code)
}
http.Redirect(w, r, s.Addr+"/success", http.StatusSeeOther)
s.Close()
})
return code, s.ListenAndServe()
}
func (s *Server) ShowSuccessPage() error {
http.HandleFunc("/success", func(w http.ResponseWriter, r *http.Request) {
})
return s.ListenAndServe()
}
func (client *Client) BuildAuthorizationUrl(authEndpoint string, state string, responseType string, scope []string) string {
return authEndpoint + "?" + "client_id=" + client.Id +
"&redirect_uri=" + util.URLEscape(strings.Join(client.RedirectUris, ",")) +
"&response_type=" + responseType +
"&state=" + state +
"&scope=" + strings.Join(scope, "+")
}
func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl string, state string) ([]byte, error) {
data := url.Values{
"grant_type": {"authorization_code"},
"code": {code},
"client_id": {client.Id},
"client_secret": {client.Secret},
"state": {state},
"redirect_uri": {strings.Join(client.RedirectUris, ",")},
}
res, err := http.PostForm(remoteUrl, data)
if err != nil {
return nil, fmt.Errorf("failed to get ID token: %s", err)
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
func (client *Client) FetchTokenFromAuthorizationServer(remoteUrl string, jwt string, scope []string) ([]byte, error) {
// hydra endpoint: /oauth/token
data := "grant_type=" + util.URLEscape("urn:ietf:params:oauth:grant-type:jwt-bearer") +
"&assertion=" + jwt +
"&scope=" + strings.Join(scope, "+")
fmt.Printf("encoded params: %v\n\n", data)
req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer([]byte(data)))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if err != nil {
return nil, fmt.Errorf("failed to make request: %s", err)
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %v", err)
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
func (client *Client) AddTrustedIssuer(remoteUrl string, idp *oidc.IdentityProvider, subject string, duration time.Duration, scope []string) ([]byte, error) {
// hydra endpoint: /admin/trust/grants/jwt-bearer/issuers
if idp == nil {
return nil, fmt.Errorf("identity provided is nil")
}
jwkstr, err := json.Marshal(idp.Key)
if err != nil {
return nil, fmt.Errorf("failed to marshal JWK: %v", err)
}
data := []byte(fmt.Sprintf(`{
"allow_any_subject": true,
"issuer": "%s",
"subject": "%s"
"expires_at": "%v"
"jwk": %v,
"scope": [ %s ],
}`, idp.Issuer, subject, time.Now().Add(duration), string(jwkstr), strings.Join(scope, ",")))
req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer(data))
// req.Header.Add("X-CSRF-Token", client.CsrfToken.Value)
if err != nil {
return nil, fmt.Errorf("failed to make request: %v", err)
}
req.Header.Add("Content-Type", "application/json")
// req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %v", err)
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
func (client *Client) CreateIdentity(remoteUrl string, idToken string) ([]byte, error) {
// kratos endpoint: /admin/identities
data := []byte(`{
"schema_id": "preset://email",
"traits": {
"email": "docs@example.org"
}
}`)
req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("failed to create a new request: %v", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
// req.Header.Add("X-CSRF-Token", client.CsrfToken.Value)
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %v", err)
}
return io.ReadAll(res.Body)
}
func (client *Client) FetchIdentities(remoteUrl string) ([]byte, error) {
req, err := http.NewRequest("GET", remoteUrl, bytes.NewBuffer([]byte{}))
if err != nil {
return nil, fmt.Errorf("failed to create a new request: %v", err)
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %v", err)
}
return io.ReadAll(res.Body)
}
func hasRequiredParams(config *Config) bool {
return config.Client.Id != "" && config.Client.Secret != ""
}