From 36f73fa6c7653dd542fc0eda31d18c4c0d81a3f2 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 31 Mar 2022 09:19:04 -0600 Subject: [PATCH] authorize: track session and service account access date (#3220) * session: add accessed at date * authorize: track session and service account access times * Revert "databroker: add support for field masks on Put (#3210)" This reverts commit 2dc778035d2296b0cb498fc7eff2d1aa7f389fd8. * add test * fix data race in test * add deadline for update * track dropped accesses --- authenticate/handlers.go | 9 +- authorize/access_tracker.go | 170 +++++++++++++++++++++++++++++++ authorize/access_tracker_test.go | 142 ++++++++++++++++++++++++++ authorize/authorize.go | 13 ++- authorize/grpc_test.go | 5 + authorize/sync.go | 3 + internal/sets/sets.go | 2 + internal/sets/size_limited.go | 36 +++++++ pkg/grpc/session/session.go | 2 + pkg/grpc/session/session.pb.go | 106 ++++++++++--------- pkg/grpc/session/session.proto | 1 + pkg/grpc/user/user.go | 11 ++ pkg/grpc/user/user.pb.go | 40 +++++--- pkg/grpc/user/user.proto | 1 + 14 files changed, 474 insertions(+), 67 deletions(-) create mode 100644 authorize/access_tracker.go create mode 100644 authorize/access_tracker_test.go create mode 100644 internal/sets/sets.go create mode 100644 internal/sets/size_limited.go diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 9dcf2f98f..8d38ab723 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -613,10 +613,11 @@ func (a *Authenticate) saveSessionToDataBroker( idTokenIssuedAt := timestamppb.New(sessionState.IssuedAt.Time()) s := &session.Session{ - Id: sessionState.ID, - UserId: sessionState.UserID(idp.Name()), - IssuedAt: timestamppb.Now(), - ExpiresAt: sessionExpiry, + Id: sessionState.ID, + UserId: sessionState.UserID(idp.Name()), + IssuedAt: timestamppb.Now(), + AccessedAt: timestamppb.Now(), + ExpiresAt: sessionExpiry, IdToken: &session.IDToken{ Issuer: sessionState.Issuer, // todo(bdd): the issuer is not authN but the downstream IdP from the claims Subject: sessionState.Subject, diff --git a/authorize/access_tracker.go b/authorize/access_tracker.go new file mode 100644 index 000000000..1a99282e9 --- /dev/null +++ b/authorize/access_tracker.go @@ -0,0 +1,170 @@ +package authorize + +import ( + "context" + "sync/atomic" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/pomerium/pomerium/internal/log" + "github.com/pomerium/pomerium/internal/sets" + "github.com/pomerium/pomerium/pkg/grpc/databroker" + "github.com/pomerium/pomerium/pkg/grpc/session" + "github.com/pomerium/pomerium/pkg/grpc/user" +) + +const ( + accessTrackerMaxSize = 1_000 + accessTrackerDebouncePeriod = 10 * time.Second + accessTrackerUpdateTimeout = 3 * time.Second +) + +// A AccessTrackerProvider provides the databroker service client for tracking session access. +type AccessTrackerProvider interface { + GetDataBrokerServiceClient() databroker.DataBrokerServiceClient +} + +// A AccessTracker tracks accesses to sessions +type AccessTracker struct { + provider AccessTrackerProvider + sessionAccesses chan string + serviceAccountAccesses chan string + maxSize int + debouncePeriod time.Duration + + droppedAccesses int64 +} + +// NewAccessTracker creates a new SessionAccessTracker. +func NewAccessTracker( + provider AccessTrackerProvider, + maxSize int, + debouncePeriod time.Duration, +) *AccessTracker { + return &AccessTracker{ + provider: provider, + sessionAccesses: make(chan string, maxSize), + serviceAccountAccesses: make(chan string, maxSize), + maxSize: maxSize, + debouncePeriod: debouncePeriod, + } +} + +// Run runs the access tracker. +func (tracker *AccessTracker) Run(ctx context.Context) { + ticker := time.NewTicker(tracker.debouncePeriod) + defer ticker.Stop() + + sessionAccesses := sets.NewSizeLimitedStringSet(tracker.maxSize) + serviceAccountAccesses := sets.NewSizeLimitedStringSet(tracker.maxSize) + runTrackSessionAccess := func(sessionID string) { + sessionAccesses.Add(sessionID) + } + runTrackServiceAccountAccess := func(serviceAccountID string) { + serviceAccountAccesses.Add(serviceAccountID) + } + runSubmit := func() { + if dropped := atomic.SwapInt64(&tracker.droppedAccesses, 0); dropped > 0 { + log.Error(ctx). + Int64("dropped", dropped). + Msg("authorize: failed to track all session accesses") + } + + client := tracker.provider.GetDataBrokerServiceClient() + + var err error + + sessionAccesses.ForEach(func(sessionID string) bool { + err = tracker.updateSession(ctx, client, sessionID) + return err == nil + }) + if err != nil { + log.Error(ctx).Err(err).Msg("authorize: error updating session last access timestamp") + return + } + + serviceAccountAccesses.ForEach(func(serviceAccountID string) bool { + err = tracker.updateServiceAccount(ctx, client, serviceAccountID) + return err == nil + }) + if err != nil { + log.Error(ctx).Err(err).Msg("authorize: error updating service account last access timestamp") + return + } + + sessionAccesses = sets.NewSizeLimitedStringSet(tracker.maxSize) + serviceAccountAccesses = sets.NewSizeLimitedStringSet(tracker.maxSize) + } + + for { + select { + case <-ctx.Done(): + return + case id := <-tracker.sessionAccesses: + runTrackSessionAccess(id) + case id := <-tracker.serviceAccountAccesses: + runTrackServiceAccountAccess(id) + case <-ticker.C: + runSubmit() + } + } +} + +// TrackServiceAccountAccess tracks a service account access. +func (tracker *AccessTracker) TrackServiceAccountAccess(serviceAccountID string) { + select { + case tracker.serviceAccountAccesses <- serviceAccountID: + default: + atomic.AddInt64(&tracker.droppedAccesses, 1) + } +} + +// TrackSessionAccess tracks a session access. +func (tracker *AccessTracker) TrackSessionAccess(sessionID string) { + select { + case tracker.sessionAccesses <- sessionID: + default: + atomic.AddInt64(&tracker.droppedAccesses, 1) + } +} + +func (tracker *AccessTracker) updateServiceAccount( + ctx context.Context, + client databroker.DataBrokerServiceClient, + serviceAccountID string, +) error { + ctx, clearTimeout := context.WithTimeout(ctx, accessTrackerUpdateTimeout) + defer clearTimeout() + + sa, err := user.GetServiceAccount(ctx, client, serviceAccountID) + if status.Code(err) == codes.NotFound { + return nil + } else if err != nil { + return err + } + sa.AccessedAt = timestamppb.Now() + _, err = user.PutServiceAccount(ctx, client, sa) + return err +} + +func (tracker *AccessTracker) updateSession( + ctx context.Context, + client databroker.DataBrokerServiceClient, + sessionID string, +) error { + ctx, clearTimeout := context.WithTimeout(ctx, accessTrackerUpdateTimeout) + defer clearTimeout() + + s, err := session.Get(ctx, client, sessionID) + if status.Code(err) == codes.NotFound { + return nil + } else if err != nil { + return err + } + s.AccessedAt = timestamppb.Now() + _, err = session.Put(ctx, client, s) + return err +} diff --git a/authorize/access_tracker_test.go b/authorize/access_tracker_test.go new file mode 100644 index 000000000..e4500a901 --- /dev/null +++ b/authorize/access_tracker_test.go @@ -0,0 +1,142 @@ +package authorize + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pomerium/pomerium/pkg/grpc/databroker" + "github.com/pomerium/pomerium/pkg/grpc/session" + "github.com/pomerium/pomerium/pkg/grpc/user" + "github.com/pomerium/pomerium/pkg/protoutil" +) + +type testAccessTrackerProvider struct { + dataBrokerServiceClient databroker.DataBrokerServiceClient +} + +func (provider *testAccessTrackerProvider) GetDataBrokerServiceClient() databroker.DataBrokerServiceClient { + return provider.dataBrokerServiceClient +} + +func TestAccessTracker(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var mu sync.Mutex + sessions := map[string]*session.Session{ + "session-0": { + Id: "session-0", + }, + "session-1": { + Id: "session-1", + }, + "session-2": { + Id: "session-2", + }, + } + serviceAccounts := map[string]*user.ServiceAccount{ + "service-account-0": { + Id: "service-account-0", + }, + "service-account-1": { + Id: "service-account-1", + }, + "service-account-2": { + Id: "service-account-2", + }, + } + tracker := NewAccessTracker(&testAccessTrackerProvider{ + dataBrokerServiceClient: &mockDataBrokerServiceClient{ + get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) { + mu.Lock() + defer mu.Unlock() + + switch in.GetType() { + case "type.googleapis.com/session.Session": + s, ok := sessions[in.GetId()] + if !ok { + return nil, status.Errorf(codes.NotFound, "unknown session") + } + return &databroker.GetResponse{ + Record: &databroker.Record{ + Type: in.GetType(), + Id: in.GetId(), + Data: protoutil.NewAny(s), + }, + }, nil + case "type.googleapis.com/user.ServiceAccount": + sa, ok := serviceAccounts[in.GetId()] + if !ok { + return nil, status.Errorf(codes.NotFound, "unknown service account") + } + return &databroker.GetResponse{ + Record: &databroker.Record{ + Type: in.GetType(), + Id: in.GetId(), + Data: protoutil.NewAny(sa), + }, + }, nil + default: + return nil, status.Errorf(codes.InvalidArgument, "unknown type: %s", in.GetType()) + } + }, + put: func(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) { + mu.Lock() + defer mu.Unlock() + + switch in.GetRecord().GetType() { + case "type.googleapis.com/session.Session": + data, _ := in.GetRecord().GetData().UnmarshalNew() + sessions[in.Record.GetId()] = data.(*session.Session) + return &databroker.PutResponse{ + Record: &databroker.Record{ + Type: in.GetRecord().GetType(), + Id: in.GetRecord().GetId(), + Data: protoutil.NewAny(data), + }, + }, nil + case "type.googleapis.com/user.ServiceAccount": + data, _ := in.GetRecord().GetData().UnmarshalNew() + serviceAccounts[in.Record.GetId()] = data.(*user.ServiceAccount) + return &databroker.PutResponse{ + Record: &databroker.Record{ + Type: in.GetRecord().GetType(), + Id: in.GetRecord().GetId(), + Data: protoutil.NewAny(data), + }, + }, nil + default: + return nil, status.Errorf(codes.InvalidArgument, "unknown type: %s", in.GetRecord().GetType()) + } + }, + }, + }, 200, time.Second) + go tracker.Run(ctx) + + for i := 0; i < 100; i++ { + tracker.TrackSessionAccess(fmt.Sprintf("session-%d", i%3)) + } + for i := 0; i < 100; i++ { + tracker.TrackServiceAccountAccess(fmt.Sprintf("service-account-%d", i%3)) + } + + assert.Eventually(t, func() bool { + mu.Lock() + defer mu.Unlock() + + return sessions["session-0"].GetAccessedAt().IsValid() && + sessions["session-1"].GetAccessedAt().IsValid() && + sessions["session-2"].GetAccessedAt().IsValid() && + serviceAccounts["service-account-0"].GetAccessedAt().IsValid() && + serviceAccounts["service-account-1"].GetAccessedAt().IsValid() && + serviceAccounts["service-account-2"].GetAccessedAt().IsValid() + }, time.Second*10, time.Millisecond*100) +} diff --git a/authorize/authorize.go b/authorize/authorize.go index 5ebf308b6..7f21a8100 100644 --- a/authorize/authorize.go +++ b/authorize/authorize.go @@ -13,6 +13,7 @@ import ( "github.com/pomerium/pomerium/internal/telemetry/metrics" "github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/pkg/cryptutil" + "github.com/pomerium/pomerium/pkg/grpc/databroker" ) // Authorize struct holds @@ -20,6 +21,7 @@ type Authorize struct { state *atomicAuthorizeState store *evaluator.Store currentOptions *config.AtomicOptions + accessTracker *AccessTracker dataBrokerInitialSync chan struct{} @@ -31,11 +33,12 @@ type Authorize struct { // New validates and creates a new Authorize service from a set of config options. func New(cfg *config.Config) (*Authorize, error) { - a := Authorize{ + a := &Authorize{ currentOptions: config.NewAtomicOptions(), store: evaluator.NewStore(), dataBrokerInitialSync: make(chan struct{}), } + a.accessTracker = NewAccessTracker(a, accessTrackerMaxSize, accessTrackerDebouncePeriod) state, err := newAuthorizeStateFromConfig(cfg, a.store) if err != nil { @@ -43,11 +46,17 @@ func New(cfg *config.Config) (*Authorize, error) { } a.state = newAtomicAuthorizeState(state) - return &a, nil + return a, nil +} + +// GetDataBrokerServiceClient returns the current DataBrokerServiceClient. +func (a *Authorize) GetDataBrokerServiceClient() databroker.DataBrokerServiceClient { + return a.state.Load().dataBrokerClient } // Run runs the authorize service. func (a *Authorize) Run(ctx context.Context) error { + go a.accessTracker.Run(ctx) return newDataBrokerSyncer(a).Run(ctx) } diff --git a/authorize/grpc_test.go b/authorize/grpc_test.go index 0e0060282..3845ae827 100644 --- a/authorize/grpc_test.go +++ b/authorize/grpc_test.go @@ -315,12 +315,17 @@ type mockDataBrokerServiceClient struct { databroker.DataBrokerServiceClient get func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) + put func(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) } func (m mockDataBrokerServiceClient) Get(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) { return m.get(ctx, in, opts...) } +func (m mockDataBrokerServiceClient) Put(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) { + return m.put(ctx, in, opts...) +} + func TestAuthorize_Check(t *testing.T) { opt := config.NewDefaultOptions() opt.AuthenticateURLString = "https://authenticate.example.com" diff --git a/authorize/sync.go b/authorize/sync.go index 61d95f013..065e5214e 100644 --- a/authorize/sync.go +++ b/authorize/sync.go @@ -118,11 +118,13 @@ func (a *Authorize) forceSyncSession(ctx context.Context, sessionID string) sess s, ok := a.store.GetRecordData(grpcutil.GetTypeURL(new(session.Session)), sessionID).(*session.Session) if ok { + a.accessTracker.TrackSessionAccess(sessionID) return s } sa, ok := a.store.GetRecordData(grpcutil.GetTypeURL(new(user.ServiceAccount)), sessionID).(*user.ServiceAccount) if ok { + a.accessTracker.TrackServiceAccountAccess(sessionID) return sa } @@ -135,6 +137,7 @@ func (a *Authorize) forceSyncSession(ctx context.Context, sessionID string) sess if !ok { return nil } + a.accessTracker.TrackSessionAccess(sessionID) return s } diff --git a/internal/sets/sets.go b/internal/sets/sets.go new file mode 100644 index 000000000..b7ff9ba3a --- /dev/null +++ b/internal/sets/sets.go @@ -0,0 +1,2 @@ +// Package sets contains set data structures. +package sets diff --git a/internal/sets/size_limited.go b/internal/sets/size_limited.go new file mode 100644 index 000000000..362c105f2 --- /dev/null +++ b/internal/sets/size_limited.go @@ -0,0 +1,36 @@ +package sets + +// A SizeLimitedStringSet is a StringSet which is limited to a given size. Once +// the capacity is reached an element will be removed at random. +type SizeLimitedStringSet struct { + m map[string]struct{} + capacity int +} + +// NewSizeLimitedStringSet create a new SizeLimitedStringSet. +func NewSizeLimitedStringSet(capacity int) *SizeLimitedStringSet { + return &SizeLimitedStringSet{ + m: make(map[string]struct{}), + capacity: capacity, + } +} + +// Add adds an element to the set. +func (s *SizeLimitedStringSet) Add(element string) { + s.m[element] = struct{}{} + for len(s.m) > s.capacity { + for k := range s.m { + delete(s.m, k) + break + } + } +} + +// ForEach iterates over all the elements in the set. +func (s *SizeLimitedStringSet) ForEach(callback func(element string) bool) { + for k := range s.m { + if !callback(k) { + return + } + } +} diff --git a/pkg/grpc/session/session.go b/pkg/grpc/session/session.go index 089ff57ae..2d713cd6c 100644 --- a/pkg/grpc/session/session.go +++ b/pkg/grpc/session/session.go @@ -5,6 +5,7 @@ import ( context "context" "fmt" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -48,6 +49,7 @@ func Get(ctx context.Context, client databroker.DataBrokerServiceClient, session // Put sets a session in the databroker. func Put(ctx context.Context, client databroker.DataBrokerServiceClient, s *Session) (*databroker.PutResponse, error) { + s = proto.Clone(s).(*Session) any := protoutil.NewAny(s) res, err := client.Put(ctx, &databroker.PutRequest{ Record: &databroker.Record{ diff --git a/pkg/grpc/session/session.pb.go b/pkg/grpc/session/session.pb.go index edab9c78b..9a3574951 100644 --- a/pkg/grpc/session/session.pb.go +++ b/pkg/grpc/session/session.pb.go @@ -184,6 +184,7 @@ type Session struct { DeviceCredentials []*Session_DeviceCredential `protobuf:"bytes,17,rep,name=device_credentials,json=deviceCredentials,proto3" json:"device_credentials,omitempty"` IssuedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + AccessedAt *timestamppb.Timestamp `protobuf:"bytes,18,opt,name=accessed_at,json=accessedAt,proto3" json:"accessed_at,omitempty"` IdToken *IDToken `protobuf:"bytes,6,opt,name=id_token,json=idToken,proto3" json:"id_token,omitempty"` OauthToken *OAuthToken `protobuf:"bytes,7,opt,name=oauth_token,json=oauthToken,proto3" json:"oauth_token,omitempty"` Claims map[string]*structpb.ListValue `protobuf:"bytes,9,rep,name=claims,proto3" json:"claims,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` @@ -265,6 +266,13 @@ func (x *Session) GetExpiresAt() *timestamppb.Timestamp { return nil } +func (x *Session) GetAccessedAt() *timestamppb.Timestamp { + if x != nil { + return x.AccessedAt + } + return nil +} + func (x *Session) GetIdToken() *IDToken { if x != nil { return x.IdToken @@ -421,7 +429,7 @@ var file_session_proto_rawDesc = []byte{ 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xfe, 0x05, 0x0a, 0x07, 0x53, 0x65, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xbb, 0x06, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, @@ -438,42 +446,45 @@ var file_session_proto_rawDesc = []byte{ 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x2b, - 0x0a, 0x08, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x44, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x07, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x41, 0x75, 0x74, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0a, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x16, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, - 0x74, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x87, - 0x01, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x0b, - 0x75, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x6e, 0x61, - 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x1a, 0x55, 0x0a, 0x0b, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, - 0x19, 0x0a, 0x17, 0x5f, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x65, 0x5f, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, - 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, - 0x72, 0x70, 0x63, 0x2f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x3b, + 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x69, + 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x07, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x0a, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, + 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6c, + 0x61, 0x69, 0x6d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x39, 0x0a, 0x16, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x65, 0x5f, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x87, 0x01, 0x0a, 0x10, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x12, 0x17, 0x0a, 0x07, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x6e, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x6e, 0x61, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x1a, 0x55, 0x0a, 0x0b, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x19, 0x0a, 0x17, + 0x5f, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, + 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, + 0x2f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -506,16 +517,17 @@ var file_session_proto_depIdxs = []int32{ 3, // 3: session.Session.device_credentials:type_name -> session.Session.DeviceCredential 5, // 4: session.Session.issued_at:type_name -> google.protobuf.Timestamp 5, // 5: session.Session.expires_at:type_name -> google.protobuf.Timestamp - 0, // 6: session.Session.id_token:type_name -> session.IDToken - 1, // 7: session.Session.oauth_token:type_name -> session.OAuthToken - 4, // 8: session.Session.claims:type_name -> session.Session.ClaimsEntry - 6, // 9: session.Session.DeviceCredential.unavailable:type_name -> google.protobuf.Empty - 7, // 10: session.Session.ClaimsEntry.value:type_name -> google.protobuf.ListValue - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 5, // 6: session.Session.accessed_at:type_name -> google.protobuf.Timestamp + 0, // 7: session.Session.id_token:type_name -> session.IDToken + 1, // 8: session.Session.oauth_token:type_name -> session.OAuthToken + 4, // 9: session.Session.claims:type_name -> session.Session.ClaimsEntry + 6, // 10: session.Session.DeviceCredential.unavailable:type_name -> google.protobuf.Empty + 7, // 11: session.Session.ClaimsEntry.value:type_name -> google.protobuf.ListValue + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_session_proto_init() } diff --git a/pkg/grpc/session/session.proto b/pkg/grpc/session/session.proto index 71ae06635..93ac32486 100644 --- a/pkg/grpc/session/session.proto +++ b/pkg/grpc/session/session.proto @@ -37,6 +37,7 @@ message Session { repeated DeviceCredential device_credentials = 17; google.protobuf.Timestamp issued_at = 14; google.protobuf.Timestamp expires_at = 4; + google.protobuf.Timestamp accessed_at = 18; IDToken id_token = 6; OAuthToken oauth_token = 7; map claims = 9; diff --git a/pkg/grpc/user/user.go b/pkg/grpc/user/user.go index 232bdb7d2..e10969175 100644 --- a/pkg/grpc/user/user.go +++ b/pkg/grpc/user/user.go @@ -16,6 +16,17 @@ func Get(ctx context.Context, client databroker.DataBrokerServiceClient, userID return u, databroker.Get(ctx, client, u) } +// GetServiceAccount gets a service account from the databroker. +func GetServiceAccount(ctx context.Context, client databroker.DataBrokerServiceClient, serviceAccountID string) (*ServiceAccount, error) { + sa := &ServiceAccount{Id: serviceAccountID} + return sa, databroker.Get(ctx, client, sa) +} + +// PutServiceAccount saves a service account to the databroker. +func PutServiceAccount(ctx context.Context, client databroker.DataBrokerServiceClient, serviceAccount *ServiceAccount) (*databroker.PutResponse, error) { + return databroker.Put(ctx, client, serviceAccount) +} + // AddClaims adds the flattened claims to the user. func (x *User) AddClaims(claims identity.FlattenedClaims) { if x.Claims == nil { diff --git a/pkg/grpc/user/user.pb.go b/pkg/grpc/user/user.pb.go index 53cb43c0a..2e7a07ab7 100644 --- a/pkg/grpc/user/user.pb.go +++ b/pkg/grpc/user/user.pb.go @@ -175,6 +175,7 @@ type ServiceAccount struct { UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` IssuedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + AccessedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=accessed_at,json=accessedAt,proto3" json:"accessed_at,omitempty"` } func (x *ServiceAccount) Reset() { @@ -251,6 +252,13 @@ func (x *ServiceAccount) GetIssuedAt() *timestamppb.Timestamp { return nil } +func (x *ServiceAccount) GetAccessedAt() *timestamppb.Timestamp { + if x != nil { + return x.AccessedAt + } + return nil +} + var File_user_proto protoreflect.FileDescriptor var file_user_proto_rawDesc = []byte{ @@ -279,7 +287,7 @@ var file_user_proto_rawDesc = []byte{ 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9d, 0x02, 0x0a, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xda, 0x02, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, @@ -295,13 +303,16 @@ var file_user_proto_rawDesc = []byte{ 0x41, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x41, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, - 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x2c, 0x5a, 0x2a, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, - 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x70, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x41, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, + 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x72, + 0x70, 0x63, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -329,12 +340,13 @@ var file_user_proto_depIdxs = []int32{ 3, // 0: user.User.claims:type_name -> user.User.ClaimsEntry 4, // 1: user.ServiceAccount.expires_at:type_name -> google.protobuf.Timestamp 4, // 2: user.ServiceAccount.issued_at:type_name -> google.protobuf.Timestamp - 5, // 3: user.User.ClaimsEntry.value:type_name -> google.protobuf.ListValue - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 4, // 3: user.ServiceAccount.accessed_at:type_name -> google.protobuf.Timestamp + 5, // 4: user.User.ClaimsEntry.value:type_name -> google.protobuf.ListValue + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_user_proto_init() } diff --git a/pkg/grpc/user/user.proto b/pkg/grpc/user/user.proto index 303f03cc3..f8f9ff11d 100644 --- a/pkg/grpc/user/user.proto +++ b/pkg/grpc/user/user.proto @@ -27,4 +27,5 @@ message ServiceAccount { string user_id = 2; google.protobuf.Timestamp expires_at = 3; google.protobuf.Timestamp issued_at = 4; + google.protobuf.Timestamp accessed_at = 10; }