Changes
2 changed files (+109/-93)
-
-
@@ -0,0 +1,109 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package workspace import ( "bytes" "context" "connectrpc.com/connect" "google.golang.org/protobuf/proto" "pocka.jp/x/yamori/backend/core/event" "pocka.jp/x/yamori/backend/core/projection" "pocka.jp/x/yamori/backend/crypto" errorV1 "pocka.jp/x/yamori/proto/go/error/v1" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2" ) func (s *Service) Login( ctx context.Context, req *connect.Request[workspaceV2.LoginRequest], ) (*connect.Response[workspaceV2.LoginResponse], error) { name := req.Msg.GetName() if name == "" { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_MissingFieldError{ MissingFieldError: &errorV1.MissingFieldError{ Path: proto.String("name"), }, }, }), nil } password := req.Msg.GetPassword() if password == "" { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_MissingFieldError{ MissingFieldError: &errorV1.MissingFieldError{ Path: proto.String("password"), }, }, }), nil } tx, err := s.core.DB.Begin() if err != nil { return nil, err } defer tx.Rollback() users, err := projection.GetUsers(tx) if err != nil { return nil, err } if err := event.UpdateProjections(tx, users); err != nil { return nil, err } for _, u := range users.Projection.Users { if u.GetName() != name { continue } if u.PasswordLogin == nil { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } hash := crypto.HashPassword([]byte(password), u.PasswordLogin.Salt) if !bytes.Equal(hash, u.PasswordLogin.Hash) { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } // TODO: JWT をSame-Origin Cookie に焼き付ける res := connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_Ok{ Ok: &workspaceV2.User{ Id: &workspaceV2.UserID{ Value: u.Id, }, Name: u.Name, DisplayName: u.DisplayName, LoginMethod: &workspaceV2.LoginMethod{ PasswordConfigured: proto.Bool(true), }, IsAdmin: u.IsAdmin, }, }, }) return res, nil } return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil }
-
-
-
@@ -4,7 +4,6 @@package workspace import ( "bytes" "context" "net/http" "strings"
-
@@ -13,9 +12,6 @@ "connectrpc.com/connect""google.golang.org/protobuf/proto" "pocka.jp/x/yamori/backend/core" "pocka.jp/x/yamori/backend/core/event" "pocka.jp/x/yamori/backend/core/projection" "pocka.jp/x/yamori/backend/crypto" errorV1 "pocka.jp/x/yamori/proto/go/error/v1" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2" workspaceV2connect "pocka.jp/x/yamori/proto/go/workspace/v2/v2connect"
-
@@ -27,95 +23,6 @@ }func New(core *core.Core) *Service { return &Service{core: core} } func (s *Service) Login( ctx context.Context, req *connect.Request[workspaceV2.LoginRequest], ) (*connect.Response[workspaceV2.LoginResponse], error) { name := req.Msg.GetName() if name == "" { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_MissingFieldError{ MissingFieldError: &errorV1.MissingFieldError{ Path: proto.String("name"), }, }, }), nil } password := req.Msg.GetPassword() if password == "" { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_MissingFieldError{ MissingFieldError: &errorV1.MissingFieldError{ Path: proto.String("password"), }, }, }), nil } tx, err := s.core.DB.Begin() if err != nil { return nil, err } defer tx.Rollback() users, err := projection.GetUsers(tx) if err != nil { return nil, err } if err := event.UpdateProjections(tx, users); err != nil { return nil, err } for _, u := range users.Projection.Users { if u.GetName() != name { continue } if u.PasswordLogin == nil { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } hash := crypto.HashPassword([]byte(password), u.PasswordLogin.Salt) if !bytes.Equal(hash, u.PasswordLogin.Hash) { return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } // TODO: JWT をSame-Origin Cookie に焼き付ける return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_Ok{ Ok: &workspaceV2.User{ Id: &workspaceV2.UserID{ Value: u.Id, }, Name: u.Name, DisplayName: u.DisplayName, LoginMethod: &workspaceV2.LoginMethod{ PasswordConfigured: proto.Bool(true), }, IsAdmin: u.IsAdmin, }, }, }), nil } return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } func (s *Service) Logout(
-