summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2022-04-21 10:21:35 -0600
committermo khan <mo@mokhan.ca>2022-04-21 10:21:35 -0600
commitc84a523b1e29fa70c18a6a8e889214ef854c6951 (patch)
treee29d324160fcc38f73b30c909c5146b11dd12767 /pkg
parented7a6333ed475b84f64dcf22e7318297867348d9 (diff)
create a minimal oidc idp
Diffstat (limited to 'pkg')
-rw-r--r--pkg/web/authorize.go53
-rw-r--r--pkg/web/default.go7
-rw-r--r--pkg/web/http_mux.go66
-rw-r--r--pkg/web/token.go48
-rw-r--r--pkg/web/well_known.go40
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)
+ }
+}