feat: sliding expiration for user sessions

This commit is contained in:
johnnyjoy 2025-06-26 23:03:28 +08:00
parent e93d695aa8
commit 1fffc41f79
17 changed files with 204 additions and 367 deletions

View file

@ -38,8 +38,9 @@ message GetCurrentSessionRequest {}
message GetCurrentSessionResponse {
User user = 1;
// Current session expiration time (if available).
google.protobuf.Timestamp expires_at = 2;
// Last time the session was accessed.
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
google.protobuf.Timestamp last_accessed_at = 2;
}
message CreateSessionRequest {
@ -78,18 +79,15 @@ message CreateSessionRequest {
// SSO provider authentication method.
SSOCredentials sso_credentials = 2;
}
// Whether the session should never expire.
// Optional field that defaults to false for security.
bool never_expire = 3 [(google.api.field_behavior) = OPTIONAL];
}
message CreateSessionResponse {
// The authenticated user information.
User user = 1;
// Token expiration time.
google.protobuf.Timestamp expires_at = 2;
// Last time the session was accessed.
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
google.protobuf.Timestamp last_accessed_at = 2;
}
message DeleteSessionRequest {}

View file

@ -481,14 +481,12 @@ message UserSession {
// The timestamp when the session was created.
google.protobuf.Timestamp create_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
// The timestamp when the session expires.
google.protobuf.Timestamp expire_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];
// The timestamp when the session was last accessed.
google.protobuf.Timestamp last_accessed_time = 5 [(google.api.field_behavior) = OUTPUT_ONLY];
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
google.protobuf.Timestamp last_accessed_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];
// Client information associated with this session.
ClientInfo client_info = 6 [(google.api.field_behavior) = OUTPUT_ONLY];
ClientInfo client_info = 5 [(google.api.field_behavior) = OUTPUT_ONLY];
message ClientInfo {
// User agent string of the client.

View file

@ -63,10 +63,11 @@ func (*GetCurrentSessionRequest) Descriptor() ([]byte, []int) {
type GetCurrentSessionResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
// Current session expiration time (if available).
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
// Last time the session was accessed.
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
LastAccessedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=last_accessed_at,json=lastAccessedAt,proto3" json:"last_accessed_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetCurrentSessionResponse) Reset() {
@ -106,9 +107,9 @@ func (x *GetCurrentSessionResponse) GetUser() *User {
return nil
}
func (x *GetCurrentSessionResponse) GetExpiresAt() *timestamppb.Timestamp {
func (x *GetCurrentSessionResponse) GetLastAccessedAt() *timestamppb.Timestamp {
if x != nil {
return x.ExpiresAt
return x.LastAccessedAt
}
return nil
}
@ -122,10 +123,7 @@ type CreateSessionRequest struct {
//
// *CreateSessionRequest_PasswordCredentials_
// *CreateSessionRequest_SsoCredentials
Credentials isCreateSessionRequest_Credentials `protobuf_oneof:"credentials"`
// Whether the session should never expire.
// Optional field that defaults to false for security.
NeverExpire bool `protobuf:"varint,3,opt,name=never_expire,json=neverExpire,proto3" json:"never_expire,omitempty"`
Credentials isCreateSessionRequest_Credentials `protobuf_oneof:"credentials"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -185,13 +183,6 @@ func (x *CreateSessionRequest) GetSsoCredentials() *CreateSessionRequest_SSOCred
return nil
}
func (x *CreateSessionRequest) GetNeverExpire() bool {
if x != nil {
return x.NeverExpire
}
return false
}
type isCreateSessionRequest_Credentials interface {
isCreateSessionRequest_Credentials()
}
@ -214,10 +205,11 @@ type CreateSessionResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The authenticated user information.
User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
// Token expiration time.
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
// Last time the session was accessed.
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
LastAccessedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=last_accessed_at,json=lastAccessedAt,proto3" json:"last_accessed_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CreateSessionResponse) Reset() {
@ -257,9 +249,9 @@ func (x *CreateSessionResponse) GetUser() *User {
return nil
}
func (x *CreateSessionResponse) GetExpiresAt() *timestamppb.Timestamp {
func (x *CreateSessionResponse) GetLastAccessedAt() *timestamppb.Timestamp {
if x != nil {
return x.ExpiresAt
return x.LastAccessedAt
}
return nil
}
@ -429,15 +421,13 @@ var File_api_v1_auth_service_proto protoreflect.FileDescriptor
const file_api_v1_auth_service_proto_rawDesc = "" +
"\n" +
"\x19api/v1/auth_service.proto\x12\fmemos.api.v1\x1a\x19api/v1/user_service.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x1a\n" +
"\x18GetCurrentSessionRequest\"~\n" +
"\x18GetCurrentSessionRequest\"\x89\x01\n" +
"\x19GetCurrentSessionResponse\x12&\n" +
"\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\x129\n" +
"\n" +
"expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"\xe0\x03\n" +
"\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\x12D\n" +
"\x10last_accessed_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x0elastAccessedAt\"\xb8\x03\n" +
"\x14CreateSessionRequest\x12k\n" +
"\x14password_credentials\x18\x01 \x01(\v26.memos.api.v1.CreateSessionRequest.PasswordCredentialsH\x00R\x13passwordCredentials\x12\\\n" +
"\x0fsso_credentials\x18\x02 \x01(\v21.memos.api.v1.CreateSessionRequest.SSOCredentialsH\x00R\x0essoCredentials\x12&\n" +
"\fnever_expire\x18\x03 \x01(\bB\x03\xe0A\x01R\vneverExpire\x1aW\n" +
"\x0fsso_credentials\x18\x02 \x01(\v21.memos.api.v1.CreateSessionRequest.SSOCredentialsH\x00R\x0essoCredentials\x1aW\n" +
"\x13PasswordCredentials\x12\x1f\n" +
"\busername\x18\x01 \x01(\tB\x03\xe0A\x02R\busername\x12\x1f\n" +
"\bpassword\x18\x02 \x01(\tB\x03\xe0A\x02R\bpassword\x1am\n" +
@ -445,11 +435,10 @@ const file_api_v1_auth_service_proto_rawDesc = "" +
"\x06idp_id\x18\x01 \x01(\x05B\x03\xe0A\x02R\x05idpId\x12\x17\n" +
"\x04code\x18\x02 \x01(\tB\x03\xe0A\x02R\x04code\x12&\n" +
"\fredirect_uri\x18\x03 \x01(\tB\x03\xe0A\x02R\vredirectUriB\r\n" +
"\vcredentials\"z\n" +
"\vcredentials\"\x85\x01\n" +
"\x15CreateSessionResponse\x12&\n" +
"\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\x129\n" +
"\n" +
"expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"\x16\n" +
"\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\x12D\n" +
"\x10last_accessed_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x0elastAccessedAt\"\x16\n" +
"\x14DeleteSessionRequest2\x8b\x03\n" +
"\vAuthService\x12\x8b\x01\n" +
"\x11GetCurrentSession\x12&.memos.api.v1.GetCurrentSessionRequest\x1a'.memos.api.v1.GetCurrentSessionResponse\"%\x82\xd3\xe4\x93\x02\x1f\x12\x1d/api/v1/auth/sessions/current\x12z\n" +
@ -484,11 +473,11 @@ var file_api_v1_auth_service_proto_goTypes = []any{
}
var file_api_v1_auth_service_proto_depIdxs = []int32{
7, // 0: memos.api.v1.GetCurrentSessionResponse.user:type_name -> memos.api.v1.User
8, // 1: memos.api.v1.GetCurrentSessionResponse.expires_at:type_name -> google.protobuf.Timestamp
8, // 1: memos.api.v1.GetCurrentSessionResponse.last_accessed_at:type_name -> google.protobuf.Timestamp
5, // 2: memos.api.v1.CreateSessionRequest.password_credentials:type_name -> memos.api.v1.CreateSessionRequest.PasswordCredentials
6, // 3: memos.api.v1.CreateSessionRequest.sso_credentials:type_name -> memos.api.v1.CreateSessionRequest.SSOCredentials
7, // 4: memos.api.v1.CreateSessionResponse.user:type_name -> memos.api.v1.User
8, // 5: memos.api.v1.CreateSessionResponse.expires_at:type_name -> google.protobuf.Timestamp
8, // 5: memos.api.v1.CreateSessionResponse.last_accessed_at:type_name -> google.protobuf.Timestamp
0, // 6: memos.api.v1.AuthService.GetCurrentSession:input_type -> memos.api.v1.GetCurrentSessionRequest
2, // 7: memos.api.v1.AuthService.CreateSession:input_type -> memos.api.v1.CreateSessionRequest
4, // 8: memos.api.v1.AuthService.DeleteSession:input_type -> memos.api.v1.DeleteSessionRequest

View file

@ -1434,12 +1434,11 @@ type UserSession struct {
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// The timestamp when the session was created.
CreateTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
// The timestamp when the session expires.
ExpireTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"`
// The timestamp when the session was last accessed.
LastAccessedTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_accessed_time,json=lastAccessedTime,proto3" json:"last_accessed_time,omitempty"`
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
LastAccessedTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=last_accessed_time,json=lastAccessedTime,proto3" json:"last_accessed_time,omitempty"`
// Client information associated with this session.
ClientInfo *UserSession_ClientInfo `protobuf:"bytes,6,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"`
ClientInfo *UserSession_ClientInfo `protobuf:"bytes,5,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -1495,13 +1494,6 @@ func (x *UserSession) GetCreateTime() *timestamppb.Timestamp {
return nil
}
func (x *UserSession) GetExpireTime() *timestamppb.Timestamp {
if x != nil {
return x.ExpireTime
}
return nil
}
func (x *UserSession) GetLastAccessedTime() *timestamppb.Timestamp {
if x != nil {
return x.LastAccessedTime
@ -2055,17 +2047,15 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"\x0faccess_token_id\x18\x03 \x01(\tB\x03\xe0A\x01R\raccessTokenId\"X\n" +
"\x1cDeleteUserAccessTokenRequest\x128\n" +
"\x04name\x18\x01 \x01(\tB$\xe0A\x02\xfaA\x1e\n" +
"\x1cmemos.api.v1/UserAccessTokenR\x04name\"\xd6\x04\n" +
"\x1cmemos.api.v1/UserAccessTokenR\x04name\"\x94\x04\n" +
"\vUserSession\x12\x17\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12\"\n" +
"\n" +
"session_id\x18\x02 \x01(\tB\x03\xe0A\x03R\tsessionId\x12@\n" +
"\vcreate_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
"createTime\x12@\n" +
"\vexpire_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
"expireTime\x12M\n" +
"\x12last_accessed_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\x10lastAccessedTime\x12J\n" +
"\vclient_info\x18\x06 \x01(\v2$.memos.api.v1.UserSession.ClientInfoB\x03\xe0A\x03R\n" +
"createTime\x12M\n" +
"\x12last_accessed_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\x10lastAccessedTime\x12J\n" +
"\vclient_info\x18\x05 \x01(\v2$.memos.api.v1.UserSession.ClientInfoB\x03\xe0A\x03R\n" +
"clientInfo\x1a\xa4\x01\n" +
"\n" +
"ClientInfo\x12\x1d\n" +
@ -2190,48 +2180,47 @@ var file_api_v1_user_service_proto_depIdxs = []int32{
16, // 17: memos.api.v1.ListUserAccessTokensResponse.access_tokens:type_name -> memos.api.v1.UserAccessToken
16, // 18: memos.api.v1.CreateUserAccessTokenRequest.access_token:type_name -> memos.api.v1.UserAccessToken
31, // 19: memos.api.v1.UserSession.create_time:type_name -> google.protobuf.Timestamp
31, // 20: memos.api.v1.UserSession.expire_time:type_name -> google.protobuf.Timestamp
31, // 21: memos.api.v1.UserSession.last_accessed_time:type_name -> google.protobuf.Timestamp
29, // 22: memos.api.v1.UserSession.client_info:type_name -> memos.api.v1.UserSession.ClientInfo
21, // 23: memos.api.v1.ListUserSessionsResponse.sessions:type_name -> memos.api.v1.UserSession
11, // 24: memos.api.v1.ListAllUserStatsResponse.user_stats:type_name -> memos.api.v1.UserStats
2, // 25: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest
4, // 26: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest
5, // 27: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest
6, // 28: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest
7, // 29: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest
8, // 30: memos.api.v1.UserService.SearchUsers:input_type -> memos.api.v1.SearchUsersRequest
10, // 31: memos.api.v1.UserService.GetUserAvatar:input_type -> memos.api.v1.GetUserAvatarRequest
25, // 32: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest
12, // 33: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest
14, // 34: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest
15, // 35: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest
17, // 36: memos.api.v1.UserService.ListUserAccessTokens:input_type -> memos.api.v1.ListUserAccessTokensRequest
19, // 37: memos.api.v1.UserService.CreateUserAccessToken:input_type -> memos.api.v1.CreateUserAccessTokenRequest
20, // 38: memos.api.v1.UserService.DeleteUserAccessToken:input_type -> memos.api.v1.DeleteUserAccessTokenRequest
22, // 39: memos.api.v1.UserService.ListUserSessions:input_type -> memos.api.v1.ListUserSessionsRequest
24, // 40: memos.api.v1.UserService.RevokeUserSession:input_type -> memos.api.v1.RevokeUserSessionRequest
3, // 41: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse
1, // 42: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User
1, // 43: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User
1, // 44: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User
33, // 45: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty
9, // 46: memos.api.v1.UserService.SearchUsers:output_type -> memos.api.v1.SearchUsersResponse
34, // 47: memos.api.v1.UserService.GetUserAvatar:output_type -> google.api.HttpBody
26, // 48: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse
11, // 49: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats
13, // 50: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting
13, // 51: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting
18, // 52: memos.api.v1.UserService.ListUserAccessTokens:output_type -> memos.api.v1.ListUserAccessTokensResponse
16, // 53: memos.api.v1.UserService.CreateUserAccessToken:output_type -> memos.api.v1.UserAccessToken
33, // 54: memos.api.v1.UserService.DeleteUserAccessToken:output_type -> google.protobuf.Empty
23, // 55: memos.api.v1.UserService.ListUserSessions:output_type -> memos.api.v1.ListUserSessionsResponse
33, // 56: memos.api.v1.UserService.RevokeUserSession:output_type -> google.protobuf.Empty
41, // [41:57] is the sub-list for method output_type
25, // [25:41] is the sub-list for method input_type
25, // [25:25] is the sub-list for extension type_name
25, // [25:25] is the sub-list for extension extendee
0, // [0:25] is the sub-list for field type_name
31, // 20: memos.api.v1.UserSession.last_accessed_time:type_name -> google.protobuf.Timestamp
29, // 21: memos.api.v1.UserSession.client_info:type_name -> memos.api.v1.UserSession.ClientInfo
21, // 22: memos.api.v1.ListUserSessionsResponse.sessions:type_name -> memos.api.v1.UserSession
11, // 23: memos.api.v1.ListAllUserStatsResponse.user_stats:type_name -> memos.api.v1.UserStats
2, // 24: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest
4, // 25: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest
5, // 26: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest
6, // 27: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest
7, // 28: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest
8, // 29: memos.api.v1.UserService.SearchUsers:input_type -> memos.api.v1.SearchUsersRequest
10, // 30: memos.api.v1.UserService.GetUserAvatar:input_type -> memos.api.v1.GetUserAvatarRequest
25, // 31: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest
12, // 32: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest
14, // 33: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest
15, // 34: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest
17, // 35: memos.api.v1.UserService.ListUserAccessTokens:input_type -> memos.api.v1.ListUserAccessTokensRequest
19, // 36: memos.api.v1.UserService.CreateUserAccessToken:input_type -> memos.api.v1.CreateUserAccessTokenRequest
20, // 37: memos.api.v1.UserService.DeleteUserAccessToken:input_type -> memos.api.v1.DeleteUserAccessTokenRequest
22, // 38: memos.api.v1.UserService.ListUserSessions:input_type -> memos.api.v1.ListUserSessionsRequest
24, // 39: memos.api.v1.UserService.RevokeUserSession:input_type -> memos.api.v1.RevokeUserSessionRequest
3, // 40: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse
1, // 41: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User
1, // 42: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User
1, // 43: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User
33, // 44: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty
9, // 45: memos.api.v1.UserService.SearchUsers:output_type -> memos.api.v1.SearchUsersResponse
34, // 46: memos.api.v1.UserService.GetUserAvatar:output_type -> google.api.HttpBody
26, // 47: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse
11, // 48: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats
13, // 49: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting
13, // 50: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting
18, // 51: memos.api.v1.UserService.ListUserAccessTokens:output_type -> memos.api.v1.ListUserAccessTokensResponse
16, // 52: memos.api.v1.UserService.CreateUserAccessToken:output_type -> memos.api.v1.UserAccessToken
33, // 53: memos.api.v1.UserService.DeleteUserAccessToken:output_type -> google.protobuf.Empty
23, // 54: memos.api.v1.UserService.ListUserSessions:output_type -> memos.api.v1.ListUserSessionsResponse
33, // 55: memos.api.v1.UserService.RevokeUserSession:output_type -> google.protobuf.Empty
40, // [40:56] is the sub-list for method output_type
24, // [24:40] is the sub-list for method input_type
24, // [24:24] is the sub-list for extension type_name
24, // [24:24] is the sub-list for extension extendee
0, // [0:24] is the sub-list for field type_name
}
func init() { file_api_v1_user_service_proto_init() }

View file

@ -3282,21 +3282,18 @@ definitions:
ssoCredentials:
$ref: '#/definitions/CreateSessionRequestSSOCredentials'
description: SSO provider authentication method.
neverExpire:
type: boolean
description: |-
Whether the session should never expire.
Optional field that defaults to false for security.
v1CreateSessionResponse:
type: object
properties:
user:
$ref: '#/definitions/v1User'
description: The authenticated user information.
expiresAt:
lastAccessedAt:
type: string
format: date-time
description: Token expiration time.
description: |-
Last time the session was accessed.
Used for sliding expiration calculation (last_accessed_time + 2 weeks).
v1EmbeddedContentNode:
type: object
properties:
@ -3316,10 +3313,12 @@ definitions:
properties:
user:
$ref: '#/definitions/v1User'
expiresAt:
lastAccessedAt:
type: string
format: date-time
description: Current session expiration time (if available).
description: |-
Last time the session was accessed.
Used for sliding expiration calculation (last_accessed_time + 2 weeks).
v1HTMLElementNode:
type: object
properties:
@ -4152,15 +4151,12 @@ definitions:
format: date-time
description: The timestamp when the session was created.
readOnly: true
expireTime:
type: string
format: date-time
description: The timestamp when the session expires.
readOnly: true
lastAccessedTime:
type: string
format: date-time
description: The timestamp when the session was last accessed.
description: |-
The timestamp when the session was last accessed.
Used for sliding expiration calculation (last_accessed_time + 2 weeks).
readOnly: true
clientInfo:
$ref: '#/definitions/v1UserSessionClientInfo'

View file

@ -476,12 +476,11 @@ type SessionsUserSetting_Session struct {
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// Timestamp when the session was created.
CreateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
// Timestamp when the session expires.
ExpireTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"`
// Timestamp when the session was last accessed.
LastAccessedTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=last_accessed_time,json=lastAccessedTime,proto3" json:"last_accessed_time,omitempty"`
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
LastAccessedTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=last_accessed_time,json=lastAccessedTime,proto3" json:"last_accessed_time,omitempty"`
// Client information associated with this session.
ClientInfo *SessionsUserSetting_ClientInfo `protobuf:"bytes,5,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"`
ClientInfo *SessionsUserSetting_ClientInfo `protobuf:"bytes,4,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -530,13 +529,6 @@ func (x *SessionsUserSetting_Session) GetCreateTime() *timestamppb.Timestamp {
return nil
}
func (x *SessionsUserSetting_Session) GetExpireTime() *timestamppb.Timestamp {
if x != nil {
return x.ExpireTime
}
return nil
}
func (x *SessionsUserSetting_Session) GetLastAccessedTime() *timestamppb.Timestamp {
if x != nil {
return x.LastAccessedTime
@ -836,18 +828,16 @@ const file_store_user_setting_proto_rawDesc = "" +
"\n" +
"appearance\x18\x02 \x01(\tR\n" +
"appearance\x12'\n" +
"\x0fmemo_visibility\x18\x03 \x01(\tR\x0ememoVisibility\"\xb0\x04\n" +
"\x0fmemo_visibility\x18\x03 \x01(\tR\x0ememoVisibility\"\xf3\x03\n" +
"\x13SessionsUserSetting\x12D\n" +
"\bsessions\x18\x01 \x03(\v2(.memos.store.SessionsUserSetting.SessionR\bsessions\x1a\xba\x02\n" +
"\bsessions\x18\x01 \x03(\v2(.memos.store.SessionsUserSetting.SessionR\bsessions\x1a\xfd\x01\n" +
"\aSession\x12\x1d\n" +
"\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12;\n" +
"\vcreate_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\n" +
"createTime\x12;\n" +
"\vexpire_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" +
"expireTime\x12H\n" +
"\x12last_accessed_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x10lastAccessedTime\x12L\n" +
"\vclient_info\x18\x05 \x01(\v2+.memos.store.SessionsUserSetting.ClientInfoR\n" +
"createTime\x12H\n" +
"\x12last_accessed_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x10lastAccessedTime\x12L\n" +
"\vclient_info\x18\x04 \x01(\v2+.memos.store.SessionsUserSetting.ClientInfoR\n" +
"clientInfo\x1a\x95\x01\n" +
"\n" +
"ClientInfo\x12\x1d\n" +
@ -919,14 +909,13 @@ var file_store_user_setting_proto_depIdxs = []int32{
10, // 8: memos.store.ShortcutsUserSetting.shortcuts:type_name -> memos.store.ShortcutsUserSetting.Shortcut
11, // 9: memos.store.WebhooksUserSetting.webhooks:type_name -> memos.store.WebhooksUserSetting.Webhook
12, // 10: memos.store.SessionsUserSetting.Session.create_time:type_name -> google.protobuf.Timestamp
12, // 11: memos.store.SessionsUserSetting.Session.expire_time:type_name -> google.protobuf.Timestamp
12, // 12: memos.store.SessionsUserSetting.Session.last_accessed_time:type_name -> google.protobuf.Timestamp
8, // 13: memos.store.SessionsUserSetting.Session.client_info:type_name -> memos.store.SessionsUserSetting.ClientInfo
14, // [14:14] is the sub-list for method output_type
14, // [14:14] 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
12, // 11: memos.store.SessionsUserSetting.Session.last_accessed_time:type_name -> google.protobuf.Timestamp
8, // 12: memos.store.SessionsUserSetting.Session.client_info:type_name -> memos.store.SessionsUserSetting.ClientInfo
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_store_user_setting_proto_init() }

View file

@ -48,12 +48,11 @@ message SessionsUserSetting {
string session_id = 1;
// Timestamp when the session was created.
google.protobuf.Timestamp create_time = 2;
// Timestamp when the session expires.
google.protobuf.Timestamp expire_time = 3;
// Timestamp when the session was last accessed.
google.protobuf.Timestamp last_accessed_time = 4;
// Used for sliding expiration calculation (last_accessed_time + 2 weeks).
google.protobuf.Timestamp last_accessed_time = 3;
// Client information associated with this session.
ClientInfo client_info = 5;
ClientInfo client_info = 4;
}
message ClientInfo {

View file

@ -203,13 +203,16 @@ func (in *GRPCAuthInterceptor) updateSessionLastAccessed(ctx context.Context, us
return in.Store.UpdateUserSessionLastAccessed(ctx, userID, sessionID, timestamppb.Now())
}
// validateUserSession checks if a session exists and is still valid.
// validateUserSession checks if a session exists and is still valid using sliding expiration.
func validateUserSession(sessionID string, userSessions []*storepb.SessionsUserSetting_Session) bool {
for _, session := range userSessions {
if sessionID == session.SessionId {
// Check if session has expired
if session.ExpireTime != nil && session.ExpireTime.AsTime().Before(time.Now()) {
return false
// Use sliding expiration: check if last_accessed_time + 2 weeks > current_time
if session.LastAccessedTime != nil {
expirationTime := session.LastAccessedTime.AsTime().Add(SessionSlidingDuration)
if expirationTime.Before(time.Now()) {
return false
}
}
return true
}

View file

@ -19,11 +19,10 @@ const (
KeyID = "v1"
// AccessTokenAudienceName is the audience name of the access token.
AccessTokenAudienceName = "user.access-token"
AccessTokenDuration = 7 * 24 * time.Hour
// SessionSlidingDuration is the sliding expiration duration for user sessions (2 weeks).
// Sessions are considered valid if last_accessed_time + SessionSlidingDuration > current_time.
SessionSlidingDuration = 14 * 24 * time.Hour
// CookieExpDuration expires slightly earlier than the jwt expiration. Client would be logged out if the user
// cookie expires, thus the client would always logout first before attempting to make a request with the expired jwt.
CookieExpDuration = AccessTokenDuration - 1*time.Minute
// SessionCookieName is the cookie name of user session ID.
SessionCookieName = "user_session"
)

View file

@ -42,16 +42,20 @@ func (s *APIV1Service) GetCurrentSession(ctx context.Context, _ *v1pb.GetCurrent
return nil, status.Errorf(codes.Unauthenticated, "user not found")
}
// Update session last accessed time if we have a session ID
var lastAccessedAt *timestamppb.Timestamp
// Update session last accessed time if we have a session ID and get the current session info
if sessionID, ok := ctx.Value(sessionIDContextKey).(string); ok && sessionID != "" {
if err := s.Store.UpdateUserSessionLastAccessed(ctx, user.ID, sessionID, timestamppb.Now()); err != nil {
now := timestamppb.Now()
if err := s.Store.UpdateUserSessionLastAccessed(ctx, user.ID, sessionID, now); err != nil {
// Log error but don't fail the request
slog.Error("failed to update session last accessed time", "error", err)
}
lastAccessedAt = now
}
return &v1pb.GetCurrentSessionResponse{
User: convertUserFromStore(user),
User: convertUserFromStore(user),
LastAccessedAt: lastAccessedAt,
}, nil
}
@ -167,18 +171,15 @@ func (s *APIV1Service) CreateSession(ctx context.Context, request *v1pb.CreateSe
return nil, status.Errorf(codes.PermissionDenied, "user has been archived with username %s", existingUser.Username)
}
expireTime := time.Now().Add(AccessTokenDuration)
if request.NeverExpire {
// Set the expire time to 100 years.
expireTime = time.Now().Add(100 * 365 * 24 * time.Hour)
}
// Default session expiration time is 100 year
expireTime := time.Now().Add(100 * 365 * 24 * time.Hour)
if err := s.doSignIn(ctx, existingUser, expireTime); err != nil {
return nil, status.Errorf(codes.Internal, "failed to sign in, error: %v", err)
}
return &v1pb.CreateSessionResponse{
User: convertUserFromStore(existingUser),
ExpiresAt: timestamppb.New(expireTime),
User: convertUserFromStore(existingUser),
LastAccessedAt: timestamppb.Now(),
}, nil
}
@ -190,7 +191,7 @@ func (s *APIV1Service) doSignIn(ctx context.Context, user *store.User, expireTim
}
// Track session in user settings
if err := s.trackUserSession(ctx, user.ID, sessionID, expireTime); err != nil {
if err := s.trackUserSession(ctx, user.ID, sessionID); err != nil {
// Log the error but don't fail the login if session tracking fails
// This ensures backward compatibility
slog.Error("failed to track user session", "error", err)
@ -308,14 +309,13 @@ func (s *APIV1Service) GetCurrentUser(ctx context.Context) (*store.User, error)
}
// Helper function to track user session for session management.
func (s *APIV1Service) trackUserSession(ctx context.Context, userID int32, sessionID string, expireTime time.Time) error {
func (s *APIV1Service) trackUserSession(ctx context.Context, userID int32, sessionID string) error {
// Extract client information from the context
clientInfo := s.extractClientInfo(ctx)
session := &storepb.SessionsUserSetting_Session{
SessionId: sessionID,
CreateTime: timestamppb.Now(),
ExpireTime: timestamppb.New(expireTime),
LastAccessedTime: timestamppb.Now(),
ClientInfo: clientInfo,
}

View file

@ -650,7 +650,6 @@ func (s *APIV1Service) ListUserSessions(ctx context.Context, request *v1pb.ListU
Name: fmt.Sprintf("users/%d/sessions/%s", userID, userSession.SessionId),
SessionId: userSession.SessionId,
CreateTime: userSession.CreateTime,
ExpireTime: userSession.ExpireTime,
LastAccessedTime: userSession.LastAccessedTime,
}
@ -715,7 +714,6 @@ func (s *APIV1Service) UpsertUserSession(ctx context.Context, userID int32, sess
session := &storepb.SessionsUserSetting_Session{
SessionId: sessionID,
CreateTime: timestamppb.Now(),
ExpireTime: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), // 30 days default
LastAccessedTime: timestamppb.Now(),
ClientInfo: clientInfo,
}

View file

@ -1,4 +1,4 @@
import { Button, Checkbox, Input } from "@usememos/mui";
import { Button, Input } from "@usememos/mui";
import { LoaderIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { ClientError } from "nice-grpc-web";
@ -17,7 +17,6 @@ const PasswordSignInForm = observer(() => {
const actionBtnLoadingState = useLoading(false);
const [username, setUsername] = useState(workspaceStore.state.profile.mode === "demo" ? "yourselfhosted" : "");
const [password, setPassword] = useState(workspaceStore.state.profile.mode === "demo" ? "yourselfhosted" : "");
const [remember, setRemember] = useState(true);
const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
@ -47,7 +46,6 @@ const PasswordSignInForm = observer(() => {
actionBtnLoadingState.setLoading();
await authServiceClient.createSession({
passwordCredentials: { username, password },
neverExpire: remember,
});
await initialUserStore();
navigateTo("/");
@ -94,9 +92,6 @@ const PasswordSignInForm = observer(() => {
/>
</div>
</div>
<div className="flex flex-row justify-start items-center w-full mt-6">
<Checkbox label={t("common.remember-me")} checked={remember} onChange={(e) => setRemember(e.target.checked)} />
</div>
<div className="flex flex-row justify-end items-center w-full mt-6">
<Button
type="submit"

View file

@ -60,6 +60,18 @@ const UserSessionsSection = () => {
return parts.length > 0 ? parts.join(" • ") : "Unknown Device";
};
const getSessionExpirationDate = (session: UserSession) => {
if (!session.lastAccessedTime) return null;
// Calculate expiration as last_accessed_time + 2 weeks (14 days)
const expirationDate = new Date(session.lastAccessedTime.getTime() + 14 * 24 * 60 * 60 * 1000);
return expirationDate;
};
const isSessionExpired = (session: UserSession) => {
const expirationDate = getSessionExpirationDate(session);
return expirationDate ? expirationDate < new Date() : false;
};
const isCurrentSession = (session: UserSession) => {
// A simple heuristic: the most recently accessed session is likely the current one
if (userSessions.length === 0) return false;
@ -126,7 +138,13 @@ const UserSessionsSection = () => {
</div>
</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
{userSession.expireTime?.toLocaleString() ?? t("setting.user-sessions-section.never")}
<div className="flex items-center space-x-1">
<ClockIcon className="w-4 h-4" />
<span>
{getSessionExpirationDate(userSession)?.toLocaleString() ?? t("setting.user-sessions-section.never")}
{isSessionExpired(userSession) && <span className="ml-2 text-red-600 text-xs">(Expired)</span>}
</span>
</div>
</td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button

View file

@ -253,7 +253,7 @@
},
"user-sessions-section": {
"title": "Active Sessions",
"description": "A list of all active sessions for your account. You can revoke any session except the current one.",
"description": "A list of all active sessions for your account. Sessions automatically expire 2 weeks after the last activity. You can revoke any session except the current one.",
"device": "Device",
"location": "Location",
"last-active": "Last Active",
@ -442,4 +442,4 @@
"rename-tag": "Rename tag",
"rename-tip": "All your memos with this tag will be updated."
}
}
}

View file

@ -19,8 +19,11 @@ export interface GetCurrentSessionResponse {
user?:
| User
| undefined;
/** Current session expiration time (if available). */
expiresAt?: Date | undefined;
/**
* Last time the session was accessed.
* Used for sliding expiration calculation (last_accessed_time + 2 weeks).
*/
lastAccessedAt?: Date | undefined;
}
export interface CreateSessionRequest {
@ -29,14 +32,7 @@ export interface CreateSessionRequest {
| CreateSessionRequest_PasswordCredentials
| undefined;
/** SSO provider authentication method. */
ssoCredentials?:
| CreateSessionRequest_SSOCredentials
| undefined;
/**
* Whether the session should never expire.
* Optional field that defaults to false for security.
*/
neverExpire: boolean;
ssoCredentials?: CreateSessionRequest_SSOCredentials | undefined;
}
/** Nested message for password-based authentication credentials. */
@ -77,8 +73,11 @@ export interface CreateSessionResponse {
user?:
| User
| undefined;
/** Token expiration time. */
expiresAt?: Date | undefined;
/**
* Last time the session was accessed.
* Used for sliding expiration calculation (last_accessed_time + 2 weeks).
*/
lastAccessedAt?: Date | undefined;
}
export interface DeleteSessionRequest {
@ -119,7 +118,7 @@ export const GetCurrentSessionRequest: MessageFns<GetCurrentSessionRequest> = {
};
function createBaseGetCurrentSessionResponse(): GetCurrentSessionResponse {
return { user: undefined, expiresAt: undefined };
return { user: undefined, lastAccessedAt: undefined };
}
export const GetCurrentSessionResponse: MessageFns<GetCurrentSessionResponse> = {
@ -127,8 +126,8 @@ export const GetCurrentSessionResponse: MessageFns<GetCurrentSessionResponse> =
if (message.user !== undefined) {
User.encode(message.user, writer.uint32(10).fork()).join();
}
if (message.expiresAt !== undefined) {
Timestamp.encode(toTimestamp(message.expiresAt), writer.uint32(18).fork()).join();
if (message.lastAccessedAt !== undefined) {
Timestamp.encode(toTimestamp(message.lastAccessedAt), writer.uint32(18).fork()).join();
}
return writer;
},
@ -153,7 +152,7 @@ export const GetCurrentSessionResponse: MessageFns<GetCurrentSessionResponse> =
break;
}
message.expiresAt = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
message.lastAccessedAt = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
continue;
}
}
@ -171,13 +170,13 @@ export const GetCurrentSessionResponse: MessageFns<GetCurrentSessionResponse> =
fromPartial(object: DeepPartial<GetCurrentSessionResponse>): GetCurrentSessionResponse {
const message = createBaseGetCurrentSessionResponse();
message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined;
message.expiresAt = object.expiresAt ?? undefined;
message.lastAccessedAt = object.lastAccessedAt ?? undefined;
return message;
},
};
function createBaseCreateSessionRequest(): CreateSessionRequest {
return { passwordCredentials: undefined, ssoCredentials: undefined, neverExpire: false };
return { passwordCredentials: undefined, ssoCredentials: undefined };
}
export const CreateSessionRequest: MessageFns<CreateSessionRequest> = {
@ -188,9 +187,6 @@ export const CreateSessionRequest: MessageFns<CreateSessionRequest> = {
if (message.ssoCredentials !== undefined) {
CreateSessionRequest_SSOCredentials.encode(message.ssoCredentials, writer.uint32(18).fork()).join();
}
if (message.neverExpire !== false) {
writer.uint32(24).bool(message.neverExpire);
}
return writer;
},
@ -217,14 +213,6 @@ export const CreateSessionRequest: MessageFns<CreateSessionRequest> = {
message.ssoCredentials = CreateSessionRequest_SSOCredentials.decode(reader, reader.uint32());
continue;
}
case 3: {
if (tag !== 24) {
break;
}
message.neverExpire = reader.bool();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -245,7 +233,6 @@ export const CreateSessionRequest: MessageFns<CreateSessionRequest> = {
message.ssoCredentials = (object.ssoCredentials !== undefined && object.ssoCredentials !== null)
? CreateSessionRequest_SSOCredentials.fromPartial(object.ssoCredentials)
: undefined;
message.neverExpire = object.neverExpire ?? false;
return message;
},
};
@ -379,7 +366,7 @@ export const CreateSessionRequest_SSOCredentials: MessageFns<CreateSessionReques
};
function createBaseCreateSessionResponse(): CreateSessionResponse {
return { user: undefined, expiresAt: undefined };
return { user: undefined, lastAccessedAt: undefined };
}
export const CreateSessionResponse: MessageFns<CreateSessionResponse> = {
@ -387,8 +374,8 @@ export const CreateSessionResponse: MessageFns<CreateSessionResponse> = {
if (message.user !== undefined) {
User.encode(message.user, writer.uint32(10).fork()).join();
}
if (message.expiresAt !== undefined) {
Timestamp.encode(toTimestamp(message.expiresAt), writer.uint32(18).fork()).join();
if (message.lastAccessedAt !== undefined) {
Timestamp.encode(toTimestamp(message.lastAccessedAt), writer.uint32(18).fork()).join();
}
return writer;
},
@ -413,7 +400,7 @@ export const CreateSessionResponse: MessageFns<CreateSessionResponse> = {
break;
}
message.expiresAt = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
message.lastAccessedAt = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
continue;
}
}
@ -431,7 +418,7 @@ export const CreateSessionResponse: MessageFns<CreateSessionResponse> = {
fromPartial(object: DeepPartial<CreateSessionResponse>): CreateSessionResponse {
const message = createBaseCreateSessionResponse();
message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined;
message.expiresAt = object.expiresAt ?? undefined;
message.lastAccessedAt = object.lastAccessedAt ?? undefined;
return message;
},
};

View file

@ -365,11 +365,10 @@ export interface UserSession {
createTime?:
| Date
| undefined;
/** The timestamp when the session expires. */
expireTime?:
| Date
| undefined;
/** The timestamp when the session was last accessed. */
/**
* The timestamp when the session was last accessed.
* Used for sliding expiration calculation (last_accessed_time + 2 weeks).
*/
lastAccessedTime?:
| Date
| undefined;
@ -2073,14 +2072,7 @@ export const DeleteUserAccessTokenRequest: MessageFns<DeleteUserAccessTokenReque
};
function createBaseUserSession(): UserSession {
return {
name: "",
sessionId: "",
createTime: undefined,
expireTime: undefined,
lastAccessedTime: undefined,
clientInfo: undefined,
};
return { name: "", sessionId: "", createTime: undefined, lastAccessedTime: undefined, clientInfo: undefined };
}
export const UserSession: MessageFns<UserSession> = {
@ -2094,14 +2086,11 @@ export const UserSession: MessageFns<UserSession> = {
if (message.createTime !== undefined) {
Timestamp.encode(toTimestamp(message.createTime), writer.uint32(26).fork()).join();
}
if (message.expireTime !== undefined) {
Timestamp.encode(toTimestamp(message.expireTime), writer.uint32(34).fork()).join();
}
if (message.lastAccessedTime !== undefined) {
Timestamp.encode(toTimestamp(message.lastAccessedTime), writer.uint32(42).fork()).join();
Timestamp.encode(toTimestamp(message.lastAccessedTime), writer.uint32(34).fork()).join();
}
if (message.clientInfo !== undefined) {
UserSession_ClientInfo.encode(message.clientInfo, writer.uint32(50).fork()).join();
UserSession_ClientInfo.encode(message.clientInfo, writer.uint32(42).fork()).join();
}
return writer;
},
@ -2142,7 +2131,7 @@ export const UserSession: MessageFns<UserSession> = {
break;
}
message.expireTime = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
message.lastAccessedTime = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
continue;
}
case 5: {
@ -2150,14 +2139,6 @@ export const UserSession: MessageFns<UserSession> = {
break;
}
message.lastAccessedTime = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
continue;
}
case 6: {
if (tag !== 50) {
break;
}
message.clientInfo = UserSession_ClientInfo.decode(reader, reader.uint32());
continue;
}
@ -2178,7 +2159,6 @@ export const UserSession: MessageFns<UserSession> = {
message.name = object.name ?? "";
message.sessionId = object.sessionId ?? "";
message.createTime = object.createTime ?? undefined;
message.expireTime = object.expireTime ?? undefined;
message.lastAccessedTime = object.lastAccessedTime ?? undefined;
message.clientInfo = (object.clientInfo !== undefined && object.clientInfo !== null)
? UserSession_ClientInfo.fromPartial(object.clientInfo)

View file

@ -35,7 +35,7 @@ export enum Edition {
EDITION_2024 = "EDITION_2024",
/**
* EDITION_1_TEST_ONLY - Placeholder editions for testing feature resolution. These should not be
* used or relied on outside of tests.
* used or relyed on outside of tests.
*/
EDITION_1_TEST_ONLY = "EDITION_1_TEST_ONLY",
EDITION_2_TEST_ONLY = "EDITION_2_TEST_ONLY",
@ -177,19 +177,11 @@ export interface FileDescriptorProto {
* The supported values are "proto2", "proto3", and "editions".
*
* If `edition` is present, this value must be "editions".
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
syntax?:
| string
| undefined;
/**
* The edition of the proto file.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** The edition of the proto file. */
edition?: Edition | undefined;
}
@ -836,12 +828,7 @@ export interface FileOptions {
rubyPackage?:
| string
| undefined;
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -979,12 +966,7 @@ export interface MessageOptions {
deprecatedLegacyJsonFieldConflicts?:
| boolean
| undefined;
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -994,13 +976,12 @@ export interface MessageOptions {
export interface FieldOptions {
/**
* NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead.
* The ctype option instructs the C++ code generator to use a different
* representation of the field than it normally would. See the specific
* options below. This option is only implemented to support use of
* [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of
* type "bytes" in the open source release.
* TODO: make ctype actually deprecated.
* type "bytes" in the open source release -- sorry, we'll try to include
* other types in a future version!
*/
ctype?:
| FieldOptions_CType
@ -1089,12 +1070,7 @@ export interface FieldOptions {
retention?: FieldOptions_OptionRetention | undefined;
targets: FieldOptions_OptionTargetType[];
editionDefaults: FieldOptions_EditionDefault[];
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?: FeatureSet | undefined;
featureSupport?:
| FieldOptions_FeatureSupport
@ -1193,7 +1169,11 @@ export function fieldOptions_JSTypeToNumber(object: FieldOptions_JSType): number
}
}
/** If set to RETENTION_SOURCE, the option will be omitted from the binary. */
/**
* If set to RETENTION_SOURCE, the option will be omitted from the binary.
* Note: as of January 2023, support for this is in progress and does not yet
* have an effect (b/264593489).
*/
export enum FieldOptions_OptionRetention {
RETENTION_UNKNOWN = "RETENTION_UNKNOWN",
RETENTION_RUNTIME = "RETENTION_RUNTIME",
@ -1236,7 +1216,8 @@ export function fieldOptions_OptionRetentionToNumber(object: FieldOptions_Option
/**
* This indicates the types of entities that the field may apply to when used
* as an option. If it is unset, then the field may be freely used as an
* option on any kind of entity.
* option on any kind of entity. Note: as of January 2023, support for this is
* in progress and does not yet have an effect (b/264593489).
*/
export enum FieldOptions_OptionTargetType {
TARGET_TYPE_UNKNOWN = "TARGET_TYPE_UNKNOWN",
@ -1360,12 +1341,7 @@ export interface FieldOptions_FeatureSupport {
}
export interface OneofOptions {
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -1403,12 +1379,7 @@ export interface EnumOptions {
deprecatedLegacyJsonFieldConflicts?:
| boolean
| undefined;
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -1426,12 +1397,7 @@ export interface EnumValueOptions {
deprecated?:
| boolean
| undefined;
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -1452,12 +1418,7 @@ export interface EnumValueOptions {
}
export interface ServiceOptions {
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -1485,12 +1446,7 @@ export interface MethodOptions {
idempotencyLevel?:
| MethodOptions_IdempotencyLevel
| undefined;
/**
* Any features defined in the specific edition.
* WARNING: This field should only be used by protobuf plugins or special
* cases like the proto compiler. Other uses are discouraged and
* developers should rely on the protoreflect APIs for their client language.
*/
/** Any features defined in the specific edition. */
features?:
| FeatureSet
| undefined;
@ -1593,7 +1549,6 @@ export interface FeatureSet {
utf8Validation?: FeatureSet_Utf8Validation | undefined;
messageEncoding?: FeatureSet_MessageEncoding | undefined;
jsonFormat?: FeatureSet_JsonFormat | undefined;
enforceNamingStyle?: FeatureSet_EnforceNamingStyle | undefined;
}
export enum FeatureSet_FieldPresence {
@ -1836,45 +1791,6 @@ export function featureSet_JsonFormatToNumber(object: FeatureSet_JsonFormat): nu
}
}
export enum FeatureSet_EnforceNamingStyle {
ENFORCE_NAMING_STYLE_UNKNOWN = "ENFORCE_NAMING_STYLE_UNKNOWN",
STYLE2024 = "STYLE2024",
STYLE_LEGACY = "STYLE_LEGACY",
UNRECOGNIZED = "UNRECOGNIZED",
}
export function featureSet_EnforceNamingStyleFromJSON(object: any): FeatureSet_EnforceNamingStyle {
switch (object) {
case 0:
case "ENFORCE_NAMING_STYLE_UNKNOWN":
return FeatureSet_EnforceNamingStyle.ENFORCE_NAMING_STYLE_UNKNOWN;
case 1:
case "STYLE2024":
return FeatureSet_EnforceNamingStyle.STYLE2024;
case 2:
case "STYLE_LEGACY":
return FeatureSet_EnforceNamingStyle.STYLE_LEGACY;
case -1:
case "UNRECOGNIZED":
default:
return FeatureSet_EnforceNamingStyle.UNRECOGNIZED;
}
}
export function featureSet_EnforceNamingStyleToNumber(object: FeatureSet_EnforceNamingStyle): number {
switch (object) {
case FeatureSet_EnforceNamingStyle.ENFORCE_NAMING_STYLE_UNKNOWN:
return 0;
case FeatureSet_EnforceNamingStyle.STYLE2024:
return 1;
case FeatureSet_EnforceNamingStyle.STYLE_LEGACY:
return 2;
case FeatureSet_EnforceNamingStyle.UNRECOGNIZED:
default:
return -1;
}
}
/**
* A compiled specification for the defaults of a set of features. These
* messages are generated from FeatureSet extensions and can be used to seed
@ -4998,7 +4914,6 @@ function createBaseFeatureSet(): FeatureSet {
utf8Validation: FeatureSet_Utf8Validation.UTF8_VALIDATION_UNKNOWN,
messageEncoding: FeatureSet_MessageEncoding.MESSAGE_ENCODING_UNKNOWN,
jsonFormat: FeatureSet_JsonFormat.JSON_FORMAT_UNKNOWN,
enforceNamingStyle: FeatureSet_EnforceNamingStyle.ENFORCE_NAMING_STYLE_UNKNOWN,
};
}
@ -5033,12 +4948,6 @@ export const FeatureSet: MessageFns<FeatureSet> = {
if (message.jsonFormat !== undefined && message.jsonFormat !== FeatureSet_JsonFormat.JSON_FORMAT_UNKNOWN) {
writer.uint32(48).int32(featureSet_JsonFormatToNumber(message.jsonFormat));
}
if (
message.enforceNamingStyle !== undefined &&
message.enforceNamingStyle !== FeatureSet_EnforceNamingStyle.ENFORCE_NAMING_STYLE_UNKNOWN
) {
writer.uint32(56).int32(featureSet_EnforceNamingStyleToNumber(message.enforceNamingStyle));
}
return writer;
},
@ -5097,14 +5006,6 @@ export const FeatureSet: MessageFns<FeatureSet> = {
message.jsonFormat = featureSet_JsonFormatFromJSON(reader.int32());
continue;
}
case 7: {
if (tag !== 56) {
break;
}
message.enforceNamingStyle = featureSet_EnforceNamingStyleFromJSON(reader.int32());
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -5126,8 +5027,6 @@ export const FeatureSet: MessageFns<FeatureSet> = {
message.utf8Validation = object.utf8Validation ?? FeatureSet_Utf8Validation.UTF8_VALIDATION_UNKNOWN;
message.messageEncoding = object.messageEncoding ?? FeatureSet_MessageEncoding.MESSAGE_ENCODING_UNKNOWN;
message.jsonFormat = object.jsonFormat ?? FeatureSet_JsonFormat.JSON_FORMAT_UNKNOWN;
message.enforceNamingStyle = object.enforceNamingStyle ??
FeatureSet_EnforceNamingStyle.ENFORCE_NAMING_STYLE_UNKNOWN;
return message;
},
};