diff --git a/proto/api/v1/auth_service.proto b/proto/api/v1/auth_service.proto index 64df3ccb4..b956d35bc 100644 --- a/proto/api/v1/auth_service.proto +++ b/proto/api/v1/auth_service.proto @@ -30,15 +30,6 @@ service AuthService { rpc DeleteSession(DeleteSessionRequest) returns (google.protobuf.Empty) { option (google.api.http) = {delete: "/api/v1/auth/sessions/current"}; } - - // SignUp creates a new user account with username and password. - // Returns the newly created user information upon successful registration. - rpc SignUp(SignUpRequest) returns (User) { - option (google.api.http) = { - post: "/api/v1/auth/signup" - body: "*" - }; - } } message GetCurrentSessionRequest {} @@ -88,13 +79,3 @@ message SSOCredentials { } message DeleteSessionRequest {} - -message SignUpRequest { - // The username to sign up with. - // Required field that must be unique across the system. - string username = 1 [(google.api.field_behavior) = REQUIRED]; - - // The password to sign up with. - // Required field that should meet security requirements. - string password = 2 [(google.api.field_behavior) = REQUIRED]; -} diff --git a/proto/gen/api/v1/auth_service.pb.go b/proto/gen/api/v1/auth_service.pb.go index f6b897c2b..9dd4a2b30 100644 --- a/proto/gen/api/v1/auth_service.pb.go +++ b/proto/gen/api/v1/auth_service.pb.go @@ -358,62 +358,6 @@ func (*DeleteSessionRequest) Descriptor() ([]byte, []int) { return file_api_v1_auth_service_proto_rawDescGZIP(), []int{5} } -type SignUpRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The username to sign up with. - // Required field that must be unique across the system. - Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` - // The password to sign up with. - // Required field that should meet security requirements. - Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SignUpRequest) Reset() { - *x = SignUpRequest{} - mi := &file_api_v1_auth_service_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SignUpRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SignUpRequest) ProtoMessage() {} - -func (x *SignUpRequest) 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 SignUpRequest.ProtoReflect.Descriptor instead. -func (*SignUpRequest) Descriptor() ([]byte, []int) { - return file_api_v1_auth_service_proto_rawDescGZIP(), []int{6} -} - -func (x *SignUpRequest) GetUsername() string { - if x != nil { - return x.Username - } - return "" -} - -func (x *SignUpRequest) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - var File_api_v1_auth_service_proto protoreflect.FileDescriptor const file_api_v1_auth_service_proto_rawDesc = "" + @@ -434,15 +378,11 @@ const file_api_v1_auth_service_proto_rawDesc = "" + "\x06idp_id\x18\x01 \x01(\x05B\x03\xe0A\x02R\x05idpId\x12\x17\n" + "\x04code\x18\x02 \x01(\tB\x03\xe0A\x02R\x04code\x12&\n" + "\fredirect_uri\x18\x03 \x01(\tB\x03\xe0A\x02R\vredirectUri\"\x16\n" + - "\x14DeleteSessionRequest\"Q\n" + - "\rSignUpRequest\x12\x1f\n" + - "\busername\x18\x01 \x01(\tB\x03\xe0A\x02R\busername\x12\x1f\n" + - "\bpassword\x18\x02 \x01(\tB\x03\xe0A\x02R\bpassword2\xbf\x03\n" + + "\x14DeleteSessionRequest2\xe4\x02\n" + "\vAuthService\x12v\n" + "\x11GetCurrentSession\x12&.memos.api.v1.GetCurrentSessionRequest\x1a\x12.memos.api.v1.User\"%\x82\xd3\xe4\x93\x02\x1f\x12\x1d/api/v1/auth/sessions/current\x12i\n" + "\rCreateSession\x12\".memos.api.v1.CreateSessionRequest\x1a\x12.memos.api.v1.User\" \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/current\x12Y\n" + - "\x06SignUp\x12\x1b.memos.api.v1.SignUpRequest\x1a\x12.memos.api.v1.User\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/api/v1/auth/signupB\xa8\x01\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" + "\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 ( @@ -457,7 +397,7 @@ 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, 6) var file_api_v1_auth_service_proto_goTypes = []any{ (*GetCurrentSessionRequest)(nil), // 0: memos.api.v1.GetCurrentSessionRequest (*GetCurrentSessionResponse)(nil), // 1: memos.api.v1.GetCurrentSessionResponse @@ -465,24 +405,21 @@ var file_api_v1_auth_service_proto_goTypes = []any{ (*PasswordCredentials)(nil), // 3: memos.api.v1.PasswordCredentials (*SSOCredentials)(nil), // 4: memos.api.v1.SSOCredentials (*DeleteSessionRequest)(nil), // 5: memos.api.v1.DeleteSessionRequest - (*SignUpRequest)(nil), // 6: memos.api.v1.SignUpRequest - (*User)(nil), // 7: memos.api.v1.User - (*emptypb.Empty)(nil), // 8: google.protobuf.Empty + (*User)(nil), // 6: memos.api.v1.User + (*emptypb.Empty)(nil), // 7: 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 + 6, // 0: memos.api.v1.GetCurrentSessionResponse.user:type_name -> memos.api.v1.User 3, // 1: memos.api.v1.CreateSessionRequest.password_credentials:type_name -> memos.api.v1.PasswordCredentials 4, // 2: memos.api.v1.CreateSessionRequest.sso_credentials:type_name -> memos.api.v1.SSOCredentials 0, // 3: memos.api.v1.AuthService.GetCurrentSession:input_type -> memos.api.v1.GetCurrentSessionRequest 2, // 4: memos.api.v1.AuthService.CreateSession:input_type -> memos.api.v1.CreateSessionRequest 5, // 5: memos.api.v1.AuthService.DeleteSession:input_type -> memos.api.v1.DeleteSessionRequest - 6, // 6: memos.api.v1.AuthService.SignUp:input_type -> memos.api.v1.SignUpRequest - 7, // 7: memos.api.v1.AuthService.GetCurrentSession:output_type -> memos.api.v1.User - 7, // 8: memos.api.v1.AuthService.CreateSession:output_type -> memos.api.v1.User - 8, // 9: memos.api.v1.AuthService.DeleteSession:output_type -> google.protobuf.Empty - 7, // 10: memos.api.v1.AuthService.SignUp:output_type -> memos.api.v1.User - 7, // [7:11] is the sub-list for method output_type - 3, // [3:7] is the sub-list for method input_type + 6, // 6: memos.api.v1.AuthService.GetCurrentSession:output_type -> memos.api.v1.User + 6, // 7: memos.api.v1.AuthService.CreateSession:output_type -> memos.api.v1.User + 7, // 8: memos.api.v1.AuthService.DeleteSession:output_type -> google.protobuf.Empty + 6, // [6:9] is the sub-list for method output_type + 3, // [3:6] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -504,7 +441,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: 6, 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 e667ff9f5..1b603fa33 100644 --- a/proto/gen/api/v1/auth_service.pb.gw.go +++ b/proto/gen/api/v1/auth_service.pb.gw.go @@ -104,33 +104,6 @@ func local_request_AuthService_DeleteSession_0(ctx context.Context, marshaler ru return msg, metadata, err } -func request_AuthService_SignUp_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var ( - protoReq SignUpRequest - 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.SignUp(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err -} - -func local_request_AuthService_SignUp_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var ( - protoReq SignUpRequest - 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.SignUp(ctx, &protoReq) - return msg, metadata, err -} - // RegisterAuthServiceHandlerServer registers the http handlers for service AuthService to "mux". // UnaryRPC :call AuthServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -197,26 +170,6 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux } forward_AuthService_DeleteSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_AuthService_SignUp_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/SignUp", runtime.WithHTTPPathPattern("/api/v1/auth/signup")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_AuthService_SignUp_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_SignUp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) return nil } @@ -308,23 +261,6 @@ func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux } forward_AuthService_DeleteSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodPost, pattern_AuthService_SignUp_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/SignUp", runtime.WithHTTPPathPattern("/api/v1/auth/signup")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_AuthService_SignUp_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_SignUp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) return nil } @@ -332,12 +268,10 @@ 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_SignUp_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "signup"}, "")) ) var ( forward_AuthService_GetCurrentSession_0 = runtime.ForwardResponseMessage forward_AuthService_CreateSession_0 = runtime.ForwardResponseMessage forward_AuthService_DeleteSession_0 = runtime.ForwardResponseMessage - forward_AuthService_SignUp_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 a56b43901..a91b0fda5 100644 --- a/proto/gen/api/v1/auth_service_grpc.pb.go +++ b/proto/gen/api/v1/auth_service_grpc.pb.go @@ -23,7 +23,6 @@ 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_SignUp_FullMethodName = "/memos.api.v1.AuthService/SignUp" ) // AuthServiceClient is the client API for AuthService service. @@ -39,9 +38,6 @@ type AuthServiceClient interface { // 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) - // SignUp creates a new user account with username and password. - // Returns the newly created user information upon successful registration. - SignUp(ctx context.Context, in *SignUpRequest, opts ...grpc.CallOption) (*User, error) } type authServiceClient struct { @@ -82,16 +78,6 @@ func (c *authServiceClient) DeleteSession(ctx context.Context, in *DeleteSession return out, nil } -func (c *authServiceClient) SignUp(ctx context.Context, in *SignUpRequest, opts ...grpc.CallOption) (*User, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(User) - err := c.cc.Invoke(ctx, AuthService_SignUp_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - // AuthServiceServer is the server API for AuthService service. // All implementations must embed UnimplementedAuthServiceServer // for forward compatibility. @@ -105,9 +91,6 @@ type AuthServiceServer interface { // DeleteSession terminates the current user session. // This is an idempotent operation that invalidates the user's authentication. DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) - // SignUp creates a new user account with username and password. - // Returns the newly created user information upon successful registration. - SignUp(context.Context, *SignUpRequest) (*User, error) mustEmbedUnimplementedAuthServiceServer() } @@ -127,9 +110,6 @@ func (UnimplementedAuthServiceServer) CreateSession(context.Context, *CreateSess func (UnimplementedAuthServiceServer) DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteSession not implemented") } -func (UnimplementedAuthServiceServer) SignUp(context.Context, *SignUpRequest) (*User, error) { - return nil, status.Errorf(codes.Unimplemented, "method SignUp not implemented") -} func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} func (UnimplementedAuthServiceServer) testEmbeddedByValue() {} @@ -205,24 +185,6 @@ func _AuthService_DeleteSession_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } -func _AuthService_SignUp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SignUpRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServiceServer).SignUp(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthService_SignUp_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).SignUp(ctx, req.(*SignUpRequest)) - } - return interceptor(ctx, in, info, handler) -} - // AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -242,10 +204,6 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteSession", Handler: _AuthService_DeleteSession_Handler, }, - { - MethodName: "SignUp", - Handler: _AuthService_SignUp_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/v1/auth_service.proto", diff --git a/proto/gen/apidocs.swagger.yaml b/proto/gen/apidocs.swagger.yaml index 9c90346c1..f3d421b75 100644 --- a/proto/gen/apidocs.swagger.yaml +++ b/proto/gen/apidocs.swagger.yaml @@ -188,29 +188,6 @@ paths: $ref: '#/definitions/googlerpcStatus' tags: - AuthService - /api/v1/auth/signup: - post: - summary: |- - SignUp creates a new user account with username and password. - Returns the newly created user information upon successful registration. - operationId: AuthService_SignUp - responses: - "200": - description: A successful response. - schema: - $ref: '#/definitions/v1User' - default: - description: An unexpected error response. - schema: - $ref: '#/definitions/googlerpcStatus' - parameters: - - name: body - in: body - required: true - schema: - $ref: '#/definitions/v1SignUpRequest' - tags: - - AuthService /api/v1/identityProviders: get: summary: ListIdentityProviders lists identity providers. @@ -4097,22 +4074,6 @@ definitions: type: integer format: int32 description: The total count of matching users. - v1SignUpRequest: - type: object - properties: - username: - type: string - description: |- - The username to sign up with. - Required field that must be unique across the system. - password: - type: string - description: |- - The password to sign up with. - Required field that should meet security requirements. - required: - - username - - password v1SpoilerNode: type: object properties: diff --git a/server/router/api/v1/acl_config.go b/server/router/api/v1/acl_config.go index d14d6b305..25ad4ae31 100644 --- a/server/router/api/v1/acl_config.go +++ b/server/router/api/v1/acl_config.go @@ -7,7 +7,7 @@ var authenticationAllowlistMethods = map[string]bool{ "/memos.api.v1.IdentityProviderService/ListIdentityProviders": true, "/memos.api.v1.AuthService/CreateSession": true, "/memos.api.v1.AuthService/GetCurrentSession": true, - "/memos.api.v1.AuthService/SignUp": true, + "/memos.api.v1.UserService/CreateUser": true, "/memos.api.v1.UserService/GetUser": true, "/memos.api.v1.UserService/GetUserAvatar": true, "/memos.api.v1.UserService/GetUserStats": true, diff --git a/server/router/api/v1/auth_service.go b/server/router/api/v1/auth_service.go index 259cc76e8..d497d1208 100644 --- a/server/router/api/v1/auth_service.go +++ b/server/router/api/v1/auth_service.go @@ -17,7 +17,6 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/usememos/memos/internal/base" "github.com/usememos/memos/internal/util" "github.com/usememos/memos/plugin/idp" "github.com/usememos/memos/plugin/idp/oauth2" @@ -206,54 +205,6 @@ func (s *APIV1Service) doSignIn(ctx context.Context, user *store.User, expireTim return nil } -func (s *APIV1Service) SignUp(ctx context.Context, request *v1pb.SignUpRequest) (*v1pb.User, error) { - workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get workspace general setting, error: %v", err) - } - if workspaceGeneralSetting.DisallowUserRegistration { - return nil, status.Errorf(codes.PermissionDenied, "sign up is not allowed") - } - - passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.Password), bcrypt.DefaultCost) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to generate password hash, error: %v", err) - } - - create := &store.User{ - Username: request.Username, - Nickname: request.Username, - PasswordHash: string(passwordHash), - } - if !base.UIDMatcher.MatchString(strings.ToLower(create.Username)) { - return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", create.Username) - } - - hostUserType := store.RoleHost - existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUser{ - Role: &hostUserType, - }) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to list host users, error: %v", err) - } - if len(existedHostUsers) == 0 { - // Change the default role to host if there is no host user. - create.Role = store.RoleHost - } else { - create.Role = store.RoleUser - } - - user, err := s.Store.CreateUser(ctx, create) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to create user, error: %v", err) - } - - if err := s.doSignIn(ctx, user, time.Now().Add(AccessTokenDuration)); err != nil { - return nil, status.Errorf(codes.Internal, "failed to sign in, error: %v", err) - } - return convertUserFromStore(user), nil -} - func (s *APIV1Service) DeleteSession(ctx context.Context, _ *v1pb.DeleteSessionRequest) (*emptypb.Empty, error) { user, err := s.GetCurrentUser(ctx) if err != nil { diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index 8e2c489e8..7ad61ebf0 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -144,15 +144,36 @@ func (s *APIV1Service) GetUserAvatar(ctx context.Context, request *v1pb.GetUserA } func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) { - currentUser, err := s.GetCurrentUser(ctx) + // Check if there are any existing host users (for first-time setup detection) + hostUserType := store.RoleHost + existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUser{ + Role: &hostUserType, + }) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) - } - if currentUser.Role != store.RoleHost { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") + return nil, status.Errorf(codes.Internal, "failed to list host users: %v", err) + } + + // Determine the role to assign and check permissions + var roleToAssign store.Role + if len(existedHostUsers) == 0 { + // First-time setup: create the first user as HOST (no authentication required) + roleToAssign = store.RoleHost + } else { + // Regular user creation: allow unauthenticated creation of normal users + // But if authenticated, check if user has HOST permission for any role + currentUser, err := s.GetCurrentUser(ctx) + if err == nil && currentUser != nil && currentUser.Role == store.RoleHost { + // Authenticated HOST user can create users with any role specified in request + if request.User.Role != v1pb.User_ROLE_UNSPECIFIED { + roleToAssign = convertUserRoleToStore(request.User.Role) + } else { + roleToAssign = store.RoleUser + } + } else { + // Unauthenticated or non-HOST users can only create normal users + roleToAssign = store.RoleUser + } } - // TODO: Handle request_id for idempotency - // TODO: Handle user_id field if provided if !base.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) { return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username) @@ -165,7 +186,7 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR Username: request.User.Username, Email: request.User.Email, DisplayName: request.User.DisplayName, - Role: request.User.Role, + Role: convertUserRoleFromStore(roleToAssign), }, nil } @@ -176,7 +197,7 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR user, err := s.Store.CreateUser(ctx, &store.User{ Username: request.User.Username, - Role: convertUserRoleToStore(request.User.Role), + Role: roleToAssign, Email: request.User.Email, Nickname: request.User.DisplayName, PasswordHash: string(passwordHash), diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx index 7dc6af4f4..a00ae3a1f 100644 --- a/web/src/pages/SignUp.tsx +++ b/web/src/pages/SignUp.tsx @@ -6,11 +6,12 @@ import { useState } from "react"; import { toast } from "react-hot-toast"; import { Link } from "react-router-dom"; import AuthFooter from "@/components/AuthFooter"; -import { authServiceClient } from "@/grpcweb"; +import { authServiceClient, userServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; import { workspaceStore } from "@/store/v2"; import { initialUserStore } from "@/store/v2/user"; +import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; const SignUp = observer(() => { @@ -47,7 +48,15 @@ const SignUp = observer(() => { try { actionBtnLoadingState.setLoading(); - await authServiceClient.signUp({ username, password }); + const user = User.fromPartial({ + username, + password, + role: User_Role.USER, + }); + await userServiceClient.createUser({ user }); + await authServiceClient.createSession({ + passwordCredentials: { username, password }, + }); await initialUserStore(); navigateTo("/"); } catch (error: any) { diff --git a/web/src/types/proto/api/v1/auth_service.ts b/web/src/types/proto/api/v1/auth_service.ts index 06978e593..1650ceb5c 100644 --- a/web/src/types/proto/api/v1/auth_service.ts +++ b/web/src/types/proto/api/v1/auth_service.ts @@ -68,19 +68,6 @@ export interface SSOCredentials { export interface DeleteSessionRequest { } -export interface SignUpRequest { - /** - * The username to sign up with. - * Required field that must be unique across the system. - */ - username: string; - /** - * The password to sign up with. - * Required field that should meet security requirements. - */ - password: string; -} - function createBaseGetCurrentSessionRequest(): GetCurrentSessionRequest { return {}; } @@ -397,64 +384,6 @@ export const DeleteSessionRequest: MessageFns = { }, }; -function createBaseSignUpRequest(): SignUpRequest { - return { username: "", password: "" }; -} - -export const SignUpRequest: MessageFns = { - encode(message: SignUpRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.username !== "") { - writer.uint32(10).string(message.username); - } - if (message.password !== "") { - writer.uint32(18).string(message.password); - } - return writer; - }, - - decode(input: BinaryReader | Uint8Array, length?: number): SignUpRequest { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseSignUpRequest(); - while (reader.pos < end) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 1: { - if (tag !== 10) { - break; - } - - message.username = reader.string(); - continue; - } - case 2: { - if (tag !== 18) { - break; - } - - message.password = reader.string(); - continue; - } - } - if ((tag & 7) === 4 || tag === 0) { - break; - } - reader.skip(tag & 7); - } - return message; - }, - - create(base?: DeepPartial): SignUpRequest { - return SignUpRequest.fromPartial(base ?? {}); - }, - fromPartial(object: DeepPartial): SignUpRequest { - const message = createBaseSignUpRequest(); - message.username = object.username ?? ""; - message.password = object.password ?? ""; - return message; - }, -}; - export type AuthServiceDefinition = typeof AuthServiceDefinition; export const AuthServiceDefinition = { name: "AuthService", @@ -608,50 +537,6 @@ export const AuthServiceDefinition = { }, }, }, - /** - * SignUp creates a new user account with username and password. - * Returns the newly created user information upon successful registration. - */ - signUp: { - name: "SignUp", - requestType: SignUpRequest, - requestStream: false, - responseType: User, - responseStream: false, - options: { - _unknownFields: { - 578365826: [ - new Uint8Array([ - 24, - 58, - 1, - 42, - 34, - 19, - 47, - 97, - 112, - 105, - 47, - 118, - 49, - 47, - 97, - 117, - 116, - 104, - 47, - 115, - 105, - 103, - 110, - 117, - 112, - ]), - ], - }, - }, - }, }, } as const;