diff options
| author | mo khan <mo@mokhan.ca> | 2022-04-21 10:21:35 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2022-04-21 10:21:35 -0600 |
| commit | c84a523b1e29fa70c18a6a8e889214ef854c6951 (patch) | |
| tree | e29d324160fcc38f73b30c909c5146b11dd12767 /pkg | |
| parent | ed7a6333ed475b84f64dcf22e7318297867348d9 (diff) | |
create a minimal oidc idp
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/web/authorize.go | 53 | ||||
| -rw-r--r-- | pkg/web/default.go | 7 | ||||
| -rw-r--r-- | pkg/web/http_mux.go | 66 | ||||
| -rw-r--r-- | pkg/web/token.go | 48 | ||||
| -rw-r--r-- | pkg/web/well_known.go | 40 |
5 files changed, 214 insertions, 0 deletions
diff --git a/pkg/web/authorize.go b/pkg/web/authorize.go new file mode 100644 index 0000000..b223699 --- /dev/null +++ b/pkg/web/authorize.go @@ -0,0 +1,53 @@ +package web + +import ( + "fmt" + "net/http" + + "github.com/hashicorp/uuid" +) + +type AuthorizationRequest struct { + ResponseType string + Scope string + ClientId string + State string + RedirectUri string + Nonce string +} + +func (h *HttpContext) Authorize(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + responseType := r.FormValue("response_type") + if responseType == "code" { + // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth + ar := &AuthorizationRequest{ + ResponseType: r.FormValue("response_type"), + Scope: r.FormValue("scope"), + ClientId: r.FormValue("client_id"), + State: r.FormValue("state"), + RedirectUri: r.FormValue("redirect_uri"), + } + code := uuid.GenerateUUID() + tokens[code] = uuid.GenerateUUID() + url := fmt.Sprintf("%s?code=%s&state=%s", ar.RedirectUri, code, ar.State) + http.Redirect(w, r, url, 302) + } else if responseType == "id_token token" || responseType == "id_token" { + // Implicit Flow https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth + ar := &AuthorizationRequest{ + ResponseType: r.FormValue("response_type"), + RedirectUri: r.FormValue("redirect_uri"), + Nonce: r.FormValue("nonce"), + } + idToken := h.createIdToken(r.FormValue("client_id")) + url := fmt.Sprintf("%s?access_token=example&token_type=bearer&id_token=%s&expires_in=3600&state=%s", ar.RedirectUri, idToken, ar.State) + http.Redirect(w, r, url, 302) + } else if responseType == "code id_token" || responseType == "code token" || responseType == "code id_token token" { + // Hybrid Flow https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth + w.WriteHeader(http.StatusNotImplemented) + } else { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Not Found\n") + } + } +} diff --git a/pkg/web/default.go b/pkg/web/default.go new file mode 100644 index 0000000..c9d54f2 --- /dev/null +++ b/pkg/web/default.go @@ -0,0 +1,7 @@ +package web + +import "net/http" + +func (h *HttpContext) Default(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} diff --git a/pkg/web/http_mux.go b/pkg/web/http_mux.go new file mode 100644 index 0000000..fbb6b67 --- /dev/null +++ b/pkg/web/http_mux.go @@ -0,0 +1,66 @@ +package web + +import ( + "io/ioutil" + "log" + "net/http" + "os" + "time" + + "github.com/golang-jwt/jwt" + "github.com/hashicorp/uuid" +) + +var ( + tokens = map[string]string{} +) + +type IdTokenFactory func(clientId string) string + +func (h *HttpContext) createIdToken(clientId string) string { + now := time.Now() + if clientId == "" { + clientId = "clientId" + } + expiresAt := now.Add(time.Hour * time.Duration(1)) + + host, ok := os.LookupEnv("HOST") + if !ok { + host = "http://localhost:8282" + } + idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, &jwt.StandardClaims{ + Issuer: host, + Subject: "1", + Audience: clientId, + ExpiresAt: expiresAt.Unix(), + NotBefore: now.Unix(), + IssuedAt: now.Unix(), + Id: uuid.GenerateUUID(), + }) + + key, _ := jwt.ParseRSAPrivateKeyFromPEM(h.keyData) + signedIdToken, _ := idToken.SignedString(key) + return signedIdToken +} + +type HttpContext struct { + log *log.Logger + keyData []byte +} + +func NewHandler() http.Handler { + keyData, _ := ioutil.ReadFile("insecure.pem") + h := &HttpContext{ + log: log.Default(), + keyData: keyData, + } + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc(h.Default)) + mux.Handle("/.well-known/jwks.json", http.HandlerFunc(h.WellKnown)) + mux.Handle("/.well-known/openid-configuration", http.HandlerFunc(h.WellKnown)) + mux.Handle("/authorize", http.HandlerFunc(h.Authorize)) + mux.Handle("/revoke", http.HandlerFunc(http.NotFound)) + mux.Handle("/token", http.HandlerFunc(h.Token)) + mux.Handle("/userinfo", http.HandlerFunc(http.NotFound)) + return mux +} diff --git a/pkg/web/token.go b/pkg/web/token.go new file mode 100644 index 0000000..d6fbdfb --- /dev/null +++ b/pkg/web/token.go @@ -0,0 +1,48 @@ +package web + +import ( + "fmt" + "net/http" +) + +type TokenRequest struct { + GrantType string + Code string + RedirectUri string +} + +type TokenResponse struct { + AccessToken string + TokenType string + RefreshToken string + ExpiresIn int + IdToken string +} + +func (h *HttpContext) Token(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + tr := &TokenRequest{ + GrantType: r.FormValue("grant_type"), + Code: r.FormValue("code"), + RedirectUri: r.FormValue("redirect_uri"), + } + if tr.GrantType == "authorization_code" { + // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth + r := &TokenResponse{ + AccessToken: tokens[tr.Code], + TokenType: "Bearer", + RefreshToken: "TODO::", + ExpiresIn: 3600, + IdToken: h.createIdToken(r.FormValue("client_id")), + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + fmt.Fprintf(w, `{"access_token": "%s","token_type": "%s","refresh_token": "%s","expires_in": %d,"id_token": "%s"}`, r.AccessToken, r.TokenType, r.RefreshToken, r.ExpiresIn, r.IdToken) + } else { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Not Found\n") + } + } +} diff --git a/pkg/web/well_known.go b/pkg/web/well_known.go new file mode 100644 index 0000000..4a87a79 --- /dev/null +++ b/pkg/web/well_known.go @@ -0,0 +1,40 @@ +package web + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "os" + "text/template" + + "github.com/lestrrat-go/jwx/v2/jwk" +) + +func (h *HttpContext) WellKnown(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.URL.Path) + if r.URL.Path == "/.well-known/openid-configuration" { + w.Header().Set("Content-Type", "application/json") + data, _ := ioutil.ReadFile("./public/openid-configuration.json") + tmpl, _ := template.New("test").Parse(string(data)) + host, ok := os.LookupEnv("HOST") + if !ok { + host = "http://localhost:8282" + } + tmpl.Execute(w, struct{ Host string }{Host: host}) + } else if r.URL.Path == "/.well-known/jwks.json" { + w.Header().Set("Content-Type", "application/json") + privatePem, _ := pem.Decode(h.keyData) + parsedKey, _ := x509.ParsePKCS1PrivateKey(privatePem.Bytes) + key, _ := jwk.FromRaw(parsedKey) + pubKey, _ := jwk.PublicKeyOf(key) + pubKey.Set(jwk.KeyIDKey, "X") + pubKey.Set(jwk.KeyUsageKey, "sig") + + set := jwk.NewSet() + set.Add(pubKey) + json.NewEncoder(w).Encode(set) + } +} |
