Commit ed7a633
Changed files (5)
go.mod
@@ -1,10 +1,20 @@
-module git.mokhan.ca/xlgmokha/oauth
+module mokhan.ca/xlgmokha/oauth
go 1.18
require (
- github.com/davecgh/go-spew v1.1.0 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/stretchr/testify v1.7.1 // indirect
- gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+ github.com/golang-jwt/jwt v3.2.2+incompatible
+ github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c
+ github.com/lestrrat-go/jwx/v2 v2.0.0-beta1
+)
+
+require (
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+ github.com/goccy/go-json v0.9.6 // indirect
+ github.com/lestrrat-go/blackmagic v1.0.1 // indirect
+ github.com/lestrrat-go/httpcc v1.0.1 // indirect
+ github.com/lestrrat-go/httprc v1.0.1 // indirect
+ github.com/lestrrat-go/iter v1.0.2 // indirect
+ github.com/lestrrat-go/option v1.0.0 // indirect
+ golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
)
go.sum
@@ -1,10 +1,41 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
+github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c h1:nQcv325vxv2fFHJsOt53eSRf1eINt6vOdYUFfXs4rgk=
+github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0=
+github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
+github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
+github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
+github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
+github.com/lestrrat-go/httprc v1.0.1 h1:Cnc4NxIySph38pQPzKbjg5OkKsGR/Cf5xcWt5OlSUDI=
+github.com/lestrrat-go/httprc v1.0.1/go.mod h1:5Ml+nB++j6IC0e6LzefJnrpMQDKgDwDCaIQQzhbqhJM=
+github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
+github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
+github.com/lestrrat-go/jwx/v2 v2.0.0-beta1 h1:zVHfLjzsWPjAF21CdoTCV3x7X3zixSi3kTXBLmbSI4Y=
+github.com/lestrrat-go/jwx/v2 v2.0.0-beta1/go.mod h1:G8yN95iNzKc/y82IpU2MW+mOeGrDm5j773pE5M0w/7w=
+github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
+github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
main.go
@@ -1,20 +1,181 @@
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"
)
-func health(w http.ResponseWriter, req *http.Request) {
- fmt.Fprintf(w, "OK")
+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() {
- server := NewServer()
+ log.Println("Starting server, listening on port 8282.")
- http.Handle("/", http.FileServer(http.Dir("public")))
- http.HandleFunc("/api/", server.ServeHTTP)
+ 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(http.ListenAndServe(":8090", nil))
+ log.Fatal(server.ListenAndServe())
}
server.go
@@ -1,17 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/http"
-)
-
-type Server struct {
-}
-
-func NewServer() Server {
- return Server{}
-}
-
-func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "OK")
-}
server_test.go
@@ -1,22 +0,0 @@
-package main
-
-import (
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestServer(t *testing.T) {
- t.Run("GET /health", func(t *testing.T) {
- response := httptest.NewRecorder()
- request, _ := http.NewRequest("GET", "/health", nil)
-
- server := NewServer()
- server.ServeHTTP(response, request)
-
- assert.Equal(t, http.StatusOK, response.Code)
- assert.Equal(t, "OK", response.Body.String())
- })
-}