Commit c84a523
Changed files (9)
cmd
server
public
cmd/server/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "log"
+ "net/http"
+
+ "mokhan.ca/xlgmokha/oauth/pkg/web"
+)
+
+func main() {
+ log.Println("Starting server, listening on port 8282.")
+ server := &http.Server{
+ Addr: ":8282",
+ Handler: web.NewHandler(),
+ ReadTimeout: 0,
+ WriteTimeout: 0,
+ IdleTimeout: 0,
+ }
+ log.Fatal(server.ListenAndServe())
+}
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)
+}
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
+}
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")
+ }
+ }
+}
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)
+ }
+}
public/openid-configuration.json
@@ -0,0 +1,37 @@
+{
+ "issuer": "{{.Host}}",
+ "authorization_endpoint": "{{.Host}}/authorize",
+ "token_endpoint": "{{.Host}}/token",
+ "userinfo_endpoint": "{{.Host}}/userinfo",
+ "jwks_uri": "{{.Host}}/.well-known/jwks.json",
+ "revocation_endpoint": "{{.Host}}/revoke",
+ "scopes_supported": [
+ "openid"
+ ],
+ "response_types_supported": [
+ "code id_token token",
+ "code id_token",
+ "code token",
+ "code",
+ "id_token token",
+ "id_token"
+ ],
+ "response_modes_supported": [
+ "query",
+ "fragment",
+ "form_post"
+ ],
+ "subject_types_supported": [
+ "public"
+ ],
+ "id_token_signing_alg_values_supported": [
+ "RS256"
+ ],
+ "claims_supported": [
+ "aud",
+ "exp",
+ "iat",
+ "iss",
+ "sub"
+ ]
+}
insecure.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
main.go
@@ -1,181 +0,0 @@
-package main
-
-import (
- "crypto/x509"
- "encoding/json"
- "encoding/pem"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "text/template"
- "time"
-
- "github.com/golang-jwt/jwt"
- "github.com/hashicorp/uuid"
- "github.com/lestrrat-go/jwx/v2/jwk"
-)
-
-type AuthorizationRequest struct {
- ResponseType string
- Scope string
- ClientId string
- State string
- RedirectUri string
- Nonce string
-}
-
-type TokenRequest struct {
- GrantType string
- Code string
- RedirectUri string
-}
-
-type TokenResponse struct {
- AccessToken string
- TokenType string
- RefreshToken string
- ExpiresIn int
- IdToken string
-}
-
-var (
- tokens = map[string]string{}
-)
-
-func 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(),
- })
-
- keyData, _ := ioutil.ReadFile("insecure.pem")
- key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
- signedIdToken, _ := idToken.SignedString(key)
- return signedIdToken
-}
-
-func handler(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/" && r.Method == "GET" {
- w.WriteHeader(http.StatusOK)
- fmt.Fprintf(w, "Hello, world!\n")
- } else if r.URL.Path == "/authorize" && 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 := 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")
- }
- } else if r.URL.Path == "/token" && 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: 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")
- }
- } else if r.URL.Path == "/.well-known/openid-configuration" {
- w.Header().Set("Content-Type", "application/json")
- data, _ := ioutil.ReadFile("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 == "/userinfo" {
- w.WriteHeader(http.StatusNotImplemented)
- } else if r.URL.Path == "/.well-known/jwks.json" {
- w.Header().Set("Content-Type", "application/json")
- keyData, _ := ioutil.ReadFile("insecure.pem")
- privatePem, _ := pem.Decode(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)
- } else if r.URL.Path == "/revoke" {
- w.WriteHeader(http.StatusNotImplemented)
- } else {
- w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "Not Found\n")
- }
-}
-
-func main() {
- log.Println("Starting server, listening on port 8282.")
-
- server := &http.Server{
- Addr: ":8282",
- Handler: http.HandlerFunc(handler),
- ReadTimeout: 0,
- WriteTimeout: 0,
- IdleTimeout: 0,
- }
- // config, _ := server.LoadConfigFile(os.Args[1])
- // srv, _ := server.New(config)
- // srv.Start()
-
- log.Fatal(server.ListenAndServe())
-}