diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index 7465ae0..39e297e 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -140,6 +140,9 @@ func (s *AuthStorage) AuthRequestByID(_ context.Context, id string) (op.AuthRequ } return a, nil } +func (s *AuthStorage) CreateToken(_ context.Context, authReq op.AuthRequest) (string, time.Time, error) { + return authReq.GetID(), time.Now().UTC().Add(5 * time.Minute), nil +} func (s *AuthStorage) GetSigningKey(_ context.Context, keyCh chan<- jose.SigningKey, _ chan<- error, _ <-chan time.Time) { keyCh <- jose.SigningKey{Algorithm: jose.RS256, Key: s.key} } @@ -243,9 +246,6 @@ func (c *ConfClient) GetAuthMethod() op.AuthMethod { return c.authMethod } -func (c *ConfClient) AccessTokenLifetime() time.Duration { - return time.Duration(5 * time.Minute) -} func (c *ConfClient) IDTokenLifetime() time.Duration { return time.Duration(5 * time.Minute) } diff --git a/go.mod b/go.mod index 70bbb7f..06215fd 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 // indirect github.com/google/uuid v1.1.1 + github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.3 github.com/gorilla/schema v1.1.0 github.com/gorilla/securecookie v1.1.1 diff --git a/go.sum b/go.sum index d586cb1..a37928a 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= @@ -54,8 +56,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX8rnJTxKm9dFPuI68UM= golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -74,7 +74,5 @@ gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/op/authrequest.go b/pkg/op/authrequest.go index 9f9505d..0e13df2 100644 --- a/pkg/op/authrequest.go +++ b/pkg/op/authrequest.go @@ -179,7 +179,7 @@ func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthReques func AuthResponseToken(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer, client Client) { createAccessToken := authReq.GetResponseType() != oidc.ResponseTypeIDTokenOnly - resp, err := CreateTokenResponse(authReq, client, authorizer, createAccessToken, "") + resp, err := CreateTokenResponse(r.Context(), authReq, client, authorizer, createAccessToken, "") if err != nil { AuthRequestError(w, r, authReq, err, authorizer.Encoder()) return diff --git a/pkg/op/client.go b/pkg/op/client.go index 33c30a4..5422933 100644 --- a/pkg/op/client.go +++ b/pkg/op/client.go @@ -18,7 +18,6 @@ type Client interface { GetAuthMethod() AuthMethod LoginURL(string) string AccessTokenType() AccessTokenType - AccessTokenLifetime() time.Duration IDTokenLifetime() time.Duration } diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go index 9ae2201..b0b9244 100644 --- a/pkg/op/mock/client.mock.go +++ b/pkg/op/mock/client.mock.go @@ -34,20 +34,6 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } -// AccessTokenLifetime mocks base method -func (m *MockClient) AccessTokenLifetime() time.Duration { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AccessTokenLifetime") - ret0, _ := ret[0].(time.Duration) - return ret0 -} - -// AccessTokenLifetime indicates an expected call of AccessTokenLifetime -func (mr *MockClientMockRecorder) AccessTokenLifetime() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessTokenLifetime", reflect.TypeOf((*MockClient)(nil).AccessTokenLifetime)) -} - // AccessTokenType mocks base method func (m *MockClient) AccessTokenType() op.AccessTokenType { m.ctrl.T.Helper() diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go index 11a8ab5..14f106e 100644 --- a/pkg/op/mock/storage.mock.go +++ b/pkg/op/mock/storage.mock.go @@ -81,6 +81,22 @@ func (mr *MockStorageMockRecorder) CreateAuthRequest(arg0, arg1 interface{}) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAuthRequest", reflect.TypeOf((*MockStorage)(nil).CreateAuthRequest), arg0, arg1) } +// CreateToken mocks base method +func (m *MockStorage) CreateToken(arg0 context.Context, arg1 op.AuthRequest) (string, time.Time, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateToken", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(time.Time) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// CreateToken indicates an expected call of CreateToken +func (mr *MockStorageMockRecorder) CreateToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateToken", reflect.TypeOf((*MockStorage)(nil).CreateToken), arg0, arg1) +} + // DeleteAuthRequest mocks base method func (m *MockStorage) DeleteAuthRequest(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() diff --git a/pkg/op/op.go b/pkg/op/op.go index 7bdd08e..e6cdeb4 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -40,6 +41,7 @@ func CreateRouter(o OpenIDProvider, h HttpInterceptor) *mux.Router { h = DefaultInterceptor } router := mux.NewRouter() + router.Use(handlers.CORS()) router.HandleFunc(healthzEndpoint, Healthz) router.HandleFunc(readinessEndpoint, o.HandleReady) router.HandleFunc(oidc.DiscoveryEndpoint, o.HandleDiscovery) diff --git a/pkg/op/storage.go b/pkg/op/storage.go index fd9a582..5d6725d 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -14,6 +14,8 @@ type AuthStorage interface { AuthRequestByID(context.Context, string) (AuthRequest, error) DeleteAuthRequest(context.Context, string) error + CreateToken(context.Context, AuthRequest) (string, time.Time, error) + GetSigningKey(context.Context, chan<- jose.SigningKey, chan<- error, <-chan time.Time) GetKeySet(context.Context) (*jose.JSONWebKeySet, error) SaveNewKeyPair(context.Context) error diff --git a/pkg/op/token.go b/pkg/op/token.go index 7c1dedc..ff74d69 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -1,6 +1,7 @@ package op import ( + "context" "time" "github.com/caos/oidc/pkg/oidc" @@ -13,11 +14,12 @@ type TokenCreator interface { Crypto() Crypto } -func CreateTokenResponse(authReq AuthRequest, client Client, creator TokenCreator, createAccessToken bool, code string) (*oidc.AccessTokenResponse, error) { +func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client, creator TokenCreator, createAccessToken bool, code string) (*oidc.AccessTokenResponse, error) { var accessToken string + var validity time.Duration if createAccessToken { var err error - accessToken, err = CreateAccessToken(authReq, client, creator) + accessToken, validity, err = CreateAccessToken(ctx, authReq, client, creator) if err != nil { return nil, err } @@ -26,7 +28,8 @@ func CreateTokenResponse(authReq AuthRequest, client Client, creator TokenCreato if err != nil { return nil, err } - exp := uint64(client.AccessTokenLifetime().Seconds()) + + exp := uint64(validity.Seconds()) return &oidc.AccessTokenResponse{ AccessToken: accessToken, IDToken: idToken, @@ -35,21 +38,27 @@ func CreateTokenResponse(authReq AuthRequest, client Client, creator TokenCreato }, nil } -func CreateAccessToken(authReq AuthRequest, client Client, creator TokenCreator) (string, error) { - if client.AccessTokenType() == AccessTokenTypeJWT { - return CreateJWT(creator.Issuer(), authReq, client, creator.Signer()) +func CreateAccessToken(ctx context.Context, authReq AuthRequest, client Client, creator TokenCreator) (token string, validity time.Duration, err error) { + id, exp, err := creator.Storage().CreateToken(ctx, authReq) + if err != nil { + return "", 0, err } - return CreateBearerToken(authReq, creator.Crypto()) + validity = exp.Sub(time.Now().UTC()) + if client.AccessTokenType() == AccessTokenTypeJWT { + token, err = CreateJWT(creator.Issuer(), authReq, exp, id, creator.Signer()) + return + } + token, err = CreateBearerToken(id, creator.Crypto()) + return } -func CreateBearerToken(authReq AuthRequest, crypto Crypto) (string, error) { - return crypto.Encrypt(authReq.GetID()) +func CreateBearerToken(id string, crypto Crypto) (string, error) { + return crypto.Encrypt(id) } -func CreateJWT(issuer string, authReq AuthRequest, client Client, signer Signer) (string, error) { +func CreateJWT(issuer string, authReq AuthRequest, exp time.Time, id string, signer Signer) (string, error) { now := time.Now().UTC() nbf := now - exp := now.Add(client.AccessTokenLifetime()) claims := &oidc.AccessTokenClaims{ Issuer: issuer, Subject: authReq.GetSubject(), @@ -57,6 +66,7 @@ func CreateJWT(issuer string, authReq AuthRequest, client Client, signer Signer) Expiration: exp, IssuedAt: now, NotBefore: nbf, + JWTID: id, } return signer.SignAccessToken(claims) } diff --git a/pkg/op/tokenrequest.go b/pkg/op/tokenrequest.go index c8a7fe8..cdd5396 100644 --- a/pkg/op/tokenrequest.go +++ b/pkg/op/tokenrequest.go @@ -39,7 +39,7 @@ func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { ExchangeRequestError(w, r, err) return } - resp, err := CreateTokenResponse(authReq, client, exchanger, true, tokenReq.Code) + resp, err := CreateTokenResponse(r.Context(), authReq, client, exchanger, true, tokenReq.Code) if err != nil { ExchangeRequestError(w, r, err) return