diff --git a/plugin/idp/oauth2/oauth2.go b/plugin/idp/oauth2/oauth2.go index 4ab1d12bc..f2282cd3c 100644 --- a/plugin/idp/oauth2/oauth2.go +++ b/plugin/idp/oauth2/oauth2.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" "io" - "log/slog" "net/http" + "time" "github.com/pkg/errors" "golang.org/x/oauth2" @@ -79,7 +79,7 @@ func (p *IdentityProvider) ExchangeToken(ctx context.Context, redirectURL, code, // UserInfo returns the parsed user information using the given OAuth2 token. func (p *IdentityProvider) UserInfo(token string) (*idp.IdentityProviderUserInfo, error) { - client := &http.Client{} + client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, p.config.UserInfoUrl, nil) if err != nil { return nil, errors.Wrap(err, "failed to create http request") @@ -101,7 +101,6 @@ func (p *IdentityProvider) UserInfo(token string) (*idp.IdentityProviderUserInfo if err := json.Unmarshal(body, &claims); err != nil { return nil, errors.Wrap(err, "failed to unmarshal response body") } - slog.Info("user info claims", "claims", claims) userInfo := &idp.IdentityProviderUserInfo{} if v, ok := claims[p.config.FieldMapping.Identifier].(string); ok { userInfo.Identifier = v @@ -129,6 +128,5 @@ func (p *IdentityProvider) UserInfo(token string) (*idp.IdentityProviderUserInfo userInfo.AvatarURL = v } } - slog.Info("user info", "userInfo", userInfo) return userInfo, nil } diff --git a/server/router/api/v1/auth_service.go b/server/router/api/v1/auth_service.go index 87cc55a63..bc45b82f5 100644 --- a/server/router/api/v1/auth_service.go +++ b/server/router/api/v1/auth_service.go @@ -83,8 +83,8 @@ func (s *APIV1Service) SignIn(ctx context.Context, request *v1pb.SignInRequest) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get instance general setting, error: %v", err) } - // Check if the password auth in is allowed. - if instanceGeneralSetting.DisallowPasswordAuth && user.Role == store.RoleUser { + // Check if password auth is allowed. Enforce for all roles including admins. + if instanceGeneralSetting.DisallowPasswordAuth { return nil, status.Errorf(codes.PermissionDenied, "password signin is not allowed") } existingUser = user diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index e718a8b15..8a40cd3fc 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -160,6 +160,9 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR }, nil } + if len(request.User.Password) < 8 { + return nil, status.Errorf(codes.InvalidArgument, "password must be at least 8 characters") + } passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost) if err != nil { return nil, status.Errorf(codes.Internal, "failed to generate password hash: %v", err) @@ -269,6 +272,9 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR role := convertUserRoleToStore(request.User.Role) update.Role = &role case "password": + if len(request.User.Password) < 8 { + return nil, status.Errorf(codes.InvalidArgument, "password must be at least 8 characters") + } passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost) if err != nil { return nil, status.Errorf(codes.Internal, "failed to generate password hash: %v", err) diff --git a/server/router/api/v1/v1.go b/server/router/api/v1/v1.go index 79c140f07..74645d54c 100644 --- a/server/router/api/v1/v1.go +++ b/server/router/api/v1/v1.go @@ -115,7 +115,17 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech } gwGroup := echoServer.Group("") gwGroup.Use(middleware.CORSWithConfig(middleware.CORSConfig{ - AllowOrigins: []string{"*"}, + UnsafeAllowOriginFunc: func(_ *echo.Context, origin string) (string, bool, error) { + // In demo mode, allow all origins for development convenience. + // In production, deny cross-origin requests (same-origin only). + if s.Profile.Demo { + return origin, true, nil + } + return "", false, nil + }, + AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodOptions}, + AllowHeaders: []string{"Content-Type", "Authorization"}, + AllowCredentials: true, })) // Register SSE endpoint with same CORS as rest of /api/v1. gwGroup.GET("/api/v1/sse", func(c *echo.Context) error { @@ -141,7 +151,11 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech // Wrap with CORS for browser access corsHandler := middleware.CORSWithConfig(middleware.CORSConfig{ UnsafeAllowOriginFunc: func(_ *echo.Context, origin string) (string, bool, error) { - return origin, true, nil + // In demo mode, allow all origins for development convenience. + if s.Profile.Demo { + return origin, true, nil + } + return "", false, nil }, AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodOptions}, AllowHeaders: []string{"*"}, diff --git a/server/server.go b/server/server.go index dd3247def..1b6ab3ba1 100644 --- a/server/server.go +++ b/server/server.go @@ -50,11 +50,9 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store return nil, errors.Wrap(err, "failed to get instance basic setting") } - secret := "usememos" - if !profile.Demo { - secret = instanceBasicSetting.SecretKey - } - s.Secret = secret + // Always use the instance secret key, regardless of mode. + // Never fall back to a hardcoded secret, as it allows token forgery. + s.Secret = instanceBasicSetting.SecretKey // Register healthz endpoint. echoServer.GET("/healthz", func(c *echo.Context) error {