Changes
3 changed files (+153/-18)
-
-
@@ -0,0 +1,96 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package core import ( "fmt" "net/http" "github.com/golang-jwt/jwt/v5" workspace "pocka.jp/x/yamori/proto/go/backend/projections/workspace/v1" "pocka.jp/x/yamori/backend/core/projection" ) const cookieName = "yamori-login-token" type token string func (core *Core) LoadTokenFromCookie(header *http.Header) (*token, error) { for _, header := range header.Values("Cookie") { cookies, err := http.ParseCookie(header) if err != nil { return nil, err } for _, cookie := range cookies { if cookie.Name == cookieName { token := token(cookie.Value) return &token, nil } } } return nil, nil } func (core *Core) IssueToken(secret *projection.LoginJwtSecret, user *workspace.Users_User) (*token, error) { t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": user.GetId(), }) signed, err := t.SignedString(secret.Projection) if err != nil { return nil, err } token := token(signed) return &token, nil } func (t *token) SaveToCookie(header *http.Header) { cookie := http.Cookie{ Name: cookieName, Value: string(*t), SameSite: http.SameSiteStrictMode, Secure: true, HttpOnly: true, } header.Add("Set-Cookie", cookie.String()) } func (t *token) Validate(secret *projection.LoginJwtSecret) error { _, err := jwt.Parse(string(*t), func(token *jwt.Token) (interface{}, error) { return secret.Projection, nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) return err } func (t *token) FindUser( secret *projection.LoginJwtSecret, users *projection.Users, ) (*workspace.Users_User, error) { parsed, err := jwt.Parse(string(*t), func(token *jwt.Token) (interface{}, error) { return secret.Projection, nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) if err != nil { return nil, err } sub, err := parsed.Claims.GetSubject() if err != nil { return nil, err } for _, u := range users.Projection.Users { if u.GetId() == sub { return u, nil } } return nil, fmt.Errorf("No user found for sub=%s", sub) }
-
-
-
@@ -6,10 +6,8 @@import ( "bytes" "context" "net/http" "connectrpc.com/connect" "github.com/golang-jwt/jwt/v5" "google.golang.org/protobuf/proto" errorV1 "pocka.jp/x/yamori/proto/go/error/v1"
-
@@ -19,8 +17,6 @@ "pocka.jp/x/yamori/backend/core/event""pocka.jp/x/yamori/backend/core/projection" "pocka.jp/x/yamori/backend/crypto" ) const cookieName = "yamori-session-id" func (s *Service) Login( ctx context.Context,
-
@@ -106,24 +102,13 @@ },}, }) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": u.GetId(), }) jwt, err := token.SignedString(secret.Projection) token, err := s.core.IssueToken(secret, u) if err != nil { return nil, err } cookie := http.Cookie{ Name: cookieName, Value: jwt, SameSite: http.SameSiteStrictMode, Secure: true, HttpOnly: true, } res.Header().Add("Set-Cookie", cookie.String()) header := res.Header() token.SaveToCookie(&header) if err := tx.Commit(); err != nil { return nil, err
-
-
-
@@ -12,6 +12,8 @@ "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" 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"
-
@@ -73,6 +75,58 @@ func (s *Service) CreateUser(ctx context.Context, req *connect.Request[workspaceV2.CreateUserRequest], ) (*connect.Response[workspaceV2.CreateUserResponse], error) { logger := s.core.Logger.With( "service", "yamori.workspace.v2.WorkspaceService", "method", "CreateUser", ) 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 } secret, err := projection.GetLoginJwtSecret(tx) if err != nil { return nil, err } if err := event.UpdateProjections(tx, users, secret); err != nil { return nil, err } header := req.Header() token, err := s.core.LoadTokenFromCookie(&header) if token == nil { if err != nil { logger.Warn("Failed to load token from cookie", "error", err) } else { logger.Debug("Unauthorized request made") } return connect.NewResponse(&workspaceV2.CreateUserResponse{ Result: &workspaceV2.CreateUserResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } _, err = token.FindUser(secret, users) if err != nil { logger.Warn("Malformed token found", "error", err) return connect.NewResponse(&workspaceV2.CreateUserResponse{ Result: &workspaceV2.CreateUserResponse_AuthenticationError{ AuthenticationError: &errorV1.AuthenticationError{}, }, }), nil } name := req.Msg.GetName() if name == "" { return connect.NewResponse(&workspaceV2.CreateUserResponse{
-