From 7932f6d0d0e3f15dcf57186ec33a33055ccc71da Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 18 Dec 2025 18:15:51 +0800 Subject: [PATCH] refactor: user auth improvements (#5360) --- .gitignore | 4 + proto/api/v1/auth_service.proto | 87 +- proto/api/v1/user_service.proto | 140 +-- .../v1/apiv1connect/auth_service.connect.go | 179 ++-- .../v1/apiv1connect/user_service.connect.go | 264 +++--- proto/gen/api/v1/auth_service.pb.go | 421 +++++---- proto/gen/api/v1/auth_service.pb.gw.go | 162 +++- proto/gen/api/v1/auth_service_grpc.pb.go | 160 ++-- proto/gen/api/v1/user_service.pb.go | 871 ++++++++++-------- proto/gen/api/v1/user_service.pb.gw.go | 252 +++-- proto/gen/api/v1/user_service_grpc.pb.go | 210 +++-- proto/gen/openapi.yaml | 652 +++++++------ proto/gen/store/user_setting.pb.go | 457 +++++++-- proto/store/user_setting.proto | 40 + server/auth/authenticator.go | 233 ++--- server/auth/context.go | 28 + server/auth/extract.go | 13 + server/auth/token.go | 154 ++++ server/auth/token_test.go | 306 ++++++ server/router/api/v1/acl_config.go | 6 +- server/router/api/v1/acl_config_test.go | 9 +- server/router/api/v1/attachment_service.go | 6 +- server/router/api/v1/auth_service.go | 271 +++--- server/router/api/v1/connect_interceptors.go | 15 +- server/router/api/v1/connect_services.go | 42 +- server/router/api/v1/idp_service.go | 10 +- server/router/api/v1/instance_service.go | 4 +- .../router/api/v1/memo_attachment_service.go | 2 +- server/router/api/v1/memo_relation_service.go | 4 +- server/router/api/v1/memo_service.go | 12 +- server/router/api/v1/reaction_service.go | 4 +- server/router/api/v1/shortcut_service.go | 10 +- server/router/api/v1/test/auth_test.go | 655 +++++++++++++ server/router/api/v1/user_service.go | 526 +++-------- server/router/api/v1/user_service_stats.go | 4 +- server/router/api/v1/v1.go | 11 +- server/router/fileserver/fileserver.go | 37 +- store/db/mysql/user_setting.go | 32 +- store/db/postgres/user_setting.go | 36 +- store/db/sqlite/user_setting.go | 40 +- store/driver.go | 2 +- store/user_setting.go | 251 +++-- web/src/auth-state.ts | 21 + .../components/CreateAccessTokenDialog.tsx | 26 +- web/src/components/PasswordSignInForm.tsx | 8 +- .../Settings/AccessTokenSection.tsx | 79 +- .../Settings/UserSessionsSection.tsx | 38 +- web/src/components/UserMenu.tsx | 2 +- web/src/connect.ts | 64 +- web/src/pages/AuthCallback.tsx | 8 +- web/src/pages/SignUp.tsx | 8 +- web/src/store/user.ts | 49 +- .../types/proto/api/v1/activity_service_pb.ts | 2 +- .../proto/api/v1/attachment_service_pb.ts | 2 +- web/src/types/proto/api/v1/auth_service_pb.ts | 213 +++-- web/src/types/proto/api/v1/common_pb.ts | 2 +- web/src/types/proto/api/v1/idp_service_pb.ts | 2 +- .../types/proto/api/v1/instance_service_pb.ts | 2 +- web/src/types/proto/api/v1/memo_service_pb.ts | 2 +- .../types/proto/api/v1/shortcut_service_pb.ts | 2 +- web/src/types/proto/api/v1/user_service_pb.ts | 332 ++++--- .../types/proto/google/api/annotations_pb.ts | 2 +- web/src/types/proto/google/api/client_pb.ts | 2 +- .../proto/google/api/field_behavior_pb.ts | 2 +- web/src/types/proto/google/api/http_pb.ts | 2 +- .../types/proto/google/api/launch_stage_pb.ts | 2 +- web/src/types/proto/google/api/resource_pb.ts | 2 +- 67 files changed, 4739 insertions(+), 2757 deletions(-) create mode 100644 server/auth/token_test.go create mode 100644 server/router/api/v1/test/auth_test.go create mode 100644 web/src/auth-state.ts diff --git a/.gitignore b/.gitignore index e15372555..94acb9860 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ web/dist # Build artifacts build/ bin/ +memos + +# Plan/design documents +docs/plans/ .DS_Store diff --git a/proto/api/v1/auth_service.proto b/proto/api/v1/auth_service.proto index 941e307b5..545a5f62d 100644 --- a/proto/api/v1/auth_service.proto +++ b/proto/api/v1/auth_service.proto @@ -11,87 +11,104 @@ import "google/protobuf/timestamp.proto"; option go_package = "gen/api/v1"; service AuthService { - // GetCurrentSession returns the current active session information. - // This method is idempotent and safe, suitable for checking current session state. - rpc GetCurrentSession(GetCurrentSessionRequest) returns (GetCurrentSessionResponse) { - option (google.api.http) = {get: "/api/v1/auth/sessions/current"}; + // GetCurrentUser returns the authenticated user's information. + // Validates the access token and returns user details. + // Similar to OIDC's /userinfo endpoint. + rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse) { + option (google.api.http) = {get: "/api/v1/auth/me"}; } - // CreateSession authenticates a user and creates a new session. - // Returns the authenticated user information upon successful authentication. - rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse) { + // SignIn authenticates a user with credentials and returns tokens. + // On success, returns an access token and sets a refresh token cookie. + // Supports password-based and SSO authentication methods. + rpc SignIn(SignInRequest) returns (SignInResponse) { option (google.api.http) = { - post: "/api/v1/auth/sessions" + post: "/api/v1/auth/signin" body: "*" }; } - // DeleteSession terminates the current user session. - // This is an idempotent operation that invalidates the user's authentication. - rpc DeleteSession(DeleteSessionRequest) returns (google.protobuf.Empty) { - option (google.api.http) = {delete: "/api/v1/auth/sessions/current"}; + // SignOut terminates the user's authentication. + // Revokes the refresh token and clears the authentication cookie. + rpc SignOut(SignOutRequest) returns (google.protobuf.Empty) { + option (google.api.http) = {post: "/api/v1/auth/signout"}; + } + + // RefreshToken exchanges a valid refresh token for a new access token. + // The refresh token is read from the HttpOnly cookie. + // Returns a new short-lived access token. + rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse) { + option (google.api.http) = { + post: "/api/v1/auth/refresh" + body: "*" + }; } } -message GetCurrentSessionRequest {} +message GetCurrentUserRequest {} -message GetCurrentSessionResponse { +message GetCurrentUserResponse { + // The authenticated user's information. User user = 1; - - // 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 { +message SignInRequest { // Nested message for password-based authentication credentials. message PasswordCredentials { // The username to sign in with. - // Required field for password-based authentication. string username = 1 [(google.api.field_behavior) = REQUIRED]; // The password to sign in with. - // Required field for password-based authentication. string password = 2 [(google.api.field_behavior) = REQUIRED]; } // Nested message for SSO authentication credentials. message SSOCredentials { // The ID of the SSO provider. - // Required field to identify the SSO provider. int32 idp_id = 1 [(google.api.field_behavior) = REQUIRED]; // The authorization code from the SSO provider. - // Required field for completing the SSO flow. string code = 2 [(google.api.field_behavior) = REQUIRED]; // The redirect URI used in the SSO flow. - // Required field for security validation. string redirect_uri = 3 [(google.api.field_behavior) = REQUIRED]; // The PKCE code verifier for enhanced security (RFC 7636). - // Optional field - if provided, enables PKCE flow protection against authorization code interception. + // Optional - enables PKCE flow protection against authorization code interception. string code_verifier = 4 [(google.api.field_behavior) = OPTIONAL]; } - // Provide one authentication method (username/password or SSO). - // Required field to specify the authentication method. + // Authentication credentials. Provide one method. oneof credentials { - // Username and password authentication method. + // Username and password authentication. PasswordCredentials password_credentials = 1; - // SSO provider authentication method. + // SSO provider authentication. SSOCredentials sso_credentials = 2; } } -message CreateSessionResponse { - // The authenticated user information. +message SignInResponse { + // The authenticated user's information. User user = 1; - // Last time the session was accessed. - // Used for sliding expiration calculation (last_accessed_time + 2 weeks). - google.protobuf.Timestamp last_accessed_at = 2; + // The short-lived access token for API requests. + // Store in memory only, not in localStorage. + string access_token = 2; + + // When the access token expires. + // Client should call RefreshToken before this time. + google.protobuf.Timestamp access_token_expires_at = 3; } -message DeleteSessionRequest {} +message SignOutRequest {} + +message RefreshTokenRequest {} + +message RefreshTokenResponse { + // The new short-lived access token. + string access_token = 1; + + // When the access token expires. + google.protobuf.Timestamp expires_at = 2; +} diff --git a/proto/api/v1/user_service.proto b/proto/api/v1/user_service.proto index 8eff1bebd..d552e1b24 100644 --- a/proto/api/v1/user_service.proto +++ b/proto/api/v1/user_service.proto @@ -84,35 +84,39 @@ service UserService { option (google.api.method_signature) = "parent"; } - // ListUserAccessTokens returns a list of access tokens for a user. - rpc ListUserAccessTokens(ListUserAccessTokensRequest) returns (ListUserAccessTokensResponse) { - option (google.api.http) = {get: "/api/v1/{parent=users/*}/accessTokens"}; + // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + rpc ListPersonalAccessTokens(ListPersonalAccessTokensRequest) returns (ListPersonalAccessTokensResponse) { + option (google.api.http) = {get: "/api/v1/{parent=users/*}/personalAccessTokens"}; option (google.api.method_signature) = "parent"; } - // CreateUserAccessToken creates a new access token for a user. - rpc CreateUserAccessToken(CreateUserAccessTokenRequest) returns (UserAccessToken) { + // CreatePersonalAccessToken creates a new Personal Access Token for a user. + // The token value is only returned once upon creation. + rpc CreatePersonalAccessToken(CreatePersonalAccessTokenRequest) returns (CreatePersonalAccessTokenResponse) { option (google.api.http) = { - post: "/api/v1/{parent=users/*}/accessTokens" - body: "access_token" + post: "/api/v1/{parent=users/*}/personalAccessTokens" + body: "*" }; - option (google.api.method_signature) = "parent,access_token"; } - // DeleteUserAccessToken deletes an access token. - rpc DeleteUserAccessToken(DeleteUserAccessTokenRequest) returns (google.protobuf.Empty) { - option (google.api.http) = {delete: "/api/v1/{name=users/*/accessTokens/*}"}; + // DeletePersonalAccessToken deletes a Personal Access Token. + rpc DeletePersonalAccessToken(DeletePersonalAccessTokenRequest) returns (google.protobuf.Empty) { + option (google.api.http) = {delete: "/api/v1/{name=users/*/personalAccessTokens/*}"}; option (google.api.method_signature) = "name"; } - // ListUserSessions returns a list of active sessions for a user. - rpc ListUserSessions(ListUserSessionsRequest) returns (ListUserSessionsResponse) { + // ListSessions returns a list of active login sessions for a user. + // Each session represents a browser/device where the user is logged in. + // Sessions are backed by refresh tokens with sliding expiration. + rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse) { option (google.api.http) = {get: "/api/v1/{parent=users/*}/sessions"}; option (google.api.method_signature) = "parent"; } - // RevokeUserSession revokes a specific session for a user. - rpc RevokeUserSession(RevokeUserSessionRequest) returns (google.protobuf.Empty) { + // RevokeSession revokes a specific login session. + // This invalidates the refresh token, forcing re-authentication on that device. + rpc RevokeSession(RevokeSessionRequest) returns (google.protobuf.Empty) { option (google.api.http) = {delete: "/api/v1/{name=users/*/sessions/*}"}; option (google.api.method_signature) = "name"; } @@ -398,9 +402,9 @@ message UserSetting { KEY_UNSPECIFIED = 0; // GENERAL is the key for general user settings. GENERAL = 1; - // SESSIONS is the key for user authentication sessions. + // SESSIONS is the key for user login sessions (refresh tokens). SESSIONS = 2; - // ACCESS_TOKENS is the key for access tokens. + // ACCESS_TOKENS is the key for Personal Access Tokens (PATs). ACCESS_TOKENS = 3; // WEBHOOKS is the key for user webhooks. WEBHOOKS = 4; @@ -420,14 +424,14 @@ message UserSetting { // User authentication sessions configuration. message SessionsSetting { - // List of active user sessions. - repeated UserSession sessions = 1; + // List of active login sessions. + repeated Session sessions = 1; } - // User access tokens configuration. + // Personal access tokens configuration. message AccessTokensSetting { - // List of user access tokens. - repeated UserAccessToken access_tokens = 1; + // List of personal access tokens (PATs). + repeated PersonalAccessToken personal_access_tokens = 1; } // User webhooks configuration. @@ -487,85 +491,97 @@ message ListUserSettingsResponse { int32 total_size = 3; } -// User access token message -message UserAccessToken { +// PersonalAccessToken represents a long-lived token for API/script access. +// PATs are distinct from short-lived JWT access tokens used for session authentication. +message PersonalAccessToken { option (google.api.resource) = { - type: "memos.api.v1/UserAccessToken" - pattern: "users/{user}/accessTokens/{access_token}" - singular: "userAccessToken" - plural: "userAccessTokens" + type: "memos.api.v1/PersonalAccessToken" + pattern: "users/{user}/personalAccessTokens/{personal_access_token}" + singular: "personalAccessToken" + plural: "personalAccessTokens" }; - // The resource name of the access token. - // Format: users/{user}/accessTokens/{access_token} + // The resource name of the personal access token. + // Format: users/{user}/personalAccessTokens/{personal_access_token} string name = 1 [(google.api.field_behavior) = IDENTIFIER]; - // Output only. The access token value. - string access_token = 2 [(google.api.field_behavior) = OUTPUT_ONLY]; + // The description of the token. + string description = 2 [(google.api.field_behavior) = OPTIONAL]; - // The description of the access token. - string description = 3 [(google.api.field_behavior) = OPTIONAL]; - - // Output only. The issued timestamp. - google.protobuf.Timestamp issued_at = 4 [(google.api.field_behavior) = OUTPUT_ONLY]; + // Output only. The creation timestamp. + google.protobuf.Timestamp created_at = 3 [(google.api.field_behavior) = OUTPUT_ONLY]; // Optional. The expiration timestamp. - google.protobuf.Timestamp expires_at = 5 [(google.api.field_behavior) = OPTIONAL]; + google.protobuf.Timestamp expires_at = 4 [(google.api.field_behavior) = OPTIONAL]; + + // Output only. The last used timestamp. + google.protobuf.Timestamp last_used_at = 5 [(google.api.field_behavior) = OUTPUT_ONLY]; } -message ListUserAccessTokensRequest { - // Required. The parent resource whose access tokens will be listed. +message ListPersonalAccessTokensRequest { + // Required. The parent resource whose personal access tokens will be listed. // Format: users/{user} string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = {type: "memos.api.v1/User"} ]; - // Optional. The maximum number of access tokens to return. + // Optional. The maximum number of tokens to return. int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL]; // Optional. A page token for pagination. string page_token = 3 [(google.api.field_behavior) = OPTIONAL]; } -message ListUserAccessTokensResponse { - // The list of access tokens. - repeated UserAccessToken access_tokens = 1; +message ListPersonalAccessTokensResponse { + // The list of personal access tokens. + repeated PersonalAccessToken personal_access_tokens = 1; // A token for the next page of results. string next_page_token = 2; - // The total count of access tokens. + // The total count of personal access tokens. int32 total_size = 3; } -message CreateUserAccessTokenRequest { - // Required. The parent resource where this access token will be created. +message CreatePersonalAccessTokenRequest { + // Required. The parent resource where this token will be created. // Format: users/{user} string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = {type: "memos.api.v1/User"} ]; - // Required. The access token to create. - UserAccessToken access_token = 2 [(google.api.field_behavior) = REQUIRED]; + // Optional. Description of the personal access token. + string description = 2 [(google.api.field_behavior) = OPTIONAL]; - // Optional. The access token ID to use. - string access_token_id = 3 [(google.api.field_behavior) = OPTIONAL]; + // Optional. Expiration duration in days (0 = never expires). + int32 expires_in_days = 3 [(google.api.field_behavior) = OPTIONAL]; } -message DeleteUserAccessTokenRequest { - // Required. The resource name of the access token to delete. - // Format: users/{user}/accessTokens/{access_token} +message CreatePersonalAccessTokenResponse { + // The personal access token metadata. + PersonalAccessToken personal_access_token = 1; + + // The actual token value - only returned on creation. + // This is the only time the token value will be visible. + string token = 2; +} + +message DeletePersonalAccessTokenRequest { + // Required. The resource name of the personal access token to delete. + // Format: users/{user}/personalAccessTokens/{personal_access_token} string name = 1 [ (google.api.field_behavior) = REQUIRED, - (google.api.resource_reference) = {type: "memos.api.v1/UserAccessToken"} + (google.api.resource_reference) = {type: "memos.api.v1/PersonalAccessToken"} ]; } -message UserSession { +// Session represents a user's login session on a specific device/browser. +// Sessions are backed by refresh tokens with sliding expiration. +message Session { option (google.api.resource) = { - type: "memos.api.v1/UserSession" + type: "memos.api.v1/Session" pattern: "users/{user}/sessions/{session}" name_field: "name" }; @@ -605,7 +621,7 @@ message UserSession { } } -message ListUserSessionsRequest { +message ListSessionsRequest { // Required. The resource name of the parent. // Format: users/{user} string parent = 1 [ @@ -614,12 +630,12 @@ message ListUserSessionsRequest { ]; } -message ListUserSessionsResponse { - // The list of user sessions. - repeated UserSession sessions = 1; +message ListSessionsResponse { + // The list of sessions. + repeated Session sessions = 1; } -message RevokeUserSessionRequest { +message RevokeSessionRequest { // The name of the session to revoke. // Format: users/{user}/sessions/{session} string name = 1 [(google.api.field_behavior) = REQUIRED]; diff --git a/proto/gen/api/v1/apiv1connect/auth_service.connect.go b/proto/gen/api/v1/apiv1connect/auth_service.connect.go index 536ff0eb0..1293e4026 100644 --- a/proto/gen/api/v1/apiv1connect/auth_service.connect.go +++ b/proto/gen/api/v1/apiv1connect/auth_service.connect.go @@ -34,28 +34,35 @@ const ( // reflection-formatted method names, remove the leading slash and convert the remaining slash to a // period. const ( - // AuthServiceGetCurrentSessionProcedure is the fully-qualified name of the AuthService's - // GetCurrentSession RPC. - AuthServiceGetCurrentSessionProcedure = "/memos.api.v1.AuthService/GetCurrentSession" - // AuthServiceCreateSessionProcedure is the fully-qualified name of the AuthService's CreateSession + // AuthServiceGetCurrentUserProcedure is the fully-qualified name of the AuthService's + // GetCurrentUser RPC. + AuthServiceGetCurrentUserProcedure = "/memos.api.v1.AuthService/GetCurrentUser" + // AuthServiceSignInProcedure is the fully-qualified name of the AuthService's SignIn RPC. + AuthServiceSignInProcedure = "/memos.api.v1.AuthService/SignIn" + // AuthServiceSignOutProcedure is the fully-qualified name of the AuthService's SignOut RPC. + AuthServiceSignOutProcedure = "/memos.api.v1.AuthService/SignOut" + // AuthServiceRefreshTokenProcedure is the fully-qualified name of the AuthService's RefreshToken // RPC. - AuthServiceCreateSessionProcedure = "/memos.api.v1.AuthService/CreateSession" - // AuthServiceDeleteSessionProcedure is the fully-qualified name of the AuthService's DeleteSession - // RPC. - AuthServiceDeleteSessionProcedure = "/memos.api.v1.AuthService/DeleteSession" + AuthServiceRefreshTokenProcedure = "/memos.api.v1.AuthService/RefreshToken" ) // AuthServiceClient is a client for the memos.api.v1.AuthService service. type AuthServiceClient interface { - // GetCurrentSession returns the current active session information. - // This method is idempotent and safe, suitable for checking current session state. - GetCurrentSession(context.Context, *connect.Request[v1.GetCurrentSessionRequest]) (*connect.Response[v1.GetCurrentSessionResponse], error) - // CreateSession authenticates a user and creates a new session. - // Returns the authenticated user information upon successful authentication. - CreateSession(context.Context, *connect.Request[v1.CreateSessionRequest]) (*connect.Response[v1.CreateSessionResponse], error) - // DeleteSession terminates the current user session. - // This is an idempotent operation that invalidates the user's authentication. - DeleteSession(context.Context, *connect.Request[v1.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) + // GetCurrentUser returns the authenticated user's information. + // Validates the access token and returns user details. + // Similar to OIDC's /userinfo endpoint. + GetCurrentUser(context.Context, *connect.Request[v1.GetCurrentUserRequest]) (*connect.Response[v1.GetCurrentUserResponse], error) + // SignIn authenticates a user with credentials and returns tokens. + // On success, returns an access token and sets a refresh token cookie. + // Supports password-based and SSO authentication methods. + SignIn(context.Context, *connect.Request[v1.SignInRequest]) (*connect.Response[v1.SignInResponse], error) + // SignOut terminates the user's authentication. + // Revokes the refresh token and clears the authentication cookie. + SignOut(context.Context, *connect.Request[v1.SignOutRequest]) (*connect.Response[emptypb.Empty], error) + // RefreshToken exchanges a valid refresh token for a new access token. + // The refresh token is read from the HttpOnly cookie. + // Returns a new short-lived access token. + RefreshToken(context.Context, *connect.Request[v1.RefreshTokenRequest]) (*connect.Response[v1.RefreshTokenResponse], error) } // NewAuthServiceClient constructs a client for the memos.api.v1.AuthService service. By default, it @@ -69,22 +76,28 @@ func NewAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. baseURL = strings.TrimRight(baseURL, "/") authServiceMethods := v1.File_api_v1_auth_service_proto.Services().ByName("AuthService").Methods() return &authServiceClient{ - getCurrentSession: connect.NewClient[v1.GetCurrentSessionRequest, v1.GetCurrentSessionResponse]( + getCurrentUser: connect.NewClient[v1.GetCurrentUserRequest, v1.GetCurrentUserResponse]( httpClient, - baseURL+AuthServiceGetCurrentSessionProcedure, - connect.WithSchema(authServiceMethods.ByName("GetCurrentSession")), + baseURL+AuthServiceGetCurrentUserProcedure, + connect.WithSchema(authServiceMethods.ByName("GetCurrentUser")), connect.WithClientOptions(opts...), ), - createSession: connect.NewClient[v1.CreateSessionRequest, v1.CreateSessionResponse]( + signIn: connect.NewClient[v1.SignInRequest, v1.SignInResponse]( httpClient, - baseURL+AuthServiceCreateSessionProcedure, - connect.WithSchema(authServiceMethods.ByName("CreateSession")), + baseURL+AuthServiceSignInProcedure, + connect.WithSchema(authServiceMethods.ByName("SignIn")), connect.WithClientOptions(opts...), ), - deleteSession: connect.NewClient[v1.DeleteSessionRequest, emptypb.Empty]( + signOut: connect.NewClient[v1.SignOutRequest, emptypb.Empty]( httpClient, - baseURL+AuthServiceDeleteSessionProcedure, - connect.WithSchema(authServiceMethods.ByName("DeleteSession")), + baseURL+AuthServiceSignOutProcedure, + connect.WithSchema(authServiceMethods.ByName("SignOut")), + connect.WithClientOptions(opts...), + ), + refreshToken: connect.NewClient[v1.RefreshTokenRequest, v1.RefreshTokenResponse]( + httpClient, + baseURL+AuthServiceRefreshTokenProcedure, + connect.WithSchema(authServiceMethods.ByName("RefreshToken")), connect.WithClientOptions(opts...), ), } @@ -92,37 +105,49 @@ func NewAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // authServiceClient implements AuthServiceClient. type authServiceClient struct { - getCurrentSession *connect.Client[v1.GetCurrentSessionRequest, v1.GetCurrentSessionResponse] - createSession *connect.Client[v1.CreateSessionRequest, v1.CreateSessionResponse] - deleteSession *connect.Client[v1.DeleteSessionRequest, emptypb.Empty] + getCurrentUser *connect.Client[v1.GetCurrentUserRequest, v1.GetCurrentUserResponse] + signIn *connect.Client[v1.SignInRequest, v1.SignInResponse] + signOut *connect.Client[v1.SignOutRequest, emptypb.Empty] + refreshToken *connect.Client[v1.RefreshTokenRequest, v1.RefreshTokenResponse] } -// GetCurrentSession calls memos.api.v1.AuthService.GetCurrentSession. -func (c *authServiceClient) GetCurrentSession(ctx context.Context, req *connect.Request[v1.GetCurrentSessionRequest]) (*connect.Response[v1.GetCurrentSessionResponse], error) { - return c.getCurrentSession.CallUnary(ctx, req) +// GetCurrentUser calls memos.api.v1.AuthService.GetCurrentUser. +func (c *authServiceClient) GetCurrentUser(ctx context.Context, req *connect.Request[v1.GetCurrentUserRequest]) (*connect.Response[v1.GetCurrentUserResponse], error) { + return c.getCurrentUser.CallUnary(ctx, req) } -// CreateSession calls memos.api.v1.AuthService.CreateSession. -func (c *authServiceClient) CreateSession(ctx context.Context, req *connect.Request[v1.CreateSessionRequest]) (*connect.Response[v1.CreateSessionResponse], error) { - return c.createSession.CallUnary(ctx, req) +// SignIn calls memos.api.v1.AuthService.SignIn. +func (c *authServiceClient) SignIn(ctx context.Context, req *connect.Request[v1.SignInRequest]) (*connect.Response[v1.SignInResponse], error) { + return c.signIn.CallUnary(ctx, req) } -// DeleteSession calls memos.api.v1.AuthService.DeleteSession. -func (c *authServiceClient) DeleteSession(ctx context.Context, req *connect.Request[v1.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deleteSession.CallUnary(ctx, req) +// SignOut calls memos.api.v1.AuthService.SignOut. +func (c *authServiceClient) SignOut(ctx context.Context, req *connect.Request[v1.SignOutRequest]) (*connect.Response[emptypb.Empty], error) { + return c.signOut.CallUnary(ctx, req) +} + +// RefreshToken calls memos.api.v1.AuthService.RefreshToken. +func (c *authServiceClient) RefreshToken(ctx context.Context, req *connect.Request[v1.RefreshTokenRequest]) (*connect.Response[v1.RefreshTokenResponse], error) { + return c.refreshToken.CallUnary(ctx, req) } // AuthServiceHandler is an implementation of the memos.api.v1.AuthService service. type AuthServiceHandler interface { - // GetCurrentSession returns the current active session information. - // This method is idempotent and safe, suitable for checking current session state. - GetCurrentSession(context.Context, *connect.Request[v1.GetCurrentSessionRequest]) (*connect.Response[v1.GetCurrentSessionResponse], error) - // CreateSession authenticates a user and creates a new session. - // Returns the authenticated user information upon successful authentication. - CreateSession(context.Context, *connect.Request[v1.CreateSessionRequest]) (*connect.Response[v1.CreateSessionResponse], error) - // DeleteSession terminates the current user session. - // This is an idempotent operation that invalidates the user's authentication. - DeleteSession(context.Context, *connect.Request[v1.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) + // GetCurrentUser returns the authenticated user's information. + // Validates the access token and returns user details. + // Similar to OIDC's /userinfo endpoint. + GetCurrentUser(context.Context, *connect.Request[v1.GetCurrentUserRequest]) (*connect.Response[v1.GetCurrentUserResponse], error) + // SignIn authenticates a user with credentials and returns tokens. + // On success, returns an access token and sets a refresh token cookie. + // Supports password-based and SSO authentication methods. + SignIn(context.Context, *connect.Request[v1.SignInRequest]) (*connect.Response[v1.SignInResponse], error) + // SignOut terminates the user's authentication. + // Revokes the refresh token and clears the authentication cookie. + SignOut(context.Context, *connect.Request[v1.SignOutRequest]) (*connect.Response[emptypb.Empty], error) + // RefreshToken exchanges a valid refresh token for a new access token. + // The refresh token is read from the HttpOnly cookie. + // Returns a new short-lived access token. + RefreshToken(context.Context, *connect.Request[v1.RefreshTokenRequest]) (*connect.Response[v1.RefreshTokenResponse], error) } // NewAuthServiceHandler builds an HTTP handler from the service implementation. It returns the path @@ -132,32 +157,40 @@ type AuthServiceHandler interface { // and JSON codecs. They also support gzip compression. func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { authServiceMethods := v1.File_api_v1_auth_service_proto.Services().ByName("AuthService").Methods() - authServiceGetCurrentSessionHandler := connect.NewUnaryHandler( - AuthServiceGetCurrentSessionProcedure, - svc.GetCurrentSession, - connect.WithSchema(authServiceMethods.ByName("GetCurrentSession")), + authServiceGetCurrentUserHandler := connect.NewUnaryHandler( + AuthServiceGetCurrentUserProcedure, + svc.GetCurrentUser, + connect.WithSchema(authServiceMethods.ByName("GetCurrentUser")), connect.WithHandlerOptions(opts...), ) - authServiceCreateSessionHandler := connect.NewUnaryHandler( - AuthServiceCreateSessionProcedure, - svc.CreateSession, - connect.WithSchema(authServiceMethods.ByName("CreateSession")), + authServiceSignInHandler := connect.NewUnaryHandler( + AuthServiceSignInProcedure, + svc.SignIn, + connect.WithSchema(authServiceMethods.ByName("SignIn")), connect.WithHandlerOptions(opts...), ) - authServiceDeleteSessionHandler := connect.NewUnaryHandler( - AuthServiceDeleteSessionProcedure, - svc.DeleteSession, - connect.WithSchema(authServiceMethods.ByName("DeleteSession")), + authServiceSignOutHandler := connect.NewUnaryHandler( + AuthServiceSignOutProcedure, + svc.SignOut, + connect.WithSchema(authServiceMethods.ByName("SignOut")), + connect.WithHandlerOptions(opts...), + ) + authServiceRefreshTokenHandler := connect.NewUnaryHandler( + AuthServiceRefreshTokenProcedure, + svc.RefreshToken, + connect.WithSchema(authServiceMethods.ByName("RefreshToken")), connect.WithHandlerOptions(opts...), ) return "/memos.api.v1.AuthService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { - case AuthServiceGetCurrentSessionProcedure: - authServiceGetCurrentSessionHandler.ServeHTTP(w, r) - case AuthServiceCreateSessionProcedure: - authServiceCreateSessionHandler.ServeHTTP(w, r) - case AuthServiceDeleteSessionProcedure: - authServiceDeleteSessionHandler.ServeHTTP(w, r) + case AuthServiceGetCurrentUserProcedure: + authServiceGetCurrentUserHandler.ServeHTTP(w, r) + case AuthServiceSignInProcedure: + authServiceSignInHandler.ServeHTTP(w, r) + case AuthServiceSignOutProcedure: + authServiceSignOutHandler.ServeHTTP(w, r) + case AuthServiceRefreshTokenProcedure: + authServiceRefreshTokenHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -167,14 +200,18 @@ func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption // UnimplementedAuthServiceHandler returns CodeUnimplemented from all methods. type UnimplementedAuthServiceHandler struct{} -func (UnimplementedAuthServiceHandler) GetCurrentSession(context.Context, *connect.Request[v1.GetCurrentSessionRequest]) (*connect.Response[v1.GetCurrentSessionResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.GetCurrentSession is not implemented")) +func (UnimplementedAuthServiceHandler) GetCurrentUser(context.Context, *connect.Request[v1.GetCurrentUserRequest]) (*connect.Response[v1.GetCurrentUserResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.GetCurrentUser is not implemented")) } -func (UnimplementedAuthServiceHandler) CreateSession(context.Context, *connect.Request[v1.CreateSessionRequest]) (*connect.Response[v1.CreateSessionResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.CreateSession is not implemented")) +func (UnimplementedAuthServiceHandler) SignIn(context.Context, *connect.Request[v1.SignInRequest]) (*connect.Response[v1.SignInResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.SignIn is not implemented")) } -func (UnimplementedAuthServiceHandler) DeleteSession(context.Context, *connect.Request[v1.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.DeleteSession is not implemented")) +func (UnimplementedAuthServiceHandler) SignOut(context.Context, *connect.Request[v1.SignOutRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.SignOut is not implemented")) +} + +func (UnimplementedAuthServiceHandler) RefreshToken(context.Context, *connect.Request[v1.RefreshTokenRequest]) (*connect.Response[v1.RefreshTokenResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.AuthService.RefreshToken is not implemented")) } diff --git a/proto/gen/api/v1/apiv1connect/user_service.connect.go b/proto/gen/api/v1/apiv1connect/user_service.connect.go index eab283971..e9034f940 100644 --- a/proto/gen/api/v1/apiv1connect/user_service.connect.go +++ b/proto/gen/api/v1/apiv1connect/user_service.connect.go @@ -59,21 +59,21 @@ const ( // UserServiceListUserSettingsProcedure is the fully-qualified name of the UserService's // ListUserSettings RPC. UserServiceListUserSettingsProcedure = "/memos.api.v1.UserService/ListUserSettings" - // UserServiceListUserAccessTokensProcedure is the fully-qualified name of the UserService's - // ListUserAccessTokens RPC. - UserServiceListUserAccessTokensProcedure = "/memos.api.v1.UserService/ListUserAccessTokens" - // UserServiceCreateUserAccessTokenProcedure is the fully-qualified name of the UserService's - // CreateUserAccessToken RPC. - UserServiceCreateUserAccessTokenProcedure = "/memos.api.v1.UserService/CreateUserAccessToken" - // UserServiceDeleteUserAccessTokenProcedure is the fully-qualified name of the UserService's - // DeleteUserAccessToken RPC. - UserServiceDeleteUserAccessTokenProcedure = "/memos.api.v1.UserService/DeleteUserAccessToken" - // UserServiceListUserSessionsProcedure is the fully-qualified name of the UserService's - // ListUserSessions RPC. - UserServiceListUserSessionsProcedure = "/memos.api.v1.UserService/ListUserSessions" - // UserServiceRevokeUserSessionProcedure is the fully-qualified name of the UserService's - // RevokeUserSession RPC. - UserServiceRevokeUserSessionProcedure = "/memos.api.v1.UserService/RevokeUserSession" + // UserServiceListPersonalAccessTokensProcedure is the fully-qualified name of the UserService's + // ListPersonalAccessTokens RPC. + UserServiceListPersonalAccessTokensProcedure = "/memos.api.v1.UserService/ListPersonalAccessTokens" + // UserServiceCreatePersonalAccessTokenProcedure is the fully-qualified name of the UserService's + // CreatePersonalAccessToken RPC. + UserServiceCreatePersonalAccessTokenProcedure = "/memos.api.v1.UserService/CreatePersonalAccessToken" + // UserServiceDeletePersonalAccessTokenProcedure is the fully-qualified name of the UserService's + // DeletePersonalAccessToken RPC. + UserServiceDeletePersonalAccessTokenProcedure = "/memos.api.v1.UserService/DeletePersonalAccessToken" + // UserServiceListSessionsProcedure is the fully-qualified name of the UserService's ListSessions + // RPC. + UserServiceListSessionsProcedure = "/memos.api.v1.UserService/ListSessions" + // UserServiceRevokeSessionProcedure is the fully-qualified name of the UserService's RevokeSession + // RPC. + UserServiceRevokeSessionProcedure = "/memos.api.v1.UserService/RevokeSession" // UserServiceListUserWebhooksProcedure is the fully-qualified name of the UserService's // ListUserWebhooks RPC. UserServiceListUserWebhooksProcedure = "/memos.api.v1.UserService/ListUserWebhooks" @@ -122,16 +122,21 @@ type UserServiceClient interface { UpdateUserSetting(context.Context, *connect.Request[v1.UpdateUserSettingRequest]) (*connect.Response[v1.UserSetting], error) // ListUserSettings returns a list of user settings. ListUserSettings(context.Context, *connect.Request[v1.ListUserSettingsRequest]) (*connect.Response[v1.ListUserSettingsResponse], error) - // ListUserAccessTokens returns a list of access tokens for a user. - ListUserAccessTokens(context.Context, *connect.Request[v1.ListUserAccessTokensRequest]) (*connect.Response[v1.ListUserAccessTokensResponse], error) - // CreateUserAccessToken creates a new access token for a user. - CreateUserAccessToken(context.Context, *connect.Request[v1.CreateUserAccessTokenRequest]) (*connect.Response[v1.UserAccessToken], error) - // DeleteUserAccessToken deletes an access token. - DeleteUserAccessToken(context.Context, *connect.Request[v1.DeleteUserAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) - // ListUserSessions returns a list of active sessions for a user. - ListUserSessions(context.Context, *connect.Request[v1.ListUserSessionsRequest]) (*connect.Response[v1.ListUserSessionsResponse], error) - // RevokeUserSession revokes a specific session for a user. - RevokeUserSession(context.Context, *connect.Request[v1.RevokeUserSessionRequest]) (*connect.Response[emptypb.Empty], error) + // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + ListPersonalAccessTokens(context.Context, *connect.Request[v1.ListPersonalAccessTokensRequest]) (*connect.Response[v1.ListPersonalAccessTokensResponse], error) + // CreatePersonalAccessToken creates a new Personal Access Token for a user. + // The token value is only returned once upon creation. + CreatePersonalAccessToken(context.Context, *connect.Request[v1.CreatePersonalAccessTokenRequest]) (*connect.Response[v1.CreatePersonalAccessTokenResponse], error) + // DeletePersonalAccessToken deletes a Personal Access Token. + DeletePersonalAccessToken(context.Context, *connect.Request[v1.DeletePersonalAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) + // ListSessions returns a list of active login sessions for a user. + // Each session represents a browser/device where the user is logged in. + // Sessions are backed by refresh tokens with sliding expiration. + ListSessions(context.Context, *connect.Request[v1.ListSessionsRequest]) (*connect.Response[v1.ListSessionsResponse], error) + // RevokeSession revokes a specific login session. + // This invalidates the refresh token, forcing re-authentication on that device. + RevokeSession(context.Context, *connect.Request[v1.RevokeSessionRequest]) (*connect.Response[emptypb.Empty], error) // ListUserWebhooks returns a list of webhooks for a user. ListUserWebhooks(context.Context, *connect.Request[v1.ListUserWebhooksRequest]) (*connect.Response[v1.ListUserWebhooksResponse], error) // CreateUserWebhook creates a new webhook for a user. @@ -219,34 +224,34 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(userServiceMethods.ByName("ListUserSettings")), connect.WithClientOptions(opts...), ), - listUserAccessTokens: connect.NewClient[v1.ListUserAccessTokensRequest, v1.ListUserAccessTokensResponse]( + listPersonalAccessTokens: connect.NewClient[v1.ListPersonalAccessTokensRequest, v1.ListPersonalAccessTokensResponse]( httpClient, - baseURL+UserServiceListUserAccessTokensProcedure, - connect.WithSchema(userServiceMethods.ByName("ListUserAccessTokens")), + baseURL+UserServiceListPersonalAccessTokensProcedure, + connect.WithSchema(userServiceMethods.ByName("ListPersonalAccessTokens")), connect.WithClientOptions(opts...), ), - createUserAccessToken: connect.NewClient[v1.CreateUserAccessTokenRequest, v1.UserAccessToken]( + createPersonalAccessToken: connect.NewClient[v1.CreatePersonalAccessTokenRequest, v1.CreatePersonalAccessTokenResponse]( httpClient, - baseURL+UserServiceCreateUserAccessTokenProcedure, - connect.WithSchema(userServiceMethods.ByName("CreateUserAccessToken")), + baseURL+UserServiceCreatePersonalAccessTokenProcedure, + connect.WithSchema(userServiceMethods.ByName("CreatePersonalAccessToken")), connect.WithClientOptions(opts...), ), - deleteUserAccessToken: connect.NewClient[v1.DeleteUserAccessTokenRequest, emptypb.Empty]( + deletePersonalAccessToken: connect.NewClient[v1.DeletePersonalAccessTokenRequest, emptypb.Empty]( httpClient, - baseURL+UserServiceDeleteUserAccessTokenProcedure, - connect.WithSchema(userServiceMethods.ByName("DeleteUserAccessToken")), + baseURL+UserServiceDeletePersonalAccessTokenProcedure, + connect.WithSchema(userServiceMethods.ByName("DeletePersonalAccessToken")), connect.WithClientOptions(opts...), ), - listUserSessions: connect.NewClient[v1.ListUserSessionsRequest, v1.ListUserSessionsResponse]( + listSessions: connect.NewClient[v1.ListSessionsRequest, v1.ListSessionsResponse]( httpClient, - baseURL+UserServiceListUserSessionsProcedure, - connect.WithSchema(userServiceMethods.ByName("ListUserSessions")), + baseURL+UserServiceListSessionsProcedure, + connect.WithSchema(userServiceMethods.ByName("ListSessions")), connect.WithClientOptions(opts...), ), - revokeUserSession: connect.NewClient[v1.RevokeUserSessionRequest, emptypb.Empty]( + revokeSession: connect.NewClient[v1.RevokeSessionRequest, emptypb.Empty]( httpClient, - baseURL+UserServiceRevokeUserSessionProcedure, - connect.WithSchema(userServiceMethods.ByName("RevokeUserSession")), + baseURL+UserServiceRevokeSessionProcedure, + connect.WithSchema(userServiceMethods.ByName("RevokeSession")), connect.WithClientOptions(opts...), ), listUserWebhooks: connect.NewClient[v1.ListUserWebhooksRequest, v1.ListUserWebhooksResponse]( @@ -296,28 +301,28 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // userServiceClient implements UserServiceClient. type userServiceClient struct { - listUsers *connect.Client[v1.ListUsersRequest, v1.ListUsersResponse] - getUser *connect.Client[v1.GetUserRequest, v1.User] - createUser *connect.Client[v1.CreateUserRequest, v1.User] - updateUser *connect.Client[v1.UpdateUserRequest, v1.User] - deleteUser *connect.Client[v1.DeleteUserRequest, emptypb.Empty] - listAllUserStats *connect.Client[v1.ListAllUserStatsRequest, v1.ListAllUserStatsResponse] - getUserStats *connect.Client[v1.GetUserStatsRequest, v1.UserStats] - getUserSetting *connect.Client[v1.GetUserSettingRequest, v1.UserSetting] - updateUserSetting *connect.Client[v1.UpdateUserSettingRequest, v1.UserSetting] - listUserSettings *connect.Client[v1.ListUserSettingsRequest, v1.ListUserSettingsResponse] - listUserAccessTokens *connect.Client[v1.ListUserAccessTokensRequest, v1.ListUserAccessTokensResponse] - createUserAccessToken *connect.Client[v1.CreateUserAccessTokenRequest, v1.UserAccessToken] - deleteUserAccessToken *connect.Client[v1.DeleteUserAccessTokenRequest, emptypb.Empty] - listUserSessions *connect.Client[v1.ListUserSessionsRequest, v1.ListUserSessionsResponse] - revokeUserSession *connect.Client[v1.RevokeUserSessionRequest, emptypb.Empty] - listUserWebhooks *connect.Client[v1.ListUserWebhooksRequest, v1.ListUserWebhooksResponse] - createUserWebhook *connect.Client[v1.CreateUserWebhookRequest, v1.UserWebhook] - updateUserWebhook *connect.Client[v1.UpdateUserWebhookRequest, v1.UserWebhook] - deleteUserWebhook *connect.Client[v1.DeleteUserWebhookRequest, emptypb.Empty] - listUserNotifications *connect.Client[v1.ListUserNotificationsRequest, v1.ListUserNotificationsResponse] - updateUserNotification *connect.Client[v1.UpdateUserNotificationRequest, v1.UserNotification] - deleteUserNotification *connect.Client[v1.DeleteUserNotificationRequest, emptypb.Empty] + listUsers *connect.Client[v1.ListUsersRequest, v1.ListUsersResponse] + getUser *connect.Client[v1.GetUserRequest, v1.User] + createUser *connect.Client[v1.CreateUserRequest, v1.User] + updateUser *connect.Client[v1.UpdateUserRequest, v1.User] + deleteUser *connect.Client[v1.DeleteUserRequest, emptypb.Empty] + listAllUserStats *connect.Client[v1.ListAllUserStatsRequest, v1.ListAllUserStatsResponse] + getUserStats *connect.Client[v1.GetUserStatsRequest, v1.UserStats] + getUserSetting *connect.Client[v1.GetUserSettingRequest, v1.UserSetting] + updateUserSetting *connect.Client[v1.UpdateUserSettingRequest, v1.UserSetting] + listUserSettings *connect.Client[v1.ListUserSettingsRequest, v1.ListUserSettingsResponse] + listPersonalAccessTokens *connect.Client[v1.ListPersonalAccessTokensRequest, v1.ListPersonalAccessTokensResponse] + createPersonalAccessToken *connect.Client[v1.CreatePersonalAccessTokenRequest, v1.CreatePersonalAccessTokenResponse] + deletePersonalAccessToken *connect.Client[v1.DeletePersonalAccessTokenRequest, emptypb.Empty] + listSessions *connect.Client[v1.ListSessionsRequest, v1.ListSessionsResponse] + revokeSession *connect.Client[v1.RevokeSessionRequest, emptypb.Empty] + listUserWebhooks *connect.Client[v1.ListUserWebhooksRequest, v1.ListUserWebhooksResponse] + createUserWebhook *connect.Client[v1.CreateUserWebhookRequest, v1.UserWebhook] + updateUserWebhook *connect.Client[v1.UpdateUserWebhookRequest, v1.UserWebhook] + deleteUserWebhook *connect.Client[v1.DeleteUserWebhookRequest, emptypb.Empty] + listUserNotifications *connect.Client[v1.ListUserNotificationsRequest, v1.ListUserNotificationsResponse] + updateUserNotification *connect.Client[v1.UpdateUserNotificationRequest, v1.UserNotification] + deleteUserNotification *connect.Client[v1.DeleteUserNotificationRequest, emptypb.Empty] } // ListUsers calls memos.api.v1.UserService.ListUsers. @@ -370,29 +375,29 @@ func (c *userServiceClient) ListUserSettings(ctx context.Context, req *connect.R return c.listUserSettings.CallUnary(ctx, req) } -// ListUserAccessTokens calls memos.api.v1.UserService.ListUserAccessTokens. -func (c *userServiceClient) ListUserAccessTokens(ctx context.Context, req *connect.Request[v1.ListUserAccessTokensRequest]) (*connect.Response[v1.ListUserAccessTokensResponse], error) { - return c.listUserAccessTokens.CallUnary(ctx, req) +// ListPersonalAccessTokens calls memos.api.v1.UserService.ListPersonalAccessTokens. +func (c *userServiceClient) ListPersonalAccessTokens(ctx context.Context, req *connect.Request[v1.ListPersonalAccessTokensRequest]) (*connect.Response[v1.ListPersonalAccessTokensResponse], error) { + return c.listPersonalAccessTokens.CallUnary(ctx, req) } -// CreateUserAccessToken calls memos.api.v1.UserService.CreateUserAccessToken. -func (c *userServiceClient) CreateUserAccessToken(ctx context.Context, req *connect.Request[v1.CreateUserAccessTokenRequest]) (*connect.Response[v1.UserAccessToken], error) { - return c.createUserAccessToken.CallUnary(ctx, req) +// CreatePersonalAccessToken calls memos.api.v1.UserService.CreatePersonalAccessToken. +func (c *userServiceClient) CreatePersonalAccessToken(ctx context.Context, req *connect.Request[v1.CreatePersonalAccessTokenRequest]) (*connect.Response[v1.CreatePersonalAccessTokenResponse], error) { + return c.createPersonalAccessToken.CallUnary(ctx, req) } -// DeleteUserAccessToken calls memos.api.v1.UserService.DeleteUserAccessToken. -func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, req *connect.Request[v1.DeleteUserAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deleteUserAccessToken.CallUnary(ctx, req) +// DeletePersonalAccessToken calls memos.api.v1.UserService.DeletePersonalAccessToken. +func (c *userServiceClient) DeletePersonalAccessToken(ctx context.Context, req *connect.Request[v1.DeletePersonalAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { + return c.deletePersonalAccessToken.CallUnary(ctx, req) } -// ListUserSessions calls memos.api.v1.UserService.ListUserSessions. -func (c *userServiceClient) ListUserSessions(ctx context.Context, req *connect.Request[v1.ListUserSessionsRequest]) (*connect.Response[v1.ListUserSessionsResponse], error) { - return c.listUserSessions.CallUnary(ctx, req) +// ListSessions calls memos.api.v1.UserService.ListSessions. +func (c *userServiceClient) ListSessions(ctx context.Context, req *connect.Request[v1.ListSessionsRequest]) (*connect.Response[v1.ListSessionsResponse], error) { + return c.listSessions.CallUnary(ctx, req) } -// RevokeUserSession calls memos.api.v1.UserService.RevokeUserSession. -func (c *userServiceClient) RevokeUserSession(ctx context.Context, req *connect.Request[v1.RevokeUserSessionRequest]) (*connect.Response[emptypb.Empty], error) { - return c.revokeUserSession.CallUnary(ctx, req) +// RevokeSession calls memos.api.v1.UserService.RevokeSession. +func (c *userServiceClient) RevokeSession(ctx context.Context, req *connect.Request[v1.RevokeSessionRequest]) (*connect.Response[emptypb.Empty], error) { + return c.revokeSession.CallUnary(ctx, req) } // ListUserWebhooks calls memos.api.v1.UserService.ListUserWebhooks. @@ -455,16 +460,21 @@ type UserServiceHandler interface { UpdateUserSetting(context.Context, *connect.Request[v1.UpdateUserSettingRequest]) (*connect.Response[v1.UserSetting], error) // ListUserSettings returns a list of user settings. ListUserSettings(context.Context, *connect.Request[v1.ListUserSettingsRequest]) (*connect.Response[v1.ListUserSettingsResponse], error) - // ListUserAccessTokens returns a list of access tokens for a user. - ListUserAccessTokens(context.Context, *connect.Request[v1.ListUserAccessTokensRequest]) (*connect.Response[v1.ListUserAccessTokensResponse], error) - // CreateUserAccessToken creates a new access token for a user. - CreateUserAccessToken(context.Context, *connect.Request[v1.CreateUserAccessTokenRequest]) (*connect.Response[v1.UserAccessToken], error) - // DeleteUserAccessToken deletes an access token. - DeleteUserAccessToken(context.Context, *connect.Request[v1.DeleteUserAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) - // ListUserSessions returns a list of active sessions for a user. - ListUserSessions(context.Context, *connect.Request[v1.ListUserSessionsRequest]) (*connect.Response[v1.ListUserSessionsResponse], error) - // RevokeUserSession revokes a specific session for a user. - RevokeUserSession(context.Context, *connect.Request[v1.RevokeUserSessionRequest]) (*connect.Response[emptypb.Empty], error) + // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + ListPersonalAccessTokens(context.Context, *connect.Request[v1.ListPersonalAccessTokensRequest]) (*connect.Response[v1.ListPersonalAccessTokensResponse], error) + // CreatePersonalAccessToken creates a new Personal Access Token for a user. + // The token value is only returned once upon creation. + CreatePersonalAccessToken(context.Context, *connect.Request[v1.CreatePersonalAccessTokenRequest]) (*connect.Response[v1.CreatePersonalAccessTokenResponse], error) + // DeletePersonalAccessToken deletes a Personal Access Token. + DeletePersonalAccessToken(context.Context, *connect.Request[v1.DeletePersonalAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) + // ListSessions returns a list of active login sessions for a user. + // Each session represents a browser/device where the user is logged in. + // Sessions are backed by refresh tokens with sliding expiration. + ListSessions(context.Context, *connect.Request[v1.ListSessionsRequest]) (*connect.Response[v1.ListSessionsResponse], error) + // RevokeSession revokes a specific login session. + // This invalidates the refresh token, forcing re-authentication on that device. + RevokeSession(context.Context, *connect.Request[v1.RevokeSessionRequest]) (*connect.Response[emptypb.Empty], error) // ListUserWebhooks returns a list of webhooks for a user. ListUserWebhooks(context.Context, *connect.Request[v1.ListUserWebhooksRequest]) (*connect.Response[v1.ListUserWebhooksResponse], error) // CreateUserWebhook creates a new webhook for a user. @@ -548,34 +558,34 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption connect.WithSchema(userServiceMethods.ByName("ListUserSettings")), connect.WithHandlerOptions(opts...), ) - userServiceListUserAccessTokensHandler := connect.NewUnaryHandler( - UserServiceListUserAccessTokensProcedure, - svc.ListUserAccessTokens, - connect.WithSchema(userServiceMethods.ByName("ListUserAccessTokens")), + userServiceListPersonalAccessTokensHandler := connect.NewUnaryHandler( + UserServiceListPersonalAccessTokensProcedure, + svc.ListPersonalAccessTokens, + connect.WithSchema(userServiceMethods.ByName("ListPersonalAccessTokens")), connect.WithHandlerOptions(opts...), ) - userServiceCreateUserAccessTokenHandler := connect.NewUnaryHandler( - UserServiceCreateUserAccessTokenProcedure, - svc.CreateUserAccessToken, - connect.WithSchema(userServiceMethods.ByName("CreateUserAccessToken")), + userServiceCreatePersonalAccessTokenHandler := connect.NewUnaryHandler( + UserServiceCreatePersonalAccessTokenProcedure, + svc.CreatePersonalAccessToken, + connect.WithSchema(userServiceMethods.ByName("CreatePersonalAccessToken")), connect.WithHandlerOptions(opts...), ) - userServiceDeleteUserAccessTokenHandler := connect.NewUnaryHandler( - UserServiceDeleteUserAccessTokenProcedure, - svc.DeleteUserAccessToken, - connect.WithSchema(userServiceMethods.ByName("DeleteUserAccessToken")), + userServiceDeletePersonalAccessTokenHandler := connect.NewUnaryHandler( + UserServiceDeletePersonalAccessTokenProcedure, + svc.DeletePersonalAccessToken, + connect.WithSchema(userServiceMethods.ByName("DeletePersonalAccessToken")), connect.WithHandlerOptions(opts...), ) - userServiceListUserSessionsHandler := connect.NewUnaryHandler( - UserServiceListUserSessionsProcedure, - svc.ListUserSessions, - connect.WithSchema(userServiceMethods.ByName("ListUserSessions")), + userServiceListSessionsHandler := connect.NewUnaryHandler( + UserServiceListSessionsProcedure, + svc.ListSessions, + connect.WithSchema(userServiceMethods.ByName("ListSessions")), connect.WithHandlerOptions(opts...), ) - userServiceRevokeUserSessionHandler := connect.NewUnaryHandler( - UserServiceRevokeUserSessionProcedure, - svc.RevokeUserSession, - connect.WithSchema(userServiceMethods.ByName("RevokeUserSession")), + userServiceRevokeSessionHandler := connect.NewUnaryHandler( + UserServiceRevokeSessionProcedure, + svc.RevokeSession, + connect.WithSchema(userServiceMethods.ByName("RevokeSession")), connect.WithHandlerOptions(opts...), ) userServiceListUserWebhooksHandler := connect.NewUnaryHandler( @@ -642,16 +652,16 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption userServiceUpdateUserSettingHandler.ServeHTTP(w, r) case UserServiceListUserSettingsProcedure: userServiceListUserSettingsHandler.ServeHTTP(w, r) - case UserServiceListUserAccessTokensProcedure: - userServiceListUserAccessTokensHandler.ServeHTTP(w, r) - case UserServiceCreateUserAccessTokenProcedure: - userServiceCreateUserAccessTokenHandler.ServeHTTP(w, r) - case UserServiceDeleteUserAccessTokenProcedure: - userServiceDeleteUserAccessTokenHandler.ServeHTTP(w, r) - case UserServiceListUserSessionsProcedure: - userServiceListUserSessionsHandler.ServeHTTP(w, r) - case UserServiceRevokeUserSessionProcedure: - userServiceRevokeUserSessionHandler.ServeHTTP(w, r) + case UserServiceListPersonalAccessTokensProcedure: + userServiceListPersonalAccessTokensHandler.ServeHTTP(w, r) + case UserServiceCreatePersonalAccessTokenProcedure: + userServiceCreatePersonalAccessTokenHandler.ServeHTTP(w, r) + case UserServiceDeletePersonalAccessTokenProcedure: + userServiceDeletePersonalAccessTokenHandler.ServeHTTP(w, r) + case UserServiceListSessionsProcedure: + userServiceListSessionsHandler.ServeHTTP(w, r) + case UserServiceRevokeSessionProcedure: + userServiceRevokeSessionHandler.ServeHTTP(w, r) case UserServiceListUserWebhooksProcedure: userServiceListUserWebhooksHandler.ServeHTTP(w, r) case UserServiceCreateUserWebhookProcedure: @@ -715,24 +725,24 @@ func (UnimplementedUserServiceHandler) ListUserSettings(context.Context, *connec return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListUserSettings is not implemented")) } -func (UnimplementedUserServiceHandler) ListUserAccessTokens(context.Context, *connect.Request[v1.ListUserAccessTokensRequest]) (*connect.Response[v1.ListUserAccessTokensResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListUserAccessTokens is not implemented")) +func (UnimplementedUserServiceHandler) ListPersonalAccessTokens(context.Context, *connect.Request[v1.ListPersonalAccessTokensRequest]) (*connect.Response[v1.ListPersonalAccessTokensResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListPersonalAccessTokens is not implemented")) } -func (UnimplementedUserServiceHandler) CreateUserAccessToken(context.Context, *connect.Request[v1.CreateUserAccessTokenRequest]) (*connect.Response[v1.UserAccessToken], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.CreateUserAccessToken is not implemented")) +func (UnimplementedUserServiceHandler) CreatePersonalAccessToken(context.Context, *connect.Request[v1.CreatePersonalAccessTokenRequest]) (*connect.Response[v1.CreatePersonalAccessTokenResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.CreatePersonalAccessToken is not implemented")) } -func (UnimplementedUserServiceHandler) DeleteUserAccessToken(context.Context, *connect.Request[v1.DeleteUserAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.DeleteUserAccessToken is not implemented")) +func (UnimplementedUserServiceHandler) DeletePersonalAccessToken(context.Context, *connect.Request[v1.DeletePersonalAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.DeletePersonalAccessToken is not implemented")) } -func (UnimplementedUserServiceHandler) ListUserSessions(context.Context, *connect.Request[v1.ListUserSessionsRequest]) (*connect.Response[v1.ListUserSessionsResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListUserSessions is not implemented")) +func (UnimplementedUserServiceHandler) ListSessions(context.Context, *connect.Request[v1.ListSessionsRequest]) (*connect.Response[v1.ListSessionsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListSessions is not implemented")) } -func (UnimplementedUserServiceHandler) RevokeUserSession(context.Context, *connect.Request[v1.RevokeUserSessionRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.RevokeUserSession is not implemented")) +func (UnimplementedUserServiceHandler) RevokeSession(context.Context, *connect.Request[v1.RevokeSessionRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.RevokeSession is not implemented")) } func (UnimplementedUserServiceHandler) ListUserWebhooks(context.Context, *connect.Request[v1.ListUserWebhooksRequest]) (*connect.Response[v1.ListUserWebhooksResponse], error) { diff --git a/proto/gen/api/v1/auth_service.pb.go b/proto/gen/api/v1/auth_service.pb.go index 7a2f58228..dade2e019 100644 --- a/proto/gen/api/v1/auth_service.pb.go +++ b/proto/gen/api/v1/auth_service.pb.go @@ -24,26 +24,26 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type GetCurrentSessionRequest struct { +type GetCurrentUserRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetCurrentSessionRequest) Reset() { - *x = GetCurrentSessionRequest{} +func (x *GetCurrentUserRequest) Reset() { + *x = GetCurrentUserRequest{} mi := &file_api_v1_auth_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetCurrentSessionRequest) String() string { +func (x *GetCurrentUserRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetCurrentSessionRequest) ProtoMessage() {} +func (*GetCurrentUserRequest) ProtoMessage() {} -func (x *GetCurrentSessionRequest) ProtoReflect() protoreflect.Message { +func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { mi := &file_api_v1_auth_service_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -55,35 +55,33 @@ func (x *GetCurrentSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCurrentSessionRequest.ProtoReflect.Descriptor instead. -func (*GetCurrentSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead. +func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{0} } -type GetCurrentSessionResponse struct { +type GetCurrentUserResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - // 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 + // The authenticated user's information. + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetCurrentSessionResponse) Reset() { - *x = GetCurrentSessionResponse{} +func (x *GetCurrentUserResponse) Reset() { + *x = GetCurrentUserResponse{} mi := &file_api_v1_auth_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetCurrentSessionResponse) String() string { +func (x *GetCurrentUserResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetCurrentSessionResponse) ProtoMessage() {} +func (*GetCurrentUserResponse) ProtoMessage() {} -func (x *GetCurrentSessionResponse) ProtoReflect() protoreflect.Message { +func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { mi := &file_api_v1_auth_service_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -95,53 +93,45 @@ func (x *GetCurrentSessionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCurrentSessionResponse.ProtoReflect.Descriptor instead. -func (*GetCurrentSessionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead. +func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{1} } -func (x *GetCurrentSessionResponse) GetUser() *User { +func (x *GetCurrentUserResponse) GetUser() *User { if x != nil { return x.User } return nil } -func (x *GetCurrentSessionResponse) GetLastAccessedAt() *timestamppb.Timestamp { - if x != nil { - return x.LastAccessedAt - } - return nil -} - -type CreateSessionRequest struct { +type SignInRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Provide one authentication method (username/password or SSO). - // Required field to specify the authentication method. + // Authentication credentials. Provide one method. // // Types that are valid to be assigned to Credentials: // - // *CreateSessionRequest_PasswordCredentials_ - // *CreateSessionRequest_SsoCredentials - Credentials isCreateSessionRequest_Credentials `protobuf_oneof:"credentials"` + // *SignInRequest_PasswordCredentials_ + // *SignInRequest_SsoCredentials + Credentials isSignInRequest_Credentials `protobuf_oneof:"credentials"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateSessionRequest) Reset() { - *x = CreateSessionRequest{} +func (x *SignInRequest) Reset() { + *x = SignInRequest{} mi := &file_api_v1_auth_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSessionRequest) String() string { +func (x *SignInRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSessionRequest) ProtoMessage() {} +func (*SignInRequest) ProtoMessage() {} -func (x *CreateSessionRequest) ProtoReflect() protoreflect.Message { +func (x *SignInRequest) ProtoReflect() protoreflect.Message { mi := &file_api_v1_auth_service_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -153,79 +143,82 @@ func (x *CreateSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSessionRequest.ProtoReflect.Descriptor instead. -func (*CreateSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use SignInRequest.ProtoReflect.Descriptor instead. +func (*SignInRequest) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{2} } -func (x *CreateSessionRequest) GetCredentials() isCreateSessionRequest_Credentials { +func (x *SignInRequest) GetCredentials() isSignInRequest_Credentials { if x != nil { return x.Credentials } return nil } -func (x *CreateSessionRequest) GetPasswordCredentials() *CreateSessionRequest_PasswordCredentials { +func (x *SignInRequest) GetPasswordCredentials() *SignInRequest_PasswordCredentials { if x != nil { - if x, ok := x.Credentials.(*CreateSessionRequest_PasswordCredentials_); ok { + if x, ok := x.Credentials.(*SignInRequest_PasswordCredentials_); ok { return x.PasswordCredentials } } return nil } -func (x *CreateSessionRequest) GetSsoCredentials() *CreateSessionRequest_SSOCredentials { +func (x *SignInRequest) GetSsoCredentials() *SignInRequest_SSOCredentials { if x != nil { - if x, ok := x.Credentials.(*CreateSessionRequest_SsoCredentials); ok { + if x, ok := x.Credentials.(*SignInRequest_SsoCredentials); ok { return x.SsoCredentials } } return nil } -type isCreateSessionRequest_Credentials interface { - isCreateSessionRequest_Credentials() +type isSignInRequest_Credentials interface { + isSignInRequest_Credentials() } -type CreateSessionRequest_PasswordCredentials_ struct { - // Username and password authentication method. - PasswordCredentials *CreateSessionRequest_PasswordCredentials `protobuf:"bytes,1,opt,name=password_credentials,json=passwordCredentials,proto3,oneof"` +type SignInRequest_PasswordCredentials_ struct { + // Username and password authentication. + PasswordCredentials *SignInRequest_PasswordCredentials `protobuf:"bytes,1,opt,name=password_credentials,json=passwordCredentials,proto3,oneof"` } -type CreateSessionRequest_SsoCredentials struct { - // SSO provider authentication method. - SsoCredentials *CreateSessionRequest_SSOCredentials `protobuf:"bytes,2,opt,name=sso_credentials,json=ssoCredentials,proto3,oneof"` +type SignInRequest_SsoCredentials struct { + // SSO provider authentication. + SsoCredentials *SignInRequest_SSOCredentials `protobuf:"bytes,2,opt,name=sso_credentials,json=ssoCredentials,proto3,oneof"` } -func (*CreateSessionRequest_PasswordCredentials_) isCreateSessionRequest_Credentials() {} +func (*SignInRequest_PasswordCredentials_) isSignInRequest_Credentials() {} -func (*CreateSessionRequest_SsoCredentials) isCreateSessionRequest_Credentials() {} +func (*SignInRequest_SsoCredentials) isSignInRequest_Credentials() {} -type CreateSessionResponse struct { +type SignInResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // The authenticated user information. + // The authenticated user's information. User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - // 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 + // The short-lived access token for API requests. + // Store in memory only, not in localStorage. + AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + // When the access token expires. + // Client should call RefreshToken before this time. + AccessTokenExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=access_token_expires_at,json=accessTokenExpiresAt,proto3" json:"access_token_expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *CreateSessionResponse) Reset() { - *x = CreateSessionResponse{} +func (x *SignInResponse) Reset() { + *x = SignInResponse{} mi := &file_api_v1_auth_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSessionResponse) String() string { +func (x *SignInResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSessionResponse) ProtoMessage() {} +func (*SignInResponse) ProtoMessage() {} -func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message { +func (x *SignInResponse) ProtoReflect() protoreflect.Message { mi := &file_api_v1_auth_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -237,45 +230,52 @@ func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSessionResponse.ProtoReflect.Descriptor instead. -func (*CreateSessionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use SignInResponse.ProtoReflect.Descriptor instead. +func (*SignInResponse) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{3} } -func (x *CreateSessionResponse) GetUser() *User { +func (x *SignInResponse) GetUser() *User { if x != nil { return x.User } return nil } -func (x *CreateSessionResponse) GetLastAccessedAt() *timestamppb.Timestamp { +func (x *SignInResponse) GetAccessToken() string { if x != nil { - return x.LastAccessedAt + return x.AccessToken + } + return "" +} + +func (x *SignInResponse) GetAccessTokenExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.AccessTokenExpiresAt } return nil } -type DeleteSessionRequest struct { +type SignOutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteSessionRequest) Reset() { - *x = DeleteSessionRequest{} +func (x *SignOutRequest) Reset() { + *x = SignOutRequest{} mi := &file_api_v1_auth_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSessionRequest) String() string { +func (x *SignOutRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSessionRequest) ProtoMessage() {} +func (*SignOutRequest) ProtoMessage() {} -func (x *DeleteSessionRequest) ProtoReflect() protoreflect.Message { +func (x *SignOutRequest) ProtoReflect() protoreflect.Message { mi := &file_api_v1_auth_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -287,39 +287,127 @@ func (x *DeleteSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSessionRequest.ProtoReflect.Descriptor instead. -func (*DeleteSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use SignOutRequest.ProtoReflect.Descriptor instead. +func (*SignOutRequest) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{4} } +type RefreshTokenRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokenRequest) Reset() { + *x = RefreshTokenRequest{} + mi := &file_api_v1_auth_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokenRequest) ProtoMessage() {} + +func (x *RefreshTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_auth_service_proto_msgTypes[5] + 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 RefreshTokenRequest.ProtoReflect.Descriptor instead. +func (*RefreshTokenRequest) Descriptor() ([]byte, []int) { + return file_api_v1_auth_service_proto_rawDescGZIP(), []int{5} +} + +type RefreshTokenResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The new short-lived access token. + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + // When the access token expires. + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokenResponse) Reset() { + *x = RefreshTokenResponse{} + mi := &file_api_v1_auth_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokenResponse) ProtoMessage() {} + +func (x *RefreshTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_auth_service_proto_msgTypes[6] + 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 RefreshTokenResponse.ProtoReflect.Descriptor instead. +func (*RefreshTokenResponse) Descriptor() ([]byte, []int) { + return file_api_v1_auth_service_proto_rawDescGZIP(), []int{6} +} + +func (x *RefreshTokenResponse) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *RefreshTokenResponse) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + // Nested message for password-based authentication credentials. -type CreateSessionRequest_PasswordCredentials struct { +type SignInRequest_PasswordCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` // The username to sign in with. - // Required field for password-based authentication. Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` // The password to sign in with. - // Required field for password-based authentication. Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateSessionRequest_PasswordCredentials) Reset() { - *x = CreateSessionRequest_PasswordCredentials{} - mi := &file_api_v1_auth_service_proto_msgTypes[5] +func (x *SignInRequest_PasswordCredentials) Reset() { + *x = SignInRequest_PasswordCredentials{} + mi := &file_api_v1_auth_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSessionRequest_PasswordCredentials) String() string { +func (x *SignInRequest_PasswordCredentials) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSessionRequest_PasswordCredentials) ProtoMessage() {} +func (*SignInRequest_PasswordCredentials) ProtoMessage() {} -func (x *CreateSessionRequest_PasswordCredentials) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_auth_service_proto_msgTypes[5] +func (x *SignInRequest_PasswordCredentials) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_auth_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -330,19 +418,19 @@ func (x *CreateSessionRequest_PasswordCredentials) ProtoReflect() protoreflect.M return mi.MessageOf(x) } -// Deprecated: Use CreateSessionRequest_PasswordCredentials.ProtoReflect.Descriptor instead. -func (*CreateSessionRequest_PasswordCredentials) Descriptor() ([]byte, []int) { +// Deprecated: Use SignInRequest_PasswordCredentials.ProtoReflect.Descriptor instead. +func (*SignInRequest_PasswordCredentials) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{2, 0} } -func (x *CreateSessionRequest_PasswordCredentials) GetUsername() string { +func (x *SignInRequest_PasswordCredentials) GetUsername() string { if x != nil { return x.Username } return "" } -func (x *CreateSessionRequest_PasswordCredentials) GetPassword() string { +func (x *SignInRequest_PasswordCredentials) GetPassword() string { if x != nil { return x.Password } @@ -350,39 +438,36 @@ func (x *CreateSessionRequest_PasswordCredentials) GetPassword() string { } // Nested message for SSO authentication credentials. -type CreateSessionRequest_SSOCredentials struct { +type SignInRequest_SSOCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` // The ID of the SSO provider. - // Required field to identify the SSO provider. IdpId int32 `protobuf:"varint,1,opt,name=idp_id,json=idpId,proto3" json:"idp_id,omitempty"` // The authorization code from the SSO provider. - // Required field for completing the SSO flow. Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` // The redirect URI used in the SSO flow. - // Required field for security validation. RedirectUri string `protobuf:"bytes,3,opt,name=redirect_uri,json=redirectUri,proto3" json:"redirect_uri,omitempty"` // The PKCE code verifier for enhanced security (RFC 7636). - // Optional field - if provided, enables PKCE flow protection against authorization code interception. + // Optional - enables PKCE flow protection against authorization code interception. CodeVerifier string `protobuf:"bytes,4,opt,name=code_verifier,json=codeVerifier,proto3" json:"code_verifier,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateSessionRequest_SSOCredentials) Reset() { - *x = CreateSessionRequest_SSOCredentials{} - mi := &file_api_v1_auth_service_proto_msgTypes[6] +func (x *SignInRequest_SSOCredentials) Reset() { + *x = SignInRequest_SSOCredentials{} + mi := &file_api_v1_auth_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSessionRequest_SSOCredentials) String() string { +func (x *SignInRequest_SSOCredentials) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSessionRequest_SSOCredentials) ProtoMessage() {} +func (*SignInRequest_SSOCredentials) ProtoMessage() {} -func (x *CreateSessionRequest_SSOCredentials) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_auth_service_proto_msgTypes[6] +func (x *SignInRequest_SSOCredentials) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_auth_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -393,33 +478,33 @@ func (x *CreateSessionRequest_SSOCredentials) ProtoReflect() protoreflect.Messag return mi.MessageOf(x) } -// Deprecated: Use CreateSessionRequest_SSOCredentials.ProtoReflect.Descriptor instead. -func (*CreateSessionRequest_SSOCredentials) Descriptor() ([]byte, []int) { +// Deprecated: Use SignInRequest_SSOCredentials.ProtoReflect.Descriptor instead. +func (*SignInRequest_SSOCredentials) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{2, 1} } -func (x *CreateSessionRequest_SSOCredentials) GetIdpId() int32 { +func (x *SignInRequest_SSOCredentials) GetIdpId() int32 { if x != nil { return x.IdpId } return 0 } -func (x *CreateSessionRequest_SSOCredentials) GetCode() string { +func (x *SignInRequest_SSOCredentials) GetCode() string { if x != nil { return x.Code } return "" } -func (x *CreateSessionRequest_SSOCredentials) GetRedirectUri() string { +func (x *SignInRequest_SSOCredentials) GetRedirectUri() string { if x != nil { return x.RedirectUri } return "" } -func (x *CreateSessionRequest_SSOCredentials) GetCodeVerifier() string { +func (x *SignInRequest_SSOCredentials) GetCodeVerifier() string { if x != nil { return x.CodeVerifier } @@ -430,14 +515,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\"\x89\x01\n" + - "\x19GetCurrentSessionResponse\x12&\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\"\xe3\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\x1aW\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\"\x17\n" + + "\x15GetCurrentUserRequest\"@\n" + + "\x16GetCurrentUserResponse\x12&\n" + + "\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\"\xce\x03\n" + + "\rSignInRequest\x12d\n" + + "\x14password_credentials\x18\x01 \x01(\v2/.memos.api.v1.SignInRequest.PasswordCredentialsH\x00R\x13passwordCredentials\x12U\n" + + "\x0fsso_credentials\x18\x02 \x01(\v2*.memos.api.v1.SignInRequest.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\x1a\x97\x01\n" + @@ -446,15 +530,22 @@ const file_api_v1_auth_service_proto_rawDesc = "" + "\x04code\x18\x02 \x01(\tB\x03\xe0A\x02R\x04code\x12&\n" + "\fredirect_uri\x18\x03 \x01(\tB\x03\xe0A\x02R\vredirectUri\x12(\n" + "\rcode_verifier\x18\x04 \x01(\tB\x03\xe0A\x01R\fcodeVerifierB\r\n" + - "\vcredentials\"\x85\x01\n" + - "\x15CreateSessionResponse\x12&\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" + - "\rCreateSession\x12\".memos.api.v1.CreateSessionRequest\x1a#.memos.api.v1.CreateSessionResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/auth/sessions\x12r\n" + - "\rDeleteSession\x12\".memos.api.v1.DeleteSessionRequest\x1a\x16.google.protobuf.Empty\"%\x82\xd3\xe4\x93\x02\x1f*\x1d/api/v1/auth/sessions/currentB\xa8\x01\n" + + "\vcredentials\"\xae\x01\n" + + "\x0eSignInResponse\x12&\n" + + "\x04user\x18\x01 \x01(\v2\x12.memos.api.v1.UserR\x04user\x12!\n" + + "\faccess_token\x18\x02 \x01(\tR\vaccessToken\x12Q\n" + + "\x17access_token_expires_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x14accessTokenExpiresAt\"\x10\n" + + "\x0eSignOutRequest\"\x15\n" + + "\x13RefreshTokenRequest\"t\n" + + "\x14RefreshTokenResponse\x12!\n" + + "\faccess_token\x18\x01 \x01(\tR\vaccessToken\x129\n" + + "\n" + + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt2\xbf\x03\n" + + "\vAuthService\x12t\n" + + "\x0eGetCurrentUser\x12#.memos.api.v1.GetCurrentUserRequest\x1a$.memos.api.v1.GetCurrentUserResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/api/v1/auth/me\x12c\n" + + "\x06SignIn\x12\x1b.memos.api.v1.SignInRequest\x1a\x1c.memos.api.v1.SignInResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/api/v1/auth/signin\x12]\n" + + "\aSignOut\x12\x1c.memos.api.v1.SignOutRequest\x1a\x16.google.protobuf.Empty\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x14/api/v1/auth/signout\x12v\n" + + "\fRefreshToken\x12!.memos.api.v1.RefreshTokenRequest\x1a\".memos.api.v1.RefreshTokenResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/auth/refreshB\xa8\x01\n" + "\x10com.memos.api.v1B\x10AuthServiceProtoP\x01Z0github.com/usememos/memos/proto/gen/api/v1;apiv1\xa2\x02\x03MAX\xaa\x02\fMemos.Api.V1\xca\x02\fMemos\\Api\\V1\xe2\x02\x18Memos\\Api\\V1\\GPBMetadata\xea\x02\x0eMemos::Api::V1b\x06proto3" var ( @@ -469,37 +560,41 @@ func file_api_v1_auth_service_proto_rawDescGZIP() []byte { return file_api_v1_auth_service_proto_rawDescData } -var file_api_v1_auth_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_api_v1_auth_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_api_v1_auth_service_proto_goTypes = []any{ - (*GetCurrentSessionRequest)(nil), // 0: memos.api.v1.GetCurrentSessionRequest - (*GetCurrentSessionResponse)(nil), // 1: memos.api.v1.GetCurrentSessionResponse - (*CreateSessionRequest)(nil), // 2: memos.api.v1.CreateSessionRequest - (*CreateSessionResponse)(nil), // 3: memos.api.v1.CreateSessionResponse - (*DeleteSessionRequest)(nil), // 4: memos.api.v1.DeleteSessionRequest - (*CreateSessionRequest_PasswordCredentials)(nil), // 5: memos.api.v1.CreateSessionRequest.PasswordCredentials - (*CreateSessionRequest_SSOCredentials)(nil), // 6: memos.api.v1.CreateSessionRequest.SSOCredentials - (*User)(nil), // 7: memos.api.v1.User - (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 9: google.protobuf.Empty + (*GetCurrentUserRequest)(nil), // 0: memos.api.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 1: memos.api.v1.GetCurrentUserResponse + (*SignInRequest)(nil), // 2: memos.api.v1.SignInRequest + (*SignInResponse)(nil), // 3: memos.api.v1.SignInResponse + (*SignOutRequest)(nil), // 4: memos.api.v1.SignOutRequest + (*RefreshTokenRequest)(nil), // 5: memos.api.v1.RefreshTokenRequest + (*RefreshTokenResponse)(nil), // 6: memos.api.v1.RefreshTokenResponse + (*SignInRequest_PasswordCredentials)(nil), // 7: memos.api.v1.SignInRequest.PasswordCredentials + (*SignInRequest_SSOCredentials)(nil), // 8: memos.api.v1.SignInRequest.SSOCredentials + (*User)(nil), // 9: memos.api.v1.User + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 11: google.protobuf.Empty } 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.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.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 - 1, // 9: memos.api.v1.AuthService.GetCurrentSession:output_type -> memos.api.v1.GetCurrentSessionResponse - 3, // 10: memos.api.v1.AuthService.CreateSession:output_type -> memos.api.v1.CreateSessionResponse - 9, // 11: memos.api.v1.AuthService.DeleteSession:output_type -> google.protobuf.Empty - 9, // [9:12] is the sub-list for method output_type - 6, // [6:9] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 9, // 0: memos.api.v1.GetCurrentUserResponse.user:type_name -> memos.api.v1.User + 7, // 1: memos.api.v1.SignInRequest.password_credentials:type_name -> memos.api.v1.SignInRequest.PasswordCredentials + 8, // 2: memos.api.v1.SignInRequest.sso_credentials:type_name -> memos.api.v1.SignInRequest.SSOCredentials + 9, // 3: memos.api.v1.SignInResponse.user:type_name -> memos.api.v1.User + 10, // 4: memos.api.v1.SignInResponse.access_token_expires_at:type_name -> google.protobuf.Timestamp + 10, // 5: memos.api.v1.RefreshTokenResponse.expires_at:type_name -> google.protobuf.Timestamp + 0, // 6: memos.api.v1.AuthService.GetCurrentUser:input_type -> memos.api.v1.GetCurrentUserRequest + 2, // 7: memos.api.v1.AuthService.SignIn:input_type -> memos.api.v1.SignInRequest + 4, // 8: memos.api.v1.AuthService.SignOut:input_type -> memos.api.v1.SignOutRequest + 5, // 9: memos.api.v1.AuthService.RefreshToken:input_type -> memos.api.v1.RefreshTokenRequest + 1, // 10: memos.api.v1.AuthService.GetCurrentUser:output_type -> memos.api.v1.GetCurrentUserResponse + 3, // 11: memos.api.v1.AuthService.SignIn:output_type -> memos.api.v1.SignInResponse + 11, // 12: memos.api.v1.AuthService.SignOut:output_type -> google.protobuf.Empty + 6, // 13: memos.api.v1.AuthService.RefreshToken:output_type -> memos.api.v1.RefreshTokenResponse + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_api_v1_auth_service_proto_init() } @@ -509,8 +604,8 @@ func file_api_v1_auth_service_proto_init() { } file_api_v1_user_service_proto_init() file_api_v1_auth_service_proto_msgTypes[2].OneofWrappers = []any{ - (*CreateSessionRequest_PasswordCredentials_)(nil), - (*CreateSessionRequest_SsoCredentials)(nil), + (*SignInRequest_PasswordCredentials_)(nil), + (*SignInRequest_SsoCredentials)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -518,7 +613,7 @@ func file_api_v1_auth_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_auth_service_proto_rawDesc), len(file_api_v1_auth_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/api/v1/auth_service.pb.gw.go b/proto/gen/api/v1/auth_service.pb.gw.go index 1b603fa33..77abeeda8 100644 --- a/proto/gen/api/v1/auth_service.pb.gw.go +++ b/proto/gen/api/v1/auth_service.pb.gw.go @@ -35,30 +35,30 @@ var ( _ = metadata.Join ) -func request_AuthService_GetCurrentSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_AuthService_GetCurrentUser_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq GetCurrentSessionRequest + protoReq GetCurrentUserRequest metadata runtime.ServerMetadata ) if req.Body != nil { _, _ = io.Copy(io.Discard, req.Body) } - msg, err := client.GetCurrentSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.GetCurrentUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_AuthService_GetCurrentSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_AuthService_GetCurrentUser_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq GetCurrentSessionRequest + protoReq GetCurrentUserRequest metadata runtime.ServerMetadata ) - msg, err := server.GetCurrentSession(ctx, &protoReq) + msg, err := server.GetCurrentUser(ctx, &protoReq) return msg, metadata, err } -func request_AuthService_CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_AuthService_SignIn_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq CreateSessionRequest + protoReq SignInRequest metadata runtime.ServerMetadata ) if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { @@ -67,40 +67,67 @@ func request_AuthService_CreateSession_0(ctx context.Context, marshaler runtime. if req.Body != nil { _, _ = io.Copy(io.Discard, req.Body) } - msg, err := client.CreateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.SignIn(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_AuthService_CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_AuthService_SignIn_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq CreateSessionRequest + protoReq SignInRequest metadata runtime.ServerMetadata ) if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.CreateSession(ctx, &protoReq) + msg, err := server.SignIn(ctx, &protoReq) return msg, metadata, err } -func request_AuthService_DeleteSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_AuthService_SignOut_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq DeleteSessionRequest + protoReq SignOutRequest metadata runtime.ServerMetadata ) if req.Body != nil { _, _ = io.Copy(io.Discard, req.Body) } - msg, err := client.DeleteSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.SignOut(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_AuthService_DeleteSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_AuthService_SignOut_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq DeleteSessionRequest + protoReq SignOutRequest metadata runtime.ServerMetadata ) - msg, err := server.DeleteSession(ctx, &protoReq) + msg, err := server.SignOut(ctx, &protoReq) + return msg, metadata, err +} + +func request_AuthService_RefreshToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RefreshTokenRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.RefreshToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AuthService_RefreshToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RefreshTokenRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.RefreshToken(ctx, &protoReq) return msg, metadata, err } @@ -110,65 +137,85 @@ func local_request_AuthService_DeleteSession_0(ctx context.Context, marshaler ru // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthServiceHandlerFromEndpoint instead. // GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer) error { - mux.Handle(http.MethodGet, pattern_AuthService_GetCurrentSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_AuthService_GetCurrentUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/GetCurrentSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions/current")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/GetCurrentUser", runtime.WithHTTPPathPattern("/api/v1/auth/me")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_AuthService_GetCurrentSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_AuthService_GetCurrentUser_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_GetCurrentSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_GetCurrentUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_AuthService_CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_AuthService_SignIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/CreateSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/SignIn", runtime.WithHTTPPathPattern("/api/v1/auth/signin")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_AuthService_CreateSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_AuthService_SignIn_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_CreateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_SignIn_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_AuthService_DeleteSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_AuthService_SignOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/DeleteSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions/current")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/SignOut", runtime.WithHTTPPathPattern("/api/v1/auth/signout")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_AuthService_DeleteSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_AuthService_SignOut_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_DeleteSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_SignOut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AuthService_RefreshToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.AuthService/RefreshToken", runtime.WithHTTPPathPattern("/api/v1/auth/refresh")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthService_RefreshToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AuthService_RefreshToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) return nil @@ -210,68 +257,87 @@ func RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in // "AuthServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthServiceClient) error { - mux.Handle(http.MethodGet, pattern_AuthService_GetCurrentSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_AuthService_GetCurrentUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/GetCurrentSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions/current")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/GetCurrentUser", runtime.WithHTTPPathPattern("/api/v1/auth/me")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_AuthService_GetCurrentSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_AuthService_GetCurrentUser_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_GetCurrentSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_GetCurrentUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_AuthService_CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_AuthService_SignIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/CreateSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/SignIn", runtime.WithHTTPPathPattern("/api/v1/auth/signin")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_AuthService_CreateSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_AuthService_SignIn_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_CreateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_SignIn_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_AuthService_DeleteSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_AuthService_SignOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/DeleteSession", runtime.WithHTTPPathPattern("/api/v1/auth/sessions/current")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/SignOut", runtime.WithHTTPPathPattern("/api/v1/auth/signout")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_AuthService_DeleteSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_AuthService_SignOut_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AuthService_DeleteSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AuthService_SignOut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AuthService_RefreshToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.AuthService/RefreshToken", runtime.WithHTTPPathPattern("/api/v1/auth/refresh")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthService_RefreshToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AuthService_RefreshToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) return nil } var ( - pattern_AuthService_GetCurrentSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "auth", "sessions", "current"}, "")) - pattern_AuthService_CreateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "sessions"}, "")) - pattern_AuthService_DeleteSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "auth", "sessions", "current"}, "")) + pattern_AuthService_GetCurrentUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "me"}, "")) + pattern_AuthService_SignIn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "signin"}, "")) + pattern_AuthService_SignOut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "signout"}, "")) + pattern_AuthService_RefreshToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "refresh"}, "")) ) var ( - forward_AuthService_GetCurrentSession_0 = runtime.ForwardResponseMessage - forward_AuthService_CreateSession_0 = runtime.ForwardResponseMessage - forward_AuthService_DeleteSession_0 = runtime.ForwardResponseMessage + forward_AuthService_GetCurrentUser_0 = runtime.ForwardResponseMessage + forward_AuthService_SignIn_0 = runtime.ForwardResponseMessage + forward_AuthService_SignOut_0 = runtime.ForwardResponseMessage + forward_AuthService_RefreshToken_0 = runtime.ForwardResponseMessage ) diff --git a/proto/gen/api/v1/auth_service_grpc.pb.go b/proto/gen/api/v1/auth_service_grpc.pb.go index 9f6555643..b9cef0215 100644 --- a/proto/gen/api/v1/auth_service_grpc.pb.go +++ b/proto/gen/api/v1/auth_service_grpc.pb.go @@ -20,24 +20,31 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AuthService_GetCurrentSession_FullMethodName = "/memos.api.v1.AuthService/GetCurrentSession" - AuthService_CreateSession_FullMethodName = "/memos.api.v1.AuthService/CreateSession" - AuthService_DeleteSession_FullMethodName = "/memos.api.v1.AuthService/DeleteSession" + AuthService_GetCurrentUser_FullMethodName = "/memos.api.v1.AuthService/GetCurrentUser" + AuthService_SignIn_FullMethodName = "/memos.api.v1.AuthService/SignIn" + AuthService_SignOut_FullMethodName = "/memos.api.v1.AuthService/SignOut" + AuthService_RefreshToken_FullMethodName = "/memos.api.v1.AuthService/RefreshToken" ) // AuthServiceClient is the client API for AuthService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AuthServiceClient interface { - // GetCurrentSession returns the current active session information. - // This method is idempotent and safe, suitable for checking current session state. - GetCurrentSession(ctx context.Context, in *GetCurrentSessionRequest, opts ...grpc.CallOption) (*GetCurrentSessionResponse, error) - // CreateSession authenticates a user and creates a new session. - // Returns the authenticated user information upon successful authentication. - CreateSession(ctx context.Context, in *CreateSessionRequest, opts ...grpc.CallOption) (*CreateSessionResponse, error) - // DeleteSession terminates the current user session. - // This is an idempotent operation that invalidates the user's authentication. - DeleteSession(ctx context.Context, in *DeleteSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // GetCurrentUser returns the authenticated user's information. + // Validates the access token and returns user details. + // Similar to OIDC's /userinfo endpoint. + GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) + // SignIn authenticates a user with credentials and returns tokens. + // On success, returns an access token and sets a refresh token cookie. + // Supports password-based and SSO authentication methods. + SignIn(ctx context.Context, in *SignInRequest, opts ...grpc.CallOption) (*SignInResponse, error) + // SignOut terminates the user's authentication. + // Revokes the refresh token and clears the authentication cookie. + SignOut(ctx context.Context, in *SignOutRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // RefreshToken exchanges a valid refresh token for a new access token. + // The refresh token is read from the HttpOnly cookie. + // Returns a new short-lived access token. + RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error) } type authServiceClient struct { @@ -48,30 +55,40 @@ func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { return &authServiceClient{cc} } -func (c *authServiceClient) GetCurrentSession(ctx context.Context, in *GetCurrentSessionRequest, opts ...grpc.CallOption) (*GetCurrentSessionResponse, error) { +func (c *authServiceClient) GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetCurrentSessionResponse) - err := c.cc.Invoke(ctx, AuthService_GetCurrentSession_FullMethodName, in, out, cOpts...) + out := new(GetCurrentUserResponse) + err := c.cc.Invoke(ctx, AuthService_GetCurrentUser_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *authServiceClient) CreateSession(ctx context.Context, in *CreateSessionRequest, opts ...grpc.CallOption) (*CreateSessionResponse, error) { +func (c *authServiceClient) SignIn(ctx context.Context, in *SignInRequest, opts ...grpc.CallOption) (*SignInResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(CreateSessionResponse) - err := c.cc.Invoke(ctx, AuthService_CreateSession_FullMethodName, in, out, cOpts...) + out := new(SignInResponse) + err := c.cc.Invoke(ctx, AuthService_SignIn_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *authServiceClient) DeleteSession(ctx context.Context, in *DeleteSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *authServiceClient) SignOut(ctx context.Context, in *SignOutRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, AuthService_DeleteSession_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, AuthService_SignOut_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RefreshTokenResponse) + err := c.cc.Invoke(ctx, AuthService_RefreshToken_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -82,15 +99,21 @@ func (c *authServiceClient) DeleteSession(ctx context.Context, in *DeleteSession // All implementations must embed UnimplementedAuthServiceServer // for forward compatibility. type AuthServiceServer interface { - // GetCurrentSession returns the current active session information. - // This method is idempotent and safe, suitable for checking current session state. - GetCurrentSession(context.Context, *GetCurrentSessionRequest) (*GetCurrentSessionResponse, error) - // CreateSession authenticates a user and creates a new session. - // Returns the authenticated user information upon successful authentication. - CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) - // DeleteSession terminates the current user session. - // This is an idempotent operation that invalidates the user's authentication. - DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) + // GetCurrentUser returns the authenticated user's information. + // Validates the access token and returns user details. + // Similar to OIDC's /userinfo endpoint. + GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) + // SignIn authenticates a user with credentials and returns tokens. + // On success, returns an access token and sets a refresh token cookie. + // Supports password-based and SSO authentication methods. + SignIn(context.Context, *SignInRequest) (*SignInResponse, error) + // SignOut terminates the user's authentication. + // Revokes the refresh token and clears the authentication cookie. + SignOut(context.Context, *SignOutRequest) (*emptypb.Empty, error) + // RefreshToken exchanges a valid refresh token for a new access token. + // The refresh token is read from the HttpOnly cookie. + // Returns a new short-lived access token. + RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) mustEmbedUnimplementedAuthServiceServer() } @@ -101,14 +124,17 @@ type AuthServiceServer interface { // pointer dereference when methods are called. type UnimplementedAuthServiceServer struct{} -func (UnimplementedAuthServiceServer) GetCurrentSession(context.Context, *GetCurrentSessionRequest) (*GetCurrentSessionResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetCurrentSession not implemented") +func (UnimplementedAuthServiceServer) GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCurrentUser not implemented") } -func (UnimplementedAuthServiceServer) CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) { - return nil, status.Error(codes.Unimplemented, "method CreateSession not implemented") +func (UnimplementedAuthServiceServer) SignIn(context.Context, *SignInRequest) (*SignInResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SignIn not implemented") } -func (UnimplementedAuthServiceServer) DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method DeleteSession not implemented") +func (UnimplementedAuthServiceServer) SignOut(context.Context, *SignOutRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SignOut not implemented") +} +func (UnimplementedAuthServiceServer) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RefreshToken not implemented") } func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} func (UnimplementedAuthServiceServer) testEmbeddedByValue() {} @@ -131,56 +157,74 @@ func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { s.RegisterService(&AuthService_ServiceDesc, srv) } -func _AuthService_GetCurrentSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetCurrentSessionRequest) +func _AuthService_GetCurrentUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCurrentUserRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AuthServiceServer).GetCurrentSession(ctx, in) + return srv.(AuthServiceServer).GetCurrentUser(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: AuthService_GetCurrentSession_FullMethodName, + FullMethod: AuthService_GetCurrentUser_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).GetCurrentSession(ctx, req.(*GetCurrentSessionRequest)) + return srv.(AuthServiceServer).GetCurrentUser(ctx, req.(*GetCurrentUserRequest)) } return interceptor(ctx, in, info, handler) } -func _AuthService_CreateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateSessionRequest) +func _AuthService_SignIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SignInRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AuthServiceServer).CreateSession(ctx, in) + return srv.(AuthServiceServer).SignIn(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: AuthService_CreateSession_FullMethodName, + FullMethod: AuthService_SignIn_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).CreateSession(ctx, req.(*CreateSessionRequest)) + return srv.(AuthServiceServer).SignIn(ctx, req.(*SignInRequest)) } return interceptor(ctx, in, info, handler) } -func _AuthService_DeleteSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteSessionRequest) +func _AuthService_SignOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SignOutRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AuthServiceServer).DeleteSession(ctx, in) + return srv.(AuthServiceServer).SignOut(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: AuthService_DeleteSession_FullMethodName, + FullMethod: AuthService_SignOut_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).DeleteSession(ctx, req.(*DeleteSessionRequest)) + return srv.(AuthServiceServer).SignOut(ctx, req.(*SignOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_RefreshToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RefreshTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).RefreshToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_RefreshToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).RefreshToken(ctx, req.(*RefreshTokenRequest)) } return interceptor(ctx, in, info, handler) } @@ -193,16 +237,20 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{ HandlerType: (*AuthServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "GetCurrentSession", - Handler: _AuthService_GetCurrentSession_Handler, + MethodName: "GetCurrentUser", + Handler: _AuthService_GetCurrentUser_Handler, }, { - MethodName: "CreateSession", - Handler: _AuthService_CreateSession_Handler, + MethodName: "SignIn", + Handler: _AuthService_SignIn_Handler, }, { - MethodName: "DeleteSession", - Handler: _AuthService_DeleteSession_Handler, + MethodName: "SignOut", + Handler: _AuthService_SignOut_Handler, + }, + { + MethodName: "RefreshToken", + Handler: _AuthService_RefreshToken_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/proto/gen/api/v1/user_service.pb.go b/proto/gen/api/v1/user_service.pb.go index d18e00752..031ef141c 100644 --- a/proto/gen/api/v1/user_service.pb.go +++ b/proto/gen/api/v1/user_service.pb.go @@ -89,9 +89,9 @@ const ( UserSetting_KEY_UNSPECIFIED UserSetting_Key = 0 // GENERAL is the key for general user settings. UserSetting_GENERAL UserSetting_Key = 1 - // SESSIONS is the key for user authentication sessions. + // SESSIONS is the key for user login sessions (refresh tokens). UserSetting_SESSIONS UserSetting_Key = 2 - // ACCESS_TOKENS is the key for access tokens. + // ACCESS_TOKENS is the key for Personal Access Tokens (PATs). UserSetting_ACCESS_TOKENS UserSetting_Key = 3 // WEBHOOKS is the key for user webhooks. UserSetting_WEBHOOKS UserSetting_Key = 4 @@ -188,7 +188,7 @@ func (x UserNotification_Status) Number() protoreflect.EnumNumber { // Deprecated: Use UserNotification_Status.Descriptor instead. func (UserNotification_Status) EnumDescriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{31, 0} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{32, 0} } type UserNotification_Type int32 @@ -234,7 +234,7 @@ func (x UserNotification_Type) Number() protoreflect.EnumNumber { // Deprecated: Use UserNotification_Type.Descriptor instead. func (UserNotification_Type) EnumDescriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{31, 1} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{32, 1} } type User struct { @@ -1348,38 +1348,39 @@ func (x *ListUserSettingsResponse) GetTotalSize() int32 { return 0 } -// User access token message -type UserAccessToken struct { +// PersonalAccessToken represents a long-lived token for API/script access. +// PATs are distinct from short-lived JWT access tokens used for session authentication. +type PersonalAccessToken struct { state protoimpl.MessageState `protogen:"open.v1"` - // The resource name of the access token. - // Format: users/{user}/accessTokens/{access_token} + // The resource name of the personal access token. + // Format: users/{user}/personalAccessTokens/{personal_access_token} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Output only. The access token value. - AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` - // The description of the access token. - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - // Output only. The issued timestamp. - IssuedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + // The description of the token. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // Output only. The creation timestamp. + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // Optional. The expiration timestamp. - ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + // Output only. The last used timestamp. + LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *UserAccessToken) Reset() { - *x = UserAccessToken{} +func (x *PersonalAccessToken) Reset() { + *x = PersonalAccessToken{} mi := &file_api_v1_user_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UserAccessToken) String() string { +func (x *PersonalAccessToken) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UserAccessToken) ProtoMessage() {} +func (*PersonalAccessToken) ProtoMessage() {} -func (x *UserAccessToken) ProtoReflect() protoreflect.Message { +func (x *PersonalAccessToken) ProtoReflect() protoreflect.Message { mi := &file_api_v1_user_service_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1391,52 +1392,52 @@ func (x *UserAccessToken) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UserAccessToken.ProtoReflect.Descriptor instead. -func (*UserAccessToken) Descriptor() ([]byte, []int) { +// Deprecated: Use PersonalAccessToken.ProtoReflect.Descriptor instead. +func (*PersonalAccessToken) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{16} } -func (x *UserAccessToken) GetName() string { +func (x *PersonalAccessToken) GetName() string { if x != nil { return x.Name } return "" } -func (x *UserAccessToken) GetAccessToken() string { - if x != nil { - return x.AccessToken - } - return "" -} - -func (x *UserAccessToken) GetDescription() string { +func (x *PersonalAccessToken) GetDescription() string { if x != nil { return x.Description } return "" } -func (x *UserAccessToken) GetIssuedAt() *timestamppb.Timestamp { +func (x *PersonalAccessToken) GetCreatedAt() *timestamppb.Timestamp { if x != nil { - return x.IssuedAt + return x.CreatedAt } return nil } -func (x *UserAccessToken) GetExpiresAt() *timestamppb.Timestamp { +func (x *PersonalAccessToken) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } return nil } -type ListUserAccessTokensRequest struct { +func (x *PersonalAccessToken) GetLastUsedAt() *timestamppb.Timestamp { + if x != nil { + return x.LastUsedAt + } + return nil +} + +type ListPersonalAccessTokensRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Required. The parent resource whose access tokens will be listed. + // Required. The parent resource whose personal access tokens will be listed. // Format: users/{user} Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` - // Optional. The maximum number of access tokens to return. + // Optional. The maximum number of tokens to return. PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Optional. A page token for pagination. PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` @@ -1444,20 +1445,20 @@ type ListUserAccessTokensRequest struct { sizeCache protoimpl.SizeCache } -func (x *ListUserAccessTokensRequest) Reset() { - *x = ListUserAccessTokensRequest{} +func (x *ListPersonalAccessTokensRequest) Reset() { + *x = ListPersonalAccessTokensRequest{} mi := &file_api_v1_user_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListUserAccessTokensRequest) String() string { +func (x *ListPersonalAccessTokensRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListUserAccessTokensRequest) ProtoMessage() {} +func (*ListPersonalAccessTokensRequest) ProtoMessage() {} -func (x *ListUserAccessTokensRequest) ProtoReflect() protoreflect.Message { +func (x *ListPersonalAccessTokensRequest) ProtoReflect() protoreflect.Message { mi := &file_api_v1_user_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1469,58 +1470,58 @@ func (x *ListUserAccessTokensRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListUserAccessTokensRequest.ProtoReflect.Descriptor instead. -func (*ListUserAccessTokensRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListPersonalAccessTokensRequest.ProtoReflect.Descriptor instead. +func (*ListPersonalAccessTokensRequest) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{17} } -func (x *ListUserAccessTokensRequest) GetParent() string { +func (x *ListPersonalAccessTokensRequest) GetParent() string { if x != nil { return x.Parent } return "" } -func (x *ListUserAccessTokensRequest) GetPageSize() int32 { +func (x *ListPersonalAccessTokensRequest) GetPageSize() int32 { if x != nil { return x.PageSize } return 0 } -func (x *ListUserAccessTokensRequest) GetPageToken() string { +func (x *ListPersonalAccessTokensRequest) GetPageToken() string { if x != nil { return x.PageToken } return "" } -type ListUserAccessTokensResponse struct { +type ListPersonalAccessTokensResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // The list of access tokens. - AccessTokens []*UserAccessToken `protobuf:"bytes,1,rep,name=access_tokens,json=accessTokens,proto3" json:"access_tokens,omitempty"` + // The list of personal access tokens. + PersonalAccessTokens []*PersonalAccessToken `protobuf:"bytes,1,rep,name=personal_access_tokens,json=personalAccessTokens,proto3" json:"personal_access_tokens,omitempty"` // A token for the next page of results. NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` - // The total count of access tokens. + // The total count of personal access tokens. TotalSize int32 `protobuf:"varint,3,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListUserAccessTokensResponse) Reset() { - *x = ListUserAccessTokensResponse{} +func (x *ListPersonalAccessTokensResponse) Reset() { + *x = ListPersonalAccessTokensResponse{} mi := &file_api_v1_user_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListUserAccessTokensResponse) String() string { +func (x *ListPersonalAccessTokensResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListUserAccessTokensResponse) ProtoMessage() {} +func (*ListPersonalAccessTokensResponse) ProtoMessage() {} -func (x *ListUserAccessTokensResponse) ProtoReflect() protoreflect.Message { +func (x *ListPersonalAccessTokensResponse) ProtoReflect() protoreflect.Message { mi := &file_api_v1_user_service_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1532,59 +1533,59 @@ func (x *ListUserAccessTokensResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListUserAccessTokensResponse.ProtoReflect.Descriptor instead. -func (*ListUserAccessTokensResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListPersonalAccessTokensResponse.ProtoReflect.Descriptor instead. +func (*ListPersonalAccessTokensResponse) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{18} } -func (x *ListUserAccessTokensResponse) GetAccessTokens() []*UserAccessToken { +func (x *ListPersonalAccessTokensResponse) GetPersonalAccessTokens() []*PersonalAccessToken { if x != nil { - return x.AccessTokens + return x.PersonalAccessTokens } return nil } -func (x *ListUserAccessTokensResponse) GetNextPageToken() string { +func (x *ListPersonalAccessTokensResponse) GetNextPageToken() string { if x != nil { return x.NextPageToken } return "" } -func (x *ListUserAccessTokensResponse) GetTotalSize() int32 { +func (x *ListPersonalAccessTokensResponse) GetTotalSize() int32 { if x != nil { return x.TotalSize } return 0 } -type CreateUserAccessTokenRequest struct { +type CreatePersonalAccessTokenRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Required. The parent resource where this access token will be created. + // Required. The parent resource where this token will be created. // Format: users/{user} Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` - // Required. The access token to create. - AccessToken *UserAccessToken `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` - // Optional. The access token ID to use. - AccessTokenId string `protobuf:"bytes,3,opt,name=access_token_id,json=accessTokenId,proto3" json:"access_token_id,omitempty"` + // Optional. Description of the personal access token. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // Optional. Expiration duration in days (0 = never expires). + ExpiresInDays int32 `protobuf:"varint,3,opt,name=expires_in_days,json=expiresInDays,proto3" json:"expires_in_days,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateUserAccessTokenRequest) Reset() { - *x = CreateUserAccessTokenRequest{} +func (x *CreatePersonalAccessTokenRequest) Reset() { + *x = CreatePersonalAccessTokenRequest{} mi := &file_api_v1_user_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateUserAccessTokenRequest) String() string { +func (x *CreatePersonalAccessTokenRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateUserAccessTokenRequest) ProtoMessage() {} +func (*CreatePersonalAccessTokenRequest) ProtoMessage() {} -func (x *CreateUserAccessTokenRequest) ProtoReflect() protoreflect.Message { +func (x *CreatePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { mi := &file_api_v1_user_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1596,55 +1597,57 @@ func (x *CreateUserAccessTokenRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateUserAccessTokenRequest.ProtoReflect.Descriptor instead. -func (*CreateUserAccessTokenRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreatePersonalAccessTokenRequest.ProtoReflect.Descriptor instead. +func (*CreatePersonalAccessTokenRequest) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{19} } -func (x *CreateUserAccessTokenRequest) GetParent() string { +func (x *CreatePersonalAccessTokenRequest) GetParent() string { if x != nil { return x.Parent } return "" } -func (x *CreateUserAccessTokenRequest) GetAccessToken() *UserAccessToken { +func (x *CreatePersonalAccessTokenRequest) GetDescription() string { if x != nil { - return x.AccessToken - } - return nil -} - -func (x *CreateUserAccessTokenRequest) GetAccessTokenId() string { - if x != nil { - return x.AccessTokenId + return x.Description } return "" } -type DeleteUserAccessTokenRequest struct { +func (x *CreatePersonalAccessTokenRequest) GetExpiresInDays() int32 { + if x != nil { + return x.ExpiresInDays + } + return 0 +} + +type CreatePersonalAccessTokenResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // Required. The resource name of the access token to delete. - // Format: users/{user}/accessTokens/{access_token} - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The personal access token metadata. + PersonalAccessToken *PersonalAccessToken `protobuf:"bytes,1,opt,name=personal_access_token,json=personalAccessToken,proto3" json:"personal_access_token,omitempty"` + // The actual token value - only returned on creation. + // This is the only time the token value will be visible. + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteUserAccessTokenRequest) Reset() { - *x = DeleteUserAccessTokenRequest{} +func (x *CreatePersonalAccessTokenResponse) Reset() { + *x = CreatePersonalAccessTokenResponse{} mi := &file_api_v1_user_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteUserAccessTokenRequest) String() string { +func (x *CreatePersonalAccessTokenResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteUserAccessTokenRequest) ProtoMessage() {} +func (*CreatePersonalAccessTokenResponse) ProtoMessage() {} -func (x *DeleteUserAccessTokenRequest) ProtoReflect() protoreflect.Message { +func (x *CreatePersonalAccessTokenResponse) ProtoReflect() protoreflect.Message { mi := &file_api_v1_user_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1656,19 +1659,74 @@ func (x *DeleteUserAccessTokenRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteUserAccessTokenRequest.ProtoReflect.Descriptor instead. -func (*DeleteUserAccessTokenRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreatePersonalAccessTokenResponse.ProtoReflect.Descriptor instead. +func (*CreatePersonalAccessTokenResponse) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{20} } -func (x *DeleteUserAccessTokenRequest) GetName() string { +func (x *CreatePersonalAccessTokenResponse) GetPersonalAccessToken() *PersonalAccessToken { + if x != nil { + return x.PersonalAccessToken + } + return nil +} + +func (x *CreatePersonalAccessTokenResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type DeletePersonalAccessTokenRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The resource name of the personal access token to delete. + // Format: users/{user}/personalAccessTokens/{personal_access_token} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePersonalAccessTokenRequest) Reset() { + *x = DeletePersonalAccessTokenRequest{} + mi := &file_api_v1_user_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePersonalAccessTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePersonalAccessTokenRequest) ProtoMessage() {} + +func (x *DeletePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[21] + 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 DeletePersonalAccessTokenRequest.ProtoReflect.Descriptor instead. +func (*DeletePersonalAccessTokenRequest) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{21} +} + +func (x *DeletePersonalAccessTokenRequest) GetName() string { if x != nil { return x.Name } return "" } -type UserSession struct { +// Session represents a user's login session on a specific device/browser. +// Sessions are backed by refresh tokens with sliding expiration. +type Session struct { state protoimpl.MessageState `protogen:"open.v1"` // The resource name of the session. // Format: users/{user}/sessions/{session} @@ -1681,26 +1739,26 @@ type UserSession struct { // 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,5,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"` + ClientInfo *Session_ClientInfo `protobuf:"bytes,5,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *UserSession) Reset() { - *x = UserSession{} - mi := &file_api_v1_user_service_proto_msgTypes[21] +func (x *Session) Reset() { + *x = Session{} + mi := &file_api_v1_user_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UserSession) String() string { +func (x *Session) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UserSession) ProtoMessage() {} +func (*Session) ProtoMessage() {} -func (x *UserSession) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[21] +func (x *Session) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1711,47 +1769,47 @@ func (x *UserSession) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UserSession.ProtoReflect.Descriptor instead. -func (*UserSession) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{21} +// Deprecated: Use Session.ProtoReflect.Descriptor instead. +func (*Session) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{22} } -func (x *UserSession) GetName() string { +func (x *Session) GetName() string { if x != nil { return x.Name } return "" } -func (x *UserSession) GetSessionId() string { +func (x *Session) GetSessionId() string { if x != nil { return x.SessionId } return "" } -func (x *UserSession) GetCreateTime() *timestamppb.Timestamp { +func (x *Session) GetCreateTime() *timestamppb.Timestamp { if x != nil { return x.CreateTime } return nil } -func (x *UserSession) GetLastAccessedTime() *timestamppb.Timestamp { +func (x *Session) GetLastAccessedTime() *timestamppb.Timestamp { if x != nil { return x.LastAccessedTime } return nil } -func (x *UserSession) GetClientInfo() *UserSession_ClientInfo { +func (x *Session) GetClientInfo() *Session_ClientInfo { if x != nil { return x.ClientInfo } return nil } -type ListUserSessionsRequest struct { +type ListSessionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The resource name of the parent. // Format: users/{user} @@ -1760,21 +1818,21 @@ type ListUserSessionsRequest struct { sizeCache protoimpl.SizeCache } -func (x *ListUserSessionsRequest) Reset() { - *x = ListUserSessionsRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[22] +func (x *ListSessionsRequest) Reset() { + *x = ListSessionsRequest{} + mi := &file_api_v1_user_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListUserSessionsRequest) String() string { +func (x *ListSessionsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListUserSessionsRequest) ProtoMessage() {} +func (*ListSessionsRequest) ProtoMessage() {} -func (x *ListUserSessionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[22] +func (x *ListSessionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1785,41 +1843,41 @@ func (x *ListUserSessionsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListUserSessionsRequest.ProtoReflect.Descriptor instead. -func (*ListUserSessionsRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{22} +// Deprecated: Use ListSessionsRequest.ProtoReflect.Descriptor instead. +func (*ListSessionsRequest) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{23} } -func (x *ListUserSessionsRequest) GetParent() string { +func (x *ListSessionsRequest) GetParent() string { if x != nil { return x.Parent } return "" } -type ListUserSessionsResponse struct { +type ListSessionsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // The list of user sessions. - Sessions []*UserSession `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` + // The list of sessions. + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListUserSessionsResponse) Reset() { - *x = ListUserSessionsResponse{} - mi := &file_api_v1_user_service_proto_msgTypes[23] +func (x *ListSessionsResponse) Reset() { + *x = ListSessionsResponse{} + mi := &file_api_v1_user_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListUserSessionsResponse) String() string { +func (x *ListSessionsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListUserSessionsResponse) ProtoMessage() {} +func (*ListSessionsResponse) ProtoMessage() {} -func (x *ListUserSessionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[23] +func (x *ListSessionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1830,19 +1888,19 @@ func (x *ListUserSessionsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListUserSessionsResponse.ProtoReflect.Descriptor instead. -func (*ListUserSessionsResponse) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{23} +// Deprecated: Use ListSessionsResponse.ProtoReflect.Descriptor instead. +func (*ListSessionsResponse) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{24} } -func (x *ListUserSessionsResponse) GetSessions() []*UserSession { +func (x *ListSessionsResponse) GetSessions() []*Session { if x != nil { return x.Sessions } return nil } -type RevokeUserSessionRequest struct { +type RevokeSessionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // The name of the session to revoke. // Format: users/{user}/sessions/{session} @@ -1851,21 +1909,21 @@ type RevokeUserSessionRequest struct { sizeCache protoimpl.SizeCache } -func (x *RevokeUserSessionRequest) Reset() { - *x = RevokeUserSessionRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[24] +func (x *RevokeSessionRequest) Reset() { + *x = RevokeSessionRequest{} + mi := &file_api_v1_user_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RevokeUserSessionRequest) String() string { +func (x *RevokeSessionRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RevokeUserSessionRequest) ProtoMessage() {} +func (*RevokeSessionRequest) ProtoMessage() {} -func (x *RevokeUserSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[24] +func (x *RevokeSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1876,12 +1934,12 @@ func (x *RevokeUserSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RevokeUserSessionRequest.ProtoReflect.Descriptor instead. -func (*RevokeUserSessionRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{24} +// Deprecated: Use RevokeSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSessionRequest) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{25} } -func (x *RevokeUserSessionRequest) GetName() string { +func (x *RevokeSessionRequest) GetName() string { if x != nil { return x.Name } @@ -1908,7 +1966,7 @@ type UserWebhook struct { func (x *UserWebhook) Reset() { *x = UserWebhook{} - mi := &file_api_v1_user_service_proto_msgTypes[25] + mi := &file_api_v1_user_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1920,7 +1978,7 @@ func (x *UserWebhook) String() string { func (*UserWebhook) ProtoMessage() {} func (x *UserWebhook) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[25] + mi := &file_api_v1_user_service_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1933,7 +1991,7 @@ func (x *UserWebhook) ProtoReflect() protoreflect.Message { // Deprecated: Use UserWebhook.ProtoReflect.Descriptor instead. func (*UserWebhook) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{25} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{26} } func (x *UserWebhook) GetName() string { @@ -1982,7 +2040,7 @@ type ListUserWebhooksRequest struct { func (x *ListUserWebhooksRequest) Reset() { *x = ListUserWebhooksRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[26] + mi := &file_api_v1_user_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1994,7 +2052,7 @@ func (x *ListUserWebhooksRequest) String() string { func (*ListUserWebhooksRequest) ProtoMessage() {} func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[26] + mi := &file_api_v1_user_service_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2007,7 +2065,7 @@ func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserWebhooksRequest.ProtoReflect.Descriptor instead. func (*ListUserWebhooksRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{26} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{27} } func (x *ListUserWebhooksRequest) GetParent() string { @@ -2027,7 +2085,7 @@ type ListUserWebhooksResponse struct { func (x *ListUserWebhooksResponse) Reset() { *x = ListUserWebhooksResponse{} - mi := &file_api_v1_user_service_proto_msgTypes[27] + mi := &file_api_v1_user_service_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2039,7 +2097,7 @@ func (x *ListUserWebhooksResponse) String() string { func (*ListUserWebhooksResponse) ProtoMessage() {} func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[27] + mi := &file_api_v1_user_service_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2052,7 +2110,7 @@ func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserWebhooksResponse.ProtoReflect.Descriptor instead. func (*ListUserWebhooksResponse) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{27} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{28} } func (x *ListUserWebhooksResponse) GetWebhooks() []*UserWebhook { @@ -2075,7 +2133,7 @@ type CreateUserWebhookRequest struct { func (x *CreateUserWebhookRequest) Reset() { *x = CreateUserWebhookRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[28] + mi := &file_api_v1_user_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2087,7 +2145,7 @@ func (x *CreateUserWebhookRequest) String() string { func (*CreateUserWebhookRequest) ProtoMessage() {} func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[28] + mi := &file_api_v1_user_service_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2100,7 +2158,7 @@ func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateUserWebhookRequest.ProtoReflect.Descriptor instead. func (*CreateUserWebhookRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{28} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{29} } func (x *CreateUserWebhookRequest) GetParent() string { @@ -2129,7 +2187,7 @@ type UpdateUserWebhookRequest struct { func (x *UpdateUserWebhookRequest) Reset() { *x = UpdateUserWebhookRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[29] + mi := &file_api_v1_user_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2141,7 +2199,7 @@ func (x *UpdateUserWebhookRequest) String() string { func (*UpdateUserWebhookRequest) ProtoMessage() {} func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[29] + mi := &file_api_v1_user_service_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2154,7 +2212,7 @@ func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserWebhookRequest.ProtoReflect.Descriptor instead. func (*UpdateUserWebhookRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{29} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{30} } func (x *UpdateUserWebhookRequest) GetWebhook() *UserWebhook { @@ -2182,7 +2240,7 @@ type DeleteUserWebhookRequest struct { func (x *DeleteUserWebhookRequest) Reset() { *x = DeleteUserWebhookRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[30] + mi := &file_api_v1_user_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2194,7 +2252,7 @@ func (x *DeleteUserWebhookRequest) String() string { func (*DeleteUserWebhookRequest) ProtoMessage() {} func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[30] + mi := &file_api_v1_user_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2207,7 +2265,7 @@ func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteUserWebhookRequest.ProtoReflect.Descriptor instead. func (*DeleteUserWebhookRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{30} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{31} } func (x *DeleteUserWebhookRequest) GetName() string { @@ -2239,7 +2297,7 @@ type UserNotification struct { func (x *UserNotification) Reset() { *x = UserNotification{} - mi := &file_api_v1_user_service_proto_msgTypes[31] + mi := &file_api_v1_user_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2251,7 +2309,7 @@ func (x *UserNotification) String() string { func (*UserNotification) ProtoMessage() {} func (x *UserNotification) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[31] + mi := &file_api_v1_user_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2264,7 +2322,7 @@ func (x *UserNotification) ProtoReflect() protoreflect.Message { // Deprecated: Use UserNotification.ProtoReflect.Descriptor instead. func (*UserNotification) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{31} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{32} } func (x *UserNotification) GetName() string { @@ -2323,7 +2381,7 @@ type ListUserNotificationsRequest struct { func (x *ListUserNotificationsRequest) Reset() { *x = ListUserNotificationsRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[32] + mi := &file_api_v1_user_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2335,7 +2393,7 @@ func (x *ListUserNotificationsRequest) String() string { func (*ListUserNotificationsRequest) ProtoMessage() {} func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[32] + mi := &file_api_v1_user_service_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2348,7 +2406,7 @@ func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserNotificationsRequest.ProtoReflect.Descriptor instead. func (*ListUserNotificationsRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{32} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{33} } func (x *ListUserNotificationsRequest) GetParent() string { @@ -2389,7 +2447,7 @@ type ListUserNotificationsResponse struct { func (x *ListUserNotificationsResponse) Reset() { *x = ListUserNotificationsResponse{} - mi := &file_api_v1_user_service_proto_msgTypes[33] + mi := &file_api_v1_user_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2401,7 +2459,7 @@ func (x *ListUserNotificationsResponse) String() string { func (*ListUserNotificationsResponse) ProtoMessage() {} func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[33] + mi := &file_api_v1_user_service_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2414,7 +2472,7 @@ func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserNotificationsResponse.ProtoReflect.Descriptor instead. func (*ListUserNotificationsResponse) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{33} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{34} } func (x *ListUserNotificationsResponse) GetNotifications() []*UserNotification { @@ -2441,7 +2499,7 @@ type UpdateUserNotificationRequest struct { func (x *UpdateUserNotificationRequest) Reset() { *x = UpdateUserNotificationRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[34] + mi := &file_api_v1_user_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2453,7 +2511,7 @@ func (x *UpdateUserNotificationRequest) String() string { func (*UpdateUserNotificationRequest) ProtoMessage() {} func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[34] + mi := &file_api_v1_user_service_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2466,7 +2524,7 @@ func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserNotificationRequest.ProtoReflect.Descriptor instead. func (*UpdateUserNotificationRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{34} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{35} } func (x *UpdateUserNotificationRequest) GetNotification() *UserNotification { @@ -2493,7 +2551,7 @@ type DeleteUserNotificationRequest struct { func (x *DeleteUserNotificationRequest) Reset() { *x = DeleteUserNotificationRequest{} - mi := &file_api_v1_user_service_proto_msgTypes[35] + mi := &file_api_v1_user_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2505,7 +2563,7 @@ func (x *DeleteUserNotificationRequest) String() string { func (*DeleteUserNotificationRequest) ProtoMessage() {} func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[35] + mi := &file_api_v1_user_service_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2518,7 +2576,7 @@ func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteUserNotificationRequest.ProtoReflect.Descriptor instead. func (*DeleteUserNotificationRequest) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{35} + return file_api_v1_user_service_proto_rawDescGZIP(), []int{36} } func (x *DeleteUserNotificationRequest) GetName() string { @@ -2541,7 +2599,7 @@ type UserStats_MemoTypeStats struct { func (x *UserStats_MemoTypeStats) Reset() { *x = UserStats_MemoTypeStats{} - mi := &file_api_v1_user_service_proto_msgTypes[37] + mi := &file_api_v1_user_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2553,7 +2611,7 @@ func (x *UserStats_MemoTypeStats) String() string { func (*UserStats_MemoTypeStats) ProtoMessage() {} func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[37] + mi := &file_api_v1_user_service_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2614,7 +2672,7 @@ type UserSetting_GeneralSetting struct { func (x *UserSetting_GeneralSetting) Reset() { *x = UserSetting_GeneralSetting{} - mi := &file_api_v1_user_service_proto_msgTypes[38] + mi := &file_api_v1_user_service_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2626,7 +2684,7 @@ func (x *UserSetting_GeneralSetting) String() string { func (*UserSetting_GeneralSetting) ProtoMessage() {} func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[38] + mi := &file_api_v1_user_service_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2666,15 +2724,15 @@ func (x *UserSetting_GeneralSetting) GetTheme() string { // User authentication sessions configuration. type UserSetting_SessionsSetting struct { state protoimpl.MessageState `protogen:"open.v1"` - // List of active user sessions. - Sessions []*UserSession `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` + // List of active login sessions. + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UserSetting_SessionsSetting) Reset() { *x = UserSetting_SessionsSetting{} - mi := &file_api_v1_user_service_proto_msgTypes[39] + mi := &file_api_v1_user_service_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2686,7 +2744,7 @@ func (x *UserSetting_SessionsSetting) String() string { func (*UserSetting_SessionsSetting) ProtoMessage() {} func (x *UserSetting_SessionsSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[39] + mi := &file_api_v1_user_service_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2702,25 +2760,25 @@ func (*UserSetting_SessionsSetting) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{11, 1} } -func (x *UserSetting_SessionsSetting) GetSessions() []*UserSession { +func (x *UserSetting_SessionsSetting) GetSessions() []*Session { if x != nil { return x.Sessions } return nil } -// User access tokens configuration. +// Personal access tokens configuration. type UserSetting_AccessTokensSetting struct { state protoimpl.MessageState `protogen:"open.v1"` - // List of user access tokens. - AccessTokens []*UserAccessToken `protobuf:"bytes,1,rep,name=access_tokens,json=accessTokens,proto3" json:"access_tokens,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // List of personal access tokens (PATs). + PersonalAccessTokens []*PersonalAccessToken `protobuf:"bytes,1,rep,name=personal_access_tokens,json=personalAccessTokens,proto3" json:"personal_access_tokens,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UserSetting_AccessTokensSetting) Reset() { *x = UserSetting_AccessTokensSetting{} - mi := &file_api_v1_user_service_proto_msgTypes[40] + mi := &file_api_v1_user_service_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2732,7 +2790,7 @@ func (x *UserSetting_AccessTokensSetting) String() string { func (*UserSetting_AccessTokensSetting) ProtoMessage() {} func (x *UserSetting_AccessTokensSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[40] + mi := &file_api_v1_user_service_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2748,9 +2806,9 @@ func (*UserSetting_AccessTokensSetting) Descriptor() ([]byte, []int) { return file_api_v1_user_service_proto_rawDescGZIP(), []int{11, 2} } -func (x *UserSetting_AccessTokensSetting) GetAccessTokens() []*UserAccessToken { +func (x *UserSetting_AccessTokensSetting) GetPersonalAccessTokens() []*PersonalAccessToken { if x != nil { - return x.AccessTokens + return x.PersonalAccessTokens } return nil } @@ -2766,7 +2824,7 @@ type UserSetting_WebhooksSetting struct { func (x *UserSetting_WebhooksSetting) Reset() { *x = UserSetting_WebhooksSetting{} - mi := &file_api_v1_user_service_proto_msgTypes[41] + mi := &file_api_v1_user_service_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2778,7 +2836,7 @@ func (x *UserSetting_WebhooksSetting) String() string { func (*UserSetting_WebhooksSetting) ProtoMessage() {} func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[41] + mi := &file_api_v1_user_service_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2801,7 +2859,7 @@ func (x *UserSetting_WebhooksSetting) GetWebhooks() []*UserWebhook { return nil } -type UserSession_ClientInfo struct { +type Session_ClientInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // User agent string of the client. UserAgent string `protobuf:"bytes,1,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` @@ -2817,21 +2875,21 @@ type UserSession_ClientInfo struct { sizeCache protoimpl.SizeCache } -func (x *UserSession_ClientInfo) Reset() { - *x = UserSession_ClientInfo{} - mi := &file_api_v1_user_service_proto_msgTypes[42] +func (x *Session_ClientInfo) Reset() { + *x = Session_ClientInfo{} + mi := &file_api_v1_user_service_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UserSession_ClientInfo) String() string { +func (x *Session_ClientInfo) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UserSession_ClientInfo) ProtoMessage() {} +func (*Session_ClientInfo) ProtoMessage() {} -func (x *UserSession_ClientInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_user_service_proto_msgTypes[42] +func (x *Session_ClientInfo) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_user_service_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2842,40 +2900,40 @@ func (x *UserSession_ClientInfo) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UserSession_ClientInfo.ProtoReflect.Descriptor instead. -func (*UserSession_ClientInfo) Descriptor() ([]byte, []int) { - return file_api_v1_user_service_proto_rawDescGZIP(), []int{21, 0} +// Deprecated: Use Session_ClientInfo.ProtoReflect.Descriptor instead. +func (*Session_ClientInfo) Descriptor() ([]byte, []int) { + return file_api_v1_user_service_proto_rawDescGZIP(), []int{22, 0} } -func (x *UserSession_ClientInfo) GetUserAgent() string { +func (x *Session_ClientInfo) GetUserAgent() string { if x != nil { return x.UserAgent } return "" } -func (x *UserSession_ClientInfo) GetIpAddress() string { +func (x *Session_ClientInfo) GetIpAddress() string { if x != nil { return x.IpAddress } return "" } -func (x *UserSession_ClientInfo) GetDeviceType() string { +func (x *Session_ClientInfo) GetDeviceType() string { if x != nil { return x.DeviceType } return "" } -func (x *UserSession_ClientInfo) GetOs() string { +func (x *Session_ClientInfo) GetOs() string { if x != nil { return x.Os } return "" } -func (x *UserSession_ClientInfo) GetBrowser() string { +func (x *Session_ClientInfo) GetBrowser() string { if x != nil { return x.Browser } @@ -2964,7 +3022,7 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\x11memos.api.v1/UserR\x04name\"\x19\n" + "\x17ListAllUserStatsRequest\"I\n" + "\x18ListAllUserStatsResponse\x12-\n" + - "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xb3\a\n" + + "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xc4\a\n" + "\vUserSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12S\n" + "\x0fgeneral_setting\x18\x02 \x01(\v2(.memos.api.v1.UserSetting.GeneralSettingH\x00R\x0egeneralSetting\x12V\n" + @@ -2974,11 +3032,11 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\x0eGeneralSetting\x12\x1b\n" + "\x06locale\x18\x01 \x01(\tB\x03\xe0A\x01R\x06locale\x12,\n" + "\x0fmemo_visibility\x18\x03 \x01(\tB\x03\xe0A\x01R\x0ememoVisibility\x12\x19\n" + - "\x05theme\x18\x04 \x01(\tB\x03\xe0A\x01R\x05theme\x1aH\n" + - "\x0fSessionsSetting\x125\n" + - "\bsessions\x18\x01 \x03(\v2\x19.memos.api.v1.UserSessionR\bsessions\x1aY\n" + - "\x13AccessTokensSetting\x12B\n" + - "\raccess_tokens\x18\x01 \x03(\v2\x1d.memos.api.v1.UserAccessTokenR\faccessTokens\x1aH\n" + + "\x05theme\x18\x04 \x01(\tB\x03\xe0A\x01R\x05theme\x1aD\n" + + "\x0fSessionsSetting\x121\n" + + "\bsessions\x18\x01 \x03(\v2\x15.memos.api.v1.SessionR\bsessions\x1an\n" + + "\x13AccessTokensSetting\x12W\n" + + "\x16personal_access_tokens\x18\x01 \x03(\v2!.memos.api.v1.PersonalAccessTokenR\x14personalAccessTokens\x1aH\n" + "\x0fWebhooksSetting\x125\n" + "\bwebhooks\x18\x01 \x03(\v2\x19.memos.api.v1.UserWebhookR\bwebhooks\"V\n" + "\x03Key\x12\x13\n" + @@ -3006,42 +3064,47 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\bsettings\x18\x01 \x03(\v2\x19.memos.api.v1.UserSettingR\bsettings\x12&\n" + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" + "\n" + - "total_size\x18\x03 \x01(\x05R\ttotalSize\"\xe7\x02\n" + - "\x0fUserAccessToken\x12\x17\n" + - "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12&\n" + - "\faccess_token\x18\x02 \x01(\tB\x03\xe0A\x03R\vaccessToken\x12%\n" + - "\vdescription\x18\x03 \x01(\tB\x03\xe0A\x01R\vdescription\x12<\n" + - "\tissued_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\bissuedAt\x12>\n" + + "total_size\x18\x03 \x01(\x05R\ttotalSize\"\xa7\x03\n" + + "\x13PersonalAccessToken\x12\x17\n" + + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12%\n" + + "\vdescription\x18\x02 \x01(\tB\x03\xe0A\x01R\vdescription\x12>\n" + "\n" + - "expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x01R\texpiresAt:n\xeaAk\n" + - "\x1cmemos.api.v1/UserAccessToken\x12(users/{user}/accessTokens/{access_token}*\x10userAccessTokens2\x0fuserAccessToken\"\x96\x01\n" + - "\x1bListUserAccessTokensRequest\x121\n" + + "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\tcreatedAt\x12>\n" + + "\n" + + "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x01R\texpiresAt\x12A\n" + + "\flast_used_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" + + "lastUsedAt:\x8c\x01\xeaA\x88\x01\n" + + " memos.api.v1/PersonalAccessToken\x129users/{user}/personalAccessTokens/{personal_access_token}*\x14personalAccessTokens2\x13personalAccessToken\"\x9a\x01\n" + + "\x1fListPersonalAccessTokensRequest\x121\n" + "\x06parent\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" + "\x11memos.api.v1/UserR\x06parent\x12 \n" + "\tpage_size\x18\x02 \x01(\x05B\x03\xe0A\x01R\bpageSize\x12\"\n" + "\n" + - "page_token\x18\x03 \x01(\tB\x03\xe0A\x01R\tpageToken\"\xa9\x01\n" + - "\x1cListUserAccessTokensResponse\x12B\n" + - "\raccess_tokens\x18\x01 \x03(\v2\x1d.memos.api.v1.UserAccessTokenR\faccessTokens\x12&\n" + + "page_token\x18\x03 \x01(\tB\x03\xe0A\x01R\tpageToken\"\xc2\x01\n" + + " ListPersonalAccessTokensResponse\x12W\n" + + "\x16personal_access_tokens\x18\x01 \x03(\v2!.memos.api.v1.PersonalAccessTokenR\x14personalAccessTokens\x12&\n" + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" + "\n" + - "total_size\x18\x03 \x01(\x05R\ttotalSize\"\xc5\x01\n" + - "\x1cCreateUserAccessTokenRequest\x121\n" + + "total_size\x18\x03 \x01(\x05R\ttotalSize\"\xa9\x01\n" + + " CreatePersonalAccessTokenRequest\x121\n" + "\x06parent\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" + - "\x11memos.api.v1/UserR\x06parent\x12E\n" + - "\faccess_token\x18\x02 \x01(\v2\x1d.memos.api.v1.UserAccessTokenB\x03\xe0A\x02R\vaccessToken\x12+\n" + - "\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\"\x94\x04\n" + - "\vUserSession\x12\x17\n" + + "\x11memos.api.v1/UserR\x06parent\x12%\n" + + "\vdescription\x18\x02 \x01(\tB\x03\xe0A\x01R\vdescription\x12+\n" + + "\x0fexpires_in_days\x18\x03 \x01(\x05B\x03\xe0A\x01R\rexpiresInDays\"\x90\x01\n" + + "!CreatePersonalAccessTokenResponse\x12U\n" + + "\x15personal_access_token\x18\x01 \x01(\v2!.memos.api.v1.PersonalAccessTokenR\x13personalAccessToken\x12\x14\n" + + "\x05token\x18\x02 \x01(\tR\x05token\"`\n" + + " DeletePersonalAccessTokenRequest\x12<\n" + + "\x04name\x18\x01 \x01(\tB(\xe0A\x02\xfaA\"\n" + + " memos.api.v1/PersonalAccessTokenR\x04name\"\x88\x04\n" + + "\aSession\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\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" + + "\x12last_accessed_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\x10lastAccessedTime\x12F\n" + + "\vclient_info\x18\x05 \x01(\v2 .memos.api.v1.Session.ClientInfoB\x03\xe0A\x03R\n" + "clientInfo\x1a\xa4\x01\n" + "\n" + "ClientInfo\x12\x1d\n" + @@ -3052,14 +3115,14 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\vdevice_type\x18\x03 \x01(\tB\x03\xe0A\x01R\n" + "deviceType\x12\x13\n" + "\x02os\x18\x04 \x01(\tB\x03\xe0A\x01R\x02os\x12\x1d\n" + - "\abrowser\x18\x05 \x01(\tB\x03\xe0A\x01R\abrowser:D\xeaAA\n" + - "\x18memos.api.v1/UserSession\x12\x1fusers/{user}/sessions/{session}\x1a\x04name\"L\n" + - "\x17ListUserSessionsRequest\x121\n" + + "\abrowser\x18\x05 \x01(\tB\x03\xe0A\x01R\abrowser:@\xeaA=\n" + + "\x14memos.api.v1/Session\x12\x1fusers/{user}/sessions/{session}\x1a\x04name\"H\n" + + "\x13ListSessionsRequest\x121\n" + "\x06parent\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" + - "\x11memos.api.v1/UserR\x06parent\"Q\n" + - "\x18ListUserSessionsResponse\x125\n" + - "\bsessions\x18\x01 \x03(\v2\x19.memos.api.v1.UserSessionR\bsessions\"3\n" + - "\x18RevokeUserSessionRequest\x12\x17\n" + + "\x11memos.api.v1/UserR\x06parent\"I\n" + + "\x14ListSessionsResponse\x121\n" + + "\bsessions\x18\x01 \x03(\v2\x15.memos.api.v1.SessionR\bsessions\"/\n" + + "\x14RevokeSessionRequest\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name\"\xda\x01\n" + "\vUserWebhook\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + @@ -3118,7 +3181,7 @@ const file_api_v1_user_service_proto_rawDesc = "" + "updateMask\"Z\n" + "\x1dDeleteUserNotificationRequest\x129\n" + "\x04name\x18\x01 \x01(\tB%\xe0A\x02\xfaA\x1f\n" + - "\x1dmemos.api.v1/UserNotificationR\x04name2\xfe\x18\n" + + "\x1dmemos.api.v1/UserNotificationR\x04name2\x8e\x19\n" + "\vUserService\x12c\n" + "\tListUsers\x12\x1e.memos.api.v1.ListUsersRequest\x1a\x1f.memos.api.v1.ListUsersResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/api/v1/users\x12b\n" + "\aGetUser\x12\x1c.memos.api.v1.GetUserRequest\x1a\x12.memos.api.v1.User\"%\xdaA\x04name\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/{name=users/*}\x12e\n" + @@ -3132,12 +3195,12 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\fGetUserStats\x12!.memos.api.v1.GetUserStatsRequest\x1a\x17.memos.api.v1.UserStats\".\xdaA\x04name\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/{name=users/*}:getStats\x12\x82\x01\n" + "\x0eGetUserSetting\x12#.memos.api.v1.GetUserSettingRequest\x1a\x19.memos.api.v1.UserSetting\"0\xdaA\x04name\x82\xd3\xe4\x93\x02#\x12!/api/v1/{name=users/*/settings/*}\x12\xa8\x01\n" + "\x11UpdateUserSetting\x12&.memos.api.v1.UpdateUserSettingRequest\x1a\x19.memos.api.v1.UserSetting\"P\xdaA\x13setting,update_mask\x82\xd3\xe4\x93\x024:\asetting2)/api/v1/{setting.name=users/*/settings/*}\x12\x95\x01\n" + - "\x10ListUserSettings\x12%.memos.api.v1.ListUserSettingsRequest\x1a&.memos.api.v1.ListUserSettingsResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/settings\x12\xa5\x01\n" + - "\x14ListUserAccessTokens\x12).memos.api.v1.ListUserAccessTokensRequest\x1a*.memos.api.v1.ListUserAccessTokensResponse\"6\xdaA\x06parent\x82\xd3\xe4\x93\x02'\x12%/api/v1/{parent=users/*}/accessTokens\x12\xb5\x01\n" + - "\x15CreateUserAccessToken\x12*.memos.api.v1.CreateUserAccessTokenRequest\x1a\x1d.memos.api.v1.UserAccessToken\"Q\xdaA\x13parent,access_token\x82\xd3\xe4\x93\x025:\faccess_token\"%/api/v1/{parent=users/*}/accessTokens\x12\x91\x01\n" + - "\x15DeleteUserAccessToken\x12*.memos.api.v1.DeleteUserAccessTokenRequest\x1a\x16.google.protobuf.Empty\"4\xdaA\x04name\x82\xd3\xe4\x93\x02'*%/api/v1/{name=users/*/accessTokens/*}\x12\x95\x01\n" + - "\x10ListUserSessions\x12%.memos.api.v1.ListUserSessionsRequest\x1a&.memos.api.v1.ListUserSessionsResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/sessions\x12\x85\x01\n" + - "\x11RevokeUserSession\x12&.memos.api.v1.RevokeUserSessionRequest\x1a\x16.google.protobuf.Empty\"0\xdaA\x04name\x82\xd3\xe4\x93\x02#*!/api/v1/{name=users/*/sessions/*}\x12\x95\x01\n" + + "\x10ListUserSettings\x12%.memos.api.v1.ListUserSettingsRequest\x1a&.memos.api.v1.ListUserSettingsResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/settings\x12\xb9\x01\n" + + "\x18ListPersonalAccessTokens\x12-.memos.api.v1.ListPersonalAccessTokensRequest\x1a..memos.api.v1.ListPersonalAccessTokensResponse\">\xdaA\x06parent\x82\xd3\xe4\x93\x02/\x12-/api/v1/{parent=users/*}/personalAccessTokens\x12\xb6\x01\n" + + "\x19CreatePersonalAccessToken\x12..memos.api.v1.CreatePersonalAccessTokenRequest\x1a/.memos.api.v1.CreatePersonalAccessTokenResponse\"8\x82\xd3\xe4\x93\x022:\x01*\"-/api/v1/{parent=users/*}/personalAccessTokens\x12\xa1\x01\n" + + "\x19DeletePersonalAccessToken\x12..memos.api.v1.DeletePersonalAccessTokenRequest\x1a\x16.google.protobuf.Empty\"<\xdaA\x04name\x82\xd3\xe4\x93\x02/*-/api/v1/{name=users/*/personalAccessTokens/*}\x12\x89\x01\n" + + "\fListSessions\x12!.memos.api.v1.ListSessionsRequest\x1a\".memos.api.v1.ListSessionsResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/sessions\x12}\n" + + "\rRevokeSession\x12\".memos.api.v1.RevokeSessionRequest\x1a\x16.google.protobuf.Empty\"0\xdaA\x04name\x82\xd3\xe4\x93\x02#*!/api/v1/{name=users/*/sessions/*}\x12\x95\x01\n" + "\x10ListUserWebhooks\x12%.memos.api.v1.ListUserWebhooksRequest\x1a&.memos.api.v1.ListUserWebhooksResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/webhooks\x12\x9b\x01\n" + "\x11CreateUserWebhook\x12&.memos.api.v1.CreateUserWebhookRequest\x1a\x19.memos.api.v1.UserWebhook\"C\xdaA\x0eparent,webhook\x82\xd3\xe4\x93\x02,:\awebhook\"!/api/v1/{parent=users/*}/webhooks\x12\xa8\x01\n" + "\x11UpdateUserWebhook\x12&.memos.api.v1.UpdateUserWebhookRequest\x1a\x19.memos.api.v1.UserWebhook\"P\xdaA\x13webhook,update_mask\x82\xd3\xe4\x93\x024:\awebhook2)/api/v1/{webhook.name=users/*/webhooks/*}\x12\x85\x01\n" + @@ -3160,153 +3223,155 @@ func file_api_v1_user_service_proto_rawDescGZIP() []byte { } var file_api_v1_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 43) +var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 44) var file_api_v1_user_service_proto_goTypes = []any{ - (User_Role)(0), // 0: memos.api.v1.User.Role - (UserSetting_Key)(0), // 1: memos.api.v1.UserSetting.Key - (UserNotification_Status)(0), // 2: memos.api.v1.UserNotification.Status - (UserNotification_Type)(0), // 3: memos.api.v1.UserNotification.Type - (*User)(nil), // 4: memos.api.v1.User - (*ListUsersRequest)(nil), // 5: memos.api.v1.ListUsersRequest - (*ListUsersResponse)(nil), // 6: memos.api.v1.ListUsersResponse - (*GetUserRequest)(nil), // 7: memos.api.v1.GetUserRequest - (*CreateUserRequest)(nil), // 8: memos.api.v1.CreateUserRequest - (*UpdateUserRequest)(nil), // 9: memos.api.v1.UpdateUserRequest - (*DeleteUserRequest)(nil), // 10: memos.api.v1.DeleteUserRequest - (*UserStats)(nil), // 11: memos.api.v1.UserStats - (*GetUserStatsRequest)(nil), // 12: memos.api.v1.GetUserStatsRequest - (*ListAllUserStatsRequest)(nil), // 13: memos.api.v1.ListAllUserStatsRequest - (*ListAllUserStatsResponse)(nil), // 14: memos.api.v1.ListAllUserStatsResponse - (*UserSetting)(nil), // 15: memos.api.v1.UserSetting - (*GetUserSettingRequest)(nil), // 16: memos.api.v1.GetUserSettingRequest - (*UpdateUserSettingRequest)(nil), // 17: memos.api.v1.UpdateUserSettingRequest - (*ListUserSettingsRequest)(nil), // 18: memos.api.v1.ListUserSettingsRequest - (*ListUserSettingsResponse)(nil), // 19: memos.api.v1.ListUserSettingsResponse - (*UserAccessToken)(nil), // 20: memos.api.v1.UserAccessToken - (*ListUserAccessTokensRequest)(nil), // 21: memos.api.v1.ListUserAccessTokensRequest - (*ListUserAccessTokensResponse)(nil), // 22: memos.api.v1.ListUserAccessTokensResponse - (*CreateUserAccessTokenRequest)(nil), // 23: memos.api.v1.CreateUserAccessTokenRequest - (*DeleteUserAccessTokenRequest)(nil), // 24: memos.api.v1.DeleteUserAccessTokenRequest - (*UserSession)(nil), // 25: memos.api.v1.UserSession - (*ListUserSessionsRequest)(nil), // 26: memos.api.v1.ListUserSessionsRequest - (*ListUserSessionsResponse)(nil), // 27: memos.api.v1.ListUserSessionsResponse - (*RevokeUserSessionRequest)(nil), // 28: memos.api.v1.RevokeUserSessionRequest - (*UserWebhook)(nil), // 29: memos.api.v1.UserWebhook - (*ListUserWebhooksRequest)(nil), // 30: memos.api.v1.ListUserWebhooksRequest - (*ListUserWebhooksResponse)(nil), // 31: memos.api.v1.ListUserWebhooksResponse - (*CreateUserWebhookRequest)(nil), // 32: memos.api.v1.CreateUserWebhookRequest - (*UpdateUserWebhookRequest)(nil), // 33: memos.api.v1.UpdateUserWebhookRequest - (*DeleteUserWebhookRequest)(nil), // 34: memos.api.v1.DeleteUserWebhookRequest - (*UserNotification)(nil), // 35: memos.api.v1.UserNotification - (*ListUserNotificationsRequest)(nil), // 36: memos.api.v1.ListUserNotificationsRequest - (*ListUserNotificationsResponse)(nil), // 37: memos.api.v1.ListUserNotificationsResponse - (*UpdateUserNotificationRequest)(nil), // 38: memos.api.v1.UpdateUserNotificationRequest - (*DeleteUserNotificationRequest)(nil), // 39: memos.api.v1.DeleteUserNotificationRequest - nil, // 40: memos.api.v1.UserStats.TagCountEntry - (*UserStats_MemoTypeStats)(nil), // 41: memos.api.v1.UserStats.MemoTypeStats - (*UserSetting_GeneralSetting)(nil), // 42: memos.api.v1.UserSetting.GeneralSetting - (*UserSetting_SessionsSetting)(nil), // 43: memos.api.v1.UserSetting.SessionsSetting - (*UserSetting_AccessTokensSetting)(nil), // 44: memos.api.v1.UserSetting.AccessTokensSetting - (*UserSetting_WebhooksSetting)(nil), // 45: memos.api.v1.UserSetting.WebhooksSetting - (*UserSession_ClientInfo)(nil), // 46: memos.api.v1.UserSession.ClientInfo - (State)(0), // 47: memos.api.v1.State - (*timestamppb.Timestamp)(nil), // 48: google.protobuf.Timestamp - (*fieldmaskpb.FieldMask)(nil), // 49: google.protobuf.FieldMask - (*emptypb.Empty)(nil), // 50: google.protobuf.Empty + (User_Role)(0), // 0: memos.api.v1.User.Role + (UserSetting_Key)(0), // 1: memos.api.v1.UserSetting.Key + (UserNotification_Status)(0), // 2: memos.api.v1.UserNotification.Status + (UserNotification_Type)(0), // 3: memos.api.v1.UserNotification.Type + (*User)(nil), // 4: memos.api.v1.User + (*ListUsersRequest)(nil), // 5: memos.api.v1.ListUsersRequest + (*ListUsersResponse)(nil), // 6: memos.api.v1.ListUsersResponse + (*GetUserRequest)(nil), // 7: memos.api.v1.GetUserRequest + (*CreateUserRequest)(nil), // 8: memos.api.v1.CreateUserRequest + (*UpdateUserRequest)(nil), // 9: memos.api.v1.UpdateUserRequest + (*DeleteUserRequest)(nil), // 10: memos.api.v1.DeleteUserRequest + (*UserStats)(nil), // 11: memos.api.v1.UserStats + (*GetUserStatsRequest)(nil), // 12: memos.api.v1.GetUserStatsRequest + (*ListAllUserStatsRequest)(nil), // 13: memos.api.v1.ListAllUserStatsRequest + (*ListAllUserStatsResponse)(nil), // 14: memos.api.v1.ListAllUserStatsResponse + (*UserSetting)(nil), // 15: memos.api.v1.UserSetting + (*GetUserSettingRequest)(nil), // 16: memos.api.v1.GetUserSettingRequest + (*UpdateUserSettingRequest)(nil), // 17: memos.api.v1.UpdateUserSettingRequest + (*ListUserSettingsRequest)(nil), // 18: memos.api.v1.ListUserSettingsRequest + (*ListUserSettingsResponse)(nil), // 19: memos.api.v1.ListUserSettingsResponse + (*PersonalAccessToken)(nil), // 20: memos.api.v1.PersonalAccessToken + (*ListPersonalAccessTokensRequest)(nil), // 21: memos.api.v1.ListPersonalAccessTokensRequest + (*ListPersonalAccessTokensResponse)(nil), // 22: memos.api.v1.ListPersonalAccessTokensResponse + (*CreatePersonalAccessTokenRequest)(nil), // 23: memos.api.v1.CreatePersonalAccessTokenRequest + (*CreatePersonalAccessTokenResponse)(nil), // 24: memos.api.v1.CreatePersonalAccessTokenResponse + (*DeletePersonalAccessTokenRequest)(nil), // 25: memos.api.v1.DeletePersonalAccessTokenRequest + (*Session)(nil), // 26: memos.api.v1.Session + (*ListSessionsRequest)(nil), // 27: memos.api.v1.ListSessionsRequest + (*ListSessionsResponse)(nil), // 28: memos.api.v1.ListSessionsResponse + (*RevokeSessionRequest)(nil), // 29: memos.api.v1.RevokeSessionRequest + (*UserWebhook)(nil), // 30: memos.api.v1.UserWebhook + (*ListUserWebhooksRequest)(nil), // 31: memos.api.v1.ListUserWebhooksRequest + (*ListUserWebhooksResponse)(nil), // 32: memos.api.v1.ListUserWebhooksResponse + (*CreateUserWebhookRequest)(nil), // 33: memos.api.v1.CreateUserWebhookRequest + (*UpdateUserWebhookRequest)(nil), // 34: memos.api.v1.UpdateUserWebhookRequest + (*DeleteUserWebhookRequest)(nil), // 35: memos.api.v1.DeleteUserWebhookRequest + (*UserNotification)(nil), // 36: memos.api.v1.UserNotification + (*ListUserNotificationsRequest)(nil), // 37: memos.api.v1.ListUserNotificationsRequest + (*ListUserNotificationsResponse)(nil), // 38: memos.api.v1.ListUserNotificationsResponse + (*UpdateUserNotificationRequest)(nil), // 39: memos.api.v1.UpdateUserNotificationRequest + (*DeleteUserNotificationRequest)(nil), // 40: memos.api.v1.DeleteUserNotificationRequest + nil, // 41: memos.api.v1.UserStats.TagCountEntry + (*UserStats_MemoTypeStats)(nil), // 42: memos.api.v1.UserStats.MemoTypeStats + (*UserSetting_GeneralSetting)(nil), // 43: memos.api.v1.UserSetting.GeneralSetting + (*UserSetting_SessionsSetting)(nil), // 44: memos.api.v1.UserSetting.SessionsSetting + (*UserSetting_AccessTokensSetting)(nil), // 45: memos.api.v1.UserSetting.AccessTokensSetting + (*UserSetting_WebhooksSetting)(nil), // 46: memos.api.v1.UserSetting.WebhooksSetting + (*Session_ClientInfo)(nil), // 47: memos.api.v1.Session.ClientInfo + (State)(0), // 48: memos.api.v1.State + (*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp + (*fieldmaskpb.FieldMask)(nil), // 50: google.protobuf.FieldMask + (*emptypb.Empty)(nil), // 51: google.protobuf.Empty } var file_api_v1_user_service_proto_depIdxs = []int32{ 0, // 0: memos.api.v1.User.role:type_name -> memos.api.v1.User.Role - 47, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State - 48, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp - 48, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp + 48, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State + 49, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp + 49, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp 4, // 4: memos.api.v1.ListUsersResponse.users:type_name -> memos.api.v1.User - 49, // 5: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask + 50, // 5: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask 4, // 6: memos.api.v1.CreateUserRequest.user:type_name -> memos.api.v1.User 4, // 7: memos.api.v1.UpdateUserRequest.user:type_name -> memos.api.v1.User - 49, // 8: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask - 48, // 9: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp - 41, // 10: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats - 40, // 11: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry + 50, // 8: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask + 49, // 9: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp + 42, // 10: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats + 41, // 11: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry 11, // 12: memos.api.v1.ListAllUserStatsResponse.stats:type_name -> memos.api.v1.UserStats - 42, // 13: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting - 43, // 14: memos.api.v1.UserSetting.sessions_setting:type_name -> memos.api.v1.UserSetting.SessionsSetting - 44, // 15: memos.api.v1.UserSetting.access_tokens_setting:type_name -> memos.api.v1.UserSetting.AccessTokensSetting - 45, // 16: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting + 43, // 13: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting + 44, // 14: memos.api.v1.UserSetting.sessions_setting:type_name -> memos.api.v1.UserSetting.SessionsSetting + 45, // 15: memos.api.v1.UserSetting.access_tokens_setting:type_name -> memos.api.v1.UserSetting.AccessTokensSetting + 46, // 16: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting 15, // 17: memos.api.v1.UpdateUserSettingRequest.setting:type_name -> memos.api.v1.UserSetting - 49, // 18: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask + 50, // 18: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask 15, // 19: memos.api.v1.ListUserSettingsResponse.settings:type_name -> memos.api.v1.UserSetting - 48, // 20: memos.api.v1.UserAccessToken.issued_at:type_name -> google.protobuf.Timestamp - 48, // 21: memos.api.v1.UserAccessToken.expires_at:type_name -> google.protobuf.Timestamp - 20, // 22: memos.api.v1.ListUserAccessTokensResponse.access_tokens:type_name -> memos.api.v1.UserAccessToken - 20, // 23: memos.api.v1.CreateUserAccessTokenRequest.access_token:type_name -> memos.api.v1.UserAccessToken - 48, // 24: memos.api.v1.UserSession.create_time:type_name -> google.protobuf.Timestamp - 48, // 25: memos.api.v1.UserSession.last_accessed_time:type_name -> google.protobuf.Timestamp - 46, // 26: memos.api.v1.UserSession.client_info:type_name -> memos.api.v1.UserSession.ClientInfo - 25, // 27: memos.api.v1.ListUserSessionsResponse.sessions:type_name -> memos.api.v1.UserSession - 48, // 28: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp - 48, // 29: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp - 29, // 30: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook - 29, // 31: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook - 29, // 32: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook - 49, // 33: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask - 2, // 34: memos.api.v1.UserNotification.status:type_name -> memos.api.v1.UserNotification.Status - 48, // 35: memos.api.v1.UserNotification.create_time:type_name -> google.protobuf.Timestamp - 3, // 36: memos.api.v1.UserNotification.type:type_name -> memos.api.v1.UserNotification.Type - 35, // 37: memos.api.v1.ListUserNotificationsResponse.notifications:type_name -> memos.api.v1.UserNotification - 35, // 38: memos.api.v1.UpdateUserNotificationRequest.notification:type_name -> memos.api.v1.UserNotification - 49, // 39: memos.api.v1.UpdateUserNotificationRequest.update_mask:type_name -> google.protobuf.FieldMask - 25, // 40: memos.api.v1.UserSetting.SessionsSetting.sessions:type_name -> memos.api.v1.UserSession - 20, // 41: memos.api.v1.UserSetting.AccessTokensSetting.access_tokens:type_name -> memos.api.v1.UserAccessToken - 29, // 42: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook - 5, // 43: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest - 7, // 44: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest - 8, // 45: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest - 9, // 46: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest - 10, // 47: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest - 13, // 48: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest - 12, // 49: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest - 16, // 50: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest - 17, // 51: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest - 18, // 52: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest - 21, // 53: memos.api.v1.UserService.ListUserAccessTokens:input_type -> memos.api.v1.ListUserAccessTokensRequest - 23, // 54: memos.api.v1.UserService.CreateUserAccessToken:input_type -> memos.api.v1.CreateUserAccessTokenRequest - 24, // 55: memos.api.v1.UserService.DeleteUserAccessToken:input_type -> memos.api.v1.DeleteUserAccessTokenRequest - 26, // 56: memos.api.v1.UserService.ListUserSessions:input_type -> memos.api.v1.ListUserSessionsRequest - 28, // 57: memos.api.v1.UserService.RevokeUserSession:input_type -> memos.api.v1.RevokeUserSessionRequest - 30, // 58: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest - 32, // 59: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest - 33, // 60: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest - 34, // 61: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest - 36, // 62: memos.api.v1.UserService.ListUserNotifications:input_type -> memos.api.v1.ListUserNotificationsRequest - 38, // 63: memos.api.v1.UserService.UpdateUserNotification:input_type -> memos.api.v1.UpdateUserNotificationRequest - 39, // 64: memos.api.v1.UserService.DeleteUserNotification:input_type -> memos.api.v1.DeleteUserNotificationRequest - 6, // 65: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse - 4, // 66: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User - 4, // 67: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User - 4, // 68: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User - 50, // 69: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty - 14, // 70: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse - 11, // 71: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats - 15, // 72: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting - 15, // 73: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting - 19, // 74: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse - 22, // 75: memos.api.v1.UserService.ListUserAccessTokens:output_type -> memos.api.v1.ListUserAccessTokensResponse - 20, // 76: memos.api.v1.UserService.CreateUserAccessToken:output_type -> memos.api.v1.UserAccessToken - 50, // 77: memos.api.v1.UserService.DeleteUserAccessToken:output_type -> google.protobuf.Empty - 27, // 78: memos.api.v1.UserService.ListUserSessions:output_type -> memos.api.v1.ListUserSessionsResponse - 50, // 79: memos.api.v1.UserService.RevokeUserSession:output_type -> google.protobuf.Empty - 31, // 80: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse - 29, // 81: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook - 29, // 82: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook - 50, // 83: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty - 37, // 84: memos.api.v1.UserService.ListUserNotifications:output_type -> memos.api.v1.ListUserNotificationsResponse - 35, // 85: memos.api.v1.UserService.UpdateUserNotification:output_type -> memos.api.v1.UserNotification - 50, // 86: memos.api.v1.UserService.DeleteUserNotification:output_type -> google.protobuf.Empty - 65, // [65:87] is the sub-list for method output_type - 43, // [43:65] is the sub-list for method input_type - 43, // [43:43] is the sub-list for extension type_name - 43, // [43:43] is the sub-list for extension extendee - 0, // [0:43] is the sub-list for field type_name + 49, // 20: memos.api.v1.PersonalAccessToken.created_at:type_name -> google.protobuf.Timestamp + 49, // 21: memos.api.v1.PersonalAccessToken.expires_at:type_name -> google.protobuf.Timestamp + 49, // 22: memos.api.v1.PersonalAccessToken.last_used_at:type_name -> google.protobuf.Timestamp + 20, // 23: memos.api.v1.ListPersonalAccessTokensResponse.personal_access_tokens:type_name -> memos.api.v1.PersonalAccessToken + 20, // 24: memos.api.v1.CreatePersonalAccessTokenResponse.personal_access_token:type_name -> memos.api.v1.PersonalAccessToken + 49, // 25: memos.api.v1.Session.create_time:type_name -> google.protobuf.Timestamp + 49, // 26: memos.api.v1.Session.last_accessed_time:type_name -> google.protobuf.Timestamp + 47, // 27: memos.api.v1.Session.client_info:type_name -> memos.api.v1.Session.ClientInfo + 26, // 28: memos.api.v1.ListSessionsResponse.sessions:type_name -> memos.api.v1.Session + 49, // 29: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp + 49, // 30: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp + 30, // 31: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook + 30, // 32: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook + 30, // 33: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook + 50, // 34: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask + 2, // 35: memos.api.v1.UserNotification.status:type_name -> memos.api.v1.UserNotification.Status + 49, // 36: memos.api.v1.UserNotification.create_time:type_name -> google.protobuf.Timestamp + 3, // 37: memos.api.v1.UserNotification.type:type_name -> memos.api.v1.UserNotification.Type + 36, // 38: memos.api.v1.ListUserNotificationsResponse.notifications:type_name -> memos.api.v1.UserNotification + 36, // 39: memos.api.v1.UpdateUserNotificationRequest.notification:type_name -> memos.api.v1.UserNotification + 50, // 40: memos.api.v1.UpdateUserNotificationRequest.update_mask:type_name -> google.protobuf.FieldMask + 26, // 41: memos.api.v1.UserSetting.SessionsSetting.sessions:type_name -> memos.api.v1.Session + 20, // 42: memos.api.v1.UserSetting.AccessTokensSetting.personal_access_tokens:type_name -> memos.api.v1.PersonalAccessToken + 30, // 43: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook + 5, // 44: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest + 7, // 45: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest + 8, // 46: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest + 9, // 47: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest + 10, // 48: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest + 13, // 49: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest + 12, // 50: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest + 16, // 51: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest + 17, // 52: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest + 18, // 53: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest + 21, // 54: memos.api.v1.UserService.ListPersonalAccessTokens:input_type -> memos.api.v1.ListPersonalAccessTokensRequest + 23, // 55: memos.api.v1.UserService.CreatePersonalAccessToken:input_type -> memos.api.v1.CreatePersonalAccessTokenRequest + 25, // 56: memos.api.v1.UserService.DeletePersonalAccessToken:input_type -> memos.api.v1.DeletePersonalAccessTokenRequest + 27, // 57: memos.api.v1.UserService.ListSessions:input_type -> memos.api.v1.ListSessionsRequest + 29, // 58: memos.api.v1.UserService.RevokeSession:input_type -> memos.api.v1.RevokeSessionRequest + 31, // 59: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest + 33, // 60: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest + 34, // 61: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest + 35, // 62: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest + 37, // 63: memos.api.v1.UserService.ListUserNotifications:input_type -> memos.api.v1.ListUserNotificationsRequest + 39, // 64: memos.api.v1.UserService.UpdateUserNotification:input_type -> memos.api.v1.UpdateUserNotificationRequest + 40, // 65: memos.api.v1.UserService.DeleteUserNotification:input_type -> memos.api.v1.DeleteUserNotificationRequest + 6, // 66: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse + 4, // 67: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User + 4, // 68: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User + 4, // 69: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User + 51, // 70: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty + 14, // 71: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse + 11, // 72: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats + 15, // 73: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting + 15, // 74: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting + 19, // 75: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse + 22, // 76: memos.api.v1.UserService.ListPersonalAccessTokens:output_type -> memos.api.v1.ListPersonalAccessTokensResponse + 24, // 77: memos.api.v1.UserService.CreatePersonalAccessToken:output_type -> memos.api.v1.CreatePersonalAccessTokenResponse + 51, // 78: memos.api.v1.UserService.DeletePersonalAccessToken:output_type -> google.protobuf.Empty + 28, // 79: memos.api.v1.UserService.ListSessions:output_type -> memos.api.v1.ListSessionsResponse + 51, // 80: memos.api.v1.UserService.RevokeSession:output_type -> google.protobuf.Empty + 32, // 81: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse + 30, // 82: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook + 30, // 83: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook + 51, // 84: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty + 38, // 85: memos.api.v1.UserService.ListUserNotifications:output_type -> memos.api.v1.ListUserNotificationsResponse + 36, // 86: memos.api.v1.UserService.UpdateUserNotification:output_type -> memos.api.v1.UserNotification + 51, // 87: memos.api.v1.UserService.DeleteUserNotification:output_type -> google.protobuf.Empty + 66, // [66:88] is the sub-list for method output_type + 44, // [44:66] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name } func init() { file_api_v1_user_service_proto_init() } @@ -3321,14 +3386,14 @@ func file_api_v1_user_service_proto_init() { (*UserSetting_AccessTokensSetting_)(nil), (*UserSetting_WebhooksSetting_)(nil), } - file_api_v1_user_service_proto_msgTypes[31].OneofWrappers = []any{} + file_api_v1_user_service_proto_msgTypes[32].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_user_service_proto_rawDesc), len(file_api_v1_user_service_proto_rawDesc)), NumEnums: 4, - NumMessages: 43, + NumMessages: 44, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/api/v1/user_service.pb.gw.go b/proto/gen/api/v1/user_service.pb.gw.go index 37b7dac70..342077a77 100644 --- a/proto/gen/api/v1/user_service.pb.gw.go +++ b/proto/gen/api/v1/user_service.pb.gw.go @@ -531,11 +531,11 @@ func local_request_UserService_ListUserSettings_0(ctx context.Context, marshaler return msg, metadata, err } -var filter_UserService_ListUserAccessTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{"parent": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +var filter_UserService_ListPersonalAccessTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{"parent": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} -func request_UserService_ListUserAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_UserService_ListPersonalAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListUserAccessTokensRequest + protoReq ListPersonalAccessTokensRequest metadata runtime.ServerMetadata err error ) @@ -553,16 +553,16 @@ func request_UserService_ListUserAccessTokens_0(ctx context.Context, marshaler r if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListUserAccessTokens_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListPersonalAccessTokens_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.ListUserAccessTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ListPersonalAccessTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_UserService_ListUserAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_UserService_ListPersonalAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListUserAccessTokensRequest + protoReq ListPersonalAccessTokensRequest metadata runtime.ServerMetadata err error ) @@ -577,22 +577,20 @@ func local_request_UserService_ListUserAccessTokens_0(ctx context.Context, marsh if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListUserAccessTokens_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListPersonalAccessTokens_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.ListUserAccessTokens(ctx, &protoReq) + msg, err := server.ListPersonalAccessTokens(ctx, &protoReq) return msg, metadata, err } -var filter_UserService_CreateUserAccessToken_0 = &utilities.DoubleArray{Encoding: map[string]int{"access_token": 0, "parent": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} - -func request_UserService_CreateUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_UserService_CreatePersonalAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq CreateUserAccessTokenRequest + protoReq CreatePersonalAccessTokenRequest metadata runtime.ServerMetadata err error ) - if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.AccessToken); err != nil && !errors.Is(err, io.EOF) { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if req.Body != nil { @@ -606,23 +604,17 @@ func request_UserService_CreateUserAccessToken_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err) } - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_CreateUserAccessToken_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - msg, err := client.CreateUserAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.CreatePersonalAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_UserService_CreateUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_UserService_CreatePersonalAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq CreateUserAccessTokenRequest + protoReq CreatePersonalAccessTokenRequest metadata runtime.ServerMetadata err error ) - if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.AccessToken); err != nil && !errors.Is(err, io.EOF) { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } val, ok := pathParams["parent"] @@ -633,19 +625,13 @@ func local_request_UserService_CreateUserAccessToken_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err) } - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_CreateUserAccessToken_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - msg, err := server.CreateUserAccessToken(ctx, &protoReq) + msg, err := server.CreatePersonalAccessToken(ctx, &protoReq) return msg, metadata, err } -func request_UserService_DeleteUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_UserService_DeletePersonalAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq DeleteUserAccessTokenRequest + protoReq DeletePersonalAccessTokenRequest metadata runtime.ServerMetadata err error ) @@ -660,13 +646,13 @@ func request_UserService_DeleteUserAccessToken_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := client.DeleteUserAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.DeletePersonalAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_UserService_DeleteUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_UserService_DeletePersonalAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq DeleteUserAccessTokenRequest + protoReq DeletePersonalAccessTokenRequest metadata runtime.ServerMetadata err error ) @@ -678,13 +664,13 @@ func local_request_UserService_DeleteUserAccessToken_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := server.DeleteUserAccessToken(ctx, &protoReq) + msg, err := server.DeletePersonalAccessToken(ctx, &protoReq) return msg, metadata, err } -func request_UserService_ListUserSessions_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_UserService_ListSessions_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListUserSessionsRequest + protoReq ListSessionsRequest metadata runtime.ServerMetadata err error ) @@ -699,13 +685,13 @@ func request_UserService_ListUserSessions_0(ctx context.Context, marshaler runti if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err) } - msg, err := client.ListUserSessions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ListSessions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_UserService_ListUserSessions_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_UserService_ListSessions_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListUserSessionsRequest + protoReq ListSessionsRequest metadata runtime.ServerMetadata err error ) @@ -717,13 +703,13 @@ func local_request_UserService_ListUserSessions_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err) } - msg, err := server.ListUserSessions(ctx, &protoReq) + msg, err := server.ListSessions(ctx, &protoReq) return msg, metadata, err } -func request_UserService_RevokeUserSession_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_UserService_RevokeSession_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq RevokeUserSessionRequest + protoReq RevokeSessionRequest metadata runtime.ServerMetadata err error ) @@ -738,13 +724,13 @@ func request_UserService_RevokeUserSession_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := client.RevokeUserSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.RevokeSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_UserService_RevokeUserSession_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_UserService_RevokeSession_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq RevokeUserSessionRequest + protoReq RevokeSessionRequest metadata runtime.ServerMetadata err error ) @@ -756,7 +742,7 @@ func local_request_UserService_RevokeUserSession_0(ctx context.Context, marshale if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := server.RevokeUserSession(ctx, &protoReq) + msg, err := server.RevokeSession(ctx, &protoReq) return msg, metadata, err } @@ -1343,105 +1329,105 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux } forward_UserService_ListUserSettings_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_UserService_ListUserAccessTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_UserService_ListPersonalAccessTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserAccessTokens", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/accessTokens")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/ListPersonalAccessTokens", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/personalAccessTokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_UserService_ListUserAccessTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_UserService_ListPersonalAccessTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_ListUserAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_ListPersonalAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_UserService_CreateUserAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_UserService_CreatePersonalAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/CreateUserAccessToken", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/accessTokens")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/CreatePersonalAccessToken", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/personalAccessTokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_UserService_CreateUserAccessToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_UserService_CreatePersonalAccessToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_CreateUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_CreatePersonalAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_UserService_DeleteUserAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodDelete, pattern_UserService_DeletePersonalAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/DeleteUserAccessToken", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/accessTokens/*}")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/DeletePersonalAccessToken", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/personalAccessTokens/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_UserService_DeleteUserAccessToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_UserService_DeletePersonalAccessToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_DeleteUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_DeletePersonalAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_UserService_ListUserSessions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_UserService_ListSessions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserSessions", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/sessions")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/ListSessions", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/sessions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_UserService_ListUserSessions_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_UserService_ListSessions_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_ListUserSessions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_ListSessions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_UserService_RevokeUserSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodDelete, pattern_UserService_RevokeSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/RevokeUserSession", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/sessions/*}")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/RevokeSession", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/sessions/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_UserService_RevokeUserSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_UserService_RevokeSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_RevokeUserSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_RevokeSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) mux.Handle(http.MethodGet, pattern_UserService_ListUserWebhooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) @@ -1793,90 +1779,90 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux } forward_UserService_ListUserSettings_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_UserService_ListUserAccessTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_UserService_ListPersonalAccessTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserAccessTokens", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/accessTokens")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListPersonalAccessTokens", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/personalAccessTokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_UserService_ListUserAccessTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_UserService_ListPersonalAccessTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_ListUserAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_ListPersonalAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_UserService_CreateUserAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_UserService_CreatePersonalAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/CreateUserAccessToken", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/accessTokens")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/CreatePersonalAccessToken", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/personalAccessTokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_UserService_CreateUserAccessToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_UserService_CreatePersonalAccessToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_CreateUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_CreatePersonalAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_UserService_DeleteUserAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodDelete, pattern_UserService_DeletePersonalAccessToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/DeleteUserAccessToken", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/accessTokens/*}")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/DeletePersonalAccessToken", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/personalAccessTokens/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_UserService_DeleteUserAccessToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_UserService_DeletePersonalAccessToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_DeleteUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_DeletePersonalAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_UserService_ListUserSessions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_UserService_ListSessions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserSessions", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/sessions")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListSessions", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/sessions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_UserService_ListUserSessions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_UserService_ListSessions_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_ListUserSessions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_ListSessions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodDelete, pattern_UserService_RevokeUserSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodDelete, pattern_UserService_RevokeSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/RevokeUserSession", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/sessions/*}")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/RevokeSession", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/sessions/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_UserService_RevokeUserSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_UserService_RevokeSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_UserService_RevokeUserSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_UserService_RevokeSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) mux.Handle(http.MethodGet, pattern_UserService_ListUserWebhooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) @@ -2001,51 +1987,51 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux } var ( - pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) - pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) - pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) - pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, "")) - pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) - pattern_UserService_ListAllUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "stats")) - pattern_UserService_GetUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "getStats")) - pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "name"}, "")) - pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "setting.name"}, "")) - pattern_UserService_ListUserSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "settings"}, "")) - pattern_UserService_ListUserAccessTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "accessTokens"}, "")) - pattern_UserService_CreateUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "accessTokens"}, "")) - pattern_UserService_DeleteUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "accessTokens", "name"}, "")) - pattern_UserService_ListUserSessions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "sessions"}, "")) - pattern_UserService_RevokeUserSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "sessions", "name"}, "")) - pattern_UserService_ListUserWebhooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "webhooks"}, "")) - pattern_UserService_CreateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "webhooks"}, "")) - pattern_UserService_UpdateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "webhook.name"}, "")) - pattern_UserService_DeleteUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "name"}, "")) - pattern_UserService_ListUserNotifications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "notifications"}, "")) - pattern_UserService_UpdateUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "notification.name"}, "")) - pattern_UserService_DeleteUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "name"}, "")) + pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) + pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) + pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) + pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, "")) + pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) + pattern_UserService_ListAllUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "stats")) + pattern_UserService_GetUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "getStats")) + pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "name"}, "")) + pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "setting.name"}, "")) + pattern_UserService_ListUserSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "settings"}, "")) + pattern_UserService_ListPersonalAccessTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "personalAccessTokens"}, "")) + pattern_UserService_CreatePersonalAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "personalAccessTokens"}, "")) + pattern_UserService_DeletePersonalAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "personalAccessTokens", "name"}, "")) + pattern_UserService_ListSessions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "sessions"}, "")) + pattern_UserService_RevokeSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "sessions", "name"}, "")) + pattern_UserService_ListUserWebhooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "webhooks"}, "")) + pattern_UserService_CreateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "webhooks"}, "")) + pattern_UserService_UpdateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "webhook.name"}, "")) + pattern_UserService_DeleteUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "name"}, "")) + pattern_UserService_ListUserNotifications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "parent", "notifications"}, "")) + pattern_UserService_UpdateUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "notification.name"}, "")) + pattern_UserService_DeleteUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "name"}, "")) ) var ( - forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage - forward_UserService_GetUser_0 = runtime.ForwardResponseMessage - forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage - forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage - forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage - forward_UserService_ListAllUserStats_0 = runtime.ForwardResponseMessage - forward_UserService_GetUserStats_0 = runtime.ForwardResponseMessage - forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage - forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage - forward_UserService_ListUserSettings_0 = runtime.ForwardResponseMessage - forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage - forward_UserService_CreateUserAccessToken_0 = runtime.ForwardResponseMessage - forward_UserService_DeleteUserAccessToken_0 = runtime.ForwardResponseMessage - forward_UserService_ListUserSessions_0 = runtime.ForwardResponseMessage - forward_UserService_RevokeUserSession_0 = runtime.ForwardResponseMessage - forward_UserService_ListUserWebhooks_0 = runtime.ForwardResponseMessage - forward_UserService_CreateUserWebhook_0 = runtime.ForwardResponseMessage - forward_UserService_UpdateUserWebhook_0 = runtime.ForwardResponseMessage - forward_UserService_DeleteUserWebhook_0 = runtime.ForwardResponseMessage - forward_UserService_ListUserNotifications_0 = runtime.ForwardResponseMessage - forward_UserService_UpdateUserNotification_0 = runtime.ForwardResponseMessage - forward_UserService_DeleteUserNotification_0 = runtime.ForwardResponseMessage + forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage + forward_UserService_GetUser_0 = runtime.ForwardResponseMessage + forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage + forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage + forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage + forward_UserService_ListAllUserStats_0 = runtime.ForwardResponseMessage + forward_UserService_GetUserStats_0 = runtime.ForwardResponseMessage + forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage + forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage + forward_UserService_ListUserSettings_0 = runtime.ForwardResponseMessage + forward_UserService_ListPersonalAccessTokens_0 = runtime.ForwardResponseMessage + forward_UserService_CreatePersonalAccessToken_0 = runtime.ForwardResponseMessage + forward_UserService_DeletePersonalAccessToken_0 = runtime.ForwardResponseMessage + forward_UserService_ListSessions_0 = runtime.ForwardResponseMessage + forward_UserService_RevokeSession_0 = runtime.ForwardResponseMessage + forward_UserService_ListUserWebhooks_0 = runtime.ForwardResponseMessage + forward_UserService_CreateUserWebhook_0 = runtime.ForwardResponseMessage + forward_UserService_UpdateUserWebhook_0 = runtime.ForwardResponseMessage + forward_UserService_DeleteUserWebhook_0 = runtime.ForwardResponseMessage + forward_UserService_ListUserNotifications_0 = runtime.ForwardResponseMessage + forward_UserService_UpdateUserNotification_0 = runtime.ForwardResponseMessage + forward_UserService_DeleteUserNotification_0 = runtime.ForwardResponseMessage ) diff --git a/proto/gen/api/v1/user_service_grpc.pb.go b/proto/gen/api/v1/user_service_grpc.pb.go index b0deb0f93..90459126f 100644 --- a/proto/gen/api/v1/user_service_grpc.pb.go +++ b/proto/gen/api/v1/user_service_grpc.pb.go @@ -20,28 +20,28 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers" - UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser" - UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser" - UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser" - UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser" - UserService_ListAllUserStats_FullMethodName = "/memos.api.v1.UserService/ListAllUserStats" - UserService_GetUserStats_FullMethodName = "/memos.api.v1.UserService/GetUserStats" - UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting" - UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting" - UserService_ListUserSettings_FullMethodName = "/memos.api.v1.UserService/ListUserSettings" - UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens" - UserService_CreateUserAccessToken_FullMethodName = "/memos.api.v1.UserService/CreateUserAccessToken" - UserService_DeleteUserAccessToken_FullMethodName = "/memos.api.v1.UserService/DeleteUserAccessToken" - UserService_ListUserSessions_FullMethodName = "/memos.api.v1.UserService/ListUserSessions" - UserService_RevokeUserSession_FullMethodName = "/memos.api.v1.UserService/RevokeUserSession" - UserService_ListUserWebhooks_FullMethodName = "/memos.api.v1.UserService/ListUserWebhooks" - UserService_CreateUserWebhook_FullMethodName = "/memos.api.v1.UserService/CreateUserWebhook" - UserService_UpdateUserWebhook_FullMethodName = "/memos.api.v1.UserService/UpdateUserWebhook" - UserService_DeleteUserWebhook_FullMethodName = "/memos.api.v1.UserService/DeleteUserWebhook" - UserService_ListUserNotifications_FullMethodName = "/memos.api.v1.UserService/ListUserNotifications" - UserService_UpdateUserNotification_FullMethodName = "/memos.api.v1.UserService/UpdateUserNotification" - UserService_DeleteUserNotification_FullMethodName = "/memos.api.v1.UserService/DeleteUserNotification" + UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers" + UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser" + UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser" + UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser" + UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser" + UserService_ListAllUserStats_FullMethodName = "/memos.api.v1.UserService/ListAllUserStats" + UserService_GetUserStats_FullMethodName = "/memos.api.v1.UserService/GetUserStats" + UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting" + UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting" + UserService_ListUserSettings_FullMethodName = "/memos.api.v1.UserService/ListUserSettings" + UserService_ListPersonalAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListPersonalAccessTokens" + UserService_CreatePersonalAccessToken_FullMethodName = "/memos.api.v1.UserService/CreatePersonalAccessToken" + UserService_DeletePersonalAccessToken_FullMethodName = "/memos.api.v1.UserService/DeletePersonalAccessToken" + UserService_ListSessions_FullMethodName = "/memos.api.v1.UserService/ListSessions" + UserService_RevokeSession_FullMethodName = "/memos.api.v1.UserService/RevokeSession" + UserService_ListUserWebhooks_FullMethodName = "/memos.api.v1.UserService/ListUserWebhooks" + UserService_CreateUserWebhook_FullMethodName = "/memos.api.v1.UserService/CreateUserWebhook" + UserService_UpdateUserWebhook_FullMethodName = "/memos.api.v1.UserService/UpdateUserWebhook" + UserService_DeleteUserWebhook_FullMethodName = "/memos.api.v1.UserService/DeleteUserWebhook" + UserService_ListUserNotifications_FullMethodName = "/memos.api.v1.UserService/ListUserNotifications" + UserService_UpdateUserNotification_FullMethodName = "/memos.api.v1.UserService/UpdateUserNotification" + UserService_DeleteUserNotification_FullMethodName = "/memos.api.v1.UserService/DeleteUserNotification" ) // UserServiceClient is the client API for UserService service. @@ -71,16 +71,21 @@ type UserServiceClient interface { UpdateUserSetting(ctx context.Context, in *UpdateUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error) // ListUserSettings returns a list of user settings. ListUserSettings(ctx context.Context, in *ListUserSettingsRequest, opts ...grpc.CallOption) (*ListUserSettingsResponse, error) - // ListUserAccessTokens returns a list of access tokens for a user. - ListUserAccessTokens(ctx context.Context, in *ListUserAccessTokensRequest, opts ...grpc.CallOption) (*ListUserAccessTokensResponse, error) - // CreateUserAccessToken creates a new access token for a user. - CreateUserAccessToken(ctx context.Context, in *CreateUserAccessTokenRequest, opts ...grpc.CallOption) (*UserAccessToken, error) - // DeleteUserAccessToken deletes an access token. - DeleteUserAccessToken(ctx context.Context, in *DeleteUserAccessTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // ListUserSessions returns a list of active sessions for a user. - ListUserSessions(ctx context.Context, in *ListUserSessionsRequest, opts ...grpc.CallOption) (*ListUserSessionsResponse, error) - // RevokeUserSession revokes a specific session for a user. - RevokeUserSession(ctx context.Context, in *RevokeUserSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + ListPersonalAccessTokens(ctx context.Context, in *ListPersonalAccessTokensRequest, opts ...grpc.CallOption) (*ListPersonalAccessTokensResponse, error) + // CreatePersonalAccessToken creates a new Personal Access Token for a user. + // The token value is only returned once upon creation. + CreatePersonalAccessToken(ctx context.Context, in *CreatePersonalAccessTokenRequest, opts ...grpc.CallOption) (*CreatePersonalAccessTokenResponse, error) + // DeletePersonalAccessToken deletes a Personal Access Token. + DeletePersonalAccessToken(ctx context.Context, in *DeletePersonalAccessTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // ListSessions returns a list of active login sessions for a user. + // Each session represents a browser/device where the user is logged in. + // Sessions are backed by refresh tokens with sliding expiration. + ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error) + // RevokeSession revokes a specific login session. + // This invalidates the refresh token, forcing re-authentication on that device. + RevokeSession(ctx context.Context, in *RevokeSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // ListUserWebhooks returns a list of webhooks for a user. ListUserWebhooks(ctx context.Context, in *ListUserWebhooksRequest, opts ...grpc.CallOption) (*ListUserWebhooksResponse, error) // CreateUserWebhook creates a new webhook for a user. @@ -205,50 +210,50 @@ func (c *userServiceClient) ListUserSettings(ctx context.Context, in *ListUserSe return out, nil } -func (c *userServiceClient) ListUserAccessTokens(ctx context.Context, in *ListUserAccessTokensRequest, opts ...grpc.CallOption) (*ListUserAccessTokensResponse, error) { +func (c *userServiceClient) ListPersonalAccessTokens(ctx context.Context, in *ListPersonalAccessTokensRequest, opts ...grpc.CallOption) (*ListPersonalAccessTokensResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListUserAccessTokensResponse) - err := c.cc.Invoke(ctx, UserService_ListUserAccessTokens_FullMethodName, in, out, cOpts...) + out := new(ListPersonalAccessTokensResponse) + err := c.cc.Invoke(ctx, UserService_ListPersonalAccessTokens_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *userServiceClient) CreateUserAccessToken(ctx context.Context, in *CreateUserAccessTokenRequest, opts ...grpc.CallOption) (*UserAccessToken, error) { +func (c *userServiceClient) CreatePersonalAccessToken(ctx context.Context, in *CreatePersonalAccessTokenRequest, opts ...grpc.CallOption) (*CreatePersonalAccessTokenResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(UserAccessToken) - err := c.cc.Invoke(ctx, UserService_CreateUserAccessToken_FullMethodName, in, out, cOpts...) + out := new(CreatePersonalAccessTokenResponse) + err := c.cc.Invoke(ctx, UserService_CreatePersonalAccessToken_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, in *DeleteUserAccessTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *userServiceClient) DeletePersonalAccessToken(ctx context.Context, in *DeletePersonalAccessTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, UserService_DeleteUserAccessToken_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, UserService_DeletePersonalAccessToken_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *userServiceClient) ListUserSessions(ctx context.Context, in *ListUserSessionsRequest, opts ...grpc.CallOption) (*ListUserSessionsResponse, error) { +func (c *userServiceClient) ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListUserSessionsResponse) - err := c.cc.Invoke(ctx, UserService_ListUserSessions_FullMethodName, in, out, cOpts...) + out := new(ListSessionsResponse) + err := c.cc.Invoke(ctx, UserService_ListSessions_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *userServiceClient) RevokeUserSession(ctx context.Context, in *RevokeUserSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *userServiceClient) RevokeSession(ctx context.Context, in *RevokeSessionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, UserService_RevokeUserSession_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, UserService_RevokeSession_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -352,16 +357,21 @@ type UserServiceServer interface { UpdateUserSetting(context.Context, *UpdateUserSettingRequest) (*UserSetting, error) // ListUserSettings returns a list of user settings. ListUserSettings(context.Context, *ListUserSettingsRequest) (*ListUserSettingsResponse, error) - // ListUserAccessTokens returns a list of access tokens for a user. - ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error) - // CreateUserAccessToken creates a new access token for a user. - CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*UserAccessToken, error) - // DeleteUserAccessToken deletes an access token. - DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*emptypb.Empty, error) - // ListUserSessions returns a list of active sessions for a user. - ListUserSessions(context.Context, *ListUserSessionsRequest) (*ListUserSessionsResponse, error) - // RevokeUserSession revokes a specific session for a user. - RevokeUserSession(context.Context, *RevokeUserSessionRequest) (*emptypb.Empty, error) + // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + ListPersonalAccessTokens(context.Context, *ListPersonalAccessTokensRequest) (*ListPersonalAccessTokensResponse, error) + // CreatePersonalAccessToken creates a new Personal Access Token for a user. + // The token value is only returned once upon creation. + CreatePersonalAccessToken(context.Context, *CreatePersonalAccessTokenRequest) (*CreatePersonalAccessTokenResponse, error) + // DeletePersonalAccessToken deletes a Personal Access Token. + DeletePersonalAccessToken(context.Context, *DeletePersonalAccessTokenRequest) (*emptypb.Empty, error) + // ListSessions returns a list of active login sessions for a user. + // Each session represents a browser/device where the user is logged in. + // Sessions are backed by refresh tokens with sliding expiration. + ListSessions(context.Context, *ListSessionsRequest) (*ListSessionsResponse, error) + // RevokeSession revokes a specific login session. + // This invalidates the refresh token, forcing re-authentication on that device. + RevokeSession(context.Context, *RevokeSessionRequest) (*emptypb.Empty, error) // ListUserWebhooks returns a list of webhooks for a user. ListUserWebhooks(context.Context, *ListUserWebhooksRequest) (*ListUserWebhooksResponse, error) // CreateUserWebhook creates a new webhook for a user. @@ -416,20 +426,20 @@ func (UnimplementedUserServiceServer) UpdateUserSetting(context.Context, *Update func (UnimplementedUserServiceServer) ListUserSettings(context.Context, *ListUserSettingsRequest) (*ListUserSettingsResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListUserSettings not implemented") } -func (UnimplementedUserServiceServer) ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListUserAccessTokens not implemented") +func (UnimplementedUserServiceServer) ListPersonalAccessTokens(context.Context, *ListPersonalAccessTokensRequest) (*ListPersonalAccessTokensResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPersonalAccessTokens not implemented") } -func (UnimplementedUserServiceServer) CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*UserAccessToken, error) { - return nil, status.Error(codes.Unimplemented, "method CreateUserAccessToken not implemented") +func (UnimplementedUserServiceServer) CreatePersonalAccessToken(context.Context, *CreatePersonalAccessTokenRequest) (*CreatePersonalAccessTokenResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreatePersonalAccessToken not implemented") } -func (UnimplementedUserServiceServer) DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method DeleteUserAccessToken not implemented") +func (UnimplementedUserServiceServer) DeletePersonalAccessToken(context.Context, *DeletePersonalAccessTokenRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method DeletePersonalAccessToken not implemented") } -func (UnimplementedUserServiceServer) ListUserSessions(context.Context, *ListUserSessionsRequest) (*ListUserSessionsResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListUserSessions not implemented") +func (UnimplementedUserServiceServer) ListSessions(context.Context, *ListSessionsRequest) (*ListSessionsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSessions not implemented") } -func (UnimplementedUserServiceServer) RevokeUserSession(context.Context, *RevokeUserSessionRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method RevokeUserSession not implemented") +func (UnimplementedUserServiceServer) RevokeSession(context.Context, *RevokeSessionRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeSession not implemented") } func (UnimplementedUserServiceServer) ListUserWebhooks(context.Context, *ListUserWebhooksRequest) (*ListUserWebhooksResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListUserWebhooks not implemented") @@ -653,92 +663,92 @@ func _UserService_ListUserSettings_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _UserService_ListUserAccessTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListUserAccessTokensRequest) +func _UserService_ListPersonalAccessTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPersonalAccessTokensRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(UserServiceServer).ListUserAccessTokens(ctx, in) + return srv.(UserServiceServer).ListPersonalAccessTokens(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: UserService_ListUserAccessTokens_FullMethodName, + FullMethod: UserService_ListPersonalAccessTokens_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(UserServiceServer).ListUserAccessTokens(ctx, req.(*ListUserAccessTokensRequest)) + return srv.(UserServiceServer).ListPersonalAccessTokens(ctx, req.(*ListPersonalAccessTokensRequest)) } return interceptor(ctx, in, info, handler) } -func _UserService_CreateUserAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateUserAccessTokenRequest) +func _UserService_CreatePersonalAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePersonalAccessTokenRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(UserServiceServer).CreateUserAccessToken(ctx, in) + return srv.(UserServiceServer).CreatePersonalAccessToken(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: UserService_CreateUserAccessToken_FullMethodName, + FullMethod: UserService_CreatePersonalAccessToken_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(UserServiceServer).CreateUserAccessToken(ctx, req.(*CreateUserAccessTokenRequest)) + return srv.(UserServiceServer).CreatePersonalAccessToken(ctx, req.(*CreatePersonalAccessTokenRequest)) } return interceptor(ctx, in, info, handler) } -func _UserService_DeleteUserAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteUserAccessTokenRequest) +func _UserService_DeletePersonalAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePersonalAccessTokenRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(UserServiceServer).DeleteUserAccessToken(ctx, in) + return srv.(UserServiceServer).DeletePersonalAccessToken(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: UserService_DeleteUserAccessToken_FullMethodName, + FullMethod: UserService_DeletePersonalAccessToken_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(UserServiceServer).DeleteUserAccessToken(ctx, req.(*DeleteUserAccessTokenRequest)) + return srv.(UserServiceServer).DeletePersonalAccessToken(ctx, req.(*DeletePersonalAccessTokenRequest)) } return interceptor(ctx, in, info, handler) } -func _UserService_ListUserSessions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListUserSessionsRequest) +func _UserService_ListSessions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSessionsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(UserServiceServer).ListUserSessions(ctx, in) + return srv.(UserServiceServer).ListSessions(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: UserService_ListUserSessions_FullMethodName, + FullMethod: UserService_ListSessions_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(UserServiceServer).ListUserSessions(ctx, req.(*ListUserSessionsRequest)) + return srv.(UserServiceServer).ListSessions(ctx, req.(*ListSessionsRequest)) } return interceptor(ctx, in, info, handler) } -func _UserService_RevokeUserSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RevokeUserSessionRequest) +func _UserService_RevokeSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeSessionRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(UserServiceServer).RevokeUserSession(ctx, in) + return srv.(UserServiceServer).RevokeSession(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: UserService_RevokeUserSession_FullMethodName, + FullMethod: UserService_RevokeSession_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(UserServiceServer).RevokeUserSession(ctx, req.(*RevokeUserSessionRequest)) + return srv.(UserServiceServer).RevokeSession(ctx, req.(*RevokeSessionRequest)) } return interceptor(ctx, in, info, handler) } @@ -917,24 +927,24 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ Handler: _UserService_ListUserSettings_Handler, }, { - MethodName: "ListUserAccessTokens", - Handler: _UserService_ListUserAccessTokens_Handler, + MethodName: "ListPersonalAccessTokens", + Handler: _UserService_ListPersonalAccessTokens_Handler, }, { - MethodName: "CreateUserAccessToken", - Handler: _UserService_CreateUserAccessToken_Handler, + MethodName: "CreatePersonalAccessToken", + Handler: _UserService_CreatePersonalAccessToken_Handler, }, { - MethodName: "DeleteUserAccessToken", - Handler: _UserService_DeleteUserAccessToken_Handler, + MethodName: "DeletePersonalAccessToken", + Handler: _UserService_DeletePersonalAccessToken_Handler, }, { - MethodName: "ListUserSessions", - Handler: _UserService_ListUserSessions_Handler, + MethodName: "ListSessions", + Handler: _UserService_ListSessions_Handler, }, { - MethodName: "RevokeUserSession", - Handler: _UserService_RevokeUserSession_Handler, + MethodName: "RevokeSession", + Handler: _UserService_RevokeSession_Handler, }, { MethodName: "ListUserWebhooks", diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index 412b0a8ee..c83072a03 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -239,19 +239,42 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/auth/sessions: + /api/v1/auth/me: + get: + tags: + - AuthService + description: |- + GetCurrentUser returns the authenticated user's information. + Validates the access token and returns user details. + Similar to OIDC's /userinfo endpoint. + operationId: AuthService_GetCurrentUser + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetCurrentUserResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/auth/refresh: post: tags: - AuthService description: |- - CreateSession authenticates a user and creates a new session. - Returns the authenticated user information upon successful authentication. - operationId: AuthService_CreateSession + RefreshToken exchanges a valid refresh token for a new access token. + The refresh token is read from the HttpOnly cookie. + Returns a new short-lived access token. + operationId: AuthService_RefreshToken requestBody: content: application/json: schema: - $ref: '#/components/schemas/CreateSessionRequest' + $ref: '#/components/schemas/RefreshTokenRequest' required: true responses: "200": @@ -259,41 +282,49 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateSessionResponse' + $ref: '#/components/schemas/RefreshTokenResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/auth/sessions/current: - get: + /api/v1/auth/signin: + post: tags: - AuthService description: |- - GetCurrentSession returns the current active session information. - This method is idempotent and safe, suitable for checking current session state. - operationId: AuthService_GetCurrentSession + SignIn authenticates a user with credentials and returns tokens. + On success, returns an access token and sets a refresh token cookie. + Supports password-based and SSO authentication methods. + operationId: AuthService_SignIn + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SignInRequest' + required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/GetCurrentSessionResponse' + $ref: '#/components/schemas/SignInResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - delete: + /api/v1/auth/signout: + post: tags: - AuthService description: |- - DeleteSession terminates the current user session. - This is an idempotent operation that invalidates the user's authentication. - operationId: AuthService_DeleteSession + SignOut terminates the user's authentication. + Revokes the refresh token and clears the authentication cookie. + operationId: AuthService_SignOut responses: "200": description: OK @@ -1220,108 +1251,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/users/{user}/accessTokens: - get: - tags: - - UserService - description: ListUserAccessTokens returns a list of access tokens for a user. - operationId: UserService_ListUserAccessTokens - parameters: - - name: user - in: path - description: The user id. - required: true - schema: - type: string - - name: pageSize - in: query - description: Optional. The maximum number of access tokens to return. - schema: - type: integer - format: int32 - - name: pageToken - in: query - description: Optional. A page token for pagination. - schema: - type: string - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ListUserAccessTokensResponse' - default: - description: Default error response - content: - application/json: - schema: - $ref: '#/components/schemas/Status' - post: - tags: - - UserService - description: CreateUserAccessToken creates a new access token for a user. - operationId: UserService_CreateUserAccessToken - parameters: - - name: user - in: path - description: The user id. - required: true - schema: - type: string - - name: accessTokenId - in: query - description: Optional. The access token ID to use. - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UserAccessToken' - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/UserAccessToken' - default: - description: Default error response - content: - application/json: - schema: - $ref: '#/components/schemas/Status' - /api/v1/users/{user}/accessTokens/{accessToken}: - delete: - tags: - - UserService - description: DeleteUserAccessToken deletes an access token. - operationId: UserService_DeleteUserAccessToken - parameters: - - name: user - in: path - description: The user id. - required: true - schema: - type: string - - name: accessToken - in: path - description: The accessToken id. - required: true - schema: - type: string - responses: - "200": - description: OK - content: {} - default: - description: Default error response - content: - application/json: - schema: - $ref: '#/components/schemas/Status' /api/v1/users/{user}/notifications: get: tags: @@ -1432,12 +1361,116 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/users/{user}/personalAccessTokens: + get: + tags: + - UserService + description: |- + ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user. + PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens. + operationId: UserService_ListPersonalAccessTokens + parameters: + - name: user + in: path + description: The user id. + required: true + schema: + type: string + - name: pageSize + in: query + description: Optional. The maximum number of tokens to return. + schema: + type: integer + format: int32 + - name: pageToken + in: query + description: Optional. A page token for pagination. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListPersonalAccessTokensResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - UserService + description: |- + CreatePersonalAccessToken creates a new Personal Access Token for a user. + The token value is only returned once upon creation. + operationId: UserService_CreatePersonalAccessToken + parameters: + - name: user + in: path + description: The user id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePersonalAccessTokenRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePersonalAccessTokenResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/users/{user}/personalAccessTokens/{personalAccessToken}: + delete: + tags: + - UserService + description: DeletePersonalAccessToken deletes a Personal Access Token. + operationId: UserService_DeletePersonalAccessToken + parameters: + - name: user + in: path + description: The user id. + required: true + schema: + type: string + - name: personalAccessToken + in: path + description: The personalAccessToken id. + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/users/{user}/sessions: get: tags: - UserService - description: ListUserSessions returns a list of active sessions for a user. - operationId: UserService_ListUserSessions + description: |- + ListSessions returns a list of active login sessions for a user. + Each session represents a browser/device where the user is logged in. + Sessions are backed by refresh tokens with sliding expiration. + operationId: UserService_ListSessions parameters: - name: user in: path @@ -1451,7 +1484,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ListUserSessionsResponse' + $ref: '#/components/schemas/ListSessionsResponse' default: description: Default error response content: @@ -1462,8 +1495,10 @@ paths: delete: tags: - UserService - description: RevokeUserSession revokes a specific session for a user. - operationId: UserService_RevokeUserSession + description: |- + RevokeSession revokes a specific login session. + This invalidates the refresh token, forcing re-authentication on that device. + operationId: UserService_RevokeSession parameters: - name: user in: path @@ -2049,76 +2084,35 @@ components: description: |- Optional. The related memo. Refer to `Memo.name`. Format: memos/{memo} - CreateSessionRequest: - type: object - properties: - passwordCredentials: - allOf: - - $ref: '#/components/schemas/CreateSessionRequest_PasswordCredentials' - description: Username and password authentication method. - ssoCredentials: - allOf: - - $ref: '#/components/schemas/CreateSessionRequest_SSOCredentials' - description: SSO provider authentication method. - CreateSessionRequest_PasswordCredentials: + CreatePersonalAccessTokenRequest: required: - - username - - password + - parent type: object properties: - username: + parent: type: string description: |- - The username to sign in with. - Required field for password-based authentication. - password: + Required. The parent resource where this token will be created. + Format: users/{user} + description: type: string - description: |- - The password to sign in with. - Required field for password-based authentication. - description: Nested message for password-based authentication credentials. - CreateSessionRequest_SSOCredentials: - required: - - idpId - - code - - redirectUri - type: object - properties: - idpId: + description: Optional. Description of the personal access token. + expiresInDays: type: integer - description: |- - The ID of the SSO provider. - Required field to identify the SSO provider. + description: Optional. Expiration duration in days (0 = never expires). format: int32 - code: - type: string - description: |- - The authorization code from the SSO provider. - Required field for completing the SSO flow. - redirectUri: - type: string - description: |- - The redirect URI used in the SSO flow. - Required field for security validation. - codeVerifier: - type: string - description: |- - The PKCE code verifier for enhanced security (RFC 7636). - Optional field - if provided, enables PKCE flow protection against authorization code interception. - description: Nested message for SSO authentication credentials. - CreateSessionResponse: + CreatePersonalAccessTokenResponse: type: object properties: - user: + personalAccessToken: allOf: - - $ref: '#/components/schemas/User' - description: The authenticated user information. - lastAccessedAt: + - $ref: '#/components/schemas/PersonalAccessToken' + description: The personal access token metadata. + token: type: string description: |- - Last time the session was accessed. - Used for sliding expiration calculation (last_accessed_time + 2 weeks). - format: date-time + The actual token value - only returned on creation. + This is the only time the token value will be visible. FieldMapping: type: object properties: @@ -2140,17 +2134,13 @@ components: logoUrl: type: string description: Custom profile configuration for instance branding. - GetCurrentSessionResponse: + GetCurrentUserResponse: type: object properties: user: - $ref: '#/components/schemas/User' - lastAccessedAt: - type: string - description: |- - Last time the session was accessed. - Used for sliding expiration calculation (last_accessed_time + 2 weeks). - format: date-time + allOf: + - $ref: '#/components/schemas/User' + description: The authenticated user's information. GoogleProtobufAny: type: object properties: @@ -2426,6 +2416,29 @@ components: description: |- A token that can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages. + ListPersonalAccessTokensResponse: + type: object + properties: + personalAccessTokens: + type: array + items: + $ref: '#/components/schemas/PersonalAccessToken' + description: The list of personal access tokens. + nextPageToken: + type: string + description: A token for the next page of results. + totalSize: + type: integer + description: The total count of personal access tokens. + format: int32 + ListSessionsResponse: + type: object + properties: + sessions: + type: array + items: + $ref: '#/components/schemas/Session' + description: The list of sessions. ListShortcutsResponse: type: object properties: @@ -2434,21 +2447,6 @@ components: items: $ref: '#/components/schemas/Shortcut' description: The list of shortcuts. - ListUserAccessTokensResponse: - type: object - properties: - accessTokens: - type: array - items: - $ref: '#/components/schemas/UserAccessToken' - description: The list of access tokens. - nextPageToken: - type: string - description: A token for the next page of results. - totalSize: - type: integer - description: The total count of access tokens. - format: int32 ListUserNotificationsResponse: type: object properties: @@ -2458,14 +2456,6 @@ components: $ref: '#/components/schemas/UserNotification' nextPageToken: type: string - ListUserSessionsResponse: - type: object - properties: - sessions: - type: array - items: - $ref: '#/components/schemas/UserSession' - description: The list of user sessions. ListUserSettingsResponse: type: object properties: @@ -2687,6 +2677,34 @@ components: type: string fieldMapping: $ref: '#/components/schemas/FieldMapping' + PersonalAccessToken: + type: object + properties: + name: + type: string + description: |- + The resource name of the personal access token. + Format: users/{user}/personalAccessTokens/{personal_access_token} + description: + type: string + description: The description of the token. + createdAt: + readOnly: true + type: string + description: Output only. The creation timestamp. + format: date-time + expiresAt: + type: string + description: Optional. The expiration timestamp. + format: date-time + lastUsedAt: + readOnly: true + type: string + description: Output only. The last used timestamp. + format: date-time + description: |- + PersonalAccessToken represents a long-lived token for API/script access. + PATs are distinct from short-lived JWT access tokens used for session authentication. Reaction: required: - contentId @@ -2719,6 +2737,69 @@ components: type: string description: Output only. The creation timestamp. format: date-time + RefreshTokenRequest: + type: object + properties: {} + RefreshTokenResponse: + type: object + properties: + accessToken: + type: string + description: The new short-lived access token. + expiresAt: + type: string + description: When the access token expires. + format: date-time + Session: + type: object + properties: + name: + type: string + description: |- + The resource name of the session. + Format: users/{user}/sessions/{session} + sessionId: + readOnly: true + type: string + description: The session ID. + createTime: + readOnly: true + type: string + description: The timestamp when the session was created. + format: date-time + lastAccessedTime: + readOnly: true + type: string + description: |- + The timestamp when the session was last accessed. + Used for sliding expiration calculation (last_accessed_time + 2 weeks). + format: date-time + clientInfo: + readOnly: true + allOf: + - $ref: '#/components/schemas/Session_ClientInfo' + description: Client information associated with this session. + description: |- + Session represents a user's login session on a specific device/browser. + Sessions are backed by refresh tokens with sliding expiration. + Session_ClientInfo: + type: object + properties: + userAgent: + type: string + description: User agent string of the client. + ipAddress: + type: string + description: IP address of the client. + deviceType: + type: string + description: Optional. Device type (e.g., "mobile", "desktop", "tablet"). + os: + type: string + description: Optional. Operating system (e.g., "iOS 17.0", "Windows 11"). + browser: + type: string + description: Optional. Browser name and version (e.g., "Chrome 119.0"). SetMemoAttachmentsRequest: required: - name @@ -2767,6 +2848,71 @@ components: filter: type: string description: The filter expression for the shortcut. + SignInRequest: + type: object + properties: + passwordCredentials: + allOf: + - $ref: '#/components/schemas/SignInRequest_PasswordCredentials' + description: Username and password authentication. + ssoCredentials: + allOf: + - $ref: '#/components/schemas/SignInRequest_SSOCredentials' + description: SSO provider authentication. + SignInRequest_PasswordCredentials: + required: + - username + - password + type: object + properties: + username: + type: string + description: The username to sign in with. + password: + type: string + description: The password to sign in with. + description: Nested message for password-based authentication credentials. + SignInRequest_SSOCredentials: + required: + - idpId + - code + - redirectUri + type: object + properties: + idpId: + type: integer + description: The ID of the SSO provider. + format: int32 + code: + type: string + description: The authorization code from the SSO provider. + redirectUri: + type: string + description: The redirect URI used in the SSO flow. + codeVerifier: + type: string + description: |- + The PKCE code verifier for enhanced security (RFC 7636). + Optional - enables PKCE flow protection against authorization code interception. + description: Nested message for SSO authentication credentials. + SignInResponse: + type: object + properties: + user: + allOf: + - $ref: '#/components/schemas/User' + description: The authenticated user's information. + accessToken: + type: string + description: |- + The short-lived access token for API requests. + Store in memory only, not in localStorage. + accessTokenExpiresAt: + type: string + description: |- + When the access token expires. + Client should call RefreshToken before this time. + format: date-time Status: type: object properties: @@ -2874,31 +3020,6 @@ components: type: string description: Output only. The last update timestamp. format: date-time - UserAccessToken: - type: object - properties: - name: - type: string - description: |- - The resource name of the access token. - Format: users/{user}/accessTokens/{access_token} - accessToken: - readOnly: true - type: string - description: Output only. The access token value. - description: - type: string - description: The description of the access token. - issuedAt: - readOnly: true - type: string - description: Output only. The issued timestamp. - format: date-time - expiresAt: - type: string - description: Optional. The expiration timestamp. - format: date-time - description: User access token message UserNotification: type: object properties: @@ -2939,53 +3060,6 @@ components: type: integer description: The activity ID associated with this notification. format: int32 - UserSession: - type: object - properties: - name: - type: string - description: |- - The resource name of the session. - Format: users/{user}/sessions/{session} - sessionId: - readOnly: true - type: string - description: The session ID. - createTime: - readOnly: true - type: string - description: The timestamp when the session was created. - format: date-time - lastAccessedTime: - readOnly: true - type: string - description: |- - The timestamp when the session was last accessed. - Used for sliding expiration calculation (last_accessed_time + 2 weeks). - format: date-time - clientInfo: - readOnly: true - allOf: - - $ref: '#/components/schemas/UserSession_ClientInfo' - description: Client information associated with this session. - UserSession_ClientInfo: - type: object - properties: - userAgent: - type: string - description: User agent string of the client. - ipAddress: - type: string - description: IP address of the client. - deviceType: - type: string - description: Optional. Device type (e.g., "mobile", "desktop", "tablet"). - os: - type: string - description: Optional. Operating system (e.g., "iOS 17.0", "Windows 11"). - browser: - type: string - description: Optional. Browser name and version (e.g., "Chrome 119.0"). UserSetting: type: object properties: @@ -3007,12 +3081,12 @@ components: UserSetting_AccessTokensSetting: type: object properties: - accessTokens: + personalAccessTokens: type: array items: - $ref: '#/components/schemas/UserAccessToken' - description: List of user access tokens. - description: User access tokens configuration. + $ref: '#/components/schemas/PersonalAccessToken' + description: List of personal access tokens (PATs). + description: Personal access tokens configuration. UserSetting_GeneralSetting: type: object properties: @@ -3035,8 +3109,8 @@ components: sessions: type: array items: - $ref: '#/components/schemas/UserSession' - description: List of active user sessions. + $ref: '#/components/schemas/Session' + description: List of active login sessions. description: User authentication sessions configuration. UserSetting_WebhooksSetting: type: object diff --git a/proto/gen/store/user_setting.pb.go b/proto/gen/store/user_setting.pb.go index 6279f96af..b00b1876b 100644 --- a/proto/gen/store/user_setting.pb.go +++ b/proto/gen/store/user_setting.pb.go @@ -36,6 +36,10 @@ const ( UserSetting_SHORTCUTS UserSetting_Key = 4 // The webhooks of the user. UserSetting_WEBHOOKS UserSetting_Key = 5 + // Refresh tokens for the user. + UserSetting_REFRESH_TOKENS UserSetting_Key = 6 + // Personal access tokens for the user. + UserSetting_PERSONAL_ACCESS_TOKENS UserSetting_Key = 7 ) // Enum value maps for UserSetting_Key. @@ -47,14 +51,18 @@ var ( 3: "ACCESS_TOKENS", 4: "SHORTCUTS", 5: "WEBHOOKS", + 6: "REFRESH_TOKENS", + 7: "PERSONAL_ACCESS_TOKENS", } UserSetting_Key_value = map[string]int32{ - "KEY_UNSPECIFIED": 0, - "GENERAL": 1, - "SESSIONS": 2, - "ACCESS_TOKENS": 3, - "SHORTCUTS": 4, - "WEBHOOKS": 5, + "KEY_UNSPECIFIED": 0, + "GENERAL": 1, + "SESSIONS": 2, + "ACCESS_TOKENS": 3, + "SHORTCUTS": 4, + "WEBHOOKS": 5, + "REFRESH_TOKENS": 6, + "PERSONAL_ACCESS_TOKENS": 7, } ) @@ -96,6 +104,8 @@ type UserSetting struct { // *UserSetting_AccessTokens // *UserSetting_Shortcuts // *UserSetting_Webhooks + // *UserSetting_RefreshTokens + // *UserSetting_PersonalAccessTokens Value isUserSetting_Value `protobuf_oneof:"value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -197,6 +207,24 @@ func (x *UserSetting) GetWebhooks() *WebhooksUserSetting { return nil } +func (x *UserSetting) GetRefreshTokens() *RefreshTokensUserSetting { + if x != nil { + if x, ok := x.Value.(*UserSetting_RefreshTokens); ok { + return x.RefreshTokens + } + } + return nil +} + +func (x *UserSetting) GetPersonalAccessTokens() *PersonalAccessTokensUserSetting { + if x != nil { + if x, ok := x.Value.(*UserSetting_PersonalAccessTokens); ok { + return x.PersonalAccessTokens + } + } + return nil +} + type isUserSetting_Value interface { isUserSetting_Value() } @@ -221,6 +249,14 @@ type UserSetting_Webhooks struct { Webhooks *WebhooksUserSetting `protobuf:"bytes,7,opt,name=webhooks,proto3,oneof"` } +type UserSetting_RefreshTokens struct { + RefreshTokens *RefreshTokensUserSetting `protobuf:"bytes,8,opt,name=refresh_tokens,json=refreshTokens,proto3,oneof"` +} + +type UserSetting_PersonalAccessTokens struct { + PersonalAccessTokens *PersonalAccessTokensUserSetting `protobuf:"bytes,9,opt,name=personal_access_tokens,json=personalAccessTokens,proto3,oneof"` +} + func (*UserSetting_General) isUserSetting_Value() {} func (*UserSetting_Sessions) isUserSetting_Value() {} @@ -231,6 +267,10 @@ func (*UserSetting_Shortcuts) isUserSetting_Value() {} func (*UserSetting_Webhooks) isUserSetting_Value() {} +func (*UserSetting_RefreshTokens) isUserSetting_Value() {} + +func (*UserSetting_PersonalAccessTokens) isUserSetting_Value() {} + type GeneralUserSetting struct { state protoimpl.MessageState `protogen:"open.v1"` // The user's locale. @@ -383,6 +423,94 @@ func (x *AccessTokensUserSetting) GetAccessTokens() []*AccessTokensUserSetting_A return nil } +type RefreshTokensUserSetting struct { + state protoimpl.MessageState `protogen:"open.v1"` + RefreshTokens []*RefreshTokensUserSetting_RefreshToken `protobuf:"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3" json:"refresh_tokens,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokensUserSetting) Reset() { + *x = RefreshTokensUserSetting{} + mi := &file_store_user_setting_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokensUserSetting) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokensUserSetting) ProtoMessage() {} + +func (x *RefreshTokensUserSetting) ProtoReflect() protoreflect.Message { + mi := &file_store_user_setting_proto_msgTypes[4] + 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 RefreshTokensUserSetting.ProtoReflect.Descriptor instead. +func (*RefreshTokensUserSetting) Descriptor() ([]byte, []int) { + return file_store_user_setting_proto_rawDescGZIP(), []int{4} +} + +func (x *RefreshTokensUserSetting) GetRefreshTokens() []*RefreshTokensUserSetting_RefreshToken { + if x != nil { + return x.RefreshTokens + } + return nil +} + +type PersonalAccessTokensUserSetting struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tokens []*PersonalAccessTokensUserSetting_PersonalAccessToken `protobuf:"bytes,1,rep,name=tokens,proto3" json:"tokens,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PersonalAccessTokensUserSetting) Reset() { + *x = PersonalAccessTokensUserSetting{} + mi := &file_store_user_setting_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PersonalAccessTokensUserSetting) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PersonalAccessTokensUserSetting) ProtoMessage() {} + +func (x *PersonalAccessTokensUserSetting) ProtoReflect() protoreflect.Message { + mi := &file_store_user_setting_proto_msgTypes[5] + 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 PersonalAccessTokensUserSetting.ProtoReflect.Descriptor instead. +func (*PersonalAccessTokensUserSetting) Descriptor() ([]byte, []int) { + return file_store_user_setting_proto_rawDescGZIP(), []int{5} +} + +func (x *PersonalAccessTokensUserSetting) GetTokens() []*PersonalAccessTokensUserSetting_PersonalAccessToken { + if x != nil { + return x.Tokens + } + return nil +} + type ShortcutsUserSetting struct { state protoimpl.MessageState `protogen:"open.v1"` Shortcuts []*ShortcutsUserSetting_Shortcut `protobuf:"bytes,1,rep,name=shortcuts,proto3" json:"shortcuts,omitempty"` @@ -392,7 +520,7 @@ type ShortcutsUserSetting struct { func (x *ShortcutsUserSetting) Reset() { *x = ShortcutsUserSetting{} - mi := &file_store_user_setting_proto_msgTypes[4] + mi := &file_store_user_setting_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -404,7 +532,7 @@ func (x *ShortcutsUserSetting) String() string { func (*ShortcutsUserSetting) ProtoMessage() {} func (x *ShortcutsUserSetting) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[4] + mi := &file_store_user_setting_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -417,7 +545,7 @@ func (x *ShortcutsUserSetting) ProtoReflect() protoreflect.Message { // Deprecated: Use ShortcutsUserSetting.ProtoReflect.Descriptor instead. func (*ShortcutsUserSetting) Descriptor() ([]byte, []int) { - return file_store_user_setting_proto_rawDescGZIP(), []int{4} + return file_store_user_setting_proto_rawDescGZIP(), []int{6} } func (x *ShortcutsUserSetting) GetShortcuts() []*ShortcutsUserSetting_Shortcut { @@ -436,7 +564,7 @@ type WebhooksUserSetting struct { func (x *WebhooksUserSetting) Reset() { *x = WebhooksUserSetting{} - mi := &file_store_user_setting_proto_msgTypes[5] + mi := &file_store_user_setting_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -448,7 +576,7 @@ func (x *WebhooksUserSetting) String() string { func (*WebhooksUserSetting) ProtoMessage() {} func (x *WebhooksUserSetting) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[5] + mi := &file_store_user_setting_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -461,7 +589,7 @@ func (x *WebhooksUserSetting) ProtoReflect() protoreflect.Message { // Deprecated: Use WebhooksUserSetting.ProtoReflect.Descriptor instead. func (*WebhooksUserSetting) Descriptor() ([]byte, []int) { - return file_store_user_setting_proto_rawDescGZIP(), []int{5} + return file_store_user_setting_proto_rawDescGZIP(), []int{7} } func (x *WebhooksUserSetting) GetWebhooks() []*WebhooksUserSetting_Webhook { @@ -488,7 +616,7 @@ type SessionsUserSetting_Session struct { func (x *SessionsUserSetting_Session) Reset() { *x = SessionsUserSetting_Session{} - mi := &file_store_user_setting_proto_msgTypes[6] + mi := &file_store_user_setting_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -500,7 +628,7 @@ func (x *SessionsUserSetting_Session) String() string { func (*SessionsUserSetting_Session) ProtoMessage() {} func (x *SessionsUserSetting_Session) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[6] + mi := &file_store_user_setting_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -562,7 +690,7 @@ type SessionsUserSetting_ClientInfo struct { func (x *SessionsUserSetting_ClientInfo) Reset() { *x = SessionsUserSetting_ClientInfo{} - mi := &file_store_user_setting_proto_msgTypes[7] + mi := &file_store_user_setting_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -574,7 +702,7 @@ func (x *SessionsUserSetting_ClientInfo) String() string { func (*SessionsUserSetting_ClientInfo) ProtoMessage() {} func (x *SessionsUserSetting_ClientInfo) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[7] + mi := &file_store_user_setting_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -638,7 +766,7 @@ type AccessTokensUserSetting_AccessToken struct { func (x *AccessTokensUserSetting_AccessToken) Reset() { *x = AccessTokensUserSetting_AccessToken{} - mi := &file_store_user_setting_proto_msgTypes[8] + mi := &file_store_user_setting_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -650,7 +778,7 @@ func (x *AccessTokensUserSetting_AccessToken) String() string { func (*AccessTokensUserSetting_AccessToken) ProtoMessage() {} func (x *AccessTokensUserSetting_AccessToken) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[8] + mi := &file_store_user_setting_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -680,6 +808,177 @@ func (x *AccessTokensUserSetting_AccessToken) GetDescription() string { return "" } +type RefreshTokensUserSetting_RefreshToken struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier (matches 'tid' claim in JWT) + TokenId string `protobuf:"bytes,1,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty"` + // When the token expires + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + // When the token was created + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + // Client information for session management UI + ClientInfo *SessionsUserSetting_ClientInfo `protobuf:"bytes,4,opt,name=client_info,json=clientInfo,proto3" json:"client_info,omitempty"` + // Optional description + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokensUserSetting_RefreshToken) Reset() { + *x = RefreshTokensUserSetting_RefreshToken{} + mi := &file_store_user_setting_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokensUserSetting_RefreshToken) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokensUserSetting_RefreshToken) ProtoMessage() {} + +func (x *RefreshTokensUserSetting_RefreshToken) ProtoReflect() protoreflect.Message { + mi := &file_store_user_setting_proto_msgTypes[11] + 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 RefreshTokensUserSetting_RefreshToken.ProtoReflect.Descriptor instead. +func (*RefreshTokensUserSetting_RefreshToken) Descriptor() ([]byte, []int) { + return file_store_user_setting_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *RefreshTokensUserSetting_RefreshToken) GetTokenId() string { + if x != nil { + return x.TokenId + } + return "" +} + +func (x *RefreshTokensUserSetting_RefreshToken) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +func (x *RefreshTokensUserSetting_RefreshToken) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *RefreshTokensUserSetting_RefreshToken) GetClientInfo() *SessionsUserSetting_ClientInfo { + if x != nil { + return x.ClientInfo + } + return nil +} + +func (x *RefreshTokensUserSetting_RefreshToken) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +type PersonalAccessTokensUserSetting_PersonalAccessToken struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier for this token + TokenId string `protobuf:"bytes,1,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty"` + // SHA-256 hash of the actual token + TokenHash string `protobuf:"bytes,2,opt,name=token_hash,json=tokenHash,proto3" json:"token_hash,omitempty"` + // User-provided description + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // When the token expires (null = never) + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + // When the token was created + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + // When the token was last used + LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) Reset() { + *x = PersonalAccessTokensUserSetting_PersonalAccessToken{} + mi := &file_store_user_setting_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PersonalAccessTokensUserSetting_PersonalAccessToken) ProtoMessage() {} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) ProtoReflect() protoreflect.Message { + mi := &file_store_user_setting_proto_msgTypes[12] + 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 PersonalAccessTokensUserSetting_PersonalAccessToken.ProtoReflect.Descriptor instead. +func (*PersonalAccessTokensUserSetting_PersonalAccessToken) Descriptor() ([]byte, []int) { + return file_store_user_setting_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetTokenId() string { + if x != nil { + return x.TokenId + } + return "" +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetTokenHash() string { + if x != nil { + return x.TokenHash + } + return "" +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *PersonalAccessTokensUserSetting_PersonalAccessToken) GetLastUsedAt() *timestamppb.Timestamp { + if x != nil { + return x.LastUsedAt + } + return nil +} + type ShortcutsUserSetting_Shortcut struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -691,7 +990,7 @@ type ShortcutsUserSetting_Shortcut struct { func (x *ShortcutsUserSetting_Shortcut) Reset() { *x = ShortcutsUserSetting_Shortcut{} - mi := &file_store_user_setting_proto_msgTypes[9] + mi := &file_store_user_setting_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -703,7 +1002,7 @@ func (x *ShortcutsUserSetting_Shortcut) String() string { func (*ShortcutsUserSetting_Shortcut) ProtoMessage() {} func (x *ShortcutsUserSetting_Shortcut) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[9] + mi := &file_store_user_setting_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -716,7 +1015,7 @@ func (x *ShortcutsUserSetting_Shortcut) ProtoReflect() protoreflect.Message { // Deprecated: Use ShortcutsUserSetting_Shortcut.ProtoReflect.Descriptor instead. func (*ShortcutsUserSetting_Shortcut) Descriptor() ([]byte, []int) { - return file_store_user_setting_proto_rawDescGZIP(), []int{4, 0} + return file_store_user_setting_proto_rawDescGZIP(), []int{6, 0} } func (x *ShortcutsUserSetting_Shortcut) GetId() string { @@ -754,7 +1053,7 @@ type WebhooksUserSetting_Webhook struct { func (x *WebhooksUserSetting_Webhook) Reset() { *x = WebhooksUserSetting_Webhook{} - mi := &file_store_user_setting_proto_msgTypes[10] + mi := &file_store_user_setting_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -766,7 +1065,7 @@ func (x *WebhooksUserSetting_Webhook) String() string { func (*WebhooksUserSetting_Webhook) ProtoMessage() {} func (x *WebhooksUserSetting_Webhook) ProtoReflect() protoreflect.Message { - mi := &file_store_user_setting_proto_msgTypes[10] + mi := &file_store_user_setting_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -779,7 +1078,7 @@ func (x *WebhooksUserSetting_Webhook) ProtoReflect() protoreflect.Message { // Deprecated: Use WebhooksUserSetting_Webhook.ProtoReflect.Descriptor instead. func (*WebhooksUserSetting_Webhook) Descriptor() ([]byte, []int) { - return file_store_user_setting_proto_rawDescGZIP(), []int{5, 0} + return file_store_user_setting_proto_rawDescGZIP(), []int{7, 0} } func (x *WebhooksUserSetting_Webhook) GetId() string { @@ -807,7 +1106,7 @@ var File_store_user_setting_proto protoreflect.FileDescriptor const file_store_user_setting_proto_rawDesc = "" + "\n" + - "\x18store/user_setting.proto\x12\vmemos.store\x1a\x1fgoogle/protobuf/timestamp.proto\"\x93\x04\n" + + "\x18store/user_setting.proto\x12\vmemos.store\x1a\x1fgoogle/protobuf/timestamp.proto\"\xfa\x05\n" + "\vUserSetting\x12\x17\n" + "\auser_id\x18\x01 \x01(\x05R\x06userId\x12.\n" + "\x03key\x18\x02 \x01(\x0e2\x1c.memos.store.UserSetting.KeyR\x03key\x12;\n" + @@ -815,14 +1114,18 @@ const file_store_user_setting_proto_rawDesc = "" + "\bsessions\x18\x04 \x01(\v2 .memos.store.SessionsUserSettingH\x00R\bsessions\x12K\n" + "\raccess_tokens\x18\x05 \x01(\v2$.memos.store.AccessTokensUserSettingH\x00R\faccessTokens\x12A\n" + "\tshortcuts\x18\x06 \x01(\v2!.memos.store.ShortcutsUserSettingH\x00R\tshortcuts\x12>\n" + - "\bwebhooks\x18\a \x01(\v2 .memos.store.WebhooksUserSettingH\x00R\bwebhooks\"e\n" + + "\bwebhooks\x18\a \x01(\v2 .memos.store.WebhooksUserSettingH\x00R\bwebhooks\x12N\n" + + "\x0erefresh_tokens\x18\b \x01(\v2%.memos.store.RefreshTokensUserSettingH\x00R\rrefreshTokens\x12d\n" + + "\x16personal_access_tokens\x18\t \x01(\v2,.memos.store.PersonalAccessTokensUserSettingH\x00R\x14personalAccessTokens\"\x95\x01\n" + "\x03Key\x12\x13\n" + "\x0fKEY_UNSPECIFIED\x10\x00\x12\v\n" + "\aGENERAL\x10\x01\x12\f\n" + "\bSESSIONS\x10\x02\x12\x11\n" + "\rACCESS_TOKENS\x10\x03\x12\r\n" + "\tSHORTCUTS\x10\x04\x12\f\n" + - "\bWEBHOOKS\x10\x05B\a\n" + + "\bWEBHOOKS\x10\x05\x12\x12\n" + + "\x0eREFRESH_TOKENS\x10\x06\x12\x1a\n" + + "\x16PERSONAL_ACCESS_TOKENS\x10\aB\a\n" + "\x05value\"k\n" + "\x12GeneralUserSetting\x12\x16\n" + "\x06locale\x18\x01 \x01(\tR\x06locale\x12'\n" + @@ -852,7 +1155,31 @@ const file_store_user_setting_proto_rawDesc = "" + "\raccess_tokens\x18\x01 \x03(\v20.memos.store.AccessTokensUserSetting.AccessTokenR\faccessTokens\x1aR\n" + "\vAccessToken\x12!\n" + "\faccess_token\x18\x01 \x01(\tR\vaccessToken\x12 \n" + - "\vdescription\x18\x02 \x01(\tR\vdescription\"\xaa\x01\n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\"\x87\x03\n" + + "\x18RefreshTokensUserSetting\x12Y\n" + + "\x0erefresh_tokens\x18\x01 \x03(\v22.memos.store.RefreshTokensUserSetting.RefreshTokenR\rrefreshTokens\x1a\x8f\x02\n" + + "\fRefreshToken\x12\x19\n" + + "\btoken_id\x18\x01 \x01(\tR\atokenId\x129\n" + + "\n" + + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x129\n" + + "\n" + + "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12L\n" + + "\vclient_info\x18\x04 \x01(\v2+.memos.store.SessionsUserSetting.ClientInfoR\n" + + "clientInfo\x12 \n" + + "\vdescription\x18\x05 \x01(\tR\vdescription\"\xa3\x03\n" + + "\x1fPersonalAccessTokensUserSetting\x12X\n" + + "\x06tokens\x18\x01 \x03(\v2@.memos.store.PersonalAccessTokensUserSetting.PersonalAccessTokenR\x06tokens\x1a\xa5\x02\n" + + "\x13PersonalAccessToken\x12\x19\n" + + "\btoken_id\x18\x01 \x01(\tR\atokenId\x12\x1d\n" + + "\n" + + "token_hash\x18\x02 \x01(\tR\ttokenHash\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x129\n" + + "\n" + + "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x129\n" + + "\n" + + "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12<\n" + + "\flast_used_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "lastUsedAt\"\xaa\x01\n" + "\x14ShortcutsUserSetting\x12H\n" + "\tshortcuts\x18\x01 \x03(\v2*.memos.store.ShortcutsUserSetting.ShortcutR\tshortcuts\x1aH\n" + "\bShortcut\x12\x0e\n" + @@ -880,41 +1207,55 @@ func file_store_user_setting_proto_rawDescGZIP() []byte { } var file_store_user_setting_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_store_user_setting_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_store_user_setting_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_store_user_setting_proto_goTypes = []any{ - (UserSetting_Key)(0), // 0: memos.store.UserSetting.Key - (*UserSetting)(nil), // 1: memos.store.UserSetting - (*GeneralUserSetting)(nil), // 2: memos.store.GeneralUserSetting - (*SessionsUserSetting)(nil), // 3: memos.store.SessionsUserSetting - (*AccessTokensUserSetting)(nil), // 4: memos.store.AccessTokensUserSetting - (*ShortcutsUserSetting)(nil), // 5: memos.store.ShortcutsUserSetting - (*WebhooksUserSetting)(nil), // 6: memos.store.WebhooksUserSetting - (*SessionsUserSetting_Session)(nil), // 7: memos.store.SessionsUserSetting.Session - (*SessionsUserSetting_ClientInfo)(nil), // 8: memos.store.SessionsUserSetting.ClientInfo - (*AccessTokensUserSetting_AccessToken)(nil), // 9: memos.store.AccessTokensUserSetting.AccessToken - (*ShortcutsUserSetting_Shortcut)(nil), // 10: memos.store.ShortcutsUserSetting.Shortcut - (*WebhooksUserSetting_Webhook)(nil), // 11: memos.store.WebhooksUserSetting.Webhook - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp + (UserSetting_Key)(0), // 0: memos.store.UserSetting.Key + (*UserSetting)(nil), // 1: memos.store.UserSetting + (*GeneralUserSetting)(nil), // 2: memos.store.GeneralUserSetting + (*SessionsUserSetting)(nil), // 3: memos.store.SessionsUserSetting + (*AccessTokensUserSetting)(nil), // 4: memos.store.AccessTokensUserSetting + (*RefreshTokensUserSetting)(nil), // 5: memos.store.RefreshTokensUserSetting + (*PersonalAccessTokensUserSetting)(nil), // 6: memos.store.PersonalAccessTokensUserSetting + (*ShortcutsUserSetting)(nil), // 7: memos.store.ShortcutsUserSetting + (*WebhooksUserSetting)(nil), // 8: memos.store.WebhooksUserSetting + (*SessionsUserSetting_Session)(nil), // 9: memos.store.SessionsUserSetting.Session + (*SessionsUserSetting_ClientInfo)(nil), // 10: memos.store.SessionsUserSetting.ClientInfo + (*AccessTokensUserSetting_AccessToken)(nil), // 11: memos.store.AccessTokensUserSetting.AccessToken + (*RefreshTokensUserSetting_RefreshToken)(nil), // 12: memos.store.RefreshTokensUserSetting.RefreshToken + (*PersonalAccessTokensUserSetting_PersonalAccessToken)(nil), // 13: memos.store.PersonalAccessTokensUserSetting.PersonalAccessToken + (*ShortcutsUserSetting_Shortcut)(nil), // 14: memos.store.ShortcutsUserSetting.Shortcut + (*WebhooksUserSetting_Webhook)(nil), // 15: memos.store.WebhooksUserSetting.Webhook + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp } var file_store_user_setting_proto_depIdxs = []int32{ 0, // 0: memos.store.UserSetting.key:type_name -> memos.store.UserSetting.Key 2, // 1: memos.store.UserSetting.general:type_name -> memos.store.GeneralUserSetting 3, // 2: memos.store.UserSetting.sessions:type_name -> memos.store.SessionsUserSetting 4, // 3: memos.store.UserSetting.access_tokens:type_name -> memos.store.AccessTokensUserSetting - 5, // 4: memos.store.UserSetting.shortcuts:type_name -> memos.store.ShortcutsUserSetting - 6, // 5: memos.store.UserSetting.webhooks:type_name -> memos.store.WebhooksUserSetting - 7, // 6: memos.store.SessionsUserSetting.sessions:type_name -> memos.store.SessionsUserSetting.Session - 9, // 7: memos.store.AccessTokensUserSetting.access_tokens:type_name -> memos.store.AccessTokensUserSetting.AccessToken - 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.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 + 7, // 4: memos.store.UserSetting.shortcuts:type_name -> memos.store.ShortcutsUserSetting + 8, // 5: memos.store.UserSetting.webhooks:type_name -> memos.store.WebhooksUserSetting + 5, // 6: memos.store.UserSetting.refresh_tokens:type_name -> memos.store.RefreshTokensUserSetting + 6, // 7: memos.store.UserSetting.personal_access_tokens:type_name -> memos.store.PersonalAccessTokensUserSetting + 9, // 8: memos.store.SessionsUserSetting.sessions:type_name -> memos.store.SessionsUserSetting.Session + 11, // 9: memos.store.AccessTokensUserSetting.access_tokens:type_name -> memos.store.AccessTokensUserSetting.AccessToken + 12, // 10: memos.store.RefreshTokensUserSetting.refresh_tokens:type_name -> memos.store.RefreshTokensUserSetting.RefreshToken + 13, // 11: memos.store.PersonalAccessTokensUserSetting.tokens:type_name -> memos.store.PersonalAccessTokensUserSetting.PersonalAccessToken + 14, // 12: memos.store.ShortcutsUserSetting.shortcuts:type_name -> memos.store.ShortcutsUserSetting.Shortcut + 15, // 13: memos.store.WebhooksUserSetting.webhooks:type_name -> memos.store.WebhooksUserSetting.Webhook + 16, // 14: memos.store.SessionsUserSetting.Session.create_time:type_name -> google.protobuf.Timestamp + 16, // 15: memos.store.SessionsUserSetting.Session.last_accessed_time:type_name -> google.protobuf.Timestamp + 10, // 16: memos.store.SessionsUserSetting.Session.client_info:type_name -> memos.store.SessionsUserSetting.ClientInfo + 16, // 17: memos.store.RefreshTokensUserSetting.RefreshToken.expires_at:type_name -> google.protobuf.Timestamp + 16, // 18: memos.store.RefreshTokensUserSetting.RefreshToken.created_at:type_name -> google.protobuf.Timestamp + 10, // 19: memos.store.RefreshTokensUserSetting.RefreshToken.client_info:type_name -> memos.store.SessionsUserSetting.ClientInfo + 16, // 20: memos.store.PersonalAccessTokensUserSetting.PersonalAccessToken.expires_at:type_name -> google.protobuf.Timestamp + 16, // 21: memos.store.PersonalAccessTokensUserSetting.PersonalAccessToken.created_at:type_name -> google.protobuf.Timestamp + 16, // 22: memos.store.PersonalAccessTokensUserSetting.PersonalAccessToken.last_used_at:type_name -> google.protobuf.Timestamp + 23, // [23:23] is the sub-list for method output_type + 23, // [23:23] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_store_user_setting_proto_init() } @@ -928,6 +1269,8 @@ func file_store_user_setting_proto_init() { (*UserSetting_AccessTokens)(nil), (*UserSetting_Shortcuts)(nil), (*UserSetting_Webhooks)(nil), + (*UserSetting_RefreshTokens)(nil), + (*UserSetting_PersonalAccessTokens)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -935,7 +1278,7 @@ func file_store_user_setting_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_store_user_setting_proto_rawDesc), len(file_store_user_setting_proto_rawDesc)), NumEnums: 1, - NumMessages: 11, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/store/user_setting.proto b/proto/store/user_setting.proto index 87c8657f4..f3fc1eab9 100644 --- a/proto/store/user_setting.proto +++ b/proto/store/user_setting.proto @@ -19,6 +19,10 @@ message UserSetting { SHORTCUTS = 4; // The webhooks of the user. WEBHOOKS = 5; + // Refresh tokens for the user. + REFRESH_TOKENS = 6; + // Personal access tokens for the user. + PERSONAL_ACCESS_TOKENS = 7; } int32 user_id = 1; @@ -30,6 +34,8 @@ message UserSetting { AccessTokensUserSetting access_tokens = 5; ShortcutsUserSetting shortcuts = 6; WebhooksUserSetting webhooks = 7; + RefreshTokensUserSetting refresh_tokens = 8; + PersonalAccessTokensUserSetting personal_access_tokens = 9; } } @@ -83,6 +89,40 @@ message AccessTokensUserSetting { repeated AccessToken access_tokens = 1; } +message RefreshTokensUserSetting { + message RefreshToken { + // Unique identifier (matches 'tid' claim in JWT) + string token_id = 1; + // When the token expires + google.protobuf.Timestamp expires_at = 2; + // When the token was created + google.protobuf.Timestamp created_at = 3; + // Client information for session management UI + SessionsUserSetting.ClientInfo client_info = 4; + // Optional description + string description = 5; + } + repeated RefreshToken refresh_tokens = 1; +} + +message PersonalAccessTokensUserSetting { + message PersonalAccessToken { + // Unique identifier for this token + string token_id = 1; + // SHA-256 hash of the actual token + string token_hash = 2; + // User-provided description + string description = 3; + // When the token expires (null = never) + google.protobuf.Timestamp expires_at = 4; + // When the token was created + google.protobuf.Timestamp created_at = 5; + // When the token was last used + google.protobuf.Timestamp last_used_at = 6; + } + repeated PersonalAccessToken tokens = 1; +} + message ShortcutsUserSetting { message Shortcut { string id = 1; diff --git a/server/auth/authenticator.go b/server/auth/authenticator.go index e6bab9f36..049cadbe6 100644 --- a/server/auth/authenticator.go +++ b/server/auth/authenticator.go @@ -2,9 +2,10 @@ package auth import ( "context" + "log/slog" + "strings" "time" - "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" "google.golang.org/protobuf/types/known/timestamppb" @@ -35,182 +36,128 @@ func NewAuthenticator(store *store.Store, secret string) *Authenticator { } } -// AuthenticateBySession validates a session cookie and returns the authenticated user. -// -// Validation steps: -// 1. Use session ID to find the user and session details (single DB query) -// 2. Verify user exists and is not archived -// 3. Check session hasn't expired (sliding expiration: 14 days from last access) -// -// Returns the user if authentication succeeds, or an error describing the failure. -func (a *Authenticator) AuthenticateBySession(ctx context.Context, sessionID string) (*store.User, error) { - if sessionID == "" { - return nil, errors.New("session ID not found") - } - - // Find the session and user in a single database query - result, err := a.store.GetUserSessionByID(ctx, sessionID) +// AuthenticateByAccessTokenV2 validates a short-lived access token. +// Returns claims without database query (stateless validation). +func (a *Authenticator) AuthenticateByAccessTokenV2(accessToken string) (*UserClaims, error) { + claims, err := ParseAccessTokenV2(accessToken, []byte(a.secret)) if err != nil { - return nil, errors.Wrap(err, "session not found") - } - - user, err := a.store.GetUser(ctx, &store.FindUser{ID: &result.UserID}) - if err != nil { - return nil, errors.Wrap(err, "failed to get user") - } - if user == nil { - return nil, errors.New("user not found") - } - if user.RowStatus == store.Archived { - return nil, errors.New("user is archived") - } - - // Validate session expiration - if result.Session.LastAccessedTime != nil { - expiration := result.Session.LastAccessedTime.AsTime().Add(SessionSlidingDuration) - if expiration.Before(time.Now()) { - return nil, errors.New("session expired") - } - } - - return user, nil -} - -// AuthenticateByJWT validates a JWT access token and returns the authenticated user. -// -// Validation steps: -// 1. Parse and verify JWT signature using server secret -// 2. Verify key ID matches expected version -// 3. Extract user ID from JWT claims (subject field) -// 4. Verify user exists and is not archived -// 5. Verify token exists in user's access_tokens list (for revocation support) -// -// Returns the user if authentication succeeds, or an error describing the failure. -func (a *Authenticator) AuthenticateByJWT(ctx context.Context, accessToken string) (*store.User, error) { - if accessToken == "" { - return nil, errors.New("access token not found") - } - - claims := &ClaimsMessage{} - _, err := jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) { - if t.Method.Alg() != jwt.SigningMethodHS256.Name { - return nil, errors.Errorf("unexpected signing method: %v", t.Header["alg"]) - } - kid, ok := t.Header["kid"].(string) - if !ok || kid != KeyID { - return nil, errors.Errorf("unexpected kid: %v", t.Header["kid"]) - } - return []byte(a.secret), nil - }) - if err != nil { - return nil, errors.New("invalid or expired access token") + return nil, errors.Wrap(err, "invalid access token") } userID, err := util.ConvertStringToInt32(claims.Subject) if err != nil { - return nil, errors.Wrap(err, "malformed ID in token") + return nil, errors.Wrap(err, "invalid user ID in token") } + return &UserClaims{ + UserID: userID, + Username: claims.Username, + Role: claims.Role, + Status: claims.Status, + }, nil +} + +// AuthenticateByRefreshToken validates a refresh token against the database. +func (a *Authenticator) AuthenticateByRefreshToken(ctx context.Context, refreshToken string) (*store.User, string, error) { + claims, err := ParseRefreshToken(refreshToken, []byte(a.secret)) + if err != nil { + return nil, "", errors.Wrap(err, "invalid refresh token") + } + + userID, err := util.ConvertStringToInt32(claims.Subject) + if err != nil { + return nil, "", errors.Wrap(err, "invalid user ID in token") + } + + // Check token exists in database (revocation check) + token, err := a.store.GetUserRefreshTokenByID(ctx, userID, claims.TokenID) + if err != nil { + return nil, "", errors.Wrap(err, "failed to get refresh token") + } + if token == nil { + return nil, "", errors.New("refresh token revoked") + } + + // Check token not expired + if token.ExpiresAt != nil && token.ExpiresAt.AsTime().Before(time.Now()) { + return nil, "", errors.New("refresh token expired") + } + + // Get user user, err := a.store.GetUser(ctx, &store.FindUser{ID: &userID}) if err != nil { - return nil, errors.Wrap(err, "failed to get user") + return nil, "", errors.Wrap(err, "failed to get user") } if user == nil { - return nil, errors.Errorf("user %d not found", userID) + return nil, "", errors.New("user not found") } if user.RowStatus == store.Archived { - return nil, errors.Errorf("user %d is archived", userID) + return nil, "", errors.New("user is archived") } - accessTokens, err := a.store.GetUserAccessTokens(ctx, user.ID) + return user, claims.TokenID, nil +} + +// AuthenticateByPAT validates a Personal Access Token. +func (a *Authenticator) AuthenticateByPAT(ctx context.Context, token string) (*store.User, *storepb.PersonalAccessTokensUserSetting_PersonalAccessToken, error) { + if !strings.HasPrefix(token, PersonalAccessTokenPrefix) { + return nil, nil, errors.New("invalid PAT format") + } + + tokenHash := HashPersonalAccessToken(token) + result, err := a.store.GetUserByPATHash(ctx, tokenHash) if err != nil { - return nil, errors.Wrap(err, "failed to get user access tokens") - } - if !validateAccessToken(accessToken, accessTokens) { - return nil, errors.New("invalid access token") + return nil, nil, errors.Wrap(err, "invalid PAT") } - return user, nil -} - -// AuthorizeAndSetContext checks user authorization for the given procedure and sets context values. -// -// Authorization checks: -// - Admin-only methods require Host or Admin role (checked via isAdminOnly function) -// -// Context values set: -// - UserIDContextKey: Always set with the user's ID -// - SessionIDContextKey: Set if authenticated via session cookie -// - AccessTokenContextKey: Set if authenticated via JWT token -// -// Also updates session last accessed time for session-based auth (sliding expiration). -// -// Returns the updated context or an error if authorization fails. -func (a *Authenticator) AuthorizeAndSetContext(ctx context.Context, procedure string, user *store.User, sessionID, accessToken string, isAdminOnly func(string) bool) (context.Context, error) { - // Check admin-only method authorization - if isAdminOnly != nil && isAdminOnly(procedure) && user.Role != store.RoleHost && user.Role != store.RoleAdmin { - return nil, errors.Errorf("user %q is not authorized for this operation", user.Username) + // Check expiry + if result.PAT.ExpiresAt != nil && result.PAT.ExpiresAt.AsTime().Before(time.Now()) { + return nil, nil, errors.New("PAT expired") } - // Set user ID in context (always) - ctx = context.WithValue(ctx, UserIDContextKey, user.ID) - - // Set authentication method specific context values - if sessionID != "" { - ctx = context.WithValue(ctx, SessionIDContextKey, sessionID) - // Update session last accessed time for sliding expiration - _ = a.store.UpdateUserSessionLastAccessed(ctx, user.ID, sessionID, timestamppb.Now()) - } else if accessToken != "" { - ctx = context.WithValue(ctx, AccessTokenContextKey, accessToken) + // Check user status + if result.User.RowStatus == store.Archived { + return nil, nil, errors.New("user is archived") } - return ctx, nil -} - -// validateAccessToken checks if the token exists in the user's access tokens list. -// This enables token revocation: deleted tokens are removed from the list. -func validateAccessToken(token string, tokens []*storepb.AccessTokensUserSetting_AccessToken) bool { - for _, t := range tokens { - if token == t.AccessToken { - return true - } - } - return false -} - -// UpdateSessionLastAccessed updates the last accessed time for a session. -// This implements sliding expiration - sessions remain valid as long as they're used. -// Should be called after successful session-based authentication. -func (a *Authenticator) UpdateSessionLastAccessed(ctx context.Context, userID int32, sessionID string) { - // Fire-and-forget update; failures are logged but don't block the request - _ = a.store.UpdateUserSessionLastAccessed(ctx, userID, sessionID, timestamppb.Now()) + return result.User, result.PAT, nil } // AuthResult contains the result of an authentication attempt. type AuthResult struct { - User *store.User - SessionID string // Non-empty if authenticated via session cookie - AccessToken string // Non-empty if authenticated via JWT + User *store.User // Set for PAT and legacy auth + Claims *UserClaims // Set for Access Token V2 (stateless) + SessionID string // Non-empty if authenticated via session cookie + AccessToken string // Non-empty if authenticated via JWT } // Authenticate tries to authenticate using the provided credentials. -// It tries session cookie first, then JWT token. +// Priority: 1. Access Token V2, 2. PAT // Returns nil if no valid credentials are provided. -// On successful session auth, it also updates the session sliding expiration. -func (a *Authenticator) Authenticate(ctx context.Context, sessionID, authHeader string) *AuthResult { - // Try session cookie authentication first - if sessionID != "" { - user, err := a.AuthenticateBySession(ctx, sessionID) - if err == nil && user != nil { - a.UpdateSessionLastAccessed(ctx, user.ID, sessionID) - return &AuthResult{User: user, SessionID: sessionID} +func (a *Authenticator) Authenticate(ctx context.Context, _, authHeader string) *AuthResult { + token := ExtractBearerToken(authHeader) + + // Try Access Token V2 (stateless) + if token != "" && !strings.HasPrefix(token, PersonalAccessTokenPrefix) { + claims, err := a.AuthenticateByAccessTokenV2(token) + if err == nil && claims != nil { + return &AuthResult{ + Claims: claims, + AccessToken: token, + } } } - // Try JWT token authentication - if token := ExtractBearerToken(authHeader); token != "" { - user, err := a.AuthenticateByJWT(ctx, token) + // Try PAT + if token != "" && strings.HasPrefix(token, PersonalAccessTokenPrefix) { + user, pat, err := a.AuthenticateByPAT(ctx, token) if err == nil && user != nil { + // Update last used (fire-and-forget with logging) + go func() { + if err := a.store.UpdatePATLastUsed(context.Background(), user.ID, pat.TokenId, timestamppb.Now()); err != nil { + slog.Warn("failed to update PAT last used time", "error", err, "userID", user.ID) + } + }() return &AuthResult{User: user, AccessToken: token} } } diff --git a/server/auth/context.go b/server/auth/context.go index 7069ad38d..d01ed9f04 100644 --- a/server/auth/context.go +++ b/server/auth/context.go @@ -23,6 +23,12 @@ const ( // AccessTokenContextKey stores the JWT token for token-based auth. // Only set when authenticated via Bearer token. AccessTokenContextKey + + // UserClaimsContextKey stores the claims from access token. + UserClaimsContextKey + + // RefreshTokenIDContextKey stores the refresh token ID. + RefreshTokenIDContextKey ) // GetUserID retrieves the authenticated user's ID from the context. @@ -70,3 +76,25 @@ func SetUserInContext(ctx context.Context, user *store.User, sessionID, accessTo } return ctx } + +// UserClaims represents authenticated user info from access token. +type UserClaims struct { + UserID int32 + Username string + Role string + Status string +} + +// GetUserClaims retrieves the user claims from context. +// Returns nil if not authenticated via access token. +func GetUserClaims(ctx context.Context) *UserClaims { + if v, ok := ctx.Value(UserClaimsContextKey).(*UserClaims); ok { + return v + } + return nil +} + +// SetUserClaimsInContext sets the user claims in context. +func SetUserClaimsInContext(ctx context.Context, claims *UserClaims) context.Context { + return context.WithValue(ctx, UserClaimsContextKey, claims) +} diff --git a/server/auth/extract.go b/server/auth/extract.go index 2d264a9d0..b48f688f3 100644 --- a/server/auth/extract.go +++ b/server/auth/extract.go @@ -33,3 +33,16 @@ func ExtractBearerToken(authHeader string) string { } return parts[1] } + +// ExtractRefreshTokenFromCookie extracts the refresh token from cookie header. +func ExtractRefreshTokenFromCookie(cookieHeader string) string { + if cookieHeader == "" { + return "" + } + req := &http.Request{Header: http.Header{"Cookie": []string{cookieHeader}}} + cookie, err := req.Cookie(RefreshTokenCookieName) + if err != nil { + return "" + } + return cookie.Value +} diff --git a/server/auth/token.go b/server/auth/token.go index 5d0ff69a2..0079dbc19 100644 --- a/server/auth/token.go +++ b/server/auth/token.go @@ -10,10 +10,13 @@ package auth import ( + "crypto/sha256" + "encoding/hex" "fmt" "time" "github.com/golang-jwt/jwt/v5" + "github.com/pkg/errors" "github.com/usememos/memos/internal/util" ) @@ -40,6 +43,21 @@ const ( // SessionCookieName is the HTTP cookie name used to store session information. // Cookie value is the session ID (UUID). SessionCookieName = "user_session" + + // AccessTokenDuration is the lifetime of access tokens (15 minutes). + AccessTokenDuration = 15 * time.Minute + + // RefreshTokenDuration is the lifetime of refresh tokens (30 days). + RefreshTokenDuration = 30 * 24 * time.Hour + + // RefreshTokenAudienceName is the audience claim for refresh tokens. + RefreshTokenAudienceName = "user.refresh-token" + + // RefreshTokenCookieName is the cookie name for refresh tokens. + RefreshTokenCookieName = "memos_refresh" + + // PersonalAccessTokenPrefix is the prefix for PAT tokens. + PersonalAccessTokenPrefix = "memos_pat_" ) // ClaimsMessage represents the claims structure in a JWT token. @@ -56,6 +74,24 @@ type ClaimsMessage struct { jwt.RegisteredClaims } +// AccessTokenClaims contains claims for short-lived access tokens. +// These tokens are validated by signature only (stateless). +type AccessTokenClaims struct { + Type string `json:"type"` // "access" + Role string `json:"role"` // User role + Status string `json:"status"` // User status + Username string `json:"username"` // Username for display + jwt.RegisteredClaims +} + +// RefreshTokenClaims contains claims for long-lived refresh tokens. +// These tokens are validated against the database for revocation. +type RefreshTokenClaims struct { + Type string `json:"type"` // "refresh" + TokenID string `json:"tid"` // Token ID for revocation lookup + jwt.RegisteredClaims +} + // GenerateAccessToken generates a JWT access token for a user. // // Parameters: @@ -110,3 +146,121 @@ func generateToken(username string, userID int32, audience string, expirationTim func GenerateSessionID() string { return util.GenUUID() } + +// GenerateAccessTokenV2 generates a short-lived access token with user claims. +func GenerateAccessTokenV2(userID int32, username, role, status string, secret []byte) (string, time.Time, error) { + expiresAt := time.Now().Add(AccessTokenDuration) + + claims := &AccessTokenClaims{ + Type: "access", + Role: role, + Status: status, + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: Issuer, + Audience: jwt.ClaimStrings{AccessTokenAudienceName}, + Subject: fmt.Sprint(userID), + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(expiresAt), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token.Header["kid"] = KeyID + + tokenString, err := token.SignedString(secret) + if err != nil { + return "", time.Time{}, err + } + + return tokenString, expiresAt, nil +} + +// GenerateRefreshToken generates a long-lived refresh token. +func GenerateRefreshToken(userID int32, tokenID string, secret []byte) (string, time.Time, error) { + expiresAt := time.Now().Add(RefreshTokenDuration) + + claims := &RefreshTokenClaims{ + Type: "refresh", + TokenID: tokenID, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: Issuer, + Audience: jwt.ClaimStrings{RefreshTokenAudienceName}, + Subject: fmt.Sprint(userID), + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(expiresAt), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token.Header["kid"] = KeyID + + tokenString, err := token.SignedString(secret) + if err != nil { + return "", time.Time{}, err + } + + return tokenString, expiresAt, nil +} + +// GeneratePersonalAccessToken generates a random PAT string. +func GeneratePersonalAccessToken() string { + randomStr, err := util.RandomString(32) + if err != nil { + // Fallback to UUID if RandomString fails + return PersonalAccessTokenPrefix + util.GenUUID() + } + return PersonalAccessTokenPrefix + randomStr +} + +// HashPersonalAccessToken returns SHA-256 hash of a PAT. +func HashPersonalAccessToken(token string) string { + hash := sha256.Sum256([]byte(token)) + return hex.EncodeToString(hash[:]) +} + +// verifyJWTKeyFunc returns a jwt.Keyfunc that validates the signing method and key ID. +func verifyJWTKeyFunc(secret []byte) jwt.Keyfunc { + return func(t *jwt.Token) (any, error) { + if t.Method.Alg() != jwt.SigningMethodHS256.Name { + return nil, errors.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + kid, ok := t.Header["kid"].(string) + if !ok || kid != KeyID { + return nil, errors.Errorf("unexpected kid: %v", t.Header["kid"]) + } + return secret, nil + } +} + +// ParseAccessTokenV2 parses and validates a short-lived access token. +func ParseAccessTokenV2(tokenString string, secret []byte) (*AccessTokenClaims, error) { + claims := &AccessTokenClaims{} + _, err := jwt.ParseWithClaims(tokenString, claims, verifyJWTKeyFunc(secret), + jwt.WithIssuer(Issuer), + jwt.WithAudience(AccessTokenAudienceName), + ) + if err != nil { + return nil, err + } + if claims.Type != "access" { + return nil, errors.New("invalid token type: expected access token") + } + return claims, nil +} + +// ParseRefreshToken parses and validates a refresh token. +func ParseRefreshToken(tokenString string, secret []byte) (*RefreshTokenClaims, error) { + claims := &RefreshTokenClaims{} + _, err := jwt.ParseWithClaims(tokenString, claims, verifyJWTKeyFunc(secret), + jwt.WithIssuer(Issuer), + jwt.WithAudience(RefreshTokenAudienceName), + ) + if err != nil { + return nil, err + } + if claims.Type != "refresh" { + return nil, errors.New("invalid token type: expected refresh token") + } + return claims, nil +} diff --git a/server/auth/token_test.go b/server/auth/token_test.go new file mode 100644 index 000000000..3b4262dd1 --- /dev/null +++ b/server/auth/token_test.go @@ -0,0 +1,306 @@ +package auth + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateAccessTokenV2(t *testing.T) { + secret := []byte("test-secret") + + t.Run("generates valid access token", func(t *testing.T) { + token, expiresAt, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + assert.NotEmpty(t, token) + assert.True(t, expiresAt.After(time.Now())) + assert.True(t, expiresAt.Before(time.Now().Add(AccessTokenDuration+time.Minute))) + }) + + t.Run("generates different tokens for same user", func(t *testing.T) { + token1, _, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + time.Sleep(2 * time.Second) // Ensure different timestamps (tokens have 1s precision) + + token2, _, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + assert.NotEqual(t, token1, token2, "tokens should be different due to different timestamps") + }) +} + +func TestParseAccessTokenV2(t *testing.T) { + secret := []byte("test-secret") + + t.Run("parses valid access token", func(t *testing.T) { + token, _, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + claims, err := ParseAccessTokenV2(token, secret) + require.NoError(t, err) + assert.Equal(t, "1", claims.Subject) + assert.Equal(t, "testuser", claims.Username) + assert.Equal(t, "USER", claims.Role) + assert.Equal(t, "ACTIVE", claims.Status) + assert.Equal(t, "access", claims.Type) + }) + + t.Run("fails with wrong secret", func(t *testing.T) { + token, _, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + wrongSecret := []byte("wrong-secret") + _, err = ParseAccessTokenV2(token, wrongSecret) + assert.Error(t, err) + }) + + t.Run("fails with invalid token", func(t *testing.T) { + _, err := ParseAccessTokenV2("invalid-token", secret) + assert.Error(t, err) + }) + + t.Run("fails with refresh token", func(t *testing.T) { + // Generate a refresh token and try to parse it as access token + // Should fail because audience mismatch is caught before type check + refreshToken, _, err := GenerateRefreshToken(1, "token-id", secret) + require.NoError(t, err) + + _, err = ParseAccessTokenV2(refreshToken, secret) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid audience") + }) + + t.Run("parses token with different roles", func(t *testing.T) { + roles := []string{"USER", "ADMIN", "HOST"} + for _, role := range roles { + token, _, err := GenerateAccessTokenV2(1, "testuser", role, "ACTIVE", secret) + require.NoError(t, err) + + claims, err := ParseAccessTokenV2(token, secret) + require.NoError(t, err) + assert.Equal(t, role, claims.Role) + } + }) +} + +func TestGenerateRefreshToken(t *testing.T) { + secret := []byte("test-secret") + + t.Run("generates valid refresh token", func(t *testing.T) { + token, expiresAt, err := GenerateRefreshToken(1, "token-id-123", secret) + require.NoError(t, err) + assert.NotEmpty(t, token) + assert.True(t, expiresAt.After(time.Now().Add(29*24*time.Hour))) + }) + + t.Run("generates different tokens for different token IDs", func(t *testing.T) { + token1, _, err := GenerateRefreshToken(1, "token-id-1", secret) + require.NoError(t, err) + + token2, _, err := GenerateRefreshToken(1, "token-id-2", secret) + require.NoError(t, err) + + assert.NotEqual(t, token1, token2) + }) +} + +func TestParseRefreshToken(t *testing.T) { + secret := []byte("test-secret") + + t.Run("parses valid refresh token", func(t *testing.T) { + token, _, err := GenerateRefreshToken(1, "token-id-123", secret) + require.NoError(t, err) + + claims, err := ParseRefreshToken(token, secret) + require.NoError(t, err) + assert.Equal(t, "1", claims.Subject) + assert.Equal(t, "token-id-123", claims.TokenID) + assert.Equal(t, "refresh", claims.Type) + }) + + t.Run("fails with wrong secret", func(t *testing.T) { + token, _, err := GenerateRefreshToken(1, "token-id-123", secret) + require.NoError(t, err) + + wrongSecret := []byte("wrong-secret") + _, err = ParseRefreshToken(token, wrongSecret) + assert.Error(t, err) + }) + + t.Run("fails with invalid token", func(t *testing.T) { + _, err := ParseRefreshToken("invalid-token", secret) + assert.Error(t, err) + }) + + t.Run("fails with access token", func(t *testing.T) { + // Generate an access token and try to parse it as refresh token + // Should fail because audience mismatch is caught before type check + accessToken, _, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + _, err = ParseRefreshToken(accessToken, secret) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid audience") + }) +} + +func TestGeneratePersonalAccessToken(t *testing.T) { + t.Run("generates token with correct prefix", func(t *testing.T) { + token := GeneratePersonalAccessToken() + assert.NotEmpty(t, token) + assert.True(t, len(token) > len(PersonalAccessTokenPrefix)) + assert.Equal(t, PersonalAccessTokenPrefix, token[:len(PersonalAccessTokenPrefix)]) + }) + + t.Run("generates unique tokens", func(t *testing.T) { + token1 := GeneratePersonalAccessToken() + token2 := GeneratePersonalAccessToken() + assert.NotEqual(t, token1, token2) + }) + + t.Run("generates token of sufficient length", func(t *testing.T) { + token := GeneratePersonalAccessToken() + // Prefix is "memos_pat_" (10 chars) + 32 random chars = at least 42 chars + assert.True(t, len(token) >= 42, "token should be at least 42 characters") + }) +} + +func TestHashPersonalAccessToken(t *testing.T) { + t.Run("generates SHA-256 hash", func(t *testing.T) { + token := "memos_pat_abc123" + hash := HashPersonalAccessToken(token) + assert.NotEmpty(t, hash) + assert.Len(t, hash, 64, "SHA-256 hex should be 64 characters") + }) + + t.Run("same input produces same hash", func(t *testing.T) { + token := "memos_pat_abc123" + hash1 := HashPersonalAccessToken(token) + hash2 := HashPersonalAccessToken(token) + assert.Equal(t, hash1, hash2) + }) + + t.Run("different inputs produce different hashes", func(t *testing.T) { + token1 := "memos_pat_abc123" + token2 := "memos_pat_xyz789" + hash1 := HashPersonalAccessToken(token1) + hash2 := HashPersonalAccessToken(token2) + assert.NotEqual(t, hash1, hash2) + }) + + t.Run("hash is deterministic", func(t *testing.T) { + token := GeneratePersonalAccessToken() + hash1 := HashPersonalAccessToken(token) + hash2 := HashPersonalAccessToken(token) + assert.Equal(t, hash1, hash2) + }) +} + +func TestAccessTokenV2Integration(t *testing.T) { + secret := []byte("test-secret") + + t.Run("full lifecycle: generate, parse, validate", func(t *testing.T) { + userID := int32(42) + username := "john_doe" + role := "ADMIN" + status := "ACTIVE" + + // Generate token + token, expiresAt, err := GenerateAccessTokenV2(userID, username, role, status, secret) + require.NoError(t, err) + assert.NotEmpty(t, token) + + // Parse token + claims, err := ParseAccessTokenV2(token, secret) + require.NoError(t, err) + + // Validate claims + assert.Equal(t, "42", claims.Subject) + assert.Equal(t, username, claims.Username) + assert.Equal(t, role, claims.Role) + assert.Equal(t, status, claims.Status) + assert.Equal(t, "access", claims.Type) + assert.Equal(t, Issuer, claims.Issuer) + assert.NotNil(t, claims.IssuedAt) + assert.NotNil(t, claims.ExpiresAt) + + // Validate expiration + assert.True(t, claims.ExpiresAt.Equal(expiresAt) || claims.ExpiresAt.Before(expiresAt)) + }) +} + +func TestRefreshTokenIntegration(t *testing.T) { + secret := []byte("test-secret") + + t.Run("full lifecycle: generate, parse, validate", func(t *testing.T) { + userID := int32(42) + tokenID := "unique-token-id-456" + + // Generate token + token, expiresAt, err := GenerateRefreshToken(userID, tokenID, secret) + require.NoError(t, err) + assert.NotEmpty(t, token) + + // Parse token + claims, err := ParseRefreshToken(token, secret) + require.NoError(t, err) + + // Validate claims + assert.Equal(t, "42", claims.Subject) + assert.Equal(t, tokenID, claims.TokenID) + assert.Equal(t, "refresh", claims.Type) + assert.Equal(t, Issuer, claims.Issuer) + assert.NotNil(t, claims.IssuedAt) + assert.NotNil(t, claims.ExpiresAt) + + // Validate expiration + assert.True(t, claims.ExpiresAt.Equal(expiresAt) || claims.ExpiresAt.Before(expiresAt)) + }) +} + +func TestPersonalAccessTokenIntegration(t *testing.T) { + t.Run("full lifecycle: generate, hash, verify", func(t *testing.T) { + // Generate token + token := GeneratePersonalAccessToken() + assert.NotEmpty(t, token) + assert.True(t, len(token) > len(PersonalAccessTokenPrefix)) + + // Hash token + hash := HashPersonalAccessToken(token) + assert.Len(t, hash, 64) + + // Verify same token produces same hash + hashAgain := HashPersonalAccessToken(token) + assert.Equal(t, hash, hashAgain) + + // Verify different token produces different hash + token2 := GeneratePersonalAccessToken() + hash2 := HashPersonalAccessToken(token2) + assert.NotEqual(t, hash, hash2) + }) +} + +func TestTokenExpiration(t *testing.T) { + secret := []byte("test-secret") + + t.Run("access token expires after AccessTokenDuration", func(t *testing.T) { + _, expiresAt, err := GenerateAccessTokenV2(1, "testuser", "USER", "ACTIVE", secret) + require.NoError(t, err) + + expectedExpiry := time.Now().Add(AccessTokenDuration) + delta := expiresAt.Sub(expectedExpiry) + assert.True(t, delta < time.Second, "expiration should be within 1 second of expected") + }) + + t.Run("refresh token expires after RefreshTokenDuration", func(t *testing.T) { + _, expiresAt, err := GenerateRefreshToken(1, "token-id", secret) + require.NoError(t, err) + + expectedExpiry := time.Now().Add(RefreshTokenDuration) + delta := expiresAt.Sub(expectedExpiry) + assert.True(t, delta < time.Second, "expiration should be within 1 second of expected") + }) +} diff --git a/server/router/api/v1/acl_config.go b/server/router/api/v1/acl_config.go index 4569e82b9..4045a7f5c 100644 --- a/server/router/api/v1/acl_config.go +++ b/server/router/api/v1/acl_config.go @@ -9,9 +9,9 @@ package v1 // Format: Full gRPC procedure path as returned by req.Spec().Procedure (Connect) // or info.FullMethod (gRPC interceptor). var PublicMethods = map[string]struct{}{ - // Auth Service - login flow must be accessible without auth - "/memos.api.v1.AuthService/CreateSession": {}, - "/memos.api.v1.AuthService/GetCurrentSession": {}, + // Auth Service - login/token endpoints must be accessible without auth + "/memos.api.v1.AuthService/SignIn": {}, + "/memos.api.v1.AuthService/RefreshToken": {}, // Token refresh uses cookie, must be accessible when access token expired // Instance Service - needed before login to show instance info "/memos.api.v1.InstanceService/GetInstanceProfile": {}, diff --git a/server/router/api/v1/acl_config_test.go b/server/router/api/v1/acl_config_test.go index 7086ce837..9a8d5a7a7 100644 --- a/server/router/api/v1/acl_config_test.go +++ b/server/router/api/v1/acl_config_test.go @@ -10,8 +10,8 @@ import ( func TestPublicMethodsArePublic(t *testing.T) { publicMethods := []string{ // Auth Service - "/memos.api.v1.AuthService/CreateSession", - "/memos.api.v1.AuthService/GetCurrentSession", + "/memos.api.v1.AuthService/SignIn", + "/memos.api.v1.AuthService/RefreshToken", // Instance Service "/memos.api.v1.InstanceService/GetInstanceProfile", "/memos.api.v1.InstanceService/GetInstanceSetting", @@ -39,8 +39,9 @@ func TestPublicMethodsArePublic(t *testing.T) { // TestProtectedMethodsRequireAuth verifies that non-public methods are recognized as protected. func TestProtectedMethodsRequireAuth(t *testing.T) { protectedMethods := []string{ - // Auth Service - logout requires auth - "/memos.api.v1.AuthService/DeleteSession", + // Auth Service - logout and get current user require auth + "/memos.api.v1.AuthService/SignOut", + "/memos.api.v1.AuthService/GetCurrentUser", // Instance Service - admin operations "/memos.api.v1.InstanceService/UpdateInstanceSetting", // User Service - modification operations diff --git a/server/router/api/v1/attachment_service.go b/server/router/api/v1/attachment_service.go index 8dbc8d705..2e05d4526 100644 --- a/server/router/api/v1/attachment_service.go +++ b/server/router/api/v1/attachment_service.go @@ -43,7 +43,7 @@ var SupportedThumbnailMimeTypes = []string{ } func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.CreateAttachmentRequest) (*v1pb.Attachment, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -123,7 +123,7 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat } func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAttachmentsRequest) (*v1pb.ListAttachmentsResponse, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -234,7 +234,7 @@ func (s *APIV1Service) DeleteAttachment(ctx context.Context, request *v1pb.Delet if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid attachment id: %v", err) } - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/auth_service.go b/server/router/api/v1/auth_service.go index d78e709de..c63f4da52 100644 --- a/server/router/api/v1/auth_service.go +++ b/server/router/api/v1/auth_service.go @@ -29,17 +29,13 @@ const ( unmatchedUsernameAndPasswordError = "unmatched username and password" ) -// GetCurrentSession retrieves the current authenticated session information. +// GetCurrentUser returns the authenticated user's information. +// Validates the access token and returns user details. // -// This endpoint is used to: -// - Check if a user is currently authenticated -// - Get the current user's information -// - Retrieve the last accessed time of the session -// -// Authentication: Required (session cookie or access token) -// Returns: User information and last accessed timestamp. -func (s *APIV1Service) GetCurrentSession(ctx context.Context, _ *v1pb.GetCurrentSessionRequest) (*v1pb.GetCurrentSessionResponse, error) { - user, err := s.GetCurrentUser(ctx) +// Authentication: Required (access token). +// Returns: User information. +func (s *APIV1Service) GetCurrentUser(ctx context.Context, _ *v1pb.GetCurrentUserRequest) (*v1pb.GetCurrentUserResponse, error) { + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Unauthenticated, "failed to get current user: %v", err) } @@ -51,37 +47,21 @@ func (s *APIV1Service) GetCurrentSession(ctx context.Context, _ *v1pb.GetCurrent return nil, status.Errorf(codes.Unauthenticated, "user not found") } - var lastAccessedAt *timestamppb.Timestamp - // Update session last accessed time if we have a session ID and get the current session info - if sessionID := auth.GetSessionID(ctx); sessionID != "" { - 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), - LastAccessedAt: lastAccessedAt, + return &v1pb.GetCurrentUserResponse{ + User: convertUserFromStore(user), }, nil } -// CreateSession authenticates a user and establishes a new session. +// SignIn authenticates a user with credentials and returns tokens. +// On success, returns an access token and sets a refresh token cookie. // -// This endpoint supports two authentication methods: -// 1. Password-based authentication (username + password) -// 2. SSO authentication (OAuth2 authorization code) +// Supports two authentication methods: +// 1. Password-based authentication (username + password). +// 2. SSO authentication (OAuth2 authorization code). // -// On successful authentication: -// - A session cookie is set for web browsers (cookie: user_session={userID}-{sessionID}) -// - Session information is stored including client details (IP, user agent, device type) -// - Sessions use sliding expiration: 14 days from last access -// -// Authentication: Not required (public endpoint) -// Returns: Authenticated user information and last accessed timestamp. -func (s *APIV1Service) CreateSession(ctx context.Context, request *v1pb.CreateSessionRequest) (*v1pb.CreateSessionResponse, error) { +// Authentication: Not required (public endpoint). +// Returns: User info, access token, and token expiry. +func (s *APIV1Service) SignIn(ctx context.Context, request *v1pb.SignInRequest) (*v1pb.SignInResponse, error) { var existingUser *store.User // Authentication Method 1: Password-based authentication @@ -197,99 +177,162 @@ 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) } - // 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) + accessToken, accessExpiresAt, err := s.doSignIn(ctx, existingUser) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to sign in: %v", err) } - return &v1pb.CreateSessionResponse{ - User: convertUserFromStore(existingUser), - LastAccessedAt: timestamppb.Now(), + return &v1pb.SignInResponse{ + User: convertUserFromStore(existingUser), + AccessToken: accessToken, + AccessTokenExpiresAt: timestamppb.New(accessExpiresAt), }, nil } // doSignIn performs the actual sign-in operation by creating a session and setting the cookie. // // This function: -// 1. Generates a unique session ID (UUID) -// 2. Tracks the session in user settings with client information -// 3. Sets a session cookie in the format: {userID}-{sessionID} -// 4. Configures cookie security settings (HttpOnly, Secure, SameSite) -// -// Cookie lifetime is 100 years, but actual session validity is controlled by -// sliding expiration (14 days from last access) checked during authentication. -func (s *APIV1Service) doSignIn(ctx context.Context, user *store.User, expireTime time.Time) error { - // Generate unique session ID for web use - sessionID := auth.GenerateSessionID() - - // Track session in user settings - 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) - } - - // Set session cookie for web use - sessionCookie, err := s.buildSessionCookie(ctx, sessionID, expireTime) +// 1. Generates refresh token and access token. +// 2. Stores refresh token metadata in user_setting. +// 3. Sets refresh token as HttpOnly cookie. +// 4. Returns access token and its expiry time. +func (s *APIV1Service) doSignIn(ctx context.Context, user *store.User) (string, time.Time, error) { + // Generate refresh token + tokenID := util.GenUUID() + refreshToken, refreshExpiresAt, err := auth.GenerateRefreshToken(user.ID, tokenID, []byte(s.Secret)) if err != nil { - return status.Errorf(codes.Internal, "failed to build session cookie, error: %v", err) - } - if err := SetResponseHeader(ctx, "Set-Cookie", sessionCookie); err != nil { - return status.Errorf(codes.Internal, "failed to set response header, error: %v", err) + return "", time.Time{}, status.Errorf(codes.Internal, "failed to generate refresh token: %v", err) } - return nil + // Store refresh token metadata + clientInfo := s.extractClientInfo(ctx) + refreshTokenRecord := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(refreshExpiresAt), + CreatedAt: timestamppb.Now(), + ClientInfo: clientInfo, + } + if err := s.Store.AddUserRefreshToken(ctx, user.ID, refreshTokenRecord); err != nil { + slog.Error("failed to store refresh token", "error", err) + } + + // Set refresh token cookie + refreshCookie := s.buildRefreshTokenCookie(ctx, refreshToken, refreshExpiresAt) + if err := SetResponseHeader(ctx, "Set-Cookie", refreshCookie); err != nil { + return "", time.Time{}, status.Errorf(codes.Internal, "failed to set refresh token cookie: %v", err) + } + + // Generate access token + accessToken, accessExpiresAt, err := auth.GenerateAccessTokenV2( + user.ID, + user.Username, + string(user.Role), + string(user.RowStatus), + []byte(s.Secret), + ) + if err != nil { + return "", time.Time{}, status.Errorf(codes.Internal, "failed to generate access token: %v", err) + } + + return accessToken, accessExpiresAt, nil } -// DeleteSession terminates the current user session (logout). +// SignOut terminates the user's authentication. +// Revokes the refresh token and clears the authentication cookie. // -// This endpoint: -// 1. Removes the session from the user's sessions list in the database -// 2. Clears the session cookie by setting it to expire immediately -// -// Authentication: Required (session cookie or access token) +// Authentication: Required (access token). // Returns: Empty response on success. -func (s *APIV1Service) DeleteSession(ctx context.Context, _ *v1pb.DeleteSessionRequest) (*emptypb.Empty, error) { - user, err := s.GetCurrentUser(ctx) - if err != nil { - return nil, status.Errorf(codes.Unauthenticated, "failed to get current user: %v", err) - } - if user == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not found") - } - - // Check if we have a session ID (from cookie-based auth) - if sessionID := auth.GetSessionID(ctx); sessionID != "" { - // Remove session from user settings - if err := s.Store.RemoveUserSession(ctx, user.ID, sessionID); err != nil { - slog.Error("failed to remove user session", "error", err) +func (s *APIV1Service) SignOut(ctx context.Context, _ *v1pb.SignOutRequest) (*emptypb.Empty, error) { + // Get user from access token claims + claims := auth.GetUserClaims(ctx) + if claims != nil { + // Revoke refresh token if we can identify it + refreshToken := "" + if md, ok := metadata.FromIncomingContext(ctx); ok { + if cookies := md.Get("cookie"); len(cookies) > 0 { + refreshToken = auth.ExtractRefreshTokenFromCookie(cookies[0]) + } + } + if refreshToken != "" { + refreshClaims, err := auth.ParseRefreshToken(refreshToken, []byte(s.Secret)) + if err == nil { + // Remove refresh token from user_setting by token_id + _ = s.Store.RemoveUserRefreshToken(ctx, claims.UserID, refreshClaims.TokenID) + } } } + // Clear refresh token cookie if err := s.clearAuthCookies(ctx); err != nil { return nil, status.Errorf(codes.Internal, "failed to clear auth cookies, error: %v", err) } return &emptypb.Empty{}, nil } -func (s *APIV1Service) clearAuthCookies(ctx context.Context) error { - // Clear session cookie - sessionCookie, err := s.buildSessionCookie(ctx, "", time.Time{}) - if err != nil { - return errors.Wrap(err, "failed to build session cookie") +// RefreshToken exchanges a valid refresh token for a new access token. +// +// This endpoint: +// 1. Extracts the refresh token from the HttpOnly cookie (memos_refresh) +// 2. Validates the refresh token against the database (checking expiry and revocation) +// 3. Generates a new short-lived access token (15 minutes) +// 4. Returns the new access token and its expiry time +// +// The refresh token remains valid and is not rotated. +// Client should store the new access token in memory and use it for API requests. +// +// Authentication: Requires valid refresh token in cookie (public endpoint) +// Returns: New access token and expiry timestamp. +func (s *APIV1Service) RefreshToken(ctx context.Context, _ *v1pb.RefreshTokenRequest) (*v1pb.RefreshTokenResponse, error) { + // Extract refresh token from cookie + refreshToken := "" + if md, ok := metadata.FromIncomingContext(ctx); ok { + if cookies := md.Get("cookie"); len(cookies) > 0 { + refreshToken = auth.ExtractRefreshTokenFromCookie(cookies[0]) + } } - // Set cookie in the response - if err := SetResponseHeader(ctx, "Set-Cookie", sessionCookie); err != nil { - return errors.Wrap(err, "failed to set response header") + if refreshToken == "" { + return nil, status.Errorf(codes.Unauthenticated, "refresh token not found") } + + // Validate refresh token + authenticator := auth.NewAuthenticator(s.Store, s.Secret) + user, _, err := authenticator.AuthenticateByRefreshToken(ctx, refreshToken) + if err != nil { + return nil, status.Errorf(codes.Unauthenticated, "invalid refresh token: %v", err) + } + + // Generate new access token + accessToken, expiresAt, err := auth.GenerateAccessTokenV2( + user.ID, + user.Username, + string(user.Role), + string(user.RowStatus), + []byte(s.Secret), + ) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err) + } + + return &v1pb.RefreshTokenResponse{ + AccessToken: accessToken, + ExpiresAt: timestamppb.New(expiresAt), + }, nil +} + +func (s *APIV1Service) clearAuthCookies(ctx context.Context) error { + // Clear refresh token cookie + refreshCookie := s.buildRefreshTokenCookie(ctx, "", time.Time{}) + if err := SetResponseHeader(ctx, "Set-Cookie", refreshCookie); err != nil { + return errors.Wrap(err, "failed to set refresh cookie") + } + return nil } -func (*APIV1Service) buildSessionCookie(ctx context.Context, sessionCookieValue string, expireTime time.Time) (string, error) { +func (*APIV1Service) buildRefreshTokenCookie(ctx context.Context, refreshToken string, expireTime time.Time) string { attrs := []string{ - fmt.Sprintf("%s=%s", auth.SessionCookieName, sessionCookieValue), + fmt.Sprintf("%s=%s", auth.RefreshTokenCookieName, refreshToken), "Path=/", "HttpOnly", } @@ -300,7 +343,7 @@ func (*APIV1Service) buildSessionCookie(ctx context.Context, sessionCookieValue } // Try to determine if the request is HTTPS by checking the origin header - // Default to non-HTTPS (Strict SameSite) if metadata is not available + // Default to non-HTTPS (Lax SameSite) if metadata is not available isHTTPS := false if md, ok := metadata.FromIncomingContext(ctx); ok { for _, v := range md.Get("origin") { @@ -312,15 +355,14 @@ func (*APIV1Service) buildSessionCookie(ctx context.Context, sessionCookieValue } if isHTTPS { - attrs = append(attrs, "SameSite=None") - attrs = append(attrs, "Secure") + attrs = append(attrs, "SameSite=Lax", "Secure") } else { - attrs = append(attrs, "SameSite=Strict") + attrs = append(attrs, "SameSite=Lax") } - return strings.Join(attrs, "; "), nil + return strings.Join(attrs, "; ") } -func (s *APIV1Service) GetCurrentUser(ctx context.Context) (*store.User, error) { +func (s *APIV1Service) fetchCurrentUser(ctx context.Context) (*store.User, error) { userID := auth.GetUserID(ctx) if userID == 0 { return nil, nil @@ -337,27 +379,6 @@ func (s *APIV1Service) GetCurrentUser(ctx context.Context) (*store.User, error) return user, nil } -// trackUserSession creates a new session record in the user's settings. -// -// Session information includes: -// - session_id: Unique UUID for this session -// - create_time: When the session was created -// - last_accessed_time: When the session was last used (for sliding expiration) -// - client_info: Device details (user agent, IP, device type, OS, browser). -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(), - LastAccessedTime: timestamppb.Now(), - ClientInfo: clientInfo, - } - - return s.Store.AddUserSession(ctx, userID, session) -} - // extractClientInfo extracts comprehensive client information from the request context. // // This function parses metadata from the gRPC context to extract: diff --git a/server/router/api/v1/connect_interceptors.go b/server/router/api/v1/connect_interceptors.go index 9f3d6b363..5af3d7b2b 100644 --- a/server/router/api/v1/connect_interceptors.go +++ b/server/router/api/v1/connect_interceptors.go @@ -43,6 +43,10 @@ func (*MetadataInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc if xri := header.Get("X-Real-Ip"); xri != "" { md.Set("x-real-ip", xri) } + // Forward Cookie header for authentication methods that need it (e.g., RefreshToken) + if cookie := header.Get("Cookie"); cookie != "" { + md.Set("cookie", cookie) + } // Set metadata in context so services can use metadata.FromIncomingContext() ctx = metadata.NewIncomingContext(ctx, md) @@ -198,9 +202,16 @@ func (in *AuthInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc { return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("authentication required")) } - // Set user in context (may be nil for public endpoints) + // Set context based on auth result if result != nil { - ctx = auth.SetUserInContext(ctx, result.User, result.SessionID, result.AccessToken) + if result.Claims != nil { + // Access Token V2 - stateless, use claims + ctx = auth.SetUserClaimsInContext(ctx, result.Claims) + ctx = context.WithValue(ctx, auth.UserIDContextKey, result.Claims.UserID) + } else if result.User != nil { + // PAT or legacy auth - have full user + ctx = auth.SetUserInContext(ctx, result.User, result.SessionID, result.AccessToken) + } } return next(ctx, req) diff --git a/server/router/api/v1/connect_services.go b/server/router/api/v1/connect_services.go index 92cebd93d..9ac7af796 100644 --- a/server/router/api/v1/connect_services.go +++ b/server/router/api/v1/connect_services.go @@ -45,21 +45,27 @@ func (s *ConnectServiceHandler) UpdateInstanceSetting(ctx context.Context, req * // We use connectWithHeaderCarrier helper to inject a header carrier into the context, // which allows the service to set headers in a protocol-agnostic way. -func (s *ConnectServiceHandler) GetCurrentSession(ctx context.Context, req *connect.Request[v1pb.GetCurrentSessionRequest]) (*connect.Response[v1pb.GetCurrentSessionResponse], error) { - return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.GetCurrentSessionResponse, error) { - return s.APIV1Service.GetCurrentSession(ctx, req.Msg) +func (s *ConnectServiceHandler) GetCurrentUser(ctx context.Context, req *connect.Request[v1pb.GetCurrentUserRequest]) (*connect.Response[v1pb.GetCurrentUserResponse], error) { + return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.GetCurrentUserResponse, error) { + return s.APIV1Service.GetCurrentUser(ctx, req.Msg) }) } -func (s *ConnectServiceHandler) CreateSession(ctx context.Context, req *connect.Request[v1pb.CreateSessionRequest]) (*connect.Response[v1pb.CreateSessionResponse], error) { - return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.CreateSessionResponse, error) { - return s.APIV1Service.CreateSession(ctx, req.Msg) +func (s *ConnectServiceHandler) SignIn(ctx context.Context, req *connect.Request[v1pb.SignInRequest]) (*connect.Response[v1pb.SignInResponse], error) { + return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.SignInResponse, error) { + return s.APIV1Service.SignIn(ctx, req.Msg) }) } -func (s *ConnectServiceHandler) DeleteSession(ctx context.Context, req *connect.Request[v1pb.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) { +func (s *ConnectServiceHandler) SignOut(ctx context.Context, req *connect.Request[v1pb.SignOutRequest]) (*connect.Response[emptypb.Empty], error) { return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*emptypb.Empty, error) { - return s.APIV1Service.DeleteSession(ctx, req.Msg) + return s.APIV1Service.SignOut(ctx, req.Msg) + }) +} + +func (s *ConnectServiceHandler) RefreshToken(ctx context.Context, req *connect.Request[v1pb.RefreshTokenRequest]) (*connect.Response[v1pb.RefreshTokenResponse], error) { + return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.RefreshTokenResponse, error) { + return s.APIV1Service.RefreshToken(ctx, req.Msg) }) } @@ -145,40 +151,40 @@ func (s *ConnectServiceHandler) ListUserSettings(ctx context.Context, req *conne return connect.NewResponse(resp), nil } -func (s *ConnectServiceHandler) ListUserAccessTokens(ctx context.Context, req *connect.Request[v1pb.ListUserAccessTokensRequest]) (*connect.Response[v1pb.ListUserAccessTokensResponse], error) { - resp, err := s.APIV1Service.ListUserAccessTokens(ctx, req.Msg) +func (s *ConnectServiceHandler) ListPersonalAccessTokens(ctx context.Context, req *connect.Request[v1pb.ListPersonalAccessTokensRequest]) (*connect.Response[v1pb.ListPersonalAccessTokensResponse], error) { + resp, err := s.APIV1Service.ListPersonalAccessTokens(ctx, req.Msg) if err != nil { return nil, convertGRPCError(err) } return connect.NewResponse(resp), nil } -func (s *ConnectServiceHandler) CreateUserAccessToken(ctx context.Context, req *connect.Request[v1pb.CreateUserAccessTokenRequest]) (*connect.Response[v1pb.UserAccessToken], error) { - resp, err := s.APIV1Service.CreateUserAccessToken(ctx, req.Msg) +func (s *ConnectServiceHandler) CreatePersonalAccessToken(ctx context.Context, req *connect.Request[v1pb.CreatePersonalAccessTokenRequest]) (*connect.Response[v1pb.CreatePersonalAccessTokenResponse], error) { + resp, err := s.APIV1Service.CreatePersonalAccessToken(ctx, req.Msg) if err != nil { return nil, convertGRPCError(err) } return connect.NewResponse(resp), nil } -func (s *ConnectServiceHandler) DeleteUserAccessToken(ctx context.Context, req *connect.Request[v1pb.DeleteUserAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { - resp, err := s.APIV1Service.DeleteUserAccessToken(ctx, req.Msg) +func (s *ConnectServiceHandler) DeletePersonalAccessToken(ctx context.Context, req *connect.Request[v1pb.DeletePersonalAccessTokenRequest]) (*connect.Response[emptypb.Empty], error) { + resp, err := s.APIV1Service.DeletePersonalAccessToken(ctx, req.Msg) if err != nil { return nil, convertGRPCError(err) } return connect.NewResponse(resp), nil } -func (s *ConnectServiceHandler) ListUserSessions(ctx context.Context, req *connect.Request[v1pb.ListUserSessionsRequest]) (*connect.Response[v1pb.ListUserSessionsResponse], error) { - resp, err := s.APIV1Service.ListUserSessions(ctx, req.Msg) +func (s *ConnectServiceHandler) ListSessions(ctx context.Context, req *connect.Request[v1pb.ListSessionsRequest]) (*connect.Response[v1pb.ListSessionsResponse], error) { + resp, err := s.APIV1Service.ListSessions(ctx, req.Msg) if err != nil { return nil, convertGRPCError(err) } return connect.NewResponse(resp), nil } -func (s *ConnectServiceHandler) RevokeUserSession(ctx context.Context, req *connect.Request[v1pb.RevokeUserSessionRequest]) (*connect.Response[emptypb.Empty], error) { - resp, err := s.APIV1Service.RevokeUserSession(ctx, req.Msg) +func (s *ConnectServiceHandler) RevokeSession(ctx context.Context, req *connect.Request[v1pb.RevokeSessionRequest]) (*connect.Response[emptypb.Empty], error) { + resp, err := s.APIV1Service.RevokeSession(ctx, req.Msg) if err != nil { return nil, convertGRPCError(err) } diff --git a/server/router/api/v1/idp_service.go b/server/router/api/v1/idp_service.go index 51b44151f..2b48d2c10 100644 --- a/server/router/api/v1/idp_service.go +++ b/server/router/api/v1/idp_service.go @@ -14,7 +14,7 @@ import ( ) func (s *APIV1Service) CreateIdentityProvider(ctx context.Context, request *v1pb.CreateIdentityProviderRequest) (*v1pb.IdentityProvider, error) { - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -41,7 +41,7 @@ func (s *APIV1Service) ListIdentityProviders(ctx context.Context, _ *v1pb.ListId // Default to lowest-privilege role, update later based on real role currentUserRole := store.RoleUser - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err == nil && currentUser != nil { currentUserRole = currentUser.Role } @@ -70,7 +70,7 @@ func (s *APIV1Service) GetIdentityProvider(ctx context.Context, request *v1pb.Ge // Default to lowest-privilege role, update later based on real role currentUserRole := store.RoleUser - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err == nil && currentUser != nil { currentUserRole = currentUser.Role } @@ -80,7 +80,7 @@ func (s *APIV1Service) GetIdentityProvider(ctx context.Context, request *v1pb.Ge } func (s *APIV1Service) UpdateIdentityProvider(ctx context.Context, request *v1pb.UpdateIdentityProviderRequest) (*v1pb.IdentityProvider, error) { - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -121,7 +121,7 @@ func (s *APIV1Service) UpdateIdentityProvider(ctx context.Context, request *v1pb } func (s *APIV1Service) DeleteIdentityProvider(ctx context.Context, request *v1pb.DeleteIdentityProviderRequest) (*emptypb.Empty, error) { - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } diff --git a/server/router/api/v1/instance_service.go b/server/router/api/v1/instance_service.go index cf5798499..8636cbfd9 100644 --- a/server/router/api/v1/instance_service.go +++ b/server/router/api/v1/instance_service.go @@ -66,7 +66,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get // For storage setting, only host can get it. if instanceSetting.Key == storepb.InstanceSettingKey_STORAGE { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -79,7 +79,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get } func (s *APIV1Service) UpdateInstanceSetting(ctx context.Context, request *v1pb.UpdateInstanceSettingRequest) (*v1pb.InstanceSetting, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/memo_attachment_service.go b/server/router/api/v1/memo_attachment_service.go index e396ac760..fead95d7d 100644 --- a/server/router/api/v1/memo_attachment_service.go +++ b/server/router/api/v1/memo_attachment_service.go @@ -14,7 +14,7 @@ import ( ) func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.SetMemoAttachmentsRequest) (*emptypb.Empty, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/memo_relation_service.go b/server/router/api/v1/memo_relation_service.go index 4f49f7975..b70088b2a 100644 --- a/server/router/api/v1/memo_relation_service.go +++ b/server/router/api/v1/memo_relation_service.go @@ -14,7 +14,7 @@ import ( ) func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMemoRelationsRequest) (*emptypb.Empty, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -84,7 +84,7 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List return nil, status.Errorf(codes.Internal, "failed to get memo") } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user") } diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index c13776aac..f407b009e 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -22,7 +22,7 @@ import ( ) func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoRequest) (*v1pb.Memo, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user") } @@ -149,7 +149,7 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq memoFind.Filters = append(memoFind.Filters, request.Filter) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user") } @@ -276,7 +276,7 @@ func (s *APIV1Service) GetMemo(ctx context.Context, request *v1pb.GetMemoRequest return nil, status.Errorf(codes.NotFound, "memo not found") } if memo.Visibility != store.Public { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user") } @@ -326,7 +326,7 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR return nil, status.Errorf(codes.NotFound, "memo not found") } - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user") } @@ -464,7 +464,7 @@ func (s *APIV1Service) DeleteMemo(ctx context.Context, request *v1pb.DeleteMemoR return nil, status.Errorf(codes.NotFound, "memo not found") } - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user") } @@ -615,7 +615,7 @@ func (s *APIV1Service) ListMemoComments(ctx context.Context, request *v1pb.ListM return nil, status.Errorf(codes.Internal, "failed to get memo") } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user") } diff --git a/server/router/api/v1/reaction_service.go b/server/router/api/v1/reaction_service.go index d644ad14b..39d406772 100644 --- a/server/router/api/v1/reaction_service.go +++ b/server/router/api/v1/reaction_service.go @@ -33,7 +33,7 @@ func (s *APIV1Service) ListMemoReactions(ctx context.Context, request *v1pb.List } func (s *APIV1Service) UpsertMemoReaction(ctx context.Context, request *v1pb.UpsertMemoReactionRequest) (*v1pb.Reaction, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user") } @@ -55,7 +55,7 @@ func (s *APIV1Service) UpsertMemoReaction(ctx context.Context, request *v1pb.Ups } func (s *APIV1Service) DeleteMemoReaction(ctx context.Context, request *v1pb.DeleteMemoReactionRequest) (*emptypb.Empty, error) { - user, err := s.GetCurrentUser(ctx) + user, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/shortcut_service.go b/server/router/api/v1/shortcut_service.go index c7edd6561..b2057dbe8 100644 --- a/server/router/api/v1/shortcut_service.go +++ b/server/router/api/v1/shortcut_service.go @@ -49,7 +49,7 @@ func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShor return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -91,7 +91,7 @@ func (s *APIV1Service) GetShortcut(ctx context.Context, request *v1pb.GetShortcu return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -130,7 +130,7 @@ func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateS return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -202,7 +202,7 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -274,7 +274,7 @@ func (s *APIV1Service) DeleteShortcut(ctx context.Context, request *v1pb.DeleteS return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/test/auth_test.go b/server/router/api/v1/test/auth_test.go new file mode 100644 index 000000000..971ae4653 --- /dev/null +++ b/server/router/api/v1/test/auth_test.go @@ -0,0 +1,655 @@ +package test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/usememos/memos/internal/util" + storepb "github.com/usememos/memos/proto/gen/store" + "github.com/usememos/memos/server/auth" + "github.com/usememos/memos/store" +) + +func TestAuthenticatorAccessTokenV2(t *testing.T) { + ctx := context.Background() + + t.Run("authenticates valid access token v2", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + // Create a test user + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate access token v2 + token, _, err := auth.GenerateAccessTokenV2( + user.ID, + user.Username, + string(user.Role), + string(user.RowStatus), + []byte(ts.Secret), + ) + require.NoError(t, err) + + // Authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + claims, err := authenticator.AuthenticateByAccessTokenV2(token) + require.NoError(t, err) + assert.NotNil(t, claims) + assert.Equal(t, user.ID, claims.UserID) + assert.Equal(t, user.Username, claims.Username) + assert.Equal(t, string(user.Role), claims.Role) + assert.Equal(t, string(user.RowStatus), claims.Status) + }) + + t.Run("fails with invalid token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, err := authenticator.AuthenticateByAccessTokenV2("invalid-token") + assert.Error(t, err) + }) + + t.Run("fails with wrong secret", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate token with one secret + token, _, err := auth.GenerateAccessTokenV2( + user.ID, + user.Username, + string(user.Role), + string(user.RowStatus), + []byte("secret-1"), + ) + require.NoError(t, err) + + // Try to authenticate with different secret + authenticator := auth.NewAuthenticator(ts.Store, "secret-2") + _, err = authenticator.AuthenticateByAccessTokenV2(token) + assert.Error(t, err) + }) +} + +func TestAuthenticatorRefreshToken(t *testing.T) { + ctx := context.Background() + + t.Run("authenticates valid refresh token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + // Create a test user + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Create refresh token record in store + tokenID := util.GenUUID() + refreshTokenRecord := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(auth.RefreshTokenDuration)), + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserRefreshToken(ctx, user.ID, refreshTokenRecord) + require.NoError(t, err) + + // Generate refresh token JWT + token, _, err := auth.GenerateRefreshToken(user.ID, tokenID, []byte(ts.Secret)) + require.NoError(t, err) + + // Authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + authenticatedUser, returnedTokenID, err := authenticator.AuthenticateByRefreshToken(ctx, token) + require.NoError(t, err) + assert.NotNil(t, authenticatedUser) + assert.Equal(t, user.ID, authenticatedUser.ID) + assert.Equal(t, tokenID, returnedTokenID) + }) + + t.Run("fails with revoked token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + tokenID := util.GenUUID() + + // Generate refresh token JWT but don't store it in database (simulates revocation) + token, _, err := auth.GenerateRefreshToken(user.ID, tokenID, []byte(ts.Secret)) + require.NoError(t, err) + + // Try to authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err = authenticator.AuthenticateByRefreshToken(ctx, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "revoked") + }) + + t.Run("fails with expired token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Create expired refresh token record in store + tokenID := util.GenUUID() + expiredToken := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(-1 * time.Hour)), // Expired + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserRefreshToken(ctx, user.ID, expiredToken) + require.NoError(t, err) + + // Generate refresh token JWT (JWT itself isn't expired yet) + token, _, err := auth.GenerateRefreshToken(user.ID, tokenID, []byte(ts.Secret)) + require.NoError(t, err) + + // Try to authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err = authenticator.AuthenticateByRefreshToken(ctx, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "expired") + }) + + t.Run("fails with archived user", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Create valid refresh token + tokenID := util.GenUUID() + refreshTokenRecord := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(auth.RefreshTokenDuration)), + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserRefreshToken(ctx, user.ID, refreshTokenRecord) + require.NoError(t, err) + + token, _, err := auth.GenerateRefreshToken(user.ID, tokenID, []byte(ts.Secret)) + require.NoError(t, err) + + // Archive the user + archivedStatus := store.Archived + _, err = ts.Store.UpdateUser(ctx, &store.UpdateUser{ + ID: user.ID, + RowStatus: &archivedStatus, + }) + require.NoError(t, err) + + // Try to authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err = authenticator.AuthenticateByRefreshToken(ctx, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "archived") + }) +} + +func TestAuthenticatorPAT(t *testing.T) { + ctx := context.Background() + + t.Run("authenticates valid PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + // Create a test user + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate PAT + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + // Store PAT in database + patRecord := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, patRecord) + require.NoError(t, err) + + // Authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + authenticatedUser, pat, err := authenticator.AuthenticateByPAT(ctx, token) + require.NoError(t, err) + assert.NotNil(t, authenticatedUser) + assert.NotNil(t, pat) + assert.Equal(t, user.ID, authenticatedUser.ID) + assert.Equal(t, tokenID, pat.TokenId) + }) + + t.Run("fails with invalid PAT format", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err := authenticator.AuthenticateByPAT(ctx, "invalid-token-without-prefix") + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid PAT format") + }) + + t.Run("fails with non-existent PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + // Generate a PAT but don't store it + token := auth.GeneratePersonalAccessToken() + + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err := authenticator.AuthenticateByPAT(ctx, token) + assert.Error(t, err) + }) + + t.Run("fails with expired PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate and store expired PAT + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + expiredPAT := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Expired PAT", + ExpiresAt: timestamppb.New(time.Now().Add(-1 * time.Hour)), // Expired + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, expiredPAT) + require.NoError(t, err) + + // Try to authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err = authenticator.AuthenticateByPAT(ctx, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "expired") + }) + + t.Run("succeeds with non-expiring PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate and store PAT without expiration + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + patRecord := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Never-expiring PAT", + ExpiresAt: nil, // No expiration + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, patRecord) + require.NoError(t, err) + + // Authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + authenticatedUser, pat, err := authenticator.AuthenticateByPAT(ctx, token) + require.NoError(t, err) + assert.NotNil(t, authenticatedUser) + assert.NotNil(t, pat) + assert.Nil(t, pat.ExpiresAt) + }) + + t.Run("fails with archived user", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Generate and store PAT + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + patRecord := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, patRecord) + require.NoError(t, err) + + // Archive the user + archivedStatus := store.Archived + _, err = ts.Store.UpdateUser(ctx, &store.UpdateUser{ + ID: user.ID, + RowStatus: &archivedStatus, + }) + require.NoError(t, err) + + // Try to authenticate + authenticator := auth.NewAuthenticator(ts.Store, ts.Secret) + _, _, err = authenticator.AuthenticateByPAT(ctx, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "archived") + }) +} + +func TestStoreRefreshTokenMethods(t *testing.T) { + ctx := context.Background() + + t.Run("adds and retrieves refresh token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + tokenID := util.GenUUID() + token := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserRefreshToken(ctx, user.ID, token) + require.NoError(t, err) + + // Retrieve tokens + tokens, err := ts.Store.GetUserRefreshTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, tokens, 1) + assert.Equal(t, tokenID, tokens[0].TokenId) + }) + + t.Run("retrieves specific refresh token by ID", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + tokenID := util.GenUUID() + token := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserRefreshToken(ctx, user.ID, token) + require.NoError(t, err) + + // Retrieve specific token + retrievedToken, err := ts.Store.GetUserRefreshTokenByID(ctx, user.ID, tokenID) + require.NoError(t, err) + assert.NotNil(t, retrievedToken) + assert.Equal(t, tokenID, retrievedToken.TokenId) + }) + + t.Run("removes refresh token", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + tokenID := util.GenUUID() + token := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID, + ExpiresAt: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserRefreshToken(ctx, user.ID, token) + require.NoError(t, err) + + // Remove token + err = ts.Store.RemoveUserRefreshToken(ctx, user.ID, tokenID) + require.NoError(t, err) + + // Verify removal + tokens, err := ts.Store.GetUserRefreshTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, tokens, 0) + }) + + t.Run("handles multiple refresh tokens", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Add multiple tokens + tokenID1 := util.GenUUID() + tokenID2 := util.GenUUID() + + token1 := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID1, + ExpiresAt: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), + CreatedAt: timestamppb.Now(), + } + token2 := &storepb.RefreshTokensUserSetting_RefreshToken{ + TokenId: tokenID2, + ExpiresAt: timestamppb.New(time.Now().Add(30 * 24 * time.Hour)), + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserRefreshToken(ctx, user.ID, token1) + require.NoError(t, err) + err = ts.Store.AddUserRefreshToken(ctx, user.ID, token2) + require.NoError(t, err) + + // Retrieve all tokens + tokens, err := ts.Store.GetUserRefreshTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, tokens, 2) + + // Remove one token + err = ts.Store.RemoveUserRefreshToken(ctx, user.ID, tokenID1) + require.NoError(t, err) + + // Verify only one token remains + tokens, err = ts.Store.GetUserRefreshTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, tokens, 1) + assert.Equal(t, tokenID2, tokens[0].TokenId) + }) +} + +func TestStorePersonalAccessTokenMethods(t *testing.T) { + ctx := context.Background() + + t.Run("adds and retrieves PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat) + require.NoError(t, err) + + // Retrieve PATs + pats, err := ts.Store.GetUserPersonalAccessTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, pats, 1) + assert.Equal(t, tokenID, pats[0].TokenId) + assert.Equal(t, tokenHash, pats[0].TokenHash) + }) + + t.Run("removes PAT", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat) + require.NoError(t, err) + + // Remove PAT + err = ts.Store.RemoveUserPersonalAccessToken(ctx, user.ID, tokenID) + require.NoError(t, err) + + // Verify removal + pats, err := ts.Store.GetUserPersonalAccessTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, pats, 0) + }) + + t.Run("updates PAT last used time", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat) + require.NoError(t, err) + + // Update last used time + lastUsed := timestamppb.Now() + err = ts.Store.UpdatePATLastUsed(ctx, user.ID, tokenID, lastUsed) + require.NoError(t, err) + + // Verify update + pats, err := ts.Store.GetUserPersonalAccessTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, pats, 1) + assert.NotNil(t, pats[0].LastUsedAt) + }) + + t.Run("handles multiple PATs", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + // Add multiple PATs + token1 := auth.GeneratePersonalAccessToken() + tokenHash1 := auth.HashPersonalAccessToken(token1) + tokenID1 := util.GenUUID() + + token2 := auth.GeneratePersonalAccessToken() + tokenHash2 := auth.HashPersonalAccessToken(token2) + tokenID2 := util.GenUUID() + + pat1 := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID1, + TokenHash: tokenHash1, + Description: "PAT 1", + CreatedAt: timestamppb.Now(), + } + pat2 := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID2, + TokenHash: tokenHash2, + Description: "PAT 2", + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat1) + require.NoError(t, err) + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat2) + require.NoError(t, err) + + // Retrieve all PATs + pats, err := ts.Store.GetUserPersonalAccessTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, pats, 2) + + // Remove one PAT + err = ts.Store.RemoveUserPersonalAccessToken(ctx, user.ID, tokenID1) + require.NoError(t, err) + + // Verify only one PAT remains + pats, err = ts.Store.GetUserPersonalAccessTokens(ctx, user.ID) + require.NoError(t, err) + assert.Len(t, pats, 1) + assert.Equal(t, tokenID2, pats[0].TokenId) + }) + + t.Run("finds user by PAT hash", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + user, err := ts.CreateRegularUser(ctx, "testuser") + require.NoError(t, err) + + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + tokenID := util.GenUUID() + + pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: "Test PAT", + CreatedAt: timestamppb.Now(), + } + + err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, pat) + require.NoError(t, err) + + // Find user by PAT hash + result, err := ts.Store.GetUserByPATHash(ctx, tokenHash) + require.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, user.ID, result.UserID) + assert.NotNil(t, result.User) + assert.Equal(t, user.Username, result.User.Username) + assert.NotNil(t, result.PAT) + assert.Equal(t, tokenID, result.PAT.TokenId) + }) +} diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index 349f4b957..a33b98d56 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -7,12 +7,10 @@ import ( "fmt" "net/http" "regexp" - "slices" "strconv" "strings" "time" - "github.com/golang-jwt/jwt/v5" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/ast" "github.com/labstack/echo/v4" @@ -32,7 +30,7 @@ import ( ) func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) { - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -107,7 +105,7 @@ func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) { // Get current user (might be nil for unauthenticated requests) - currentUser, _ := s.GetCurrentUser(ctx) + currentUser, _ := s.fetchCurrentUser(ctx) // Check if there are any existing users (for first-time setup detection) limitOne := 1 @@ -190,7 +188,7 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -278,7 +276,7 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -318,7 +316,7 @@ func (s *APIV1Service) GetUserSetting(ctx context.Context, request *v1pb.GetUser return nil, status.Errorf(codes.InvalidArgument, "invalid resource name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -355,7 +353,7 @@ func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.Upda return nil, status.Errorf(codes.InvalidArgument, "invalid resource name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -444,7 +442,7 @@ func (s *APIV1Service) ListUserSettings(ctx context.Context, request *v1pb.ListU return nil, status.Errorf(codes.InvalidArgument, "invalid parent name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -498,7 +496,7 @@ func (s *APIV1Service) ListUserSettings(ctx context.Context, request *v1pb.ListU return response, nil } -// ListUserAccessTokens retrieves all Personal Access Tokens (PATs) for a user. +// ListPersonalAccessTokens retrieves all Personal Access Tokens (PATs) for a user. // // Personal Access Tokens are used for: // - Mobile app authentication @@ -508,75 +506,46 @@ func (s *APIV1Service) ListUserSettings(ctx context.Context, request *v1pb.ListU // // Security: // - Only the token owner can list their tokens -// - Returns full token strings (so users can manage/revoke them) +// - Returns token metadata only (not the actual token value) // - Invalid or expired tokens are filtered out // // Authentication: Required (session cookie or access token) // Authorization: User can only list their own tokens. -func (s *APIV1Service) ListUserAccessTokens(ctx context.Context, request *v1pb.ListUserAccessTokensRequest) (*v1pb.ListUserAccessTokensResponse, error) { +func (s *APIV1Service) ListPersonalAccessTokens(ctx context.Context, request *v1pb.ListPersonalAccessTokensRequest) (*v1pb.ListPersonalAccessTokensResponse, error) { userID, err := ExtractUserIDFromName(request.Parent) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + // Verify permission + claims := auth.GetUserClaims(ctx) + if claims == nil || claims.UserID != userID { + currentUser, _ := s.fetchCurrentUser(ctx) + if currentUser == nil || (currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + } + + tokens, err := s.Store.GetUserPersonalAccessTokens(ctx, userID) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) - } - if currentUser == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not authenticated") - } - if currentUser.ID != userID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") + return nil, status.Errorf(codes.Internal, "failed to get access tokens: %v", err) } - userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, userID) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err) - } - - accessTokens := []*v1pb.UserAccessToken{} - for _, userAccessToken := range userAccessTokens { - claims := &auth.ClaimsMessage{} - _, err := jwt.ParseWithClaims(userAccessToken.AccessToken, claims, func(t *jwt.Token) (any, error) { - if t.Method.Alg() != jwt.SigningMethodHS256.Name { - return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256) - } - if kid, ok := t.Header["kid"].(string); ok { - if kid == "v1" { - return []byte(s.Secret), nil - } - } - return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"]) - }) - if err != nil { - // If the access token is invalid or expired, just ignore it. - continue + personalAccessTokens := make([]*v1pb.PersonalAccessToken, len(tokens)) + for i, token := range tokens { + personalAccessTokens[i] = &v1pb.PersonalAccessToken{ + Name: fmt.Sprintf("%s/personalAccessTokens/%s", request.Parent, token.TokenId), + Description: token.Description, + ExpiresAt: token.ExpiresAt, + CreatedAt: token.CreatedAt, + LastUsedAt: token.LastUsedAt, } - - accessTokenResponse := &v1pb.UserAccessToken{ - Name: fmt.Sprintf("users/%d/accessTokens/%s", userID, userAccessToken.AccessToken), - AccessToken: userAccessToken.AccessToken, - Description: userAccessToken.Description, - IssuedAt: timestamppb.New(claims.IssuedAt.Time), - } - if claims.ExpiresAt != nil { - accessTokenResponse.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time) - } - accessTokens = append(accessTokens, accessTokenResponse) } - // Sort by issued time in descending order. - slices.SortFunc(accessTokens, func(i, j *v1pb.UserAccessToken) int { - return int(i.IssuedAt.Seconds - j.IssuedAt.Seconds) - }) - response := &v1pb.ListUserAccessTokensResponse{ - AccessTokens: accessTokens, - } - return response, nil + return &v1pb.ListPersonalAccessTokensResponse{PersonalAccessTokens: personalAccessTokens}, nil } -// CreateUserAccessToken creates a new Personal Access Token (PAT) for a user. +// CreatePersonalAccessToken creates a new Personal Access Token (PAT) for a user. // // Use cases: // - User manually creates token in settings for mobile app @@ -584,8 +553,8 @@ func (s *APIV1Service) ListUserAccessTokens(ctx context.Context, request *v1pb.L // - User creates token for third-party integration // // Token properties: -// - JWT format signed with server secret -// - Contains user ID and username in claims +// - Random string with memos_pat_ prefix +// - SHA-256 hash stored in database // - Optional expiration time (can be never-expiring) // - User-provided description for identification // @@ -596,66 +565,55 @@ func (s *APIV1Service) ListUserAccessTokens(ctx context.Context, request *v1pb.L // // Authentication: Required (session cookie or access token) // Authorization: User can only create tokens for themselves. -func (s *APIV1Service) CreateUserAccessToken(ctx context.Context, request *v1pb.CreateUserAccessTokenRequest) (*v1pb.UserAccessToken, error) { +func (s *APIV1Service) CreatePersonalAccessToken(ctx context.Context, request *v1pb.CreatePersonalAccessTokenRequest) (*v1pb.CreatePersonalAccessTokenResponse, error) { userID, err := ExtractUserIDFromName(request.Parent) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) - } - if currentUser == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not authenticated") - } - if currentUser.ID != userID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - expiresAt := time.Time{} - if request.AccessToken.ExpiresAt != nil { - expiresAt = request.AccessToken.ExpiresAt.AsTime() - } - - accessToken, err := auth.GenerateAccessToken(currentUser.Username, currentUser.ID, expiresAt, []byte(s.Secret)) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err) - } - - claims := &auth.ClaimsMessage{} - _, err = jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) { - if t.Method.Alg() != jwt.SigningMethodHS256.Name { - return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256) + // Verify permission + claims := auth.GetUserClaims(ctx) + if claims == nil || claims.UserID != userID { + currentUser, _ := s.fetchCurrentUser(ctx) + if currentUser == nil || currentUser.ID != userID { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - if kid, ok := t.Header["kid"].(string); ok { - if kid == "v1" { - return []byte(s.Secret), nil - } - } - return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"]) - }) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to parse access token: %v", err) } - // Upsert the access token to user setting store. - if err := s.UpsertAccessTokenToStore(ctx, currentUser, accessToken, request.AccessToken.Description); err != nil { - return nil, status.Errorf(codes.Internal, "failed to upsert access token to store: %v", err) + // Generate PAT + tokenID := util.GenUUID() + token := auth.GeneratePersonalAccessToken() + tokenHash := auth.HashPersonalAccessToken(token) + + var expiresAt *timestamppb.Timestamp + if request.ExpiresInDays > 0 { + expiresAt = timestamppb.New(time.Now().AddDate(0, 0, int(request.ExpiresInDays))) } - userAccessToken := &v1pb.UserAccessToken{ - Name: fmt.Sprintf("users/%d/accessTokens/%s", userID, accessToken), - AccessToken: accessToken, - Description: request.AccessToken.Description, - IssuedAt: timestamppb.New(claims.IssuedAt.Time), + patRecord := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{ + TokenId: tokenID, + TokenHash: tokenHash, + Description: request.Description, + ExpiresAt: expiresAt, + CreatedAt: timestamppb.Now(), } - if claims.ExpiresAt != nil { - userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time) + + if err := s.Store.AddUserPersonalAccessToken(ctx, userID, patRecord); err != nil { + return nil, status.Errorf(codes.Internal, "failed to create access token: %v", err) } - return userAccessToken, nil + + return &v1pb.CreatePersonalAccessTokenResponse{ + PersonalAccessToken: &v1pb.PersonalAccessToken{ + Name: fmt.Sprintf("%s/personalAccessTokens/%s", request.Parent, tokenID), + Description: request.Description, + ExpiresAt: expiresAt, + CreatedAt: patRecord.CreatedAt, + }, + Token: token, // Only returned on creation + }, nil } -// DeleteUserAccessToken revokes a Personal Access Token. +// DeletePersonalAccessToken revokes a Personal Access Token. // // This endpoint: // 1. Removes the token from the user's access tokens list @@ -668,220 +626,118 @@ func (s *APIV1Service) CreateUserAccessToken(ctx context.Context, request *v1pb. // // Authentication: Required (session cookie or access token) // Authorization: User can only delete their own tokens. -func (s *APIV1Service) DeleteUserAccessToken(ctx context.Context, request *v1pb.DeleteUserAccessTokenRequest) (*emptypb.Empty, error) { - // Extract user ID from the access token resource name - // Format: users/{user}/accessTokens/{access_token} +func (s *APIV1Service) DeletePersonalAccessToken(ctx context.Context, request *v1pb.DeletePersonalAccessTokenRequest) (*emptypb.Empty, error) { + // Parse name: users/{user_id}/personalAccessTokens/{token_id} parts := strings.Split(request.Name, "/") - if len(parts) != 4 || parts[0] != "users" || parts[2] != "accessTokens" { - return nil, status.Errorf(codes.InvalidArgument, "invalid access token name format: %s", request.Name) + if len(parts) != 4 || parts[0] != "users" || parts[2] != "personalAccessTokens" { + return nil, status.Errorf(codes.InvalidArgument, "invalid personal access token name") } - userID, err := ExtractUserIDFromName(fmt.Sprintf("users/%s", parts[1])) + userID, err := util.ConvertStringToInt32(parts[1]) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "invalid user ID: %v", err) } - accessTokenToDelete := parts[3] + tokenID := parts[3] - currentUser, err := s.GetCurrentUser(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) - } - if currentUser == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not authenticated") - } - if currentUser.ID != userID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, currentUser.ID) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err) - } - updatedUserAccessTokens := []*storepb.AccessTokensUserSetting_AccessToken{} - for _, userAccessToken := range userAccessTokens { - if userAccessToken.AccessToken == accessTokenToDelete { - continue + // Verify permission + claims := auth.GetUserClaims(ctx) + if claims == nil || claims.UserID != userID { + currentUser, _ := s.fetchCurrentUser(ctx) + if currentUser == nil || currentUser.ID != userID { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - updatedUserAccessTokens = append(updatedUserAccessTokens, userAccessToken) } - if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{ - UserId: currentUser.ID, - Key: storepb.UserSetting_ACCESS_TOKENS, - Value: &storepb.UserSetting_AccessTokens{ - AccessTokens: &storepb.AccessTokensUserSetting{ - AccessTokens: updatedUserAccessTokens, - }, - }, - }); err != nil { - return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err) + + if err := s.Store.RemoveUserPersonalAccessToken(ctx, userID, tokenID); err != nil { + return nil, status.Errorf(codes.Internal, "failed to delete access token: %v", err) } return &emptypb.Empty{}, nil } -// ListUserSessions retrieves all active sessions for a user. -// -// Sessions represent active browser logins. Each session includes: -// - session_id: Unique identifier -// - create_time: When the session was created -// - last_accessed_time: Last API call time (for sliding expiration) -// - client_info: Device details (browser, OS, IP address, device type) -// -// Use cases: -// - User reviews where they're logged in -// - User identifies suspicious login attempts -// - User prepares to revoke specific sessions -// -// Authentication: Required (session cookie or access token) -// Authorization: User can only list their own sessions. -func (s *APIV1Service) ListUserSessions(ctx context.Context, request *v1pb.ListUserSessionsRequest) (*v1pb.ListUserSessionsResponse, error) { +// ListSessions returns all active login sessions for a user. +// Each session represents a device/browser where the user is logged in, +// backed by refresh tokens with sliding expiration. +func (s *APIV1Service) ListSessions(ctx context.Context, request *v1pb.ListSessionsRequest) (*v1pb.ListSessionsResponse, error) { userID, err := ExtractUserIDFromName(request.Parent) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) - } - if currentUser == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not authenticated") - } - if currentUser.ID != userID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - userSessions, err := s.Store.GetUserSessions(ctx, userID) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to list sessions: %v", err) - } - - sessions := []*v1pb.UserSession{} - for _, userSession := range userSessions { - sessionResponse := &v1pb.UserSession{ - Name: fmt.Sprintf("users/%d/sessions/%s", userID, userSession.SessionId), - SessionId: userSession.SessionId, - CreateTime: userSession.CreateTime, - LastAccessedTime: userSession.LastAccessedTime, + // Verify permission. + claims := auth.GetUserClaims(ctx) + if claims == nil || claims.UserID != userID { + currentUser, _ := s.fetchCurrentUser(ctx) + if currentUser == nil || (currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") } + } - if userSession.ClientInfo != nil { - sessionResponse.ClientInfo = &v1pb.UserSession_ClientInfo{ - UserAgent: userSession.ClientInfo.UserAgent, - IpAddress: userSession.ClientInfo.IpAddress, - DeviceType: userSession.ClientInfo.DeviceType, - Os: userSession.ClientInfo.Os, - Browser: userSession.ClientInfo.Browser, + refreshTokens, err := s.Store.GetUserRefreshTokens(ctx, userID) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get sessions: %v", err) + } + + sessions := make([]*v1pb.Session, 0, len(refreshTokens)) + for _, token := range refreshTokens { + session := &v1pb.Session{ + Name: fmt.Sprintf("%s/sessions/%s", request.Parent, token.TokenId), + SessionId: token.TokenId, + CreateTime: token.CreatedAt, + } + if token.ClientInfo != nil { + session.ClientInfo = &v1pb.Session_ClientInfo{ + UserAgent: token.ClientInfo.UserAgent, + IpAddress: token.ClientInfo.IpAddress, + DeviceType: token.ClientInfo.DeviceType, + Os: token.ClientInfo.Os, + Browser: token.ClientInfo.Browser, } } - - sessions = append(sessions, sessionResponse) + sessions = append(sessions, session) } - // Sort by last accessed time in descending order. - slices.SortFunc(sessions, func(i, j *v1pb.UserSession) int { - return int(j.LastAccessedTime.Seconds - i.LastAccessedTime.Seconds) - }) - - response := &v1pb.ListUserSessionsResponse{ - Sessions: sessions, - } - return response, nil + return &v1pb.ListSessionsResponse{Sessions: sessions}, nil } -// RevokeUserSession terminates a specific session for a user. -// -// This endpoint: -// 1. Removes the session from the user's sessions list -// 2. Immediately invalidates the session -// 3. Forces the device to re-login on next request -// -// Use cases: -// - User logs out from a specific device (e.g., "Log out my phone") -// - User removes suspicious/unknown session -// - User logs out from all devices except current one -// -// Note: This is different from DeleteSession (logout current session). -// This endpoint allows revoking ANY session, not just the current one. -// -// Authentication: Required (session cookie or access token) -// Authorization: User can only revoke their own sessions. -func (s *APIV1Service) RevokeUserSession(ctx context.Context, request *v1pb.RevokeUserSessionRequest) (*emptypb.Empty, error) { - // Extract user ID and session ID from the session resource name - // Format: users/{user}/sessions/{session} +// RevokeSession revokes a specific login session. +// This invalidates the refresh token, forcing re-authentication on that device. +func (s *APIV1Service) RevokeSession(ctx context.Context, request *v1pb.RevokeSessionRequest) (*emptypb.Empty, error) { + // Parse name: users/{user_id}/sessions/{session_id} parts := strings.Split(request.Name, "/") if len(parts) != 4 || parts[0] != "users" || parts[2] != "sessions" { - return nil, status.Errorf(codes.InvalidArgument, "invalid session name format: %s", request.Name) + return nil, status.Errorf(codes.InvalidArgument, "invalid session name") } - userID, err := ExtractUserIDFromName(fmt.Sprintf("users/%s", parts[1])) + userID, err := util.ConvertStringToInt32(parts[1]) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "invalid user ID: %v", err) } - sessionIDToRevoke := parts[3] + sessionID := parts[3] - currentUser, err := s.GetCurrentUser(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) - } - if currentUser == nil { - return nil, status.Errorf(codes.Unauthenticated, "user not authenticated") - } - if currentUser.ID != userID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") + // Verify permission. + claims := auth.GetUserClaims(ctx) + if claims == nil || claims.UserID != userID { + currentUser, _ := s.fetchCurrentUser(ctx) + if currentUser == nil || (currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } } - if err := s.Store.RemoveUserSession(ctx, userID, sessionIDToRevoke); err != nil { + if err := s.Store.RemoveUserRefreshToken(ctx, userID, sessionID); err != nil { return nil, status.Errorf(codes.Internal, "failed to revoke session: %v", err) } return &emptypb.Empty{}, nil } -// UpsertUserSession adds or updates a user session. -func (s *APIV1Service) UpsertUserSession(ctx context.Context, userID int32, sessionID string, clientInfo *storepb.SessionsUserSetting_ClientInfo) error { - session := &storepb.SessionsUserSetting_Session{ - SessionId: sessionID, - CreateTime: timestamppb.Now(), - LastAccessedTime: timestamppb.Now(), - ClientInfo: clientInfo, - } - - return s.Store.AddUserSession(ctx, userID, session) -} - -func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken, description string) error { - userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID) - if err != nil { - return errors.Wrap(err, "failed to get user access tokens") - } - userAccessToken := storepb.AccessTokensUserSetting_AccessToken{ - AccessToken: accessToken, - Description: description, - } - userAccessTokens = append(userAccessTokens, &userAccessToken) - - if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{ - UserId: user.ID, - Key: storepb.UserSetting_ACCESS_TOKENS, - Value: &storepb.UserSetting_AccessTokens{ - AccessTokens: &storepb.AccessTokensUserSetting{ - AccessTokens: userAccessTokens, - }, - }, - }); err != nil { - return errors.Wrap(err, "failed to upsert user setting") - } - return nil -} - func (s *APIV1Service) ListUserWebhooks(ctx context.Context, request *v1pb.ListUserWebhooksRequest) (*v1pb.ListUserWebhooksResponse, error) { userID, err := ExtractUserIDFromName(request.Parent) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid parent: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -913,7 +769,7 @@ func (s *APIV1Service) CreateUserWebhook(ctx context.Context, request *v1pb.Crea return nil, status.Errorf(codes.InvalidArgument, "invalid parent: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -953,7 +809,7 @@ func (s *APIV1Service) UpdateUserWebhook(ctx context.Context, request *v1pb.Upda return nil, status.Errorf(codes.InvalidArgument, "invalid webhook name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -1025,7 +881,7 @@ func (s *APIV1Service) DeleteUserWebhook(ctx context.Context, request *v1pb.Dele return nil, status.Errorf(codes.InvalidArgument, "invalid webhook name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -1187,10 +1043,6 @@ func convertSettingKeyToStore(key string) (storepb.UserSetting_Key, error) { switch key { case v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_GENERAL)]: return storepb.UserSetting_GENERAL, nil - case v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_SESSIONS)]: - return storepb.UserSetting_SESSIONS, nil - case v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_ACCESS_TOKENS)]: - return storepb.UserSetting_ACCESS_TOKENS, nil case v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_WEBHOOKS)]: return storepb.UserSetting_WEBHOOKS, nil default: @@ -1203,10 +1055,6 @@ func convertSettingKeyFromStore(key storepb.UserSetting_Key) string { switch key { case storepb.UserSetting_GENERAL: return v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_GENERAL)] - case storepb.UserSetting_SESSIONS: - return v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_SESSIONS)] - case storepb.UserSetting_ACCESS_TOKENS: - return v1pb.UserSetting_Key_name[int32(v1pb.UserSetting_ACCESS_TOKENS)] case storepb.UserSetting_SHORTCUTS: return "SHORTCUTS" // Not defined in API proto case storepb.UserSetting_WEBHOOKS: @@ -1226,18 +1074,6 @@ func convertUserSettingFromStore(storeSetting *storepb.UserSetting, userID int32 } switch key { - case storepb.UserSetting_SESSIONS: - setting.Value = &v1pb.UserSetting_SessionsSetting_{ - SessionsSetting: &v1pb.UserSetting_SessionsSetting{ - Sessions: []*v1pb.UserSession{}, - }, - } - case storepb.UserSetting_ACCESS_TOKENS: - setting.Value = &v1pb.UserSetting_AccessTokensSetting_{ - AccessTokensSetting: &v1pb.UserSetting_AccessTokensSetting{ - AccessTokens: []*v1pb.UserAccessToken{}, - }, - } case storepb.UserSetting_WEBHOOKS: setting.Value = &v1pb.UserSetting_WebhooksSetting_{ WebhooksSetting: &v1pb.UserSetting_WebhooksSetting{ @@ -1273,46 +1109,6 @@ func convertUserSettingFromStore(storeSetting *storepb.UserSetting, userID int32 GeneralSetting: getDefaultUserGeneralSetting(), } } - case storepb.UserSetting_SESSIONS: - sessions := storeSetting.GetSessions() - apiSessions := make([]*v1pb.UserSession, 0, len(sessions.Sessions)) - for _, session := range sessions.Sessions { - apiSession := &v1pb.UserSession{ - Name: fmt.Sprintf("users/%d/sessions/%s", userID, session.SessionId), - SessionId: session.SessionId, - CreateTime: session.CreateTime, - LastAccessedTime: session.LastAccessedTime, - ClientInfo: &v1pb.UserSession_ClientInfo{ - UserAgent: session.ClientInfo.UserAgent, - IpAddress: session.ClientInfo.IpAddress, - DeviceType: session.ClientInfo.DeviceType, - Os: session.ClientInfo.Os, - Browser: session.ClientInfo.Browser, - }, - } - apiSessions = append(apiSessions, apiSession) - } - setting.Value = &v1pb.UserSetting_SessionsSetting_{ - SessionsSetting: &v1pb.UserSetting_SessionsSetting{ - Sessions: apiSessions, - }, - } - case storepb.UserSetting_ACCESS_TOKENS: - accessTokens := storeSetting.GetAccessTokens() - apiTokens := make([]*v1pb.UserAccessToken, 0, len(accessTokens.AccessTokens)) - for _, token := range accessTokens.AccessTokens { - apiToken := &v1pb.UserAccessToken{ - Name: fmt.Sprintf("users/%d/accessTokens/%s", userID, token.AccessToken), - AccessToken: token.AccessToken, - Description: token.Description, - } - apiTokens = append(apiTokens, apiToken) - } - setting.Value = &v1pb.UserSetting_AccessTokensSetting_{ - AccessTokensSetting: &v1pb.UserSetting_AccessTokensSetting{ - AccessTokens: apiTokens, - }, - } case storepb.UserSetting_WEBHOOKS: webhooks := storeSetting.GetWebhooks() apiWebhooks := make([]*v1pb.UserWebhook, 0, len(webhooks.Webhooks)) @@ -1359,50 +1155,6 @@ func convertUserSettingToStore(apiSetting *v1pb.UserSetting, userID int32, key s } else { return nil, errors.Errorf("general setting is required") } - case storepb.UserSetting_SESSIONS: - if sessions := apiSetting.GetSessionsSetting(); sessions != nil { - storeSessions := make([]*storepb.SessionsUserSetting_Session, 0, len(sessions.Sessions)) - for _, session := range sessions.Sessions { - storeSession := &storepb.SessionsUserSetting_Session{ - SessionId: session.SessionId, - CreateTime: session.CreateTime, - LastAccessedTime: session.LastAccessedTime, - ClientInfo: &storepb.SessionsUserSetting_ClientInfo{ - UserAgent: session.ClientInfo.UserAgent, - IpAddress: session.ClientInfo.IpAddress, - DeviceType: session.ClientInfo.DeviceType, - Os: session.ClientInfo.Os, - Browser: session.ClientInfo.Browser, - }, - } - storeSessions = append(storeSessions, storeSession) - } - storeSetting.Value = &storepb.UserSetting_Sessions{ - Sessions: &storepb.SessionsUserSetting{ - Sessions: storeSessions, - }, - } - } else { - return nil, errors.Errorf("sessions setting is required") - } - case storepb.UserSetting_ACCESS_TOKENS: - if accessTokens := apiSetting.GetAccessTokensSetting(); accessTokens != nil { - storeTokens := make([]*storepb.AccessTokensUserSetting_AccessToken, 0, len(accessTokens.AccessTokens)) - for _, token := range accessTokens.AccessTokens { - storeToken := &storepb.AccessTokensUserSetting_AccessToken{ - AccessToken: token.AccessToken, - Description: token.Description, - } - storeTokens = append(storeTokens, storeToken) - } - storeSetting.Value = &storepb.UserSetting_AccessTokens{ - AccessTokens: &storepb.AccessTokensUserSetting{ - AccessTokens: storeTokens, - }, - } - } else { - return nil, errors.Errorf("access tokens setting is required") - } case storepb.UserSetting_WEBHOOKS: if webhooks := apiSetting.GetWebhooksSetting(); webhooks != nil { storeWebhooks := make([]*storepb.WebhooksUserSetting_Webhook, 0, len(webhooks.Webhooks)) @@ -1542,7 +1294,7 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb. } // Verify the requesting user has permission to view these notifications - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -1588,7 +1340,7 @@ func (s *APIV1Service) UpdateUserNotification(ctx context.Context, request *v1pb return nil, status.Errorf(codes.InvalidArgument, "invalid notification name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -1653,7 +1405,7 @@ func (s *APIV1Service) DeleteUserNotification(ctx context.Context, request *v1pb return nil, status.Errorf(codes.InvalidArgument, "invalid notification name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/user_service_stats.go b/server/router/api/v1/user_service_stats.go index ceb4be949..ed9ecd8a4 100644 --- a/server/router/api/v1/user_service_stats.go +++ b/server/router/api/v1/user_service_stats.go @@ -28,7 +28,7 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser RowStatus: &normalStatus, } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } @@ -121,7 +121,7 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } - currentUser, err := s.GetCurrentUser(ctx) + currentUser, err := s.fetchCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } diff --git a/server/router/api/v1/v1.go b/server/router/api/v1/v1.go index 7fcf2461c..c03ea4130 100644 --- a/server/router/api/v1/v1.go +++ b/server/router/api/v1/v1.go @@ -76,9 +76,16 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech return } - // Set user in context (may be nil for public endpoints) + // Set context based on auth result (may be nil for public endpoints) if result != nil { - ctx = auth.SetUserInContext(ctx, result.User, result.SessionID, result.AccessToken) + if result.Claims != nil { + // Access Token V2 - stateless, use claims + ctx = auth.SetUserClaimsInContext(ctx, result.Claims) + ctx = context.WithValue(ctx, auth.UserIDContextKey, result.Claims.UserID) + } else if result.User != nil { + // PAT - have full user + ctx = auth.SetUserInContext(ctx, result.User, result.SessionID, result.AccessToken) + } r = r.WithContext(ctx) } diff --git a/server/router/fileserver/fileserver.go b/server/router/fileserver/fileserver.go index 5407adbba..6bc9e1c09 100644 --- a/server/router/fileserver/fileserver.go +++ b/server/router/fileserver/fileserver.go @@ -287,25 +287,32 @@ func (s *FileServerService) checkAttachmentPermission(ctx context.Context, c ech } // getCurrentUser retrieves the current authenticated user from the Echo context. -// It checks both session cookies and Bearer tokens for authentication. +// It checks Bearer tokens for authentication (Access Token V2 or PAT). // Uses the shared Authenticator for consistent authentication logic. func (s *FileServerService) getCurrentUser(ctx context.Context, c echo.Context) (*store.User, error) { - // Try session cookie authentication first - if cookie, err := c.Cookie(auth.SessionCookieName); err == nil && cookie.Value != "" { - user, err := s.authenticator.AuthenticateBySession(ctx, cookie.Value) - if err == nil && user != nil { - return user, nil - } - } - - // Try JWT Bearer token authentication + // Try Bearer token authentication authHeader := c.Request().Header.Get("Authorization") if authHeader != "" { - parts := strings.Fields(authHeader) - if len(parts) == 2 && strings.EqualFold(parts[0], "bearer") { - user, err := s.authenticator.AuthenticateByJWT(ctx, parts[1]) - if err == nil && user != nil { - return user, nil + token := auth.ExtractBearerToken(authHeader) + if token != "" { + // Try Access Token V2 (stateless) + if !strings.HasPrefix(token, auth.PersonalAccessTokenPrefix) { + claims, err := s.authenticator.AuthenticateByAccessTokenV2(token) + if err == nil && claims != nil { + // Get user from claims + user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &claims.UserID}) + if err == nil && user != nil { + return user, nil + } + } + } + + // Try PAT + if strings.HasPrefix(token, auth.PersonalAccessTokenPrefix) { + user, _, err := s.authenticator.AuthenticateByPAT(ctx, token) + if err == nil && user != nil { + return user, nil + } } } } diff --git a/store/db/mysql/user_setting.go b/store/db/mysql/user_setting.go index effc926ef..7fb075913 100644 --- a/store/db/mysql/user_setting.go +++ b/store/db/mysql/user_setting.go @@ -57,41 +57,37 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) return userSettingList, nil } -func (d *DB) GetUserSessionByID(ctx context.Context, sessionID string) (*store.UserSessionQueryResult, error) { - // Query user_setting that contains this sessionID in the sessions array - // Use JSON_SEARCH to check if sessionID exists in the array +func (d *DB) GetUserByPATHash(ctx context.Context, tokenHash string) (*store.PATQueryResult, error) { query := ` - SELECT + SELECT user_id, value FROM user_setting - WHERE ` + "`key`" + ` = 'SESSIONS' - AND JSON_SEARCH(value, 'one', ?, NULL, '$.sessions[*].sessionId') IS NOT NULL + WHERE ` + "`key`" + ` = 'PERSONAL_ACCESS_TOKENS' + AND JSON_SEARCH(value, 'one', ?, NULL, '$.tokens[*].tokenHash') IS NOT NULL ` var userID int32 - var sessionsJSON string + var tokensJSON string - err := d.db.QueryRowContext(ctx, query, sessionID).Scan(&userID, &sessionsJSON) + err := d.db.QueryRowContext(ctx, query, tokenHash).Scan(&userID, &tokensJSON) if err != nil { return nil, err } - // Parse the entire sessions list using protobuf unmarshaler - sessionsUserSetting := &storepb.SessionsUserSetting{} - if err := protojsonUnmarshaler.Unmarshal([]byte(sessionsJSON), sessionsUserSetting); err != nil { + patsUserSetting := &storepb.PersonalAccessTokensUserSetting{} + if err := protojsonUnmarshaler.Unmarshal([]byte(tokensJSON), patsUserSetting); err != nil { return nil, err } - // Find the specific session by ID - for _, session := range sessionsUserSetting.Sessions { - if session.SessionId == sessionID { - return &store.UserSessionQueryResult{ - UserID: userID, - Session: session, + for _, pat := range patsUserSetting.Tokens { + if pat.TokenHash == tokenHash { + return &store.PATQueryResult{ + UserID: userID, + PAT: pat, }, nil } } - return nil, errors.New("session not found") + return nil, errors.New("PAT not found") } diff --git a/store/db/postgres/user_setting.go b/store/db/postgres/user_setting.go index fd3493ecc..063250d6b 100644 --- a/store/db/postgres/user_setting.go +++ b/store/db/postgres/user_setting.go @@ -70,45 +70,41 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) return userSettingList, nil } -func (d *DB) GetUserSessionByID(ctx context.Context, sessionID string) (*store.UserSessionQueryResult, error) { - // Query user_setting that contains this sessionID in the sessions array - // Use EXISTS with jsonb_array_elements to check array membership +func (d *DB) GetUserByPATHash(ctx context.Context, tokenHash string) (*store.PATQueryResult, error) { query := ` - SELECT + SELECT user_setting.user_id, user_setting.value FROM user_setting - WHERE user_setting.key = 'SESSIONS' + WHERE user_setting.key = 'PERSONAL_ACCESS_TOKENS' AND EXISTS ( - SELECT 1 - FROM jsonb_array_elements(user_setting.value::jsonb->'sessions') AS session - WHERE session->>'sessionId' = $1 + SELECT 1 + FROM jsonb_array_elements(user_setting.value::jsonb->'tokens') AS token + WHERE token->>'tokenHash' = $1 ) ` var userID int32 - var sessionsJSON string + var tokensJSON string - err := d.db.QueryRowContext(ctx, query, sessionID).Scan(&userID, &sessionsJSON) + err := d.db.QueryRowContext(ctx, query, tokenHash).Scan(&userID, &tokensJSON) if err != nil { return nil, err } - // Parse the entire sessions list using protobuf unmarshaler - sessionsUserSetting := &storepb.SessionsUserSetting{} - if err := protojsonUnmarshaler.Unmarshal([]byte(sessionsJSON), sessionsUserSetting); err != nil { + patsUserSetting := &storepb.PersonalAccessTokensUserSetting{} + if err := protojsonUnmarshaler.Unmarshal([]byte(tokensJSON), patsUserSetting); err != nil { return nil, err } - // Find the specific session by ID - for _, session := range sessionsUserSetting.Sessions { - if session.SessionId == sessionID { - return &store.UserSessionQueryResult{ - UserID: userID, - Session: session, + for _, pat := range patsUserSetting.Tokens { + if pat.TokenHash == tokenHash { + return &store.PATQueryResult{ + UserID: userID, + PAT: pat, }, nil } } - return nil, errors.New("session not found") + return nil, errors.New("PAT not found") } diff --git a/store/db/sqlite/user_setting.go b/store/db/sqlite/user_setting.go index 775273c7b..f58094928 100644 --- a/store/db/sqlite/user_setting.go +++ b/store/db/sqlite/user_setting.go @@ -69,45 +69,41 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) return userSettingList, nil } -func (d *DB) GetUserSessionByID(ctx context.Context, sessionID string) (*store.UserSessionQueryResult, error) { - // Query user_setting that contains this sessionID in the sessions array - // Use EXISTS with json_each to properly check array membership +func (d *DB) GetUserByPATHash(ctx context.Context, tokenHash string) (*store.PATQueryResult, error) { query := ` - SELECT + SELECT user_setting.user_id, user_setting.value FROM user_setting - WHERE user_setting.key = 'SESSIONS' - AND EXISTS ( - SELECT 1 - FROM json_each(json_extract(user_setting.value, '$.sessions')) AS session - WHERE json_extract(session.value, '$.sessionId') = ? - ) + WHERE user_setting.key = 'PERSONAL_ACCESS_TOKENS' + AND EXISTS ( + SELECT 1 + FROM json_each(json_extract(user_setting.value, '$.tokens')) AS token + WHERE json_extract(token.value, '$.tokenHash') = ? + ) ` var userID int32 - var sessionsJSON string + var tokensJSON string - err := d.db.QueryRowContext(ctx, query, sessionID).Scan(&userID, &sessionsJSON) + err := d.db.QueryRowContext(ctx, query, tokenHash).Scan(&userID, &tokensJSON) if err != nil { return nil, err } - // Parse the entire sessions list using protobuf unmarshaler - sessionsUserSetting := &storepb.SessionsUserSetting{} - if err := protojsonUnmarshaler.Unmarshal([]byte(sessionsJSON), sessionsUserSetting); err != nil { + patsUserSetting := &storepb.PersonalAccessTokensUserSetting{} + if err := protojsonUnmarshaler.Unmarshal([]byte(tokensJSON), patsUserSetting); err != nil { return nil, err } - // Find the specific session by ID - for _, session := range sessionsUserSetting.Sessions { - if session.SessionId == sessionID { - return &store.UserSessionQueryResult{ - UserID: userID, - Session: session, + for _, pat := range patsUserSetting.Tokens { + if pat.TokenHash == tokenHash { + return &store.PATQueryResult{ + UserID: userID, + PAT: pat, }, nil } } - return nil, errors.New("session not found") + return nil, errors.New("PAT not found") } diff --git a/store/driver.go b/store/driver.go index e06209cae..ec3ba9082 100644 --- a/store/driver.go +++ b/store/driver.go @@ -48,7 +48,7 @@ type Driver interface { // UserSetting model related methods. UpsertUserSetting(ctx context.Context, upsert *UserSetting) (*UserSetting, error) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*UserSetting, error) - GetUserSessionByID(ctx context.Context, sessionID string) (*UserSessionQueryResult, error) + GetUserByPATHash(ctx context.Context, tokenHash string) (*PATQueryResult, error) // IdentityProvider model related methods. CreateIdentityProvider(ctx context.Context, create *IdentityProvider) (*IdentityProvider, error) diff --git a/store/user_setting.go b/store/user_setting.go index fcdb0a951..619a3b2ec 100644 --- a/store/user_setting.go +++ b/store/user_setting.go @@ -21,10 +21,17 @@ type FindUserSetting struct { Key storepb.UserSetting_Key } -// UserSessionQueryResult contains the result of querying a single session by ID. -type UserSessionQueryResult struct { - UserID int32 - Session *storepb.SessionsUserSetting_Session +// RefreshTokenQueryResult contains the result of querying a refresh token. +type RefreshTokenQueryResult struct { + UserID int32 + RefreshToken *storepb.RefreshTokensUserSetting_RefreshToken +} + +// PATQueryResult contains the result of querying a PAT by hash. +type PATQueryResult struct { + UserID int32 + User *User + PAT *storepb.PersonalAccessTokensUserSetting_PersonalAccessToken } func (s *Store) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) { @@ -95,164 +102,190 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*sto return userSetting, nil } -// GetUserAccessTokens returns the access tokens of the user. -func (s *Store) GetUserAccessTokens(ctx context.Context, userID int32) ([]*storepb.AccessTokensUserSetting_AccessToken, error) { +// GetUserByPATHash finds a user by PAT hash. +func (s *Store) GetUserByPATHash(ctx context.Context, tokenHash string) (*PATQueryResult, error) { + result, err := s.driver.GetUserByPATHash(ctx, tokenHash) + if err != nil { + return nil, err + } + + // Fetch user info + user, err := s.GetUser(ctx, &FindUser{ID: &result.UserID}) + if err != nil { + return nil, err + } + if user == nil { + return nil, errors.New("user not found for PAT") + } + result.User = user + + return result, nil +} + +// GetUserRefreshTokens returns the refresh tokens of the user. +func (s *Store) GetUserRefreshTokens(ctx context.Context, userID int32) ([]*storepb.RefreshTokensUserSetting_RefreshToken, error) { userSetting, err := s.GetUserSetting(ctx, &FindUserSetting{ UserID: &userID, - Key: storepb.UserSetting_ACCESS_TOKENS, + Key: storepb.UserSetting_REFRESH_TOKENS, }) if err != nil { return nil, err } if userSetting == nil { - return []*storepb.AccessTokensUserSetting_AccessToken{}, nil + return []*storepb.RefreshTokensUserSetting_RefreshToken{}, nil } - - accessTokensUserSetting := userSetting.GetAccessTokens() - return accessTokensUserSetting.AccessTokens, nil + return userSetting.GetRefreshTokens().RefreshTokens, nil } -// RemoveUserAccessToken remove the access token of the user. -func (s *Store) RemoveUserAccessToken(ctx context.Context, userID int32, token string) error { - oldAccessTokens, err := s.GetUserAccessTokens(ctx, userID) +// AddUserRefreshToken adds a new refresh token for the user. +func (s *Store) AddUserRefreshToken(ctx context.Context, userID int32, token *storepb.RefreshTokensUserSetting_RefreshToken) error { + tokens, err := s.GetUserRefreshTokens(ctx, userID) if err != nil { return err } - newAccessTokens := make([]*storepb.AccessTokensUserSetting_AccessToken, 0, len(oldAccessTokens)) - for _, t := range oldAccessTokens { - if token != t.AccessToken { - newAccessTokens = append(newAccessTokens, t) + tokens = append(tokens, token) + + _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ + UserId: userID, + Key: storepb.UserSetting_REFRESH_TOKENS, + Value: &storepb.UserSetting_RefreshTokens{ + RefreshTokens: &storepb.RefreshTokensUserSetting{ + RefreshTokens: tokens, + }, + }, + }) + return err +} + +// RemoveUserRefreshToken removes a refresh token from the user. +func (s *Store) RemoveUserRefreshToken(ctx context.Context, userID int32, tokenID string) error { + existingTokens, err := s.GetUserRefreshTokens(ctx, userID) + if err != nil { + return err + } + + newTokens := make([]*storepb.RefreshTokensUserSetting_RefreshToken, 0, len(existingTokens)) + for _, token := range existingTokens { + if token.TokenId != tokenID { + newTokens = append(newTokens, token) } } _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ UserId: userID, - Key: storepb.UserSetting_ACCESS_TOKENS, - Value: &storepb.UserSetting_AccessTokens{ - AccessTokens: &storepb.AccessTokensUserSetting{ - AccessTokens: newAccessTokens, + Key: storepb.UserSetting_REFRESH_TOKENS, + Value: &storepb.UserSetting_RefreshTokens{ + RefreshTokens: &storepb.RefreshTokensUserSetting{ + RefreshTokens: newTokens, }, }, }) - return err } -// GetUserSessions returns the sessions of the user. -func (s *Store) GetUserSessions(ctx context.Context, userID int32) ([]*storepb.SessionsUserSetting_Session, error) { +// GetUserRefreshTokenByID returns a specific refresh token. +func (s *Store) GetUserRefreshTokenByID(ctx context.Context, userID int32, tokenID string) (*storepb.RefreshTokensUserSetting_RefreshToken, error) { + tokens, err := s.GetUserRefreshTokens(ctx, userID) + if err != nil { + return nil, err + } + for _, token := range tokens { + if token.TokenId == tokenID { + return token, nil + } + } + return nil, nil +} + +// GetUserPersonalAccessTokens returns the PATs of the user. +func (s *Store) GetUserPersonalAccessTokens(ctx context.Context, userID int32) ([]*storepb.PersonalAccessTokensUserSetting_PersonalAccessToken, error) { userSetting, err := s.GetUserSetting(ctx, &FindUserSetting{ UserID: &userID, - Key: storepb.UserSetting_SESSIONS, + Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS, }) if err != nil { return nil, err } if userSetting == nil { - return []*storepb.SessionsUserSetting_Session{}, nil + return []*storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{}, nil } - - sessionsUserSetting := userSetting.GetSessions() - return sessionsUserSetting.Sessions, nil + return userSetting.GetPersonalAccessTokens().Tokens, nil } -// RemoveUserSession removes the session of the user. -func (s *Store) RemoveUserSession(ctx context.Context, userID int32, sessionID string) error { - oldSessions, err := s.GetUserSessions(ctx, userID) +// AddUserPersonalAccessToken adds a new PAT for the user. +func (s *Store) AddUserPersonalAccessToken(ctx context.Context, userID int32, token *storepb.PersonalAccessTokensUserSetting_PersonalAccessToken) error { + tokens, err := s.GetUserPersonalAccessTokens(ctx, userID) if err != nil { return err } - newSessions := make([]*storepb.SessionsUserSetting_Session, 0, len(oldSessions)) - for _, session := range oldSessions { - if sessionID != session.SessionId { - newSessions = append(newSessions, session) + tokens = append(tokens, token) + + _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ + UserId: userID, + Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS, + Value: &storepb.UserSetting_PersonalAccessTokens{ + PersonalAccessTokens: &storepb.PersonalAccessTokensUserSetting{ + Tokens: tokens, + }, + }, + }) + return err +} + +// RemoveUserPersonalAccessToken removes a PAT from the user. +func (s *Store) RemoveUserPersonalAccessToken(ctx context.Context, userID int32, tokenID string) error { + existingTokens, err := s.GetUserPersonalAccessTokens(ctx, userID) + if err != nil { + return err + } + + newTokens := make([]*storepb.PersonalAccessTokensUserSetting_PersonalAccessToken, 0, len(existingTokens)) + for _, token := range existingTokens { + if token.TokenId != tokenID { + newTokens = append(newTokens, token) } } _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ UserId: userID, - Key: storepb.UserSetting_SESSIONS, - Value: &storepb.UserSetting_Sessions{ - Sessions: &storepb.SessionsUserSetting{ - Sessions: newSessions, + Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS, + Value: &storepb.UserSetting_PersonalAccessTokens{ + PersonalAccessTokens: &storepb.PersonalAccessTokensUserSetting{ + Tokens: newTokens, }, }, }) - return err } -// AddUserSession adds a new session for the user. -func (s *Store) AddUserSession(ctx context.Context, userID int32, session *storepb.SessionsUserSetting_Session) error { - existingSessions, err := s.GetUserSessions(ctx, userID) +// UpdatePATLastUsed updates the last_used_at timestamp of a PAT. +func (s *Store) UpdatePATLastUsed(ctx context.Context, userID int32, tokenID string, lastUsed *timestamppb.Timestamp) error { + tokens, err := s.GetUserPersonalAccessTokens(ctx, userID) if err != nil { return err } - // Check if session already exists, update if it does - var updatedSessions []*storepb.SessionsUserSetting_Session - sessionExists := false - for _, existing := range existingSessions { - if existing.SessionId == session.SessionId { - updatedSessions = append(updatedSessions, session) - sessionExists = true - } else { - updatedSessions = append(updatedSessions, existing) - } - } - - // If session doesn't exist, add it - if !sessionExists { - updatedSessions = append(updatedSessions, session) - } - - _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ - UserId: userID, - Key: storepb.UserSetting_SESSIONS, - Value: &storepb.UserSetting_Sessions{ - Sessions: &storepb.SessionsUserSetting{ - Sessions: updatedSessions, - }, - }, - }) - - return err -} - -// UpdateUserSessionLastAccessed updates the last accessed time of a session. -func (s *Store) UpdateUserSessionLastAccessed(ctx context.Context, userID int32, sessionID string, lastAccessedTime *timestamppb.Timestamp) error { - sessions, err := s.GetUserSessions(ctx, userID) - if err != nil { - return err - } - - for _, session := range sessions { - if session.SessionId == sessionID { - session.LastAccessedTime = lastAccessedTime + for _, token := range tokens { + if token.TokenId == tokenID { + token.LastUsedAt = lastUsed break } } _, err = s.UpsertUserSetting(ctx, &storepb.UserSetting{ UserId: userID, - Key: storepb.UserSetting_SESSIONS, - Value: &storepb.UserSetting_Sessions{ - Sessions: &storepb.SessionsUserSetting{ - Sessions: sessions, + Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS, + Value: &storepb.UserSetting_PersonalAccessTokens{ + PersonalAccessTokens: &storepb.PersonalAccessTokensUserSetting{ + Tokens: tokens, }, }, }) - return err } -// GetUserSessionByID returns the session details for the given session ID. -// Uses database-specific JSON queries for efficient lookup without loading all sessions. -func (s *Store) GetUserSessionByID(ctx context.Context, sessionID string) (*UserSessionQueryResult, error) { - return s.driver.GetUserSessionByID(ctx, sessionID) -} - // GetUserWebhooks returns the webhooks of the user. func (s *Store) GetUserWebhooks(ctx context.Context, userID int32) ([]*storepb.WebhooksUserSetting_Webhook, error) { userSetting, err := s.GetUserSetting(ctx, &FindUserSetting{ @@ -392,6 +425,18 @@ func convertUserSettingFromRaw(raw *UserSetting) (*storepb.UserSetting, error) { return nil, err } userSetting.Value = &storepb.UserSetting_General{General: generalUserSetting} + case storepb.UserSetting_REFRESH_TOKENS: + refreshTokensUserSetting := &storepb.RefreshTokensUserSetting{} + if err := protojsonUnmarshaler.Unmarshal([]byte(raw.Value), refreshTokensUserSetting); err != nil { + return nil, err + } + userSetting.Value = &storepb.UserSetting_RefreshTokens{RefreshTokens: refreshTokensUserSetting} + case storepb.UserSetting_PERSONAL_ACCESS_TOKENS: + patsUserSetting := &storepb.PersonalAccessTokensUserSetting{} + if err := protojsonUnmarshaler.Unmarshal([]byte(raw.Value), patsUserSetting); err != nil { + return nil, err + } + userSetting.Value = &storepb.UserSetting_PersonalAccessTokens{PersonalAccessTokens: patsUserSetting} case storepb.UserSetting_WEBHOOKS: webhooksUserSetting := &storepb.WebhooksUserSetting{} if err := protojsonUnmarshaler.Unmarshal([]byte(raw.Value), webhooksUserSetting); err != nil { @@ -439,6 +484,20 @@ func convertUserSettingToRaw(userSetting *storepb.UserSetting) (*UserSetting, er return nil, err } raw.Value = string(value) + case storepb.UserSetting_REFRESH_TOKENS: + refreshTokensUserSetting := userSetting.GetRefreshTokens() + value, err := protojson.Marshal(refreshTokensUserSetting) + if err != nil { + return nil, err + } + raw.Value = string(value) + case storepb.UserSetting_PERSONAL_ACCESS_TOKENS: + patsUserSetting := userSetting.GetPersonalAccessTokens() + value, err := protojson.Marshal(patsUserSetting) + if err != nil { + return nil, err + } + raw.Value = string(value) case storepb.UserSetting_WEBHOOKS: webhooksUserSetting := userSetting.GetWebhooks() value, err := protojson.Marshal(webhooksUserSetting) diff --git a/web/src/auth-state.ts b/web/src/auth-state.ts new file mode 100644 index 000000000..f0524ba0e --- /dev/null +++ b/web/src/auth-state.ts @@ -0,0 +1,21 @@ +// In-memory storage for access token (not persisted for security) +let accessToken: string | null = null; +let tokenExpiresAt: Date | null = null; + +export const getAccessToken = (): string | null => accessToken; + +export const setAccessToken = (token: string | null, expiresAt?: Date): void => { + accessToken = token; + tokenExpiresAt = expiresAt || null; +}; + +export const isTokenExpired = (): boolean => { + if (!tokenExpiresAt) return true; + // Consider expired 30 seconds before actual expiry for safety + return new Date() >= new Date(tokenExpiresAt.getTime() - 30000); +}; + +export const clearAccessToken = (): void => { + accessToken = null; + tokenExpiresAt = null; +}; diff --git a/web/src/components/CreateAccessTokenDialog.tsx b/web/src/components/CreateAccessTokenDialog.tsx index 5520bdad2..c6cfd5b31 100644 --- a/web/src/components/CreateAccessTokenDialog.tsx +++ b/web/src/components/CreateAccessTokenDialog.tsx @@ -1,4 +1,3 @@ -import { timestampFromDate } from "@bufbuild/protobuf/wkt"; import React, { useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; @@ -9,13 +8,13 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { userServiceClient } from "@/connect"; import useCurrentUser from "@/hooks/useCurrentUser"; import useLoading from "@/hooks/useLoading"; -import { UserAccessToken } from "@/types/proto/api/v1/user_service_pb"; +import { CreatePersonalAccessTokenResponse } from "@/types/proto/api/v1/user_service_pb"; import { useTranslate } from "@/utils/i18n"; interface Props { open: boolean; onOpenChange: (open: boolean) => void; - onSuccess: (created: UserAccessToken) => void; + onSuccess: (response: CreatePersonalAccessTokenResponse) => void; } interface State { @@ -28,18 +27,19 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) { const currentUser = useCurrentUser(); const [state, setState] = useState({ description: "", - expiration: 3600 * 8, + expiration: 30, // Default: 30 days }); const requestState = useLoading(false); + // Expiration options in days (0 = never expires) const expirationOptions = [ { - label: t("setting.access-token-section.create-dialog.duration-8h"), - value: 3600 * 8, + label: t("setting.access-token-section.create-dialog.duration-1m"), + value: 30, }, { - label: t("setting.access-token-section.create-dialog.duration-1m"), - value: 3600 * 24 * 30, + label: "90 Days", + value: 90, }, { label: t("setting.access-token-section.create-dialog.duration-never"), @@ -74,16 +74,14 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) { try { requestState.setLoading(); - const created = await userServiceClient.createUserAccessToken({ + const response = await userServiceClient.createPersonalAccessToken({ parent: currentUser.name, - accessToken: { - description: state.description, - expiresAt: state.expiration ? timestampFromDate(new Date(Date.now() + state.expiration * 1000)) : undefined, - }, + description: state.description, + expiresInDays: state.expiration, }); requestState.setFinish(); - onSuccess(created); + onSuccess(response); onOpenChange(false); } catch (error: any) { toast.error(error.message); diff --git a/web/src/components/PasswordSignInForm.tsx b/web/src/components/PasswordSignInForm.tsx index 070f5c8ec..62f1d7381 100644 --- a/web/src/components/PasswordSignInForm.tsx +++ b/web/src/components/PasswordSignInForm.tsx @@ -1,8 +1,10 @@ +import { timestampDate } from "@bufbuild/protobuf/wkt"; import { ConnectError } from "@connectrpc/connect"; import { LoaderIcon } from "lucide-react"; import { observer } from "mobx-react-lite"; import { useState } from "react"; import { toast } from "react-hot-toast"; +import { setAccessToken } from "@/auth-state"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { authServiceClient } from "@/connect"; @@ -45,12 +47,16 @@ const PasswordSignInForm = observer(() => { try { actionBtnLoadingState.setLoading(); - await authServiceClient.createSession({ + const response = await authServiceClient.signIn({ credentials: { case: "passwordCredentials", value: { username, password }, }, }); + // Store access token from login response + if (response.accessToken) { + setAccessToken(response.accessToken, response.accessTokenExpiresAt ? timestampDate(response.accessTokenExpiresAt) : undefined); + } await initialUserStore(); navigateTo("/"); } catch (error: any) { diff --git a/web/src/components/Settings/AccessTokenSection.tsx b/web/src/components/Settings/AccessTokenSection.tsx index 12bf69be8..940a1f637 100644 --- a/web/src/components/Settings/AccessTokenSection.tsx +++ b/web/src/components/Settings/AccessTokenSection.tsx @@ -1,6 +1,6 @@ import { timestampDate } from "@bufbuild/protobuf/wkt"; import copy from "copy-to-clipboard"; -import { ClipboardIcon, PlusIcon, TrashIcon } from "lucide-react"; +import { PlusIcon, TrashIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import ConfirmDialog from "@/components/ConfirmDialog"; @@ -8,66 +8,65 @@ import { Button } from "@/components/ui/button"; import { userServiceClient } from "@/connect"; import useCurrentUser from "@/hooks/useCurrentUser"; import { useDialog } from "@/hooks/useDialog"; -import { UserAccessToken } from "@/types/proto/api/v1/user_service_pb"; +import { CreatePersonalAccessTokenResponse, PersonalAccessToken } from "@/types/proto/api/v1/user_service_pb"; import { useTranslate } from "@/utils/i18n"; import CreateAccessTokenDialog from "../CreateAccessTokenDialog"; import SettingTable from "./SettingTable"; const listAccessTokens = async (parent: string) => { - const { accessTokens } = await userServiceClient.listUserAccessTokens({ parent }); - return accessTokens.sort( + const { personalAccessTokens } = await userServiceClient.listPersonalAccessTokens({ parent }); + return personalAccessTokens.sort( (a, b) => - ((b.issuedAt ? timestampDate(b.issuedAt) : undefined)?.getTime() ?? 0) - - ((a.issuedAt ? timestampDate(a.issuedAt) : undefined)?.getTime() ?? 0), + ((b.createdAt ? timestampDate(b.createdAt) : undefined)?.getTime() ?? 0) - + ((a.createdAt ? timestampDate(a.createdAt) : undefined)?.getTime() ?? 0), ); }; const AccessTokenSection = () => { const t = useTranslate(); const currentUser = useCurrentUser(); - const [userAccessTokens, setUserAccessTokens] = useState([]); + const [personalAccessTokens, setPersonalAccessTokens] = useState([]); const createTokenDialog = useDialog(); - const [deleteTarget, setDeleteTarget] = useState(undefined); + const [deleteTarget, setDeleteTarget] = useState(undefined); useEffect(() => { - listAccessTokens(currentUser.name).then((accessTokens) => { - setUserAccessTokens(accessTokens); + listAccessTokens(currentUser.name).then((tokens) => { + setPersonalAccessTokens(tokens); }); }, []); - const handleCreateAccessTokenDialogConfirm = async (created: UserAccessToken) => { - const accessTokens = await listAccessTokens(currentUser.name); - setUserAccessTokens(accessTokens); - toast.success(t("setting.access-token-section.create-dialog.access-token-created", { description: created.description })); + const handleCreateAccessTokenDialogConfirm = async (response: CreatePersonalAccessTokenResponse) => { + const tokens = await listAccessTokens(currentUser.name); + setPersonalAccessTokens(tokens); + // Copy the token to clipboard - this is the only time it will be shown + if (response.token) { + copy(response.token); + toast.success(t("setting.access-token-section.access-token-copied-to-clipboard")); + } + toast.success( + t("setting.access-token-section.create-dialog.access-token-created", { + description: response.personalAccessToken?.description ?? "", + }), + ); }; const handleCreateToken = () => { createTokenDialog.open(); }; - const copyAccessToken = (accessToken: string) => { - copy(accessToken); - toast.success(t("setting.access-token-section.access-token-copied-to-clipboard")); - }; - - const handleDeleteAccessToken = async (userAccessToken: UserAccessToken) => { - setDeleteTarget(userAccessToken); + const handleDeleteAccessToken = async (token: PersonalAccessToken) => { + setDeleteTarget(token); }; const confirmDeleteAccessToken = async () => { if (!deleteTarget) return; const { name: tokenName, description } = deleteTarget; - await userServiceClient.deleteUserAccessToken({ name: tokenName }); - // Filter by stable resource name to avoid ambiguity with duplicate token strings - setUserAccessTokens((prev) => prev.filter((token) => token.name !== tokenName)); + await userServiceClient.deletePersonalAccessToken({ name: tokenName }); + setPersonalAccessTokens((prev) => prev.filter((token) => token.name !== tokenName)); setDeleteTarget(undefined); toast.success(t("setting.access-token-section.access-token-deleted", { description })); }; - const getFormatedAccessToken = (accessToken: string) => { - return `${accessToken.slice(0, 4)}****${accessToken.slice(-4)}`; - }; - return (
@@ -83,32 +82,20 @@ const AccessTokenSection = () => { ( -
- {getFormatedAccessToken(token.accessToken)} - -
- ), - }, { key: "description", header: t("common.description"), - render: (_, token: UserAccessToken) => {token.description}, + render: (_, token: PersonalAccessToken) => {token.description}, }, { - key: "issuedAt", + key: "createdAt", header: t("setting.access-token-section.create-dialog.created-at"), - render: (_, token: UserAccessToken) => (token.issuedAt ? timestampDate(token.issuedAt) : undefined)?.toLocaleString(), + render: (_, token: PersonalAccessToken) => (token.createdAt ? timestampDate(token.createdAt) : undefined)?.toLocaleString(), }, { key: "expiresAt", header: t("setting.access-token-section.create-dialog.expires-at"), - render: (_, token: UserAccessToken) => + render: (_, token: PersonalAccessToken) => (token.expiresAt ? timestampDate(token.expiresAt) : undefined)?.toLocaleString() ?? t("setting.access-token-section.create-dialog.duration-never"), }, @@ -116,14 +103,14 @@ const AccessTokenSection = () => { key: "actions", header: "", className: "text-right", - render: (_, token: UserAccessToken) => ( + render: (_, token: PersonalAccessToken) => ( ), }, ]} - data={userAccessTokens} + data={personalAccessTokens} emptyMessage="No access tokens found" getRowKey={(token) => token.name} /> diff --git a/web/src/components/Settings/UserSessionsSection.tsx b/web/src/components/Settings/UserSessionsSection.tsx index fb698c787..66bbff53f 100644 --- a/web/src/components/Settings/UserSessionsSection.tsx +++ b/web/src/components/Settings/UserSessionsSection.tsx @@ -6,12 +6,12 @@ import ConfirmDialog from "@/components/ConfirmDialog"; import { Button } from "@/components/ui/button"; import { userServiceClient } from "@/connect"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { UserSession } from "@/types/proto/api/v1/user_service_pb"; +import { Session } from "@/types/proto/api/v1/user_service_pb"; import { useTranslate } from "@/utils/i18n"; import SettingTable from "./SettingTable"; -const listUserSessions = async (parent: string) => { - const { sessions } = await userServiceClient.listUserSessions({ parent }); +const listSessions = async (parent: string) => { + const { sessions } = await userServiceClient.listSessions({ parent }); return sessions.sort( (a, b) => ((b.lastAccessedTime ? timestampDate(b.lastAccessedTime) : undefined)?.getTime() ?? 0) - @@ -22,23 +22,23 @@ const listUserSessions = async (parent: string) => { const UserSessionsSection = () => { const t = useTranslate(); const currentUser = useCurrentUser(); - const [userSessions, setUserSessions] = useState([]); - const [revokeTarget, setRevokeTarget] = useState(undefined); + const [sessions, setSessions] = useState([]); + const [revokeTarget, setRevokeTarget] = useState(undefined); useEffect(() => { - listUserSessions(currentUser.name).then((sessions) => { - setUserSessions(sessions); + listSessions(currentUser.name).then((sessions) => { + setSessions(sessions); }); }, []); - const handleRevokeSession = async (userSession: UserSession) => { - setRevokeTarget(userSession); + const handleRevokeSession = async (session: Session) => { + setRevokeTarget(session); }; const confirmRevokeSession = async () => { if (!revokeTarget) return; - await userServiceClient.revokeUserSession({ name: revokeTarget.name }); - setUserSessions(userSessions.filter((session) => session.sessionId !== revokeTarget.sessionId)); + await userServiceClient.revokeSession({ name: revokeTarget.name }); + setSessions(sessions.filter((session) => session.sessionId !== revokeTarget.sessionId)); toast.success(t("setting.user-sessions-section.session-revoked")); setRevokeTarget(undefined); }; @@ -59,7 +59,7 @@ const UserSessionsSection = () => { } }; - const formatDeviceInfo = (clientInfo: UserSession["clientInfo"]) => { + const formatDeviceInfo = (clientInfo: Session["clientInfo"]) => { if (!clientInfo) return "Unknown Device"; const parts = []; @@ -69,10 +69,10 @@ const UserSessionsSection = () => { return parts.length > 0 ? parts.join(" • ") : "Unknown Device"; }; - const isCurrentSession = (session: UserSession) => { + const isCurrentSession = (session: Session) => { // A simple heuristic: the most recently accessed session is likely the current one - if (userSessions.length === 0) return false; - const mostRecent = userSessions[0]; + if (sessions.length === 0) return false; + const mostRecent = sessions[0]; return session.sessionId === mostRecent.sessionId; }; @@ -88,7 +88,7 @@ const UserSessionsSection = () => { { key: "device", header: t("setting.user-sessions-section.device"), - render: (_, session: UserSession) => ( + render: (_, session: Session) => (
{getDeviceIcon(session.clientInfo?.deviceType || "")}
@@ -109,7 +109,7 @@ const UserSessionsSection = () => { { key: "lastAccessedTime", header: t("setting.user-sessions-section.last-active"), - render: (_, session: UserSession) => ( + render: (_, session: Session) => (
{(session.lastAccessedTime ? timestampDate(session.lastAccessedTime) : undefined)?.toLocaleString()} @@ -120,7 +120,7 @@ const UserSessionsSection = () => { key: "actions", header: "", className: "text-right", - render: (_, session: UserSession) => ( + render: (_, session: Session) => (