diff --git a/api/v2/api.pb.go b/api/v2/api.pb.go index d7f7f16c2e..1a0d2dd4dd 100644 --- a/api/v2/api.pb.go +++ b/api/v2/api.pb.go @@ -1172,12 +1172,15 @@ func (x *ListPasswordResp) GetPasswords() []*Password { // Connector is a strategy used by Dex for authenticating a user against another identity provider type Connector struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Config []byte `protobuf:"bytes,4,opt,name=config,proto3" json:"config,omitempty"` - GrantTypes []string `protobuf:"bytes,5,rep,name=grant_types,json=grantTypes,proto3" json:"grant_types,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Config []byte `protobuf:"bytes,4,opt,name=config,proto3" json:"config,omitempty"` + GrantTypes []string `protobuf:"bytes,5,rep,name=grant_types,json=grantTypes,proto3" json:"grant_types,omitempty"` + // Per-connector expiry overrides. Each field must be at least as strict + // as the corresponding global value or the write will be rejected. + Expiry *ConnectorExpiry `protobuf:"bytes,6,opt,name=expiry,proto3" json:"expiry,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1247,6 +1250,186 @@ func (x *Connector) GetGrantTypes() []string { return nil } +func (x *Connector) GetExpiry() *ConnectorExpiry { + if x != nil { + return x.Expiry + } + return nil +} + +// ConnectorExpiry holds per-connector overrides for token lifetimes. +// Duration strings use the same format as the top-level expiry config +// ("5m", "24h"). Empty strings mean "inherit the global value". +type ConnectorExpiry struct { + state protoimpl.MessageState `protogen:"open.v1"` + IdTokens string `protobuf:"bytes,1,opt,name=id_tokens,json=idTokens,proto3" json:"id_tokens,omitempty"` + RefreshTokens *ConnectorRefreshExpiry `protobuf:"bytes,2,opt,name=refresh_tokens,json=refreshTokens,proto3" json:"refresh_tokens,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectorExpiry) Reset() { + *x = ConnectorExpiry{} + mi := &file_api_v2_api_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectorExpiry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorExpiry) ProtoMessage() {} + +func (x *ConnectorExpiry) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_api_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorExpiry.ProtoReflect.Descriptor instead. +func (*ConnectorExpiry) Descriptor() ([]byte, []int) { + return file_api_v2_api_proto_rawDescGZIP(), []int{22} +} + +func (x *ConnectorExpiry) GetIdTokens() string { + if x != nil { + return x.IdTokens + } + return "" +} + +func (x *ConnectorExpiry) GetRefreshTokens() *ConnectorRefreshExpiry { + if x != nil { + return x.RefreshTokens + } + return nil +} + +// ConnectorRefreshExpiry holds per-connector refresh-token policy overrides. +type ConnectorRefreshExpiry struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Absent means "inherit"; present (true/false) overrides the global value. + DisableRotation *bool `protobuf:"varint,1,opt,name=disable_rotation,json=disableRotation,proto3,oneof" json:"disable_rotation,omitempty"` + ReuseInterval string `protobuf:"bytes,2,opt,name=reuse_interval,json=reuseInterval,proto3" json:"reuse_interval,omitempty"` + AbsoluteLifetime string `protobuf:"bytes,3,opt,name=absolute_lifetime,json=absoluteLifetime,proto3" json:"absolute_lifetime,omitempty"` + ValidIfNotUsedFor string `protobuf:"bytes,4,opt,name=valid_if_not_used_for,json=validIfNotUsedFor,proto3" json:"valid_if_not_used_for,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectorRefreshExpiry) Reset() { + *x = ConnectorRefreshExpiry{} + mi := &file_api_v2_api_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectorRefreshExpiry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorRefreshExpiry) ProtoMessage() {} + +func (x *ConnectorRefreshExpiry) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_api_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorRefreshExpiry.ProtoReflect.Descriptor instead. +func (*ConnectorRefreshExpiry) Descriptor() ([]byte, []int) { + return file_api_v2_api_proto_rawDescGZIP(), []int{23} +} + +func (x *ConnectorRefreshExpiry) GetDisableRotation() bool { + if x != nil && x.DisableRotation != nil { + return *x.DisableRotation + } + return false +} + +func (x *ConnectorRefreshExpiry) GetReuseInterval() string { + if x != nil { + return x.ReuseInterval + } + return "" +} + +func (x *ConnectorRefreshExpiry) GetAbsoluteLifetime() string { + if x != nil { + return x.AbsoluteLifetime + } + return "" +} + +func (x *ConnectorRefreshExpiry) GetValidIfNotUsedFor() string { + if x != nil { + return x.ValidIfNotUsedFor + } + return "" +} + +// ConnectorExpiryUpdate distinguishes "leave the override alone" from +// "change it". An absent ConnectorExpiryUpdate leaves the existing value +// in place; a present one with nil Value clears the override; a present +// one with a non-nil Value installs that override. +type ConnectorExpiryUpdate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value *ConnectorExpiry `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectorExpiryUpdate) Reset() { + *x = ConnectorExpiryUpdate{} + mi := &file_api_v2_api_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectorExpiryUpdate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorExpiryUpdate) ProtoMessage() {} + +func (x *ConnectorExpiryUpdate) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_api_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorExpiryUpdate.ProtoReflect.Descriptor instead. +func (*ConnectorExpiryUpdate) Descriptor() ([]byte, []int) { + return file_api_v2_api_proto_rawDescGZIP(), []int{24} +} + +func (x *ConnectorExpiryUpdate) GetValue() *ConnectorExpiry { + if x != nil { + return x.Value + } + return nil +} + // CreateConnectorReq is a request to make a connector. type CreateConnectorReq struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1257,7 +1440,7 @@ type CreateConnectorReq struct { func (x *CreateConnectorReq) Reset() { *x = CreateConnectorReq{} - mi := &file_api_v2_api_proto_msgTypes[22] + mi := &file_api_v2_api_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1269,7 +1452,7 @@ func (x *CreateConnectorReq) String() string { func (*CreateConnectorReq) ProtoMessage() {} func (x *CreateConnectorReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[22] + mi := &file_api_v2_api_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1282,7 +1465,7 @@ func (x *CreateConnectorReq) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateConnectorReq.ProtoReflect.Descriptor instead. func (*CreateConnectorReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{22} + return file_api_v2_api_proto_rawDescGZIP(), []int{25} } func (x *CreateConnectorReq) GetConnector() *Connector { @@ -1302,7 +1485,7 @@ type CreateConnectorResp struct { func (x *CreateConnectorResp) Reset() { *x = CreateConnectorResp{} - mi := &file_api_v2_api_proto_msgTypes[23] + mi := &file_api_v2_api_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1314,7 +1497,7 @@ func (x *CreateConnectorResp) String() string { func (*CreateConnectorResp) ProtoMessage() {} func (x *CreateConnectorResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[23] + mi := &file_api_v2_api_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1327,7 +1510,7 @@ func (x *CreateConnectorResp) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateConnectorResp.ProtoReflect.Descriptor instead. func (*CreateConnectorResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{23} + return file_api_v2_api_proto_rawDescGZIP(), []int{26} } func (x *CreateConnectorResp) GetAlreadyExists() bool { @@ -1348,7 +1531,7 @@ type GrantTypes struct { func (x *GrantTypes) Reset() { *x = GrantTypes{} - mi := &file_api_v2_api_proto_msgTypes[24] + mi := &file_api_v2_api_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1360,7 +1543,7 @@ func (x *GrantTypes) String() string { func (*GrantTypes) ProtoMessage() {} func (x *GrantTypes) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[24] + mi := &file_api_v2_api_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1373,7 +1556,7 @@ func (x *GrantTypes) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantTypes.ProtoReflect.Descriptor instead. func (*GrantTypes) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{24} + return file_api_v2_api_proto_rawDescGZIP(), []int{27} } func (x *GrantTypes) GetGrantTypes() []string { @@ -1395,13 +1578,16 @@ type UpdateConnectorReq struct { // An empty grant_types list means unrestricted (all grant types allowed). // If not set (null), grant types are not modified. NewGrantTypes *GrantTypes `protobuf:"bytes,5,opt,name=new_grant_types,json=newGrantTypes,proto3" json:"new_grant_types,omitempty"` + // If set, updates the connector's expiry overrides. If unset, the + // existing expiry is left untouched. + NewExpiry *ConnectorExpiryUpdate `protobuf:"bytes,6,opt,name=new_expiry,json=newExpiry,proto3" json:"new_expiry,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateConnectorReq) Reset() { *x = UpdateConnectorReq{} - mi := &file_api_v2_api_proto_msgTypes[25] + mi := &file_api_v2_api_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1413,7 +1599,7 @@ func (x *UpdateConnectorReq) String() string { func (*UpdateConnectorReq) ProtoMessage() {} func (x *UpdateConnectorReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[25] + mi := &file_api_v2_api_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1426,7 +1612,7 @@ func (x *UpdateConnectorReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConnectorReq.ProtoReflect.Descriptor instead. func (*UpdateConnectorReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{25} + return file_api_v2_api_proto_rawDescGZIP(), []int{28} } func (x *UpdateConnectorReq) GetId() string { @@ -1464,6 +1650,13 @@ func (x *UpdateConnectorReq) GetNewGrantTypes() *GrantTypes { return nil } +func (x *UpdateConnectorReq) GetNewExpiry() *ConnectorExpiryUpdate { + if x != nil { + return x.NewExpiry + } + return nil +} + // UpdateConnectorResp returns the response from modifying an existing connector. type UpdateConnectorResp struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1474,7 +1667,7 @@ type UpdateConnectorResp struct { func (x *UpdateConnectorResp) Reset() { *x = UpdateConnectorResp{} - mi := &file_api_v2_api_proto_msgTypes[26] + mi := &file_api_v2_api_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1486,7 +1679,7 @@ func (x *UpdateConnectorResp) String() string { func (*UpdateConnectorResp) ProtoMessage() {} func (x *UpdateConnectorResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[26] + mi := &file_api_v2_api_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1499,7 +1692,7 @@ func (x *UpdateConnectorResp) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConnectorResp.ProtoReflect.Descriptor instead. func (*UpdateConnectorResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{26} + return file_api_v2_api_proto_rawDescGZIP(), []int{29} } func (x *UpdateConnectorResp) GetNotFound() bool { @@ -1519,7 +1712,7 @@ type DeleteConnectorReq struct { func (x *DeleteConnectorReq) Reset() { *x = DeleteConnectorReq{} - mi := &file_api_v2_api_proto_msgTypes[27] + mi := &file_api_v2_api_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1531,7 +1724,7 @@ func (x *DeleteConnectorReq) String() string { func (*DeleteConnectorReq) ProtoMessage() {} func (x *DeleteConnectorReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[27] + mi := &file_api_v2_api_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1544,7 +1737,7 @@ func (x *DeleteConnectorReq) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteConnectorReq.ProtoReflect.Descriptor instead. func (*DeleteConnectorReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{27} + return file_api_v2_api_proto_rawDescGZIP(), []int{30} } func (x *DeleteConnectorReq) GetId() string { @@ -1564,7 +1757,7 @@ type DeleteConnectorResp struct { func (x *DeleteConnectorResp) Reset() { *x = DeleteConnectorResp{} - mi := &file_api_v2_api_proto_msgTypes[28] + mi := &file_api_v2_api_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1576,7 +1769,7 @@ func (x *DeleteConnectorResp) String() string { func (*DeleteConnectorResp) ProtoMessage() {} func (x *DeleteConnectorResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[28] + mi := &file_api_v2_api_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1589,7 +1782,7 @@ func (x *DeleteConnectorResp) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteConnectorResp.ProtoReflect.Descriptor instead. func (*DeleteConnectorResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{28} + return file_api_v2_api_proto_rawDescGZIP(), []int{31} } func (x *DeleteConnectorResp) GetNotFound() bool { @@ -1608,7 +1801,7 @@ type ListConnectorReq struct { func (x *ListConnectorReq) Reset() { *x = ListConnectorReq{} - mi := &file_api_v2_api_proto_msgTypes[29] + mi := &file_api_v2_api_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1620,7 +1813,7 @@ func (x *ListConnectorReq) String() string { func (*ListConnectorReq) ProtoMessage() {} func (x *ListConnectorReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[29] + mi := &file_api_v2_api_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1633,7 +1826,7 @@ func (x *ListConnectorReq) ProtoReflect() protoreflect.Message { // Deprecated: Use ListConnectorReq.ProtoReflect.Descriptor instead. func (*ListConnectorReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{29} + return file_api_v2_api_proto_rawDescGZIP(), []int{32} } // ListConnectorResp returns a list of connectors. @@ -1646,7 +1839,7 @@ type ListConnectorResp struct { func (x *ListConnectorResp) Reset() { *x = ListConnectorResp{} - mi := &file_api_v2_api_proto_msgTypes[30] + mi := &file_api_v2_api_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1658,7 +1851,7 @@ func (x *ListConnectorResp) String() string { func (*ListConnectorResp) ProtoMessage() {} func (x *ListConnectorResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[30] + mi := &file_api_v2_api_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1671,7 +1864,7 @@ func (x *ListConnectorResp) ProtoReflect() protoreflect.Message { // Deprecated: Use ListConnectorResp.ProtoReflect.Descriptor instead. func (*ListConnectorResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{30} + return file_api_v2_api_proto_rawDescGZIP(), []int{33} } func (x *ListConnectorResp) GetConnectors() []*Connector { @@ -1690,7 +1883,7 @@ type VersionReq struct { func (x *VersionReq) Reset() { *x = VersionReq{} - mi := &file_api_v2_api_proto_msgTypes[31] + mi := &file_api_v2_api_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1702,7 +1895,7 @@ func (x *VersionReq) String() string { func (*VersionReq) ProtoMessage() {} func (x *VersionReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[31] + mi := &file_api_v2_api_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1715,7 +1908,7 @@ func (x *VersionReq) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionReq.ProtoReflect.Descriptor instead. func (*VersionReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{31} + return file_api_v2_api_proto_rawDescGZIP(), []int{34} } // VersionResp holds the version info of components. @@ -1732,7 +1925,7 @@ type VersionResp struct { func (x *VersionResp) Reset() { *x = VersionResp{} - mi := &file_api_v2_api_proto_msgTypes[32] + mi := &file_api_v2_api_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1744,7 +1937,7 @@ func (x *VersionResp) String() string { func (*VersionResp) ProtoMessage() {} func (x *VersionResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[32] + mi := &file_api_v2_api_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1757,7 +1950,7 @@ func (x *VersionResp) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResp.ProtoReflect.Descriptor instead. func (*VersionResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{32} + return file_api_v2_api_proto_rawDescGZIP(), []int{35} } func (x *VersionResp) GetServer() string { @@ -1783,7 +1976,7 @@ type DiscoveryReq struct { func (x *DiscoveryReq) Reset() { *x = DiscoveryReq{} - mi := &file_api_v2_api_proto_msgTypes[33] + mi := &file_api_v2_api_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1795,7 +1988,7 @@ func (x *DiscoveryReq) String() string { func (*DiscoveryReq) ProtoMessage() {} func (x *DiscoveryReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[33] + mi := &file_api_v2_api_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1808,7 +2001,7 @@ func (x *DiscoveryReq) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryReq.ProtoReflect.Descriptor instead. func (*DiscoveryReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{33} + return file_api_v2_api_proto_rawDescGZIP(), []int{36} } // DiscoverResp holds the version oidc disovery info. @@ -1835,7 +2028,7 @@ type DiscoveryResp struct { func (x *DiscoveryResp) Reset() { *x = DiscoveryResp{} - mi := &file_api_v2_api_proto_msgTypes[34] + mi := &file_api_v2_api_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1847,7 +2040,7 @@ func (x *DiscoveryResp) String() string { func (*DiscoveryResp) ProtoMessage() {} func (x *DiscoveryResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[34] + mi := &file_api_v2_api_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1860,7 +2053,7 @@ func (x *DiscoveryResp) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryResp.ProtoReflect.Descriptor instead. func (*DiscoveryResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{34} + return file_api_v2_api_proto_rawDescGZIP(), []int{37} } func (x *DiscoveryResp) GetIssuer() string { @@ -1982,7 +2175,7 @@ type RefreshTokenRef struct { func (x *RefreshTokenRef) Reset() { *x = RefreshTokenRef{} - mi := &file_api_v2_api_proto_msgTypes[35] + mi := &file_api_v2_api_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1994,7 +2187,7 @@ func (x *RefreshTokenRef) String() string { func (*RefreshTokenRef) ProtoMessage() {} func (x *RefreshTokenRef) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[35] + mi := &file_api_v2_api_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2007,7 +2200,7 @@ func (x *RefreshTokenRef) ProtoReflect() protoreflect.Message { // Deprecated: Use RefreshTokenRef.ProtoReflect.Descriptor instead. func (*RefreshTokenRef) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{35} + return file_api_v2_api_proto_rawDescGZIP(), []int{38} } func (x *RefreshTokenRef) GetId() string { @@ -2049,7 +2242,7 @@ type ListRefreshReq struct { func (x *ListRefreshReq) Reset() { *x = ListRefreshReq{} - mi := &file_api_v2_api_proto_msgTypes[36] + mi := &file_api_v2_api_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2061,7 +2254,7 @@ func (x *ListRefreshReq) String() string { func (*ListRefreshReq) ProtoMessage() {} func (x *ListRefreshReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[36] + mi := &file_api_v2_api_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2074,7 +2267,7 @@ func (x *ListRefreshReq) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRefreshReq.ProtoReflect.Descriptor instead. func (*ListRefreshReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{36} + return file_api_v2_api_proto_rawDescGZIP(), []int{39} } func (x *ListRefreshReq) GetUserId() string { @@ -2094,7 +2287,7 @@ type ListRefreshResp struct { func (x *ListRefreshResp) Reset() { *x = ListRefreshResp{} - mi := &file_api_v2_api_proto_msgTypes[37] + mi := &file_api_v2_api_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2106,7 +2299,7 @@ func (x *ListRefreshResp) String() string { func (*ListRefreshResp) ProtoMessage() {} func (x *ListRefreshResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[37] + mi := &file_api_v2_api_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2119,7 +2312,7 @@ func (x *ListRefreshResp) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRefreshResp.ProtoReflect.Descriptor instead. func (*ListRefreshResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{37} + return file_api_v2_api_proto_rawDescGZIP(), []int{40} } func (x *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef { @@ -2141,7 +2334,7 @@ type RevokeRefreshReq struct { func (x *RevokeRefreshReq) Reset() { *x = RevokeRefreshReq{} - mi := &file_api_v2_api_proto_msgTypes[38] + mi := &file_api_v2_api_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2153,7 +2346,7 @@ func (x *RevokeRefreshReq) String() string { func (*RevokeRefreshReq) ProtoMessage() {} func (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[38] + mi := &file_api_v2_api_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2166,7 +2359,7 @@ func (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeRefreshReq.ProtoReflect.Descriptor instead. func (*RevokeRefreshReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{38} + return file_api_v2_api_proto_rawDescGZIP(), []int{41} } func (x *RevokeRefreshReq) GetUserId() string { @@ -2194,7 +2387,7 @@ type RevokeRefreshResp struct { func (x *RevokeRefreshResp) Reset() { *x = RevokeRefreshResp{} - mi := &file_api_v2_api_proto_msgTypes[39] + mi := &file_api_v2_api_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2206,7 +2399,7 @@ func (x *RevokeRefreshResp) String() string { func (*RevokeRefreshResp) ProtoMessage() {} func (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[39] + mi := &file_api_v2_api_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2219,7 +2412,7 @@ func (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeRefreshResp.ProtoReflect.Descriptor instead. func (*RevokeRefreshResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{39} + return file_api_v2_api_proto_rawDescGZIP(), []int{42} } func (x *RevokeRefreshResp) GetNotFound() bool { @@ -2239,7 +2432,7 @@ type VerifyPasswordReq struct { func (x *VerifyPasswordReq) Reset() { *x = VerifyPasswordReq{} - mi := &file_api_v2_api_proto_msgTypes[40] + mi := &file_api_v2_api_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2251,7 +2444,7 @@ func (x *VerifyPasswordReq) String() string { func (*VerifyPasswordReq) ProtoMessage() {} func (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[40] + mi := &file_api_v2_api_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2264,7 +2457,7 @@ func (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyPasswordReq.ProtoReflect.Descriptor instead. func (*VerifyPasswordReq) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{40} + return file_api_v2_api_proto_rawDescGZIP(), []int{43} } func (x *VerifyPasswordReq) GetEmail() string { @@ -2291,7 +2484,7 @@ type VerifyPasswordResp struct { func (x *VerifyPasswordResp) Reset() { *x = VerifyPasswordResp{} - mi := &file_api_v2_api_proto_msgTypes[41] + mi := &file_api_v2_api_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2303,7 +2496,7 @@ func (x *VerifyPasswordResp) String() string { func (*VerifyPasswordResp) ProtoMessage() {} func (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_api_proto_msgTypes[41] + mi := &file_api_v2_api_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2316,7 +2509,7 @@ func (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyPasswordResp.ProtoReflect.Descriptor instead. func (*VerifyPasswordResp) Descriptor() ([]byte, []int) { - return file_api_v2_api_proto_rawDescGZIP(), []int{41} + return file_api_v2_api_proto_rawDescGZIP(), []int{44} } func (x *VerifyPasswordResp) GetVerified() bool { @@ -2450,217 +2643,250 @@ var file_api_v2_api_proto_rawDesc = string([]byte{ 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x73, 0x22, 0x7c, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x73, 0x22, 0x42, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, - 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, - 0x69, 0x73, 0x74, 0x73, 0x22, 0x2d, 0x0a, 0x0a, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, - 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, - 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x37, 0x0a, 0x0f, 0x6e, 0x65, 0x77, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, - 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x47, 0x72, - 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, - 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x24, 0x0a, 0x12, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, - 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x22, 0x32, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, - 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, - 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x22, 0x43, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, - 0x2e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, - 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, - 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x22, 0xb0, 0x06, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, - 0x12, 0x35, 0x0a, 0x16, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x15, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72, 0x69, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x73, 0x65, - 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x1d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x69, 0x6e, - 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x69, 0x6e, 0x74, 0x72, - 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x32, 0x0a, 0x15, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x13, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, - 0x36, 0x0a, 0x17, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x15, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x25, 0x69, 0x64, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x67, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x20, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, - 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x53, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x20, 0x63, 0x6f, 0x64, 0x65, - 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x1d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, - 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, - 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x21, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, - 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, - 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x7a, 0x0a, 0x0f, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, - 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x11, 0x52, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, - 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x45, 0x0a, 0x11, + 0x6f, 0x72, 0x64, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x22, 0x72, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22, 0xe3, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x12, 0x2e, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0f, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, + 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x75, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x62, 0x73, 0x6f, 0x6c, + 0x75, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x10, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, + 0x74, 0x69, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x15, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x69, 0x66, + 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x11, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x55, + 0x73, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x43, 0x0a, 0x15, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x42, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, + 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, + 0x74, 0x73, 0x22, 0x2d, 0x0a, 0x0a, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x22, 0xed, 0x01, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, + 0x0f, 0x6e, 0x65, 0x77, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x09, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x22, 0x32, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, + 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, + 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x24, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x32, 0x0a, 0x13, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, + 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x71, 0x22, 0x43, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0a, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, + 0x03, 0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, + 0x0e, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x22, + 0xb0, 0x06, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x16, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, + 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, + 0x72, 0x69, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, + 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x42, 0x0a, 0x1d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x15, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x38, + 0x0a, 0x18, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x12, 0x4f, 0x0a, 0x25, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x69, 0x67, + 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, + 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x20, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, + 0x6c, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x64, 0x12, 0x47, 0x0a, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, + 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1d, 0x63, 0x6f, 0x64, + 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0d, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0e, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x21, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x22, 0x7a, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, + 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, + 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, + 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x45, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, - 0x6e, 0x64, 0x32, 0x8b, 0x09, 0x0a, 0x03, 0x44, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x09, 0x47, 0x65, - 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, - 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, - 0x12, 0x3d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, - 0x3d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, - 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, - 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, - 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, - 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, - 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, + 0x73, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0x8b, 0x09, 0x0a, 0x03, + 0x44, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, + 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, + 0x12, 0x43, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, + 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, + 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, + 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, + 0x46, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, - 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, - 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, - 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, - 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x79, 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, - 0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76, - 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, + 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, + 0x46, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, + 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x12, 0x11, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, - 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, - 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, - 0x42, 0x36, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64, - 0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x36, 0x0a, 0x12, 0x63, 0x6f, 0x6d, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, + 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, + 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, + 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -2675,50 +2901,53 @@ func file_api_v2_api_proto_rawDescGZIP() []byte { return file_api_v2_api_proto_rawDescData } -var file_api_v2_api_proto_msgTypes = make([]protoimpl.MessageInfo, 42) +var file_api_v2_api_proto_msgTypes = make([]protoimpl.MessageInfo, 45) var file_api_v2_api_proto_goTypes = []any{ - (*Client)(nil), // 0: api.Client - (*ClientInfo)(nil), // 1: api.ClientInfo - (*GetClientReq)(nil), // 2: api.GetClientReq - (*GetClientResp)(nil), // 3: api.GetClientResp - (*CreateClientReq)(nil), // 4: api.CreateClientReq - (*CreateClientResp)(nil), // 5: api.CreateClientResp - (*DeleteClientReq)(nil), // 6: api.DeleteClientReq - (*DeleteClientResp)(nil), // 7: api.DeleteClientResp - (*UpdateClientReq)(nil), // 8: api.UpdateClientReq - (*UpdateClientResp)(nil), // 9: api.UpdateClientResp - (*ListClientReq)(nil), // 10: api.ListClientReq - (*ListClientResp)(nil), // 11: api.ListClientResp - (*Password)(nil), // 12: api.Password - (*CreatePasswordReq)(nil), // 13: api.CreatePasswordReq - (*CreatePasswordResp)(nil), // 14: api.CreatePasswordResp - (*UpdatePasswordReq)(nil), // 15: api.UpdatePasswordReq - (*UpdatePasswordResp)(nil), // 16: api.UpdatePasswordResp - (*DeletePasswordReq)(nil), // 17: api.DeletePasswordReq - (*DeletePasswordResp)(nil), // 18: api.DeletePasswordResp - (*ListPasswordReq)(nil), // 19: api.ListPasswordReq - (*ListPasswordResp)(nil), // 20: api.ListPasswordResp - (*Connector)(nil), // 21: api.Connector - (*CreateConnectorReq)(nil), // 22: api.CreateConnectorReq - (*CreateConnectorResp)(nil), // 23: api.CreateConnectorResp - (*GrantTypes)(nil), // 24: api.GrantTypes - (*UpdateConnectorReq)(nil), // 25: api.UpdateConnectorReq - (*UpdateConnectorResp)(nil), // 26: api.UpdateConnectorResp - (*DeleteConnectorReq)(nil), // 27: api.DeleteConnectorReq - (*DeleteConnectorResp)(nil), // 28: api.DeleteConnectorResp - (*ListConnectorReq)(nil), // 29: api.ListConnectorReq - (*ListConnectorResp)(nil), // 30: api.ListConnectorResp - (*VersionReq)(nil), // 31: api.VersionReq - (*VersionResp)(nil), // 32: api.VersionResp - (*DiscoveryReq)(nil), // 33: api.DiscoveryReq - (*DiscoveryResp)(nil), // 34: api.DiscoveryResp - (*RefreshTokenRef)(nil), // 35: api.RefreshTokenRef - (*ListRefreshReq)(nil), // 36: api.ListRefreshReq - (*ListRefreshResp)(nil), // 37: api.ListRefreshResp - (*RevokeRefreshReq)(nil), // 38: api.RevokeRefreshReq - (*RevokeRefreshResp)(nil), // 39: api.RevokeRefreshResp - (*VerifyPasswordReq)(nil), // 40: api.VerifyPasswordReq - (*VerifyPasswordResp)(nil), // 41: api.VerifyPasswordResp + (*Client)(nil), // 0: api.Client + (*ClientInfo)(nil), // 1: api.ClientInfo + (*GetClientReq)(nil), // 2: api.GetClientReq + (*GetClientResp)(nil), // 3: api.GetClientResp + (*CreateClientReq)(nil), // 4: api.CreateClientReq + (*CreateClientResp)(nil), // 5: api.CreateClientResp + (*DeleteClientReq)(nil), // 6: api.DeleteClientReq + (*DeleteClientResp)(nil), // 7: api.DeleteClientResp + (*UpdateClientReq)(nil), // 8: api.UpdateClientReq + (*UpdateClientResp)(nil), // 9: api.UpdateClientResp + (*ListClientReq)(nil), // 10: api.ListClientReq + (*ListClientResp)(nil), // 11: api.ListClientResp + (*Password)(nil), // 12: api.Password + (*CreatePasswordReq)(nil), // 13: api.CreatePasswordReq + (*CreatePasswordResp)(nil), // 14: api.CreatePasswordResp + (*UpdatePasswordReq)(nil), // 15: api.UpdatePasswordReq + (*UpdatePasswordResp)(nil), // 16: api.UpdatePasswordResp + (*DeletePasswordReq)(nil), // 17: api.DeletePasswordReq + (*DeletePasswordResp)(nil), // 18: api.DeletePasswordResp + (*ListPasswordReq)(nil), // 19: api.ListPasswordReq + (*ListPasswordResp)(nil), // 20: api.ListPasswordResp + (*Connector)(nil), // 21: api.Connector + (*ConnectorExpiry)(nil), // 22: api.ConnectorExpiry + (*ConnectorRefreshExpiry)(nil), // 23: api.ConnectorRefreshExpiry + (*ConnectorExpiryUpdate)(nil), // 24: api.ConnectorExpiryUpdate + (*CreateConnectorReq)(nil), // 25: api.CreateConnectorReq + (*CreateConnectorResp)(nil), // 26: api.CreateConnectorResp + (*GrantTypes)(nil), // 27: api.GrantTypes + (*UpdateConnectorReq)(nil), // 28: api.UpdateConnectorReq + (*UpdateConnectorResp)(nil), // 29: api.UpdateConnectorResp + (*DeleteConnectorReq)(nil), // 30: api.DeleteConnectorReq + (*DeleteConnectorResp)(nil), // 31: api.DeleteConnectorResp + (*ListConnectorReq)(nil), // 32: api.ListConnectorReq + (*ListConnectorResp)(nil), // 33: api.ListConnectorResp + (*VersionReq)(nil), // 34: api.VersionReq + (*VersionResp)(nil), // 35: api.VersionResp + (*DiscoveryReq)(nil), // 36: api.DiscoveryReq + (*DiscoveryResp)(nil), // 37: api.DiscoveryResp + (*RefreshTokenRef)(nil), // 38: api.RefreshTokenRef + (*ListRefreshReq)(nil), // 39: api.ListRefreshReq + (*ListRefreshResp)(nil), // 40: api.ListRefreshResp + (*RevokeRefreshReq)(nil), // 41: api.RevokeRefreshReq + (*RevokeRefreshResp)(nil), // 42: api.RevokeRefreshResp + (*VerifyPasswordReq)(nil), // 43: api.VerifyPasswordReq + (*VerifyPasswordResp)(nil), // 44: api.VerifyPasswordResp } var file_api_v2_api_proto_depIdxs = []int32{ 0, // 0: api.GetClientResp.client:type_name -> api.Client @@ -2727,51 +2956,55 @@ var file_api_v2_api_proto_depIdxs = []int32{ 1, // 3: api.ListClientResp.clients:type_name -> api.ClientInfo 12, // 4: api.CreatePasswordReq.password:type_name -> api.Password 12, // 5: api.ListPasswordResp.passwords:type_name -> api.Password - 21, // 6: api.CreateConnectorReq.connector:type_name -> api.Connector - 24, // 7: api.UpdateConnectorReq.new_grant_types:type_name -> api.GrantTypes - 21, // 8: api.ListConnectorResp.connectors:type_name -> api.Connector - 35, // 9: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef - 2, // 10: api.Dex.GetClient:input_type -> api.GetClientReq - 4, // 11: api.Dex.CreateClient:input_type -> api.CreateClientReq - 8, // 12: api.Dex.UpdateClient:input_type -> api.UpdateClientReq - 6, // 13: api.Dex.DeleteClient:input_type -> api.DeleteClientReq - 10, // 14: api.Dex.ListClients:input_type -> api.ListClientReq - 13, // 15: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq - 15, // 16: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq - 17, // 17: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq - 19, // 18: api.Dex.ListPasswords:input_type -> api.ListPasswordReq - 22, // 19: api.Dex.CreateConnector:input_type -> api.CreateConnectorReq - 25, // 20: api.Dex.UpdateConnector:input_type -> api.UpdateConnectorReq - 27, // 21: api.Dex.DeleteConnector:input_type -> api.DeleteConnectorReq - 29, // 22: api.Dex.ListConnectors:input_type -> api.ListConnectorReq - 31, // 23: api.Dex.GetVersion:input_type -> api.VersionReq - 33, // 24: api.Dex.GetDiscovery:input_type -> api.DiscoveryReq - 36, // 25: api.Dex.ListRefresh:input_type -> api.ListRefreshReq - 38, // 26: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq - 40, // 27: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq - 3, // 28: api.Dex.GetClient:output_type -> api.GetClientResp - 5, // 29: api.Dex.CreateClient:output_type -> api.CreateClientResp - 9, // 30: api.Dex.UpdateClient:output_type -> api.UpdateClientResp - 7, // 31: api.Dex.DeleteClient:output_type -> api.DeleteClientResp - 11, // 32: api.Dex.ListClients:output_type -> api.ListClientResp - 14, // 33: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp - 16, // 34: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp - 18, // 35: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp - 20, // 36: api.Dex.ListPasswords:output_type -> api.ListPasswordResp - 23, // 37: api.Dex.CreateConnector:output_type -> api.CreateConnectorResp - 26, // 38: api.Dex.UpdateConnector:output_type -> api.UpdateConnectorResp - 28, // 39: api.Dex.DeleteConnector:output_type -> api.DeleteConnectorResp - 30, // 40: api.Dex.ListConnectors:output_type -> api.ListConnectorResp - 32, // 41: api.Dex.GetVersion:output_type -> api.VersionResp - 34, // 42: api.Dex.GetDiscovery:output_type -> api.DiscoveryResp - 37, // 43: api.Dex.ListRefresh:output_type -> api.ListRefreshResp - 39, // 44: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp - 41, // 45: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp - 28, // [28:46] is the sub-list for method output_type - 10, // [10:28] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 22, // 6: api.Connector.expiry:type_name -> api.ConnectorExpiry + 23, // 7: api.ConnectorExpiry.refresh_tokens:type_name -> api.ConnectorRefreshExpiry + 22, // 8: api.ConnectorExpiryUpdate.value:type_name -> api.ConnectorExpiry + 21, // 9: api.CreateConnectorReq.connector:type_name -> api.Connector + 27, // 10: api.UpdateConnectorReq.new_grant_types:type_name -> api.GrantTypes + 24, // 11: api.UpdateConnectorReq.new_expiry:type_name -> api.ConnectorExpiryUpdate + 21, // 12: api.ListConnectorResp.connectors:type_name -> api.Connector + 38, // 13: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef + 2, // 14: api.Dex.GetClient:input_type -> api.GetClientReq + 4, // 15: api.Dex.CreateClient:input_type -> api.CreateClientReq + 8, // 16: api.Dex.UpdateClient:input_type -> api.UpdateClientReq + 6, // 17: api.Dex.DeleteClient:input_type -> api.DeleteClientReq + 10, // 18: api.Dex.ListClients:input_type -> api.ListClientReq + 13, // 19: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq + 15, // 20: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq + 17, // 21: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq + 19, // 22: api.Dex.ListPasswords:input_type -> api.ListPasswordReq + 25, // 23: api.Dex.CreateConnector:input_type -> api.CreateConnectorReq + 28, // 24: api.Dex.UpdateConnector:input_type -> api.UpdateConnectorReq + 30, // 25: api.Dex.DeleteConnector:input_type -> api.DeleteConnectorReq + 32, // 26: api.Dex.ListConnectors:input_type -> api.ListConnectorReq + 34, // 27: api.Dex.GetVersion:input_type -> api.VersionReq + 36, // 28: api.Dex.GetDiscovery:input_type -> api.DiscoveryReq + 39, // 29: api.Dex.ListRefresh:input_type -> api.ListRefreshReq + 41, // 30: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq + 43, // 31: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq + 3, // 32: api.Dex.GetClient:output_type -> api.GetClientResp + 5, // 33: api.Dex.CreateClient:output_type -> api.CreateClientResp + 9, // 34: api.Dex.UpdateClient:output_type -> api.UpdateClientResp + 7, // 35: api.Dex.DeleteClient:output_type -> api.DeleteClientResp + 11, // 36: api.Dex.ListClients:output_type -> api.ListClientResp + 14, // 37: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp + 16, // 38: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp + 18, // 39: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp + 20, // 40: api.Dex.ListPasswords:output_type -> api.ListPasswordResp + 26, // 41: api.Dex.CreateConnector:output_type -> api.CreateConnectorResp + 29, // 42: api.Dex.UpdateConnector:output_type -> api.UpdateConnectorResp + 31, // 43: api.Dex.DeleteConnector:output_type -> api.DeleteConnectorResp + 33, // 44: api.Dex.ListConnectors:output_type -> api.ListConnectorResp + 35, // 45: api.Dex.GetVersion:output_type -> api.VersionResp + 37, // 46: api.Dex.GetDiscovery:output_type -> api.DiscoveryResp + 40, // 47: api.Dex.ListRefresh:output_type -> api.ListRefreshResp + 42, // 48: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp + 44, // 49: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp + 32, // [32:50] is the sub-list for method output_type + 14, // [14:32] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_api_v2_api_proto_init() } @@ -2779,13 +3012,14 @@ func file_api_v2_api_proto_init() { if File_api_v2_api_proto != nil { return } + file_api_v2_api_proto_msgTypes[23].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v2_api_proto_rawDesc), len(file_api_v2_api_proto_rawDesc)), NumEnums: 0, - NumMessages: 42, + NumMessages: 45, NumExtensions: 0, NumServices: 1, }, diff --git a/api/v2/api.proto b/api/v2/api.proto index dffe32125c..27a9ea387e 100644 --- a/api/v2/api.proto +++ b/api/v2/api.proto @@ -147,6 +147,34 @@ message Connector { string name = 3; bytes config = 4; repeated string grant_types = 5; + // Per-connector expiry overrides. Each field must be at least as strict + // as the corresponding global value or the write will be rejected. + ConnectorExpiry expiry = 6; +} + +// ConnectorExpiry holds per-connector overrides for token lifetimes. +// Duration strings use the same format as the top-level expiry config +// ("5m", "24h"). Empty strings mean "inherit the global value". +message ConnectorExpiry { + string id_tokens = 1; + ConnectorRefreshExpiry refresh_tokens = 2; +} + +// ConnectorRefreshExpiry holds per-connector refresh-token policy overrides. +message ConnectorRefreshExpiry { + // Absent means "inherit"; present (true/false) overrides the global value. + optional bool disable_rotation = 1; + string reuse_interval = 2; + string absolute_lifetime = 3; + string valid_if_not_used_for = 4; +} + +// ConnectorExpiryUpdate distinguishes "leave the override alone" from +// "change it". An absent ConnectorExpiryUpdate leaves the existing value +// in place; a present one with nil Value clears the override; a present +// one with a non-nil Value installs that override. +message ConnectorExpiryUpdate { + ConnectorExpiry value = 1; } // CreateConnectorReq is a request to make a connector. @@ -176,6 +204,9 @@ message UpdateConnectorReq { // An empty grant_types list means unrestricted (all grant types allowed). // If not set (null), grant types are not modified. GrantTypes new_grant_types = 5; + // If set, updates the connector's expiry overrides. If unset, the + // existing expiry is left untouched. + ConnectorExpiryUpdate new_expiry = 6; } // UpdateConnectorResp returns the response from modifying an existing connector. diff --git a/cmd/dex/config.go b/cmd/dex/config.go index 12d3557cbc..0d4475ed79 100644 --- a/cmd/dex/config.go +++ b/cmd/dex/config.go @@ -559,6 +559,26 @@ type Connector struct { Config server.ConnectorConfig `json:"config"` GrantTypes []string `json:"grantTypes"` + + // Expiry, when set, overrides the corresponding fields of the top-level + // expiry config for tokens issued through this connector. Any field left + // unset falls back to the global value. + Expiry *ConnectorExpiry `json:"expiry"` +} + +type ConnectorExpiry struct { + IDTokens string `json:"idTokens"` + RefreshTokens *ConnectorRefreshExpiry `json:"refreshTokens"` +} + +// ConnectorRefreshExpiry mirrors RefreshToken but uses a pointer for +// DisableRotation so that "unset" can be distinguished from "false", +// allowing the field to inherit the global value when nil. +type ConnectorRefreshExpiry struct { + DisableRotation *bool `json:"disableRotation"` + ReuseInterval string `json:"reuseInterval"` + AbsoluteLifetime string `json:"absoluteLifetime"` + ValidIfNotUsedFor string `json:"validIfNotUsedFor"` } // UnmarshalJSON allows Connector to implement the unmarshaler interface to @@ -569,8 +589,9 @@ func (c *Connector) UnmarshalJSON(b []byte) error { Name string `json:"name"` ID string `json:"id"` - Config json.RawMessage `json:"config"` - GrantTypes []string `json:"grantTypes"` + Config json.RawMessage `json:"config"` + GrantTypes []string `json:"grantTypes"` + Expiry *ConnectorExpiry `json:"expiry"` } if err := configUnmarshaller(b, &conn); err != nil { return fmt.Errorf("parse connector: %v", err) @@ -613,6 +634,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error { ID: conn.ID, Config: connConfig, GrantTypes: conn.GrantTypes, + Expiry: conn.Expiry, } return nil } @@ -624,13 +646,25 @@ func ToStorageConnector(c Connector) (storage.Connector, error) { return storage.Connector{}, fmt.Errorf("failed to marshal connector config: %v", err) } - return storage.Connector{ + sc := storage.Connector{ ID: c.ID, Type: c.Type, Name: c.Name, Config: data, GrantTypes: c.GrantTypes, - }, nil + } + if c.Expiry != nil { + sc.Expiry = &storage.ConnectorExpiry{IDTokens: c.Expiry.IDTokens} + if rt := c.Expiry.RefreshTokens; rt != nil { + sc.Expiry.RefreshTokens = &storage.ConnectorRefreshExpiry{ + DisableRotation: rt.DisableRotation, + ReuseInterval: rt.ReuseInterval, + AbsoluteLifetime: rt.AbsoluteLifetime, + ValidIfNotUsedFor: rt.ValidIfNotUsedFor, + } + } + } + return sc, nil } // Expiry holds configuration for the validity period of components. diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go index 2e134007ab..f53fc055ca 100644 --- a/cmd/dex/serve.go +++ b/cmd/dex/serve.go @@ -418,6 +418,18 @@ func runServe(options serveOptions) error { serverConfig.RefreshTokenPolicy = refreshTokenPolicy + ceilings, err := buildExpiryCeilings(idTokensValidFor, c.Expiry.RefreshTokens) + if err != nil { + return fmt.Errorf("invalid global expiry config: %v", err) + } + serverConfig.ExpiryCeilings = ceilings + serverConfig.GlobalRefreshDefaults = server.RefreshTokenDefaults{ + DisableRotation: c.Expiry.RefreshTokens.DisableRotation, + ValidIfNotUsedFor: c.Expiry.RefreshTokens.ValidIfNotUsedFor, + AbsoluteLifetime: c.Expiry.RefreshTokens.AbsoluteLifetime, + ReuseInterval: c.Expiry.RefreshTokens.ReuseInterval, + } + if featureflags.SessionsEnabled.Enabled() { sessionConfig, err := parseSessionConfig(c.Sessions) if err != nil { @@ -832,6 +844,35 @@ func parseSessionConfig(s *Sessions) (*server.SessionConfig, error) { return sc, nil } +// buildExpiryCeilings parses the global expiry config into the ceilings used +// to validate per-connector overrides. The server uses these for both static +// YAML connectors at startup and dynamic API writes at runtime. +func buildExpiryCeilings(globalIDTokens time.Duration, globalRefresh RefreshToken) (server.ExpiryCeilings, error) { + c := server.ExpiryCeilings{ + IDTokens: globalIDTokens, + RefreshRotationDisabled: globalRefresh.DisableRotation, + } + for _, f := range []struct { + name string + value string + dst *time.Duration + }{ + {"expiry.refreshTokens.absoluteLifetime", globalRefresh.AbsoluteLifetime, &c.RefreshAbsoluteLifetime}, + {"expiry.refreshTokens.validIfNotUsedFor", globalRefresh.ValidIfNotUsedFor, &c.RefreshValidIfNotUsedFor}, + {"expiry.refreshTokens.reuseInterval", globalRefresh.ReuseInterval, &c.RefreshReuseInterval}, + } { + if f.value == "" { + continue + } + d, err := time.ParseDuration(f.value) + if err != nil { + return c, fmt.Errorf("parse %s: %v", f.name, err) + } + *f.dst = d + } + return c, nil +} + func buildMFAProviders(authenticators []MFAAuthenticator, issuerURL string, logger *slog.Logger) map[string]server.MFAProvider { if len(authenticators) == 0 { return nil diff --git a/cmd/dex/serve_test.go b/cmd/dex/serve_test.go index 12d0c0fff4..beea3d6480 100644 --- a/cmd/dex/serve_test.go +++ b/cmd/dex/serve_test.go @@ -3,8 +3,12 @@ package main import ( "log/slog" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/dexidp/dex/server" ) func TestNewLogger(t *testing.T) { @@ -27,3 +31,83 @@ func TestNewLogger(t *testing.T) { require.Equal(t, (*slog.Logger)(nil), logger) }) } + +func TestBuildExpiryCeilings(t *testing.T) { + tests := []struct { + name string + refresh RefreshToken + want server.ExpiryCeilings + wantErrContains string + }{ + { + name: "all fields set", + refresh: RefreshToken{ + AbsoluteLifetime: "100h", + ValidIfNotUsedFor: "24h", + ReuseInterval: "3s", + }, + want: server.ExpiryCeilings{ + IDTokens: 24 * time.Hour, + RefreshAbsoluteLifetime: 100 * time.Hour, + RefreshValidIfNotUsedFor: 24 * time.Hour, + RefreshReuseInterval: 3 * time.Second, + }, + }, + { + name: "refresh unset", + want: server.ExpiryCeilings{IDTokens: 24 * time.Hour}, + }, + { + name: "rotation disabled propagates", + refresh: RefreshToken{DisableRotation: true}, + want: server.ExpiryCeilings{ + IDTokens: 24 * time.Hour, + RefreshRotationDisabled: true, + }, + }, + { + name: "invalid duration", + refresh: RefreshToken{AbsoluteLifetime: "not-a-duration"}, + wantErrContains: "parse expiry.refreshTokens.absoluteLifetime", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := buildExpiryCeilings(24*time.Hour, tc.refresh) + if tc.wantErrContains != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErrContains) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + +func TestToStorageConnectorCarriesExpiry(t *testing.T) { + disable := true + sc, err := ToStorageConnector(Connector{ + ID: "c1", Type: "mockCallback", Name: "c1", + Expiry: &ConnectorExpiry{ + IDTokens: "15m", + RefreshTokens: &ConnectorRefreshExpiry{ + DisableRotation: &disable, + AbsoluteLifetime: "24h", + ValidIfNotUsedFor: "1h", + ReuseInterval: "3s", + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, sc.Expiry) + assert.Equal(t, "15m", sc.Expiry.IDTokens) + require.NotNil(t, sc.Expiry.RefreshTokens) + assert.Equal(t, "24h", sc.Expiry.RefreshTokens.AbsoluteLifetime) + require.NotNil(t, sc.Expiry.RefreshTokens.DisableRotation) + assert.True(t, *sc.Expiry.RefreshTokens.DisableRotation) + + sc, err = ToStorageConnector(Connector{ID: "c1", Type: "mockCallback", Name: "c1"}) + require.NoError(t, err) + assert.Nil(t, sc.Expiry) +} diff --git a/config.yaml.dist b/config.yaml.dist index d2d31c80a9..f286d796a8 100644 --- a/config.yaml.dist +++ b/config.yaml.dist @@ -194,6 +194,26 @@ web: # For LDAP nested group resolution, set groupSearch.userMatchers[].recursionGroupAttr # in the connector config. See: https://dexidp.io/docs/connectors/ldap/ # connectors: [] +# +# Per-connector expiry overrides. Any field left unset inherits the top-level +# `expiry` configuration. Overrides must be at least as strict as the global +# value: a duration that exceeds its global counterpart, or `disableRotation: +# true` while rotation is enabled globally, is rejected at config load. Global +# values left unset impose no ceiling on the corresponding override. +# connectors: +# - type: oidc +# id: partner +# name: Partner SSO +# expiry: +# idTokens: "15m" +# refreshTokens: +# absoluteLifetime: "24h" +# validIfNotUsedFor: "1h" +# config: +# issuer: https://accounts.partner.example +# clientID: ... +# clientSecret: ... +# redirectURI: https://dex.example/callback # Enable the password database. # diff --git a/server/api.go b/server/api.go index c5ee787478..1c41187660 100644 --- a/server/api.go +++ b/server/api.go @@ -473,6 +473,13 @@ func (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq } } + expiry := connectorExpiryFromProto(req.Connector.Expiry) + if d.server != nil { + if err := validateConnectorExpiry(expiry, d.server.expiryCeilings); err != nil { + return nil, fmt.Errorf("invalid expiry: %v", err) + } + } + c := storage.Connector{ ID: req.Connector.Id, Name: req.Connector.Name, @@ -480,6 +487,7 @@ func (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq ResourceVersion: "1", Config: req.Connector.Config, GrantTypes: req.Connector.GrantTypes, + Expiry: expiry, } if err := d.s.CreateConnector(ctx, c); err != nil { if err == storage.ErrAlreadyExists { @@ -489,8 +497,13 @@ func (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq return nil, fmt.Errorf("create connector: %v", err) } - // Make sure we don't reuse stale entries in the cache if d.server != nil { + // Validation already passed above, so reaching the error path here means + // a programmer bug. Log and let the storage write win; the inconsistency + // will self-heal on the next restart. + if err := d.server.upsertConnectorExpiryOverride(req.Connector.Id, expiry); err != nil { + d.logger.Error("api: failed to install connector expiry override", "err", err) + } d.server.CloseConnector(req.Connector.Id) } @@ -509,7 +522,8 @@ func (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq hasUpdate := len(req.NewConfig) != 0 || req.NewName != "" || req.NewType != "" || - req.NewGrantTypes != nil + req.NewGrantTypes != nil || + req.NewExpiry != nil if !hasUpdate { return nil, errors.New("nothing to update") } @@ -526,6 +540,24 @@ func (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq } } + // The proto has three states (absent / present-with-nil / present-with-value); + // *storage.ConnectorExpiry only has two. expiryUpdated bridges the gap: false + // means "leave alone", true with nil newExpiry means "clear", true with a + // non-nil newExpiry means "install this". + var ( + expiryUpdated bool + newExpiry *storage.ConnectorExpiry + ) + if req.NewExpiry != nil { + expiryUpdated = true + newExpiry = connectorExpiryFromProto(req.NewExpiry.Value) + if d.server != nil { + if err := validateConnectorExpiry(newExpiry, d.server.expiryCeilings); err != nil { + return nil, fmt.Errorf("invalid expiry: %v", err) + } + } + } + updater := func(old storage.Connector) (storage.Connector, error) { if req.NewType != "" { old.Type = req.NewType @@ -543,6 +575,10 @@ func (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq old.GrantTypes = req.NewGrantTypes.GrantTypes } + if expiryUpdated { + old.Expiry = newExpiry + } + if rev, err := strconv.Atoi(defaultTo(old.ResourceVersion, "0")); err == nil { old.ResourceVersion = strconv.Itoa(rev + 1) } @@ -558,6 +594,15 @@ func (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq return nil, fmt.Errorf("update connector: %v", err) } + if d.server != nil { + if expiryUpdated { + if err := d.server.upsertConnectorExpiryOverride(req.Id, newExpiry); err != nil { + d.logger.Error("api: failed to refresh connector expiry override", "err", err) + } + } + d.server.CloseConnector(req.Id) + } + return &api.UpdateConnectorResp{}, nil } @@ -579,6 +624,13 @@ func (d dexAPI) DeleteConnector(ctx context.Context, req *api.DeleteConnectorReq return nil, fmt.Errorf("delete connector: %v", err) } + if d.server != nil { + // Drop any cached override so a connector re-created with the same id + // starts fresh. upsertConnectorExpiryOverride(_, nil) cannot error. + _ = d.server.upsertConnectorExpiryOverride(req.Id, nil) + d.server.CloseConnector(req.Id) + } + return &api.DeleteConnectorResp{}, nil } @@ -602,6 +654,17 @@ func (d dexAPI) ListConnectors(ctx context.Context, req *api.ListConnectorReq) ( Config: connector.Config, GrantTypes: connector.GrantTypes, } + if e := connector.Expiry; e != nil { + c.Expiry = &api.ConnectorExpiry{IdTokens: e.IDTokens} + if rt := e.RefreshTokens; rt != nil { + c.Expiry.RefreshTokens = &api.ConnectorRefreshExpiry{ + DisableRotation: rt.DisableRotation, + ReuseInterval: rt.ReuseInterval, + AbsoluteLifetime: rt.AbsoluteLifetime, + ValidIfNotUsedFor: rt.ValidIfNotUsedFor, + } + } + } connectors = append(connectors, &c) } @@ -617,3 +680,19 @@ func defaultTo[T comparable](v, def T) T { } return v } + +func connectorExpiryFromProto(p *api.ConnectorExpiry) *storage.ConnectorExpiry { + if p == nil { + return nil + } + e := &storage.ConnectorExpiry{IDTokens: p.IdTokens} + if p.RefreshTokens != nil { + e.RefreshTokens = &storage.ConnectorRefreshExpiry{ + DisableRotation: p.RefreshTokens.DisableRotation, + ReuseInterval: p.RefreshTokens.ReuseInterval, + AbsoluteLifetime: p.RefreshTokens.AbsoluteLifetime, + ValidIfNotUsedFor: p.RefreshTokens.ValidIfNotUsedFor, + } + } + return e +} diff --git a/server/api_test.go b/server/api_test.go index 09cfa6783f..9d2afc6d84 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -34,13 +34,20 @@ func newLogger(t *testing.T) *slog.Logger { // newAPI constructs a gRCP client connected to a backing server. func newAPI(t *testing.T, s storage.Storage, logger *slog.Logger) *apiClient { + return newAPIWithServer(t, s, logger, nil) +} + +// newAPIWithServer is like newAPI but wires a *Server into the gRPC handlers, +// enabling validation paths that depend on Server state (notably expiry +// override validation). +func newAPIWithServer(t *testing.T, s storage.Storage, logger *slog.Logger, srv *Server) *apiClient { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } serv := grpc.NewServer() - api.RegisterDexServer(serv, NewAPI(s, logger, "test", nil)) + api.RegisterDexServer(serv, NewAPI(s, logger, "test", srv)) go serv.Serve(l) // NewClient will retry automatically if the serv.Serve() goroutine @@ -538,6 +545,73 @@ func TestCreateConnector(t *testing.T) { } } +func TestCreateConnectorExpiryHierarchy(t *testing.T) { + t.Setenv("DEX_API_CONNECTORS_CRUD", "true") + + logger := newLogger(t) + s := memory.New(logger) + + srv := &Server{ + logger: logger, + idTokensValidFor: time.Hour, + expiryCeilings: ExpiryCeilings{IDTokens: time.Hour}, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{}, + connectors: map[string]Connector{}, + } + client := newAPIWithServer(t, s, logger, srv) + defer client.Close() + + ctx := t.Context() + base := &api.Connector{ + Id: "c1", + Name: "c1", + Type: "mockCallback", + Config: []byte(`{}`), + } + + t.Run("override exceeding global is rejected", func(t *testing.T) { + req := &api.CreateConnectorReq{ + Connector: &api.Connector{ + Id: "looser", Name: base.Name, Type: base.Type, Config: base.Config, + Expiry: &api.ConnectorExpiry{IdTokens: "48h"}, + }, + } + if _, err := client.CreateConnector(ctx, req); err == nil { + t.Fatal("expected validation error for override above global") + } else if !strings.Contains(err.Error(), "exceeds the global value") { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("override within ceiling is accepted and installed", func(t *testing.T) { + req := &api.CreateConnectorReq{ + Connector: &api.Connector{ + Id: base.Id, Name: base.Name, Type: base.Type, Config: base.Config, + Expiry: &api.ConnectorExpiry{IdTokens: "10m"}, + }, + } + if _, err := client.CreateConnector(ctx, req); err != nil { + t.Fatalf("create connector: %v", err) + } + if got := srv.idTokensValidForConn(base.Id); got != 10*time.Minute { + t.Fatalf("override not installed: got %s, want 10m", got) + } + }) + + t.Run("update can clear the override", func(t *testing.T) { + req := &api.UpdateConnectorReq{ + Id: base.Id, + NewExpiry: &api.ConnectorExpiryUpdate{}, // present, Value nil = clear + } + if _, err := client.UpdateConnector(ctx, req); err != nil { + t.Fatalf("update connector: %v", err) + } + if got := srv.idTokensValidForConn(base.Id); got != time.Hour { + t.Fatalf("override not cleared: got %s, want 1h", got) + } + }) +} + func TestUpdateConnector(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") diff --git a/server/introspectionhandler.go b/server/introspectionhandler.go index dd7cce8387..341ba2e7fe 100644 --- a/server/introspectionhandler.go +++ b/server/introspectionhandler.go @@ -226,7 +226,7 @@ func (s *Server) introspectRefreshToken(ctx context.Context, token string) (*Int ClientID: rCtx.storageToken.ClientID, IssuedAt: rCtx.storageToken.CreatedAt.Unix(), NotBefore: rCtx.storageToken.CreatedAt.Unix(), - Expiry: rCtx.storageToken.CreatedAt.Add(s.refreshTokenPolicy.absoluteLifetime).Unix(), + Expiry: rCtx.storageToken.CreatedAt.Add(s.refreshTokenPolicyForConn(rCtx.storageToken.ConnectorID).absoluteLifetime).Unix(), Subject: subjectString, Username: rCtx.storageToken.Claims.PreferredUsername, Audience: getAudience(rCtx.storageToken.ClientID, rCtx.scopes), diff --git a/server/oauth2.go b/server/oauth2.go index 6e91facfb6..544602fa1c 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -343,9 +343,26 @@ func genSubject(userID string, connID string) (string, error) { return internal.Marshal(sub) } +func (s *Server) idTokensValidForConn(connID string) time.Duration { + s.mu.Lock() + o := s.connectorExpiryOverrides[connID] + s.mu.Unlock() + return value(o.IDTokensValidFor, s.idTokensValidFor) +} + +func (s *Server) refreshTokenPolicyForConn(connID string) *RefreshTokenPolicy { + s.mu.Lock() + o := s.connectorExpiryOverrides[connID] + s.mu.Unlock() + if o.RefreshTokenPolicy != nil { + return o.RefreshTokenPolicy + } + return s.refreshTokenPolicy +} + func (s *Server) newIDToken(ctx context.Context, clientID string, claims storage.Claims, scopes []string, nonce, accessToken, code, connID string, authTime time.Time) (idToken string, expiry time.Time, err error) { issuedAt := s.now() - expiry = issuedAt.Add(s.idTokensValidFor) + expiry = issuedAt.Add(s.idTokensValidForConn(connID)) subjectString, err := genSubject(claims.UserID, connID) if err != nil { diff --git a/server/oauth2_test.go b/server/oauth2_test.go index 403b497b92..0050493509 100644 --- a/server/oauth2_test.go +++ b/server/oauth2_test.go @@ -1269,3 +1269,107 @@ func TestParseAuthorizationRequest_IDTokenHint(t *testing.T) { assert.Equal(t, "", hintSubject) }) } + +func TestIDTokensValidForConn(t *testing.T) { + override := &RefreshTokenPolicy{} + s := &Server{ + idTokensValidFor: time.Hour, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{ + "shortlived": {IDTokensValidFor: 5 * time.Minute}, + "refreshonly": {RefreshTokenPolicy: override}, + }, + } + + assert.Equal(t, 5*time.Minute, s.idTokensValidForConn("shortlived"), + "per-connector override should win") + assert.Equal(t, time.Hour, s.idTokensValidForConn("refreshonly"), + "zero IDTokensValidFor should fall back to global") + assert.Equal(t, time.Hour, s.idTokensValidForConn("unknown"), + "missing entry should fall back to global") +} + +func TestRefreshTokenPolicyForConn(t *testing.T) { + global := &RefreshTokenPolicy{rotateRefreshTokens: true} + perConnector := &RefreshTokenPolicy{rotateRefreshTokens: false} + + s := &Server{ + refreshTokenPolicy: global, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{ + "custom": {RefreshTokenPolicy: perConnector}, + "idonly": {IDTokensValidFor: time.Minute}, + "nilpolicy": {}, + }, + } + + assert.Same(t, perConnector, s.refreshTokenPolicyForConn("custom"), + "per-connector override should win") + assert.Same(t, global, s.refreshTokenPolicyForConn("idonly"), + "nil per-connector policy should fall back to global") + assert.Same(t, global, s.refreshTokenPolicyForConn("nilpolicy"), + "empty override should fall back to global") + assert.Same(t, global, s.refreshTokenPolicyForConn("unknown"), + "missing entry should fall back to global") +} + +func TestNewIDTokenUsesConnectorOverride(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := slog.New(slog.DiscardHandler) + store := memory.New(logger) + + now := time.Now().UTC() + err := store.UpdateKeys(ctx, func(keys storage.Keys) (storage.Keys, error) { + keys.SigningKey = &jose.JSONWebKey{ + Key: testKey, + KeyID: "test-rs256", + Algorithm: string(jose.RS256), + Use: "sig", + } + keys.SigningKeyPub = &jose.JSONWebKey{ + Key: testKey.Public(), + KeyID: "test-rs256", + Algorithm: string(jose.RS256), + Use: "sig", + } + keys.NextRotation = now.Add(time.Hour) + return keys, nil + }) + require.NoError(t, err) + + localConfig := signer.LocalConfig{ + KeysRotationPeriod: time.Hour.String(), + Algorithm: jose.RS256, + } + sig, err := localConfig.Open(ctx, store, time.Hour, func() time.Time { return now }, logger) + require.NoError(t, err) + sig.Start(ctx) + + issuerURL, err := url.Parse("https://issuer.example.com") + require.NoError(t, err) + + s := &Server{ + signer: sig, + issuerURL: *issuerURL, + logger: logger, + now: func() time.Time { return now }, + idTokensValidFor: time.Hour, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{ + "short": {IDTokensValidFor: 5 * time.Minute}, + }, + } + + _, expiryShort, err := s.newIDToken(ctx, "client", + storage.Claims{UserID: "u1", Username: "alice"}, + []string{"openid"}, "n", "", "", "short", time.Time{}) + require.NoError(t, err) + assert.Equal(t, now.Add(5*time.Minute), expiryShort.UTC(), + "per-connector override must apply") + + _, expiryGlobal, err := s.newIDToken(ctx, "client", + storage.Claims{UserID: "u1", Username: "alice"}, + []string{"openid"}, "n", "", "", "unknown", time.Time{}) + require.NoError(t, err) + assert.Equal(t, now.Add(time.Hour), expiryGlobal.UTC(), + "unknown connector must fall back to global") +} diff --git a/server/refreshhandlers.go b/server/refreshhandlers.go index 7d3c7d4cd5..910846abd4 100644 --- a/server/refreshhandlers.go +++ b/server/refreshhandlers.go @@ -176,9 +176,11 @@ func (s *Server) getRefreshTokenFromStorage(ctx context.Context, clientID *strin return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest} } + policy := s.refreshTokenPolicyForConn(refresh.ConnectorID) + if refresh.Token != token.Token { switch { - case !s.refreshTokenPolicy.AllowedToReuse(refresh.LastUsed): + case !policy.AllowedToReuse(refresh.LastUsed): fallthrough case refresh.ObsoleteToken != token.Token: fallthrough @@ -188,12 +190,12 @@ func (s *Server) getRefreshTokenFromStorage(ctx context.Context, clientID *strin } } - if s.refreshTokenPolicy.CompletelyExpired(refresh.CreatedAt) { + if policy.CompletelyExpired(refresh.CreatedAt) { s.logger.ErrorContext(ctx, "refresh token expired", "token_id", refresh.ID) return nil, expiredErr } - if s.refreshTokenPolicy.ExpiredBecauseUnused(refresh.LastUsed) { + if policy.ExpiredBecauseUnused(refresh.LastUsed) { s.logger.ErrorContext(ctx, "refresh token expired due to inactivity", "token_id", refresh.ID) return nil, expiredErr } @@ -337,9 +339,11 @@ func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext, u // stored in UserIdentity at the time of the last interactive login. This aligns with the // behavior of other identity brokers (e.g., Keycloak, Auth0) that treat downstream sessions // independently from the upstream provider session lifetime. + policy := s.refreshTokenPolicyForConn(rCtx.storageToken.ConnectorID) + refreshTokenUpdater := func(old storage.RefreshToken) (storage.RefreshToken, error) { - rotationEnabled := s.refreshTokenPolicy.RotationEnabled() - reusingAllowed := s.refreshTokenPolicy.AllowedToReuse(old.LastUsed) + rotationEnabled := policy.RotationEnabled() + reusingAllowed := policy.AllowedToReuse(old.LastUsed) switch { case !rotationEnabled && reusingAllowed: diff --git a/server/server.go b/server/server.go index a3ebd63857..fd10621ff7 100644 --- a/server/server.go +++ b/server/server.go @@ -113,6 +113,14 @@ type Config struct { // Refresh token expiration settings RefreshTokenPolicy *RefreshTokenPolicy + // ExpiryCeilings define the upper bounds against which per-connector + // expiry overrides are validated. A zero duration means "no ceiling". + ExpiryCeilings ExpiryCeilings + + // GlobalRefreshDefaults provide the inheritance roots for per-connector + // refresh-token overrides that leave fields unset. + GlobalRefreshDefaults RefreshTokenDefaults + // If set, the server will use this connector to handle password grants PasswordConnector string @@ -207,11 +215,147 @@ func value(val, defaultValue time.Duration) time.Duration { return val } +// ConnectorExpiryOverride carries per-connector token lifetime overrides. +// A zero or nil field inherits the global value. +type ConnectorExpiryOverride struct { + IDTokensValidFor time.Duration + RefreshTokenPolicy *RefreshTokenPolicy +} + +// ExpiryCeilings holds the parsed global expiry values that per-connector +// overrides must not loosen. A zero duration field means "no ceiling". +// +// RefreshRotationDisabled blocks the asymmetric case where the global enables +// rotation: a per-connector override cannot disable it, since rotation-enabled +// is the stricter policy. The reverse direction is permitted. +type ExpiryCeilings struct { + IDTokens time.Duration + RefreshAbsoluteLifetime time.Duration + RefreshValidIfNotUsedFor time.Duration + RefreshReuseInterval time.Duration + RefreshRotationDisabled bool +} + +// RefreshTokenDefaults are the inheritance roots for per-connector overrides +// that leave fields unset. +type RefreshTokenDefaults struct { + DisableRotation bool + ValidIfNotUsedFor string + AbsoluteLifetime string + ReuseInterval string +} + +// discardLogger is used when a constructor logs at Info level for global +// startup config but the call is part of a per-connector hot path. +var discardLogger = slog.New(slog.DiscardHandler) + +// validateConnectorExpiry rejects per-connector overrides that loosen the +// global policy. Called from the static YAML load path and from every gRPC +// API write. +func validateConnectorExpiry(e *storage.ConnectorExpiry, c ExpiryCeilings) error { + if e == nil { + return nil + } + // idTokens=0 means "inherit"; idTokensValidForConn falls back to the global. + if err := checkCeiling("expiry.idTokens", e.IDTokens, c.IDTokens, false); err != nil { + return err + } + if e.RefreshTokens == nil { + return nil + } + for _, f := range []struct { + name string + value string + ceiling time.Duration + zeroDisables bool // RefreshTokenPolicy treats 0 as "expiration disabled" for this field + }{ + {"expiry.refreshTokens.absoluteLifetime", e.RefreshTokens.AbsoluteLifetime, c.RefreshAbsoluteLifetime, true}, + {"expiry.refreshTokens.validIfNotUsedFor", e.RefreshTokens.ValidIfNotUsedFor, c.RefreshValidIfNotUsedFor, true}, + {"expiry.refreshTokens.reuseInterval", e.RefreshTokens.ReuseInterval, c.RefreshReuseInterval, false}, + } { + if err := checkCeiling(f.name, f.value, f.ceiling, f.zeroDisables); err != nil { + return err + } + } + if dr := e.RefreshTokens.DisableRotation; dr != nil && *dr && !c.RefreshRotationDisabled { + return fmt.Errorf("expiry.refreshTokens.disableRotation cannot disable rotation when it is enabled globally") + } + return nil +} + +// checkCeiling enforces that a per-connector duration is at least as strict as +// the global ceiling. When zeroDisables is true, an override of 0 is rejected +// in the presence of a positive ceiling because RefreshTokenPolicy treats 0 as +// "no expiration" for that field — strictly looser than any positive global. +func checkCeiling(field, value string, ceiling time.Duration, zeroDisables bool) error { + if value == "" { + return nil + } + d, err := time.ParseDuration(value) + if err != nil { + return fmt.Errorf("parse %s: %v", field, err) + } + if ceiling <= 0 { + return nil + } + if d > ceiling { + return fmt.Errorf("%s (%s) exceeds the global value (%s)", field, d, ceiling) + } + if zeroDisables && d == 0 { + return fmt.Errorf("%s cannot be 0 (disables expiration) when the global value (%s) is set", field, ceiling) + } + return nil +} + +// buildConnectorExpiryOverride parses a (pre-validated) storage.ConnectorExpiry +// into a ConnectorExpiryOverride. Unset string fields inherit from the global +// refresh defaults so the resulting RefreshTokenPolicy carries the correct +// effective values. +func buildConnectorExpiryOverride(e *storage.ConnectorExpiry, defaults RefreshTokenDefaults) (ConnectorExpiryOverride, error) { + var override ConnectorExpiryOverride + if e == nil { + return override, nil + } + + if e.IDTokens != "" { + d, err := time.ParseDuration(e.IDTokens) + if err != nil { + return override, fmt.Errorf("parse expiry.idTokens: %v", err) + } + override.IDTokensValidFor = d + } + + rt := e.RefreshTokens + if rt == nil { + return override, nil + } + + disableRotation := defaults.DisableRotation + if rt.DisableRotation != nil { + disableRotation = *rt.DisableRotation + } + // NewRefreshTokenPolicy emits one Info line per field; useful for the single + // global policy but would spam logs at N connectors × 4 fields on every API + // write. Pass a discard logger and let the caller summarize. + policy, err := NewRefreshTokenPolicy( + discardLogger, + disableRotation, + defaultTo(rt.ValidIfNotUsedFor, defaults.ValidIfNotUsedFor), + defaultTo(rt.AbsoluteLifetime, defaults.AbsoluteLifetime), + defaultTo(rt.ReuseInterval, defaults.ReuseInterval), + ) + if err != nil { + return override, fmt.Errorf("refresh token policy: %v", err) + } + override.RefreshTokenPolicy = policy + return override, nil +} + // Server is the top level object. type Server struct { issuerURL url.URL - // mutex for the connectors map. + // mu guards connectors and connectorExpiryOverrides. mu sync.Mutex // Map of connector IDs to connectors. connectors map[string]Connector @@ -245,6 +389,14 @@ type Server struct { refreshTokenPolicy *RefreshTokenPolicy + // expiryCeilings and globalRefreshDefaults are immutable after construction. + expiryCeilings ExpiryCeilings + globalRefreshDefaults RefreshTokenDefaults + + // connectorExpiryOverrides is mutated by API CreateConnector / + // UpdateConnector / DeleteConnector handlers. Protected by mu. + connectorExpiryOverrides map[string]ConnectorExpiryOverride + logger *slog.Logger signer signer.Signer @@ -359,26 +511,29 @@ func newServer(ctx context.Context, c Config) (*Server, error) { } s := &Server{ - issuerURL: *issuerURL, - connectors: make(map[string]Connector), - storage: newKeyCacher(c.Storage, now), - supportedResponseTypes: supportedRes, - supportedGrantTypes: supportedGrants, - pkce: c.PKCE, - idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), - authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour), - deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute), - refreshTokenPolicy: c.RefreshTokenPolicy, - skipApproval: c.SkipApprovalScreen, - alwaysShowLogin: c.AlwaysShowLoginScreen, - now: now, - templates: tmpls, - passwordConnector: c.PasswordConnector, - logger: c.Logger, - signer: c.Signer, - sessionConfig: c.SessionConfig, - mfaProviders: c.MFAProviders, - defaultMFAChain: c.DefaultMFAChain, + issuerURL: *issuerURL, + connectors: make(map[string]Connector), + storage: newKeyCacher(c.Storage, now), + supportedResponseTypes: supportedRes, + supportedGrantTypes: supportedGrants, + pkce: c.PKCE, + idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), + authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour), + deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute), + refreshTokenPolicy: c.RefreshTokenPolicy, + expiryCeilings: c.ExpiryCeilings, + globalRefreshDefaults: c.GlobalRefreshDefaults, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{}, + skipApproval: c.SkipApprovalScreen, + alwaysShowLogin: c.AlwaysShowLoginScreen, + now: now, + templates: tmpls, + passwordConnector: c.PasswordConnector, + logger: c.Logger, + signer: c.Signer, + sessionConfig: c.SessionConfig, + mfaProviders: c.MFAProviders, + defaultMFAChain: c.DefaultMFAChain, } // Retrieves connector objects in backend storage. This list includes the static connectors @@ -394,6 +549,17 @@ func newServer(ctx context.Context, c Config) (*Server, error) { var failedCount int for _, conn := range storageConnectors { + if err := s.upsertConnectorExpiryOverride(conn.ID, conn.Expiry); err != nil { + failedCount++ + if c.ContinueOnConnectorFailure { + s.logger.Error("server: invalid connector expiry", "id", conn.ID, "err", err) + continue + } + return nil, fmt.Errorf("server: invalid connector expiry for %s: %v", conn.ID, err) + } + if conn.Expiry != nil { + s.logger.Info("server: connector expiry override installed", "id", conn.ID) + } if _, err := s.OpenConnector(conn); err != nil { failedCount++ if c.ContinueOnConnectorFailure { @@ -828,6 +994,28 @@ func (s *Server) CloseConnector(id string) { s.mu.Unlock() } +// upsertConnectorExpiryOverride validates the given storage.ConnectorExpiry +// and, on success, updates the in-memory override map. Every code path that +// can change a connector's expiry must go through this method so the live +// token-issuance path reflects the change. +func (s *Server) upsertConnectorExpiryOverride(id string, e *storage.ConnectorExpiry) error { + if err := validateConnectorExpiry(e, s.expiryCeilings); err != nil { + return err + } + override, err := buildConnectorExpiryOverride(e, s.globalRefreshDefaults) + if err != nil { + return err + } + s.mu.Lock() + defer s.mu.Unlock() + if e == nil { + delete(s.connectorExpiryOverrides, id) + return nil + } + s.connectorExpiryOverrides[id] = override + return nil +} + // getConnector retrieves the connector object with the given id from the storage // and updates the connector list for server if necessary. func (s *Server) getConnector(ctx context.Context, id string) (Connector, error) { diff --git a/server/server_test.go b/server/server_test.go index db8f12ce25..5d2f095862 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "net/http/httputil" @@ -2055,3 +2056,147 @@ func TestConnectorFailureHandling(t *testing.T) { }) } } + +func TestValidateConnectorExpiry(t *testing.T) { + disableRotation := true + enableRotation := false + tests := []struct { + name string + expiry *storage.ConnectorExpiry + ceilings ExpiryCeilings + wantErrContains string + }{ + {name: "nil expiry"}, + { + name: "idTokens within ceiling", + expiry: &storage.ConnectorExpiry{IDTokens: "10m"}, + ceilings: ExpiryCeilings{IDTokens: time.Hour}, + }, + { + name: "idTokens exceeds ceiling", + expiry: &storage.ConnectorExpiry{IDTokens: "48h"}, + ceilings: ExpiryCeilings{IDTokens: 24 * time.Hour}, + wantErrContains: "expiry.idTokens (48h0m0s) exceeds the global value", + }, + { + name: "global unset means no ceiling", + expiry: &storage.ConnectorExpiry{IDTokens: "48h"}, + }, + { + name: "invalid duration rejected even without ceiling", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{AbsoluteLifetime: "not-a-duration"}}, + wantErrContains: "parse expiry.refreshTokens.absoluteLifetime", + }, + { + name: "refresh absoluteLifetime exceeds ceiling", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{AbsoluteLifetime: "100h"}}, + ceilings: ExpiryCeilings{RefreshAbsoluteLifetime: 24 * time.Hour}, + wantErrContains: "expiry.refreshTokens.absoluteLifetime (100h0m0s) exceeds the global value", + }, + { + name: "refresh absoluteLifetime of zero disables and is rejected", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{AbsoluteLifetime: "0s"}}, + ceilings: ExpiryCeilings{RefreshAbsoluteLifetime: 24 * time.Hour}, + wantErrContains: "expiry.refreshTokens.absoluteLifetime cannot be 0", + }, + { + name: "refresh validIfNotUsedFor of zero disables and is rejected", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{ValidIfNotUsedFor: "0s"}}, + ceilings: ExpiryCeilings{RefreshValidIfNotUsedFor: time.Hour}, + wantErrContains: "expiry.refreshTokens.validIfNotUsedFor cannot be 0", + }, + { + name: "refresh reuseInterval of zero is stricter, accepted", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{ReuseInterval: "0s"}}, + ceilings: ExpiryCeilings{RefreshReuseInterval: 3 * time.Second}, + }, + { + name: "disableRotation cannot loosen global", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{DisableRotation: &disableRotation}}, + wantErrContains: "disableRotation cannot disable rotation when it is enabled globally", + }, + { + name: "enabling rotation when globally disabled is a tightening", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{DisableRotation: &enableRotation}}, + ceilings: ExpiryCeilings{RefreshRotationDisabled: true}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := validateConnectorExpiry(tc.expiry, tc.ceilings) + if tc.wantErrContains == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErrContains) + }) + } +} + +func TestBuildConnectorExpiryOverride(t *testing.T) { + disableRotation := true + tests := []struct { + name string + expiry *storage.ConnectorExpiry + defaults RefreshTokenDefaults + wantIDTokens time.Duration + wantPolicy bool + wantRotationOn bool + }{ + {name: "nil expiry yields zero override"}, + { + name: "idTokens only", + expiry: &storage.ConnectorExpiry{IDTokens: "5m"}, + wantIDTokens: 5 * time.Minute, + }, + { + name: "refresh override inherits unset fields from defaults", + expiry: &storage.ConnectorExpiry{RefreshTokens: &storage.ConnectorRefreshExpiry{ + DisableRotation: &disableRotation, + AbsoluteLifetime: "1h", + }}, + defaults: RefreshTokenDefaults{ + ValidIfNotUsedFor: "30m", AbsoluteLifetime: "100h", ReuseInterval: "3s", + }, + wantPolicy: true, + wantRotationOn: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := buildConnectorExpiryOverride(tc.expiry, tc.defaults) + require.NoError(t, err) + require.Equal(t, tc.wantIDTokens, got.IDTokensValidFor) + if !tc.wantPolicy { + require.Nil(t, got.RefreshTokenPolicy) + return + } + require.NotNil(t, got.RefreshTokenPolicy) + require.Equal(t, tc.wantRotationOn, got.RefreshTokenPolicy.RotationEnabled()) + }) + } +} + +func TestUpsertConnectorExpiryOverride(t *testing.T) { + s := &Server{ + logger: slog.New(slog.DiscardHandler), + idTokensValidFor: time.Hour, + expiryCeilings: ExpiryCeilings{IDTokens: time.Hour}, + connectorExpiryOverrides: map[string]ConnectorExpiryOverride{}, + } + + // Accept a tighter override. + require.NoError(t, s.upsertConnectorExpiryOverride("c1", &storage.ConnectorExpiry{IDTokens: "5m"})) + require.Equal(t, 5*time.Minute, s.idTokensValidForConn("c1")) + + // Reject a looser override; map is left untouched. + err := s.upsertConnectorExpiryOverride("c2", &storage.ConnectorExpiry{IDTokens: "48h"}) + require.Error(t, err) + require.Contains(t, err.Error(), "exceeds the global value") + require.Equal(t, time.Hour, s.idTokensValidForConn("c2"), "rejected override must not be installed") + + // Clearing the override via nil reverts to the global. + require.NoError(t, s.upsertConnectorExpiryOverride("c1", nil)) + require.Equal(t, time.Hour, s.idTokensValidForConn("c1")) +} diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index 33ec950cfd..855a7088be 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -735,11 +735,19 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) { id2 := storage.NewID() config2 := []byte(`{"redirectURI": "http://127.0.0.1:5556/dex/callback"}`) + disableRotation := true c2 := storage.Connector{ ID: id2, Type: "Mock", Name: "Mock", Config: config2, + Expiry: &storage.ConnectorExpiry{ + IDTokens: "5m", + RefreshTokens: &storage.ConnectorRefreshExpiry{ + DisableRotation: &disableRotation, + AbsoluteLifetime: "24h", + }, + }, } if err := s.CreateConnector(ctx, c2); err != nil { @@ -764,6 +772,7 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) { if err := s.UpdateConnector(ctx, c1.ID, func(old storage.Connector) (storage.Connector, error) { old.Type = "oidc" old.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:token-exchange"} + old.Expiry = &storage.ConnectorExpiry{IDTokens: "10m"} return old, nil }); err != nil { t.Fatalf("failed to update Connector: %v", err) @@ -771,6 +780,17 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) { c1.Type = "oidc" c1.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:token-exchange"} + c1.Expiry = &storage.ConnectorExpiry{IDTokens: "10m"} + getAndCompare(id1, c1) + + // Roundtrip clearing the expiry override. + if err := s.UpdateConnector(ctx, c1.ID, func(old storage.Connector) (storage.Connector, error) { + old.Expiry = nil + return old, nil + }); err != nil { + t.Fatalf("failed to clear connector expiry: %v", err) + } + c1.Expiry = nil getAndCompare(id1, c1) connectorList := []storage.Connector{c1, c2} diff --git a/storage/ent/client/connector.go b/storage/ent/client/connector.go index 21e7aec23c..8b6078f629 100644 --- a/storage/ent/client/connector.go +++ b/storage/ent/client/connector.go @@ -8,15 +8,17 @@ import ( // CreateConnector saves a connector into the database. func (d *Database) CreateConnector(ctx context.Context, connector storage.Connector) error { - _, err := d.client.Connector.Create(). + create := d.client.Connector.Create(). SetID(connector.ID). SetName(connector.Name). SetType(connector.Type). SetResourceVersion(connector.ResourceVersion). SetConfig(connector.Config). - SetGrantTypes(connector.GrantTypes). - Save(ctx) - if err != nil { + SetGrantTypes(connector.GrantTypes) + if connector.Expiry != nil { + create = create.SetExpiry(connector.Expiry) + } + if _, err := create.Save(ctx); err != nil { return convertDBError("create connector: %w", err) } return nil @@ -71,14 +73,18 @@ func (d *Database) UpdateConnector(ctx context.Context, id string, updater func( return rollback(tx, "update connector updating: %w", err) } - _, err = tx.Connector.UpdateOneID(newConnector.ID). + update := tx.Connector.UpdateOneID(newConnector.ID). SetName(newConnector.Name). SetType(newConnector.Type). SetResourceVersion(newConnector.ResourceVersion). SetConfig(newConnector.Config). - SetGrantTypes(newConnector.GrantTypes). - Save(ctx) - if err != nil { + SetGrantTypes(newConnector.GrantTypes) + if newConnector.Expiry == nil { + update = update.ClearExpiry() + } else { + update = update.SetExpiry(newConnector.Expiry) + } + if _, err = update.Save(ctx); err != nil { return rollback(tx, "update connector uploading: %w", err) } diff --git a/storage/ent/client/types.go b/storage/ent/client/types.go index 2e4c21789f..809801bf87 100644 --- a/storage/ent/client/types.go +++ b/storage/ent/client/types.go @@ -109,6 +109,7 @@ func toStorageConnector(c *db.Connector) storage.Connector { Name: c.Name, Config: c.Config, GrantTypes: c.GrantTypes, + Expiry: c.Expiry, } } diff --git a/storage/ent/db/connector.go b/storage/ent/db/connector.go index 812b45f704..b1419c3aac 100644 --- a/storage/ent/db/connector.go +++ b/storage/ent/db/connector.go @@ -9,6 +9,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" + "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/connector" ) @@ -26,7 +27,9 @@ type Connector struct { // Config holds the value of the "config" field. Config []byte `json:"config,omitempty"` // GrantTypes holds the value of the "grant_types" field. - GrantTypes []string `json:"grant_types,omitempty"` + GrantTypes []string `json:"grant_types,omitempty"` + // Expiry holds the value of the "expiry" field. + Expiry *storage.ConnectorExpiry `json:"expiry,omitempty"` selectValues sql.SelectValues } @@ -35,7 +38,7 @@ func (*Connector) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case connector.FieldConfig, connector.FieldGrantTypes: + case connector.FieldConfig, connector.FieldGrantTypes, connector.FieldExpiry: values[i] = new([]byte) case connector.FieldID, connector.FieldType, connector.FieldName, connector.FieldResourceVersion: values[i] = new(sql.NullString) @@ -92,6 +95,14 @@ func (_m *Connector) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field grant_types: %w", err) } } + case connector.FieldExpiry: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field expiry", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.Expiry); err != nil { + return fmt.Errorf("unmarshal field expiry: %w", err) + } + } default: _m.selectValues.Set(columns[i], values[i]) } @@ -142,6 +153,9 @@ func (_m *Connector) String() string { builder.WriteString(", ") builder.WriteString("grant_types=") builder.WriteString(fmt.Sprintf("%v", _m.GrantTypes)) + builder.WriteString(", ") + builder.WriteString("expiry=") + builder.WriteString(fmt.Sprintf("%v", _m.Expiry)) builder.WriteByte(')') return builder.String() } diff --git a/storage/ent/db/connector/connector.go b/storage/ent/db/connector/connector.go index ca603f4dc3..ddcbc2ee13 100644 --- a/storage/ent/db/connector/connector.go +++ b/storage/ent/db/connector/connector.go @@ -21,6 +21,8 @@ const ( FieldConfig = "config" // FieldGrantTypes holds the string denoting the grant_types field in the database. FieldGrantTypes = "grant_types" + // FieldExpiry holds the string denoting the expiry field in the database. + FieldExpiry = "expiry" // Table holds the table name of the connector in the database. Table = "connectors" ) @@ -33,6 +35,7 @@ var Columns = []string{ FieldResourceVersion, FieldConfig, FieldGrantTypes, + FieldExpiry, } // ValidColumn reports if the column name is valid (part of the table columns). diff --git a/storage/ent/db/connector/where.go b/storage/ent/db/connector/where.go index a09bec6087..96b359518b 100644 --- a/storage/ent/db/connector/where.go +++ b/storage/ent/db/connector/where.go @@ -327,6 +327,16 @@ func GrantTypesNotNil() predicate.Connector { return predicate.Connector(sql.FieldNotNull(FieldGrantTypes)) } +// ExpiryIsNil applies the IsNil predicate on the "expiry" field. +func ExpiryIsNil() predicate.Connector { + return predicate.Connector(sql.FieldIsNull(FieldExpiry)) +} + +// ExpiryNotNil applies the NotNil predicate on the "expiry" field. +func ExpiryNotNil() predicate.Connector { + return predicate.Connector(sql.FieldNotNull(FieldExpiry)) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Connector) predicate.Connector { return predicate.Connector(sql.AndPredicates(predicates...)) diff --git a/storage/ent/db/connector_create.go b/storage/ent/db/connector_create.go index 4e9cc35b47..131a8359bc 100644 --- a/storage/ent/db/connector_create.go +++ b/storage/ent/db/connector_create.go @@ -9,6 +9,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/connector" ) @@ -49,6 +50,12 @@ func (_c *ConnectorCreate) SetGrantTypes(v []string) *ConnectorCreate { return _c } +// SetExpiry sets the "expiry" field. +func (_c *ConnectorCreate) SetExpiry(v *storage.ConnectorExpiry) *ConnectorCreate { + _c.mutation.SetExpiry(v) + return _c +} + // SetID sets the "id" field. func (_c *ConnectorCreate) SetID(v string) *ConnectorCreate { _c.mutation.SetID(v) @@ -171,6 +178,10 @@ func (_c *ConnectorCreate) createSpec() (*Connector, *sqlgraph.CreateSpec) { _spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value) _node.GrantTypes = value } + if value, ok := _c.mutation.Expiry(); ok { + _spec.SetField(connector.FieldExpiry, field.TypeJSON, value) + _node.Expiry = value + } return _node, _spec } diff --git a/storage/ent/db/connector_update.go b/storage/ent/db/connector_update.go index d9b58d04f7..24ce68abd3 100644 --- a/storage/ent/db/connector_update.go +++ b/storage/ent/db/connector_update.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" + "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/predicate" ) @@ -94,6 +95,18 @@ func (_u *ConnectorUpdate) ClearGrantTypes() *ConnectorUpdate { return _u } +// SetExpiry sets the "expiry" field. +func (_u *ConnectorUpdate) SetExpiry(v *storage.ConnectorExpiry) *ConnectorUpdate { + _u.mutation.SetExpiry(v) + return _u +} + +// ClearExpiry clears the value of the "expiry" field. +func (_u *ConnectorUpdate) ClearExpiry() *ConnectorUpdate { + _u.mutation.ClearExpiry() + return _u +} + // Mutation returns the ConnectorMutation object of the builder. func (_u *ConnectorUpdate) Mutation() *ConnectorMutation { return _u.mutation @@ -176,6 +189,12 @@ func (_u *ConnectorUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.GrantTypesCleared() { _spec.ClearField(connector.FieldGrantTypes, field.TypeJSON) } + if value, ok := _u.mutation.Expiry(); ok { + _spec.SetField(connector.FieldExpiry, field.TypeJSON, value) + } + if _u.mutation.ExpiryCleared() { + _spec.ClearField(connector.FieldExpiry, field.TypeJSON) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{connector.Label} @@ -262,6 +281,18 @@ func (_u *ConnectorUpdateOne) ClearGrantTypes() *ConnectorUpdateOne { return _u } +// SetExpiry sets the "expiry" field. +func (_u *ConnectorUpdateOne) SetExpiry(v *storage.ConnectorExpiry) *ConnectorUpdateOne { + _u.mutation.SetExpiry(v) + return _u +} + +// ClearExpiry clears the value of the "expiry" field. +func (_u *ConnectorUpdateOne) ClearExpiry() *ConnectorUpdateOne { + _u.mutation.ClearExpiry() + return _u +} + // Mutation returns the ConnectorMutation object of the builder. func (_u *ConnectorUpdateOne) Mutation() *ConnectorMutation { return _u.mutation @@ -374,6 +405,12 @@ func (_u *ConnectorUpdateOne) sqlSave(ctx context.Context) (_node *Connector, er if _u.mutation.GrantTypesCleared() { _spec.ClearField(connector.FieldGrantTypes, field.TypeJSON) } + if value, ok := _u.mutation.Expiry(); ok { + _spec.SetField(connector.FieldExpiry, field.TypeJSON, value) + } + if _u.mutation.ExpiryCleared() { + _spec.ClearField(connector.FieldExpiry, field.TypeJSON) + } _node = &Connector{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/storage/ent/db/migrate/schema.go b/storage/ent/db/migrate/schema.go index 9ebf5faa0a..3b0a9031dd 100644 --- a/storage/ent/db/migrate/schema.go +++ b/storage/ent/db/migrate/schema.go @@ -97,6 +97,7 @@ var ( {Name: "resource_version", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "config", Type: field.TypeBytes}, {Name: "grant_types", Type: field.TypeJSON, Nullable: true}, + {Name: "expiry", Type: field.TypeJSON, Nullable: true}, } // ConnectorsTable holds the schema information for the "connectors" table. ConnectorsTable = &schema.Table{ diff --git a/storage/ent/db/mutation.go b/storage/ent/db/mutation.go index 47f98310f5..8531c003d2 100644 --- a/storage/ent/db/mutation.go +++ b/storage/ent/db/mutation.go @@ -3968,6 +3968,7 @@ type ConnectorMutation struct { _config *[]byte grant_types *[]string appendgrant_types []string + expiry **storage.ConnectorExpiry clearedFields map[string]struct{} done bool oldValue func(context.Context) (*Connector, error) @@ -4287,6 +4288,55 @@ func (m *ConnectorMutation) ResetGrantTypes() { delete(m.clearedFields, connector.FieldGrantTypes) } +// SetExpiry sets the "expiry" field. +func (m *ConnectorMutation) SetExpiry(se *storage.ConnectorExpiry) { + m.expiry = &se +} + +// Expiry returns the value of the "expiry" field in the mutation. +func (m *ConnectorMutation) Expiry() (r *storage.ConnectorExpiry, exists bool) { + v := m.expiry + if v == nil { + return + } + return *v, true +} + +// OldExpiry returns the old "expiry" field's value of the Connector entity. +// If the Connector object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ConnectorMutation) OldExpiry(ctx context.Context) (v *storage.ConnectorExpiry, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldExpiry is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldExpiry requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldExpiry: %w", err) + } + return oldValue.Expiry, nil +} + +// ClearExpiry clears the value of the "expiry" field. +func (m *ConnectorMutation) ClearExpiry() { + m.expiry = nil + m.clearedFields[connector.FieldExpiry] = struct{}{} +} + +// ExpiryCleared returns if the "expiry" field was cleared in this mutation. +func (m *ConnectorMutation) ExpiryCleared() bool { + _, ok := m.clearedFields[connector.FieldExpiry] + return ok +} + +// ResetExpiry resets all changes to the "expiry" field. +func (m *ConnectorMutation) ResetExpiry() { + m.expiry = nil + delete(m.clearedFields, connector.FieldExpiry) +} + // Where appends a list predicates to the ConnectorMutation builder. func (m *ConnectorMutation) Where(ps ...predicate.Connector) { m.predicates = append(m.predicates, ps...) @@ -4321,7 +4371,7 @@ func (m *ConnectorMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ConnectorMutation) Fields() []string { - fields := make([]string, 0, 5) + fields := make([]string, 0, 6) if m._type != nil { fields = append(fields, connector.FieldType) } @@ -4337,6 +4387,9 @@ func (m *ConnectorMutation) Fields() []string { if m.grant_types != nil { fields = append(fields, connector.FieldGrantTypes) } + if m.expiry != nil { + fields = append(fields, connector.FieldExpiry) + } return fields } @@ -4355,6 +4408,8 @@ func (m *ConnectorMutation) Field(name string) (ent.Value, bool) { return m.Config() case connector.FieldGrantTypes: return m.GrantTypes() + case connector.FieldExpiry: + return m.Expiry() } return nil, false } @@ -4374,6 +4429,8 @@ func (m *ConnectorMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldConfig(ctx) case connector.FieldGrantTypes: return m.OldGrantTypes(ctx) + case connector.FieldExpiry: + return m.OldExpiry(ctx) } return nil, fmt.Errorf("unknown Connector field %s", name) } @@ -4418,6 +4475,13 @@ func (m *ConnectorMutation) SetField(name string, value ent.Value) error { } m.SetGrantTypes(v) return nil + case connector.FieldExpiry: + v, ok := value.(*storage.ConnectorExpiry) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExpiry(v) + return nil } return fmt.Errorf("unknown Connector field %s", name) } @@ -4451,6 +4515,9 @@ func (m *ConnectorMutation) ClearedFields() []string { if m.FieldCleared(connector.FieldGrantTypes) { fields = append(fields, connector.FieldGrantTypes) } + if m.FieldCleared(connector.FieldExpiry) { + fields = append(fields, connector.FieldExpiry) + } return fields } @@ -4468,6 +4535,9 @@ func (m *ConnectorMutation) ClearField(name string) error { case connector.FieldGrantTypes: m.ClearGrantTypes() return nil + case connector.FieldExpiry: + m.ClearExpiry() + return nil } return fmt.Errorf("unknown Connector nullable field %s", name) } @@ -4491,6 +4561,9 @@ func (m *ConnectorMutation) ResetField(name string) error { case connector.FieldGrantTypes: m.ResetGrantTypes() return nil + case connector.FieldExpiry: + m.ResetExpiry() + return nil } return fmt.Errorf("unknown Connector field %s", name) } diff --git a/storage/ent/schema/connector.go b/storage/ent/schema/connector.go index 191092c574..3da5cc44c3 100644 --- a/storage/ent/schema/connector.go +++ b/storage/ent/schema/connector.go @@ -3,6 +3,8 @@ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" + + "github.com/dexidp/dex/storage" ) /* Original SQL table: @@ -40,6 +42,8 @@ func (Connector) Fields() []ent.Field { field.Bytes("config"), field.JSON("grant_types", []string{}). Optional(), + field.JSON("expiry", &storage.ConnectorExpiry{}). + Optional(), } } diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index d936865ad8..4f868b67bb 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -795,6 +795,8 @@ type Connector struct { Config []byte `json:"config,omitempty"` // GrantTypes is a list of grant types that this connector is allowed to be used with. GrantTypes []string `json:"grantTypes,omitempty"` + // Expiry holds per-connector overrides for token lifetimes. + Expiry *storage.ConnectorExpiry `json:"expiry,omitempty"` } func (cli *client) fromStorageConnector(c storage.Connector) Connector { @@ -812,6 +814,7 @@ func (cli *client) fromStorageConnector(c storage.Connector) Connector { Name: c.Name, Config: c.Config, GrantTypes: c.GrantTypes, + Expiry: c.Expiry, } } @@ -823,6 +826,7 @@ func toStorageConnector(c Connector) storage.Connector { ResourceVersion: c.ObjectMeta.ResourceVersion, Config: c.Config, GrantTypes: c.GrantTypes, + Expiry: c.Expiry, } } diff --git a/storage/sql/crud.go b/storage/sql/crud.go index a8eaf2fd4b..57edd2dbfd 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -1143,15 +1143,24 @@ func (c *conn) CreateConnector(ctx context.Context, connector storage.Connector) if err != nil { return fmt.Errorf("marshal connector grant types: %v", err) } + var expiry []byte + if connector.Expiry != nil { + // Only marshal when set; an unset override must persist as SQL NULL, + // not the literal bytes "null" that json.Marshal(nil) would produce. + expiry, err = json.Marshal(connector.Expiry) + if err != nil { + return fmt.Errorf("marshal connector expiry: %v", err) + } + } _, err = c.Exec(` insert into connector ( - id, type, name, resource_version, config, grant_types + id, type, name, resource_version, config, grant_types, expiry ) values ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7 ); `, - connector.ID, connector.Type, connector.Name, connector.ResourceVersion, connector.Config, grantTypes, + connector.ID, connector.Type, connector.Name, connector.ResourceVersion, connector.Config, grantTypes, expiry, ) if err != nil { if c.alreadyExistsCheck(err) { @@ -1177,6 +1186,13 @@ func (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s st if err != nil { return fmt.Errorf("marshal connector grant types: %v", err) } + var expiry []byte + if newConn.Expiry != nil { + expiry, err = json.Marshal(newConn.Expiry) + if err != nil { + return fmt.Errorf("marshal connector expiry: %v", err) + } + } _, err = tx.Exec(` update connector set @@ -1184,10 +1200,11 @@ func (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s st name = $2, resource_version = $3, config = $4, - grant_types = $5 - where id = $6; + grant_types = $5, + expiry = $6 + where id = $7; `, - newConn.Type, newConn.Name, newConn.ResourceVersion, newConn.Config, grantTypes, connector.ID, + newConn.Type, newConn.Name, newConn.ResourceVersion, newConn.Config, grantTypes, expiry, connector.ID, ) if err != nil { return fmt.Errorf("update connector: %v", err) @@ -1203,16 +1220,16 @@ func (c *conn) GetConnector(ctx context.Context, id string) (storage.Connector, func getConnector(ctx context.Context, q querier, id string) (storage.Connector, error) { return scanConnector(q.QueryRow(` select - id, type, name, resource_version, config, grant_types + id, type, name, resource_version, config, grant_types, expiry from connector where id = $1; `, id)) } func scanConnector(s scanner) (c storage.Connector, err error) { - var grantTypes []byte + var grantTypes, expiry []byte err = s.Scan( - &c.ID, &c.Type, &c.Name, &c.ResourceVersion, &c.Config, &grantTypes, + &c.ID, &c.Type, &c.Name, &c.ResourceVersion, &c.Config, &grantTypes, &expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -1225,13 +1242,20 @@ func scanConnector(s scanner) (c storage.Connector, err error) { return c, fmt.Errorf("unmarshal connector grant types: %v", err) } } + if len(expiry) > 0 { + var e storage.ConnectorExpiry + if err := json.Unmarshal(expiry, &e); err != nil { + return c, fmt.Errorf("unmarshal connector expiry: %v", err) + } + c.Expiry = &e + } return c, nil } func (c *conn) ListConnectors(ctx context.Context) ([]storage.Connector, error) { rows, err := c.Query(` select - id, type, name, resource_version, config, grant_types + id, type, name, resource_version, config, grant_types, expiry from connector; `) if err != nil { diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index d9b3dbed5c..51bc923968 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -467,4 +467,9 @@ var migrations = []migration{ add column sso_shared_with bytea;`, }, }, + { + stmts: []string{ + `alter table connector add column expiry bytea;`, + }, + }, } diff --git a/storage/storage.go b/storage/storage.go index e68efba097..8cc87964de 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -545,6 +545,30 @@ type Connector struct { // GrantTypes is a list of grant types that this connector is allowed to be used with. // If empty, all grant types are allowed. GrantTypes []string `json:"grantTypes,omitempty"` + + // Expiry, when set, overrides the corresponding fields of the top-level + // expiry config for tokens issued through this connector. Any field left + // unset falls back to the global value. Overrides must be at least as + // strict as their global counterpart. + Expiry *ConnectorExpiry `json:"expiry,omitempty"` +} + +// ConnectorExpiry holds per-connector overrides for token lifetimes. +// The string fields use the same duration format as the top-level expiry +// config (e.g. "5m", "24h"). An empty string means "inherit the global value". +type ConnectorExpiry struct { + IDTokens string `json:"idTokens,omitempty"` + RefreshTokens *ConnectorRefreshExpiry `json:"refreshTokens,omitempty"` +} + +// ConnectorRefreshExpiry holds per-connector refresh-token policy overrides. +// DisableRotation is a *bool so "unset" (inherit) can be distinguished from +// false. The duration strings inherit the global value when empty. +type ConnectorRefreshExpiry struct { + DisableRotation *bool `json:"disableRotation,omitempty"` + ReuseInterval string `json:"reuseInterval,omitempty"` + AbsoluteLifetime string `json:"absoluteLifetime,omitempty"` + ValidIfNotUsedFor string `json:"validIfNotUsedFor,omitempty"` } // VerificationKey is a rotated signing key which can still be used to verify