VYPR
Unrated severityNVD Advisory· Published May 22, 2026· Updated May 22, 2026

Denial of service via crafted TIFF file upload

CVE-2026-5755

Description

Mattermost versions 11.6.x <= 11.6.0, 11.5.x <= 11.5.2, 11.5.x <= 11.5.3, 11.4.x <= 11.4.4, 10.11.x <= 10.11.14 fail to validate the TIFF IFD offset in the image header before allocating memory, which allows authenticated users with file upload or posting permissions to cause a denial of service (server OOM) via uploading a crafted TIFF file or posting a URL that serves one.. Mattermost Advisory ID: MMSA-2026-00648

Affected products

1
  • Range: >= 11.6.0 <= 11.6.0, >= 11.5.0 <= 11.5.3, >= 11.4.0 <= 11.4.4, >= 10.11.0 <= 10.11.14

Patches

13
f36531c47985

Update golang.org/x/image to v0.38.0 (#36148)

https://github.com/mattermost/mattermostAlejandro García MontoroApr 17, 2026Fixed in 10.11.15via llm-release-walk
2 files changed · +27 27
  • server/go.mod+9 9 modified
    @@ -69,12 +69,12 @@ require (
     	github.com/wiggin77/merror v1.0.5
     	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
     	github.com/yuin/goldmark v1.7.11
    -	golang.org/x/crypto v0.41.0
    -	golang.org/x/image v0.27.0
    -	golang.org/x/net v0.43.0
    -	golang.org/x/sync v0.17.0
    -	golang.org/x/term v0.34.0
    -	golang.org/x/text v0.29.0
    +	golang.org/x/crypto v0.48.0
    +	golang.org/x/image v0.38.0
    +	golang.org/x/net v0.50.0
    +	golang.org/x/sync v0.20.0
    +	golang.org/x/term v0.40.0
    +	golang.org/x/text v0.35.0
     	gopkg.in/mail.v2 v2.3.1
     	gopkg.in/yaml.v3 v3.0.1
     )
    @@ -222,9 +222,9 @@ require (
     	go.yaml.in/yaml/v3 v3.0.4 // indirect
     	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
     	golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
    -	golang.org/x/mod v0.27.0 // indirect
    -	golang.org/x/sys v0.35.0 // indirect
    -	golang.org/x/tools v0.36.0 // indirect
    +	golang.org/x/mod v0.33.0 // indirect
    +	golang.org/x/sys v0.41.0 // indirect
    +	golang.org/x/tools v0.42.0 // indirect
     	google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
     	google.golang.org/grpc v1.72.0 // indirect
     	google.golang.org/protobuf v1.36.6 // indirect
    
  • server/go.sum+18 18 modified
    @@ -748,8 +748,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
     golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
     golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
     golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
    -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
    -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
    +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
    +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
     golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
    @@ -762,8 +762,8 @@ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ
     golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
     golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
     golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
    -golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
    -golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
    +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
    +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
     golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
    @@ -786,8 +786,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
     golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
    -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
    -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
    +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
    +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
     golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
    @@ -822,8 +822,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
     golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
     golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
     golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
    -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
    -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
    +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
    +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
     golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
    @@ -846,8 +846,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
     golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
    -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
    -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
    +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
    +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
     golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    @@ -894,8 +894,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
    -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
    -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
    +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
    +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
     golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
     golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
     golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
    @@ -906,8 +906,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
     golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
     golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
     golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
    -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
    -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
    +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
    +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
     golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    @@ -920,8 +920,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
     golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
    -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
    -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
    +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
    +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
     golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
    @@ -958,8 +958,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
     golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
     golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
     golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
    -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
    -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
    +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
    +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
     golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    
d37c2d9d50b5

MM-68225: Bump x/image dependency in v11.6 (#36149)

https://github.com/mattermost/mattermostAlejandro García MontoroApr 17, 2026Fixed in 11.6.1via llm-release-walk
2 files changed · +27 31
  • server/go.mod+9 9 modified
    @@ -70,12 +70,13 @@ require (
     	github.com/wiggin77/merror v1.0.5
     	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
     	github.com/yuin/goldmark v1.7.13
    -	golang.org/x/crypto v0.45.0
    -	golang.org/x/image v0.32.0
    -	golang.org/x/net v0.47.0
    -	golang.org/x/sync v0.18.0
    -	golang.org/x/sys v0.38.0
    -	golang.org/x/term v0.37.0
    +	golang.org/x/crypto v0.48.0
    +	golang.org/x/image v0.38.0
    +	golang.org/x/net v0.50.0
    +	golang.org/x/sync v0.20.0
    +	golang.org/x/sys v0.41.0
    +	golang.org/x/term v0.40.0
    +	golang.org/x/text v0.35.0
     	gopkg.in/mail.v2 v2.3.1
     )
     
    @@ -206,9 +207,8 @@ require (
     	go.yaml.in/yaml/v3 v3.0.4 // indirect
     	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
     	golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
    -	golang.org/x/mod v0.29.0 // indirect
    -	golang.org/x/text v0.31.0 // indirect
    -	golang.org/x/tools v0.38.0 // indirect
    +	golang.org/x/mod v0.33.0 // indirect
    +	golang.org/x/tools v0.42.0 // indirect
     	google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
     	google.golang.org/grpc v1.76.0 // indirect
     	google.golang.org/protobuf v1.36.10 // indirect
    
  • server/go.sum+18 22 modified
    @@ -393,8 +393,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
     github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
     github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
     github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
    -github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 h1:QwWKgMY28TAXaDl+ExRDqGQltzXqN/xypdKP86niVn8=
    -github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4=
     github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5 h1:W7p+m/AECTL3s/YR5RpQ4hz5SjNeKzZBl1q36ws12s0=
     github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM=
     github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
    @@ -409,8 +407,6 @@ github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb
     github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=
     github.com/mattermost/logr/v2 v2.0.22 h1:npFkXlkAWR9J8payh8ftPcCZvLbHSI125mAM5/r/lP4=
     github.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s=
    -github.com/mattermost/mattermost-plugin-ai v1.8.1 h1:qymxDayy3vJPhm59XA8q0oLR8uobPgx0SOB7IMG9ZMM=
    -github.com/mattermost/mattermost-plugin-ai v1.8.1/go.mod h1:Uco4K7ypsrZWcD256ezvgZDqolJfHYiExN2lYiHmVDo=
     github.com/mattermost/mattermost-plugin-ai v1.12.0 h1:cwRE2jjlqN5W42O9Xp0ncyHk7HL/ayMeypuINPX0WJ0=
     github.com/mattermost/mattermost-plugin-ai v1.12.0/go.mod h1:L/I/IpdWNGbxRfUduCstCYbhyX59OEftxcpDHtCT4EI=
     github.com/mattermost/mattermost/server/public v0.1.22-0.20251105210629-8bf4a00724e2 h1:RJtCnj9nF/wb0Fb+O0qAPgUoWP5CTTDnHzHD5ciGlJ8=
    @@ -725,8 +721,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
     golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
     golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
     golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
    -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
    -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
    +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
    +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
     golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
    @@ -739,8 +735,8 @@ golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xC
     golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
     golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
     golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
    -golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
    -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
    +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
    +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
     golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
    @@ -763,8 +759,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
     golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
    -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
    -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
    +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
    +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
     golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
    @@ -799,8 +795,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
     golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
     golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
     golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
    -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
    -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
    +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
    +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
     golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
    @@ -823,8 +819,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
     golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
    -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
    -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
    +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
    +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
     golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    @@ -871,8 +867,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
    -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
    -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
    +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
    +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
     golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
     golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
     golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
    @@ -883,8 +879,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
     golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
     golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
     golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
    -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
    -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
    +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
    +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
     golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    @@ -897,8 +893,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
     golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
    -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
    -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
    +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
    +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
     golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
    @@ -935,8 +931,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
     golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
     golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
     golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
    -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
    -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
    +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
    +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
     golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    
90eaeaad1e38

Update golang.org/x/image to v0.38.0 (#36151)

https://github.com/mattermost/mattermostAlejandro García MontoroApr 17, 2026Fixed in 11.5.4via llm-release-walk
4 files changed · +28 29
  • server/go.mod+9 9 modified
    @@ -70,12 +70,13 @@ require (
     	github.com/wiggin77/merror v1.0.5
     	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
     	github.com/yuin/goldmark v1.7.13
    -	golang.org/x/crypto v0.45.0
    -	golang.org/x/image v0.32.0
    -	golang.org/x/net v0.47.0
    -	golang.org/x/sync v0.18.0
    -	golang.org/x/sys v0.38.0
    -	golang.org/x/term v0.37.0
    +	golang.org/x/crypto v0.48.0
    +	golang.org/x/image v0.38.0
    +	golang.org/x/net v0.50.0
    +	golang.org/x/sync v0.20.0
    +	golang.org/x/sys v0.41.0
    +	golang.org/x/term v0.40.0
    +	golang.org/x/text v0.35.0
     	gopkg.in/mail.v2 v2.3.1
     )
     
    @@ -206,9 +207,8 @@ require (
     	go.yaml.in/yaml/v3 v3.0.4 // indirect
     	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
     	golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
    -	golang.org/x/mod v0.29.0 // indirect
    -	golang.org/x/text v0.31.0 // indirect
    -	golang.org/x/tools v0.38.0 // indirect
    +	golang.org/x/mod v0.33.0 // indirect
    +	golang.org/x/tools v0.42.0 // indirect
     	google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
     	google.golang.org/grpc v1.76.0 // indirect
     	google.golang.org/protobuf v1.36.10 // indirect
    
  • server/go.sum+18 18 modified
    @@ -721,8 +721,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
     golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
     golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
     golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
    -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
    -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
    +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
    +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
     golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
    @@ -735,8 +735,8 @@ golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xC
     golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
     golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
     golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
    -golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
    -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
    +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
    +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
     golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
    @@ -759,8 +759,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
     golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
    -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
    -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
    +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
    +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
     golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
    @@ -795,8 +795,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
     golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
     golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
     golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
    -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
    -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
    +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
    +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
     golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
    @@ -819,8 +819,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
     golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
    -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
    -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
    +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
    +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
     golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    @@ -867,8 +867,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
    -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
    -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
    +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
    +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
     golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
     golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
     golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
    @@ -879,8 +879,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
     golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
     golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
     golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
    -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
    -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
    +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
    +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
     golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    @@ -893,8 +893,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
     golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
    -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
    -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
    +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
    +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
     golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
    @@ -931,8 +931,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
     golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
     golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
     golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
    -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
    -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
    +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
    +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
     golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    
  • server/public/go.mod+0 2 modified
    @@ -39,7 +39,6 @@ require (
     	github.com/fatih/color v1.18.0 // indirect
     	github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
     	github.com/golang/protobuf v1.5.4 // indirect
    -	github.com/google/go-cmp v0.7.0 // indirect
     	github.com/google/uuid v1.6.0 // indirect
     	github.com/hashicorp/errwrap v1.1.0 // indirect
     	github.com/hashicorp/yamux v0.1.2 // indirect
    @@ -58,7 +57,6 @@ require (
     	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
     	github.com/wiggin77/merror v1.0.5 // indirect
     	github.com/wiggin77/srslog v1.0.1 // indirect
    -	go.opentelemetry.io/otel v1.35.0 // indirect
     	golang.org/x/sync v0.17.0 // indirect
     	golang.org/x/sys v0.37.0 // indirect
     	google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
    
  • server/public/go.sum+1 0 modified
    @@ -165,6 +165,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
     github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
     github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
     github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
    +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
     github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
     github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
     github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
    
2f91e480afde

Update golang.org/x/image to v0.38.0 (#36150)

https://github.com/mattermost/mattermostAlejandro García MontoroApr 17, 2026Fixed in 11.4.5via llm-release-walk
2 files changed · +33 33
  • server/go.mod+11 10 modified
    @@ -70,12 +70,13 @@ require (
     	github.com/wiggin77/merror v1.0.5
     	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
     	github.com/yuin/goldmark v1.7.13
    -	golang.org/x/crypto v0.45.0
    -	golang.org/x/image v0.32.0
    -	golang.org/x/net v0.47.0
    -	golang.org/x/sync v0.18.0
    -	golang.org/x/sys v0.38.0
    -	golang.org/x/term v0.37.0
    +	golang.org/x/crypto v0.48.0
    +	golang.org/x/image v0.38.0
    +	golang.org/x/net v0.50.0
    +	golang.org/x/sync v0.20.0
    +	golang.org/x/sys v0.41.0
    +	golang.org/x/term v0.40.0
    +	golang.org/x/text v0.35.0
     	gopkg.in/mail.v2 v2.3.1
     )
     
    @@ -106,7 +107,8 @@ require (
     	github.com/bodgit/plumbing v1.3.0 // indirect
     	github.com/bodgit/sevenzip v1.6.1 // indirect
     	github.com/bodgit/windows v1.0.1 // indirect
    -	github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
    +	github.com/clipperhouse/displaywidth v0.10.0 // indirect
    +	github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
     	github.com/corpix/uarand v0.2.0 // indirect
     	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
     	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
    @@ -208,9 +210,8 @@ require (
     	go.yaml.in/yaml/v3 v3.0.4 // indirect
     	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
     	golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
    -	golang.org/x/mod v0.29.0 // indirect
    -	golang.org/x/text v0.31.0 // indirect
    -	golang.org/x/tools v0.38.0 // indirect
    +	golang.org/x/mod v0.33.0 // indirect
    +	golang.org/x/tools v0.42.0 // indirect
     	google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
     	google.golang.org/grpc v1.76.0 // indirect
     	google.golang.org/protobuf v1.36.10 // indirect
    
  • server/go.sum+22 23 modified
    @@ -131,10 +131,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
     github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
     github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
     github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
    -github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
    -github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
    -github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
    -github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
    +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
    +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
    +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
    +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
     github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
     github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE=
     github.com/corpix/uarand v0.2.0/go.mod h1:/3Z1QIqWkDIhf6XWn/08/uMHoQ8JUoTIKc2iPchBOmM=
    @@ -431,7 +431,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
     github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
     github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
     github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
    -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
     github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
     github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
     github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
    @@ -728,8 +727,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
     golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
     golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
     golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
    -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
    -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
    +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
    +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
     golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
    @@ -742,8 +741,8 @@ golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xC
     golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
     golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
     golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
    -golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
    -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
    +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
    +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
     golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
     golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
    @@ -766,8 +765,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
     golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
     golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
    -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
    -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
    +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
    +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
     golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
     golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
    @@ -802,8 +801,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
     golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
     golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
     golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
    -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
    -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
    +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
    +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
     golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
     golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
    @@ -826,8 +825,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
     golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
     golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
    -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
    -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
    +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
    +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
     golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
     golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    @@ -874,8 +873,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
     golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
    -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
    -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
    +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
    +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
     golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
     golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
     golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
    @@ -886,8 +885,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
     golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
     golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
     golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
    -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
    -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
    +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
    +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
     golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    @@ -900,8 +899,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
     golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
     golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
    -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
    -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
    +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
    +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
     golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
     golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
    @@ -938,8 +937,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
     golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
     golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
     golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
    -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
    -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
    +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
    +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
     golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
     golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    
9292c9e9b1ea

Improved processing of attachments (#35854) (#36102)

https://github.com/mattermost/mattermostAndre VasconcelosApr 15, 2026Fixed in 11.4.5via llm-release-walk
3 files changed · +35 1
  • server/channels/app/slack.go+1 1 modified
    @@ -90,7 +90,7 @@ func (a *App) ProcessSlackText(rctx request.CTX, text string) string {
     // documented here: https://api.slack.com/docs/attachments
     func (a *App) ProcessSlackAttachments(rctx request.CTX, attachments []*model.SlackAttachment) []*model.SlackAttachment {
     	var nonNilAttachments = model.StringifySlackFieldValue(attachments)
    -	for _, attachment := range attachments {
    +	for _, attachment := range nonNilAttachments {
     		attachment.Pretext = a.ProcessSlackText(rctx, attachment.Pretext)
     		attachment.Text = a.ProcessSlackText(rctx, attachment.Text)
     		attachment.Title = a.ProcessSlackText(rctx, attachment.Title)
    
  • server/channels/app/slack_test.go+26 0 modified
    @@ -6,6 +6,8 @@ package app
     import (
     	"testing"
     
    +	"github.com/stretchr/testify/require"
    +
     	"github.com/mattermost/mattermost/server/public/model"
     )
     
    @@ -32,6 +34,30 @@ func TestProcessSlackText(t *testing.T) {
     	}
     }
     
    +func TestProcessMessageAttachmentsWithNilEntries(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic(t)
    +
    +	attachments := []*model.SlackAttachment{
    +		nil,
    +		{
    +			Pretext: "pretext",
    +			Text:    "text",
    +			Title:   "title",
    +		},
    +		nil,
    +		{
    +			Pretext: "pretext2",
    +			Text:    "text2",
    +		},
    +	}
    +
    +	result := th.App.ProcessSlackAttachments(th.Context, attachments)
    +	require.Len(t, result, 2)
    +	require.Equal(t, "pretext", result[0].Pretext)
    +	require.Equal(t, "pretext2", result[1].Pretext)
    +}
    +
     func TestProcessSlackAnnouncement(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
    
  • server/channels/app/webhook.go+8 0 modified
    @@ -127,6 +127,14 @@ func (a *App) TriggerWebhook(rctx request.CTX, payload *model.OutgoingWebhookPay
     
     		go func() {
     			defer wg.Done()
    +			defer func() {
    +				if r := recover(); r != nil {
    +					logger.Error("Recovered from panic in outgoing webhook goroutine",
    +						mlog.String("url", url),
    +						mlog.Any("panic", r),
    +					)
    +				}
    +			}()
     
     			var accessToken *model.OutgoingOAuthConnectionToken
     
    
d37c2d9d50b5
https://github.com/mattermost/mattermostFixed in 11.6.1via llm-release-walk
6fd14d1f514f

Cherry-pick Go 1.25.8 upgrade and FIPS password fixes to release-11.4 (#36123)

https://github.com/mattermost/mattermostJesse HallamApr 16, 2026Fixed in 11.4.5via llm-release-walk
134 files changed · +2034 1089
  • e2e-tests/cypress/tests/integration/channels/account_settings/security/password_spec.ts+14 7 modified
    @@ -13,6 +13,7 @@
     import moment from 'moment-timezone';
     
     import * as TIMEOUTS from '../../../../fixtures/timeouts';
    +import {newTestPassword} from '../../../../utils';
     
     describe('Profile', () => {
         let siteName: string;
    @@ -51,8 +52,10 @@ describe('Profile', () => {
         });
     
         it('MM-T2085 Password: Valid values in password change fields allow the form to save successfully', () => {
    +        const newPassword = newTestPassword();
    +
             // # Enter valid values in password change fields
    -        enterPasswords(testUser.password, 'passwd', 'passwd');
    +        enterPasswords(testUser.password, newPassword, newPassword);
     
             // * Check that there are no errors
             cy.get('#error_currentPassword').should('not.exist');
    @@ -68,7 +71,7 @@ describe('Profile', () => {
     
         it('MM-T2082 Password: New password confirmation mismatch produces error', () => {
             // # Enter mismatching passwords for new password and confirm fields
    -        enterPasswords(testUser.password, 'newPW', 'NewPW');
    +        enterPasswords(testUser.password, 'MismatchPass14!', 'MISmatchPass14!');
     
             // * Verify for error message: "The new passwords you entered do not match."
             cy.get('#error_confirmPassword').should('be.visible').should('have.text', 'The new passwords you entered do not match.');
    @@ -78,13 +81,15 @@ describe('Profile', () => {
             // # Enter a New password two letters long
             enterPasswords(testUser.password, 'pw', 'pw');
     
    -        // * Verify for error message: "Your password must be 5-72 characters long."
    -        cy.get('#error_newPassword').should('be.visible').should('have.text', 'Your password must be 5-72 characters long.');
    +        // * Verify for error message about password length
    +        cy.get('#error_newPassword').should('be.visible').should('have.text', 'Your password must be 14-72 characters long.');
         });
     
         it('MM-T2084 Password: Cancel out of password changes causes no changes to be made', () => {
    +        const newPassword = 'Changed4Testing!';
    +
             // # Enter new valid passwords
    -        enterPasswords(testUser.password, 'newPasswd', 'newPasswd');
    +        enterPasswords(testUser.password, newPassword, newPassword);
     
             // # Click 'Cancel'
             cy.uiCancel();
    @@ -98,7 +103,7 @@ describe('Profile', () => {
     
             // * Verify that user cannot login with the cancelled password
             cy.get('#input_loginId').type(testUser.username);
    -        cy.get('#input_password-input').type('newPasswd');
    +        cy.get('#input_password-input').type(newPassword);
             cy.get('#saveSetting').should('not.be.disabled').click();
             cy.findByText('The email/username or password is invalid.').should('be.visible');
     
    @@ -109,8 +114,10 @@ describe('Profile', () => {
         });
     
         it.skip('MM-T2086 Password: Timestamp and email', () => {
    +        const newPassword = newTestPassword();
    +
             // # Enter valid values in password change fields
    -        enterPasswords(testUser.password, 'passwd', 'passwd');
    +        enterPasswords(testUser.password, newPassword, newPassword);
     
             // # Get current date
             const now = moment(Date.now());
    
  • e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_1_spec.ts+4 4 modified
    @@ -11,7 +11,7 @@
     // Group: @channels @system_console @authentication
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
    -import {getRandomId} from '../../../utils';
    +import {getRandomId, newTestPassword} from '../../../utils';
     
     describe('Authentication', () => {
         const restrictCreationToDomains = 'mattermost.com, test.com';
    @@ -57,7 +57,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@mattermost.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             cy.get('#input_name').clear().type(`test${getRandomId()}`);
     
    @@ -109,7 +109,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             cy.get('#input_name').clear().type(`test${getRandomId()}`);
     
    @@ -142,7 +142,7 @@ describe('Authentication', () => {
     
                 cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(email);
     
    -            cy.get('#input_password-input').type('Test123456!');
    +            cy.get('#input_password-input').type(newTestPassword());
     
                 cy.get('#input_name').clear().type(username);
     
    
  • e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_2_spec.ts+21 14 modified
    @@ -19,15 +19,16 @@ describe('Authentication', () => {
             cy.apiAdminLogin();
         });
     
    -    it('MM-T1771 - Minimum password length error field shows below 5 and above 72', () => {
    +    it('MM-T1771 - Minimum password length error field shows below minimum and above 72', () => {
             cy.visit('/admin_console/authentication/password');
     
             cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('88');
     
             cy.uiSave();
     
             // * Ensure error appears when saving a password outside of the limits
    -        cy.findByText('Minimum password length must be a whole number greater than or equal to 5 and less than or equal to 72.', {timeout: TIMEOUTS.ONE_MIN}).
    +        // Note: minimum is 5 on non-FIPS builds and 14 on FIPS builds
    +        cy.contains(/Minimum password length must be a whole number greater than or equal to (5|14) and less than or equal to 72\./, {timeout: TIMEOUTS.ONE_MIN}).
                 should('exist').
                 and('be.visible');
     
    @@ -36,19 +37,19 @@ describe('Authentication', () => {
             cy.uiSave();
     
             // * Ensure error appears when saving a password outside of the limits
    -        cy.findByText('Minimum password length must be a whole number greater than or equal to 5 and less than or equal to 72.', {timeout: TIMEOUTS.ONE_MIN}).
    +        cy.contains(/Minimum password length must be a whole number greater than or equal to (5|14) and less than or equal to 72\./, {timeout: TIMEOUTS.ONE_MIN}).
                 should('exist').
                 and('be.visible');
         });
     
         it('MM-T1772 - Change minimum password length, verify help text and error message', () => {
             cy.visit('/admin_console/authentication/password');
     
    -        cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('7');
    +        cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('15');
     
             cy.uiSave();
     
    -        cy.findByText('Your password must be 7-72 characters long.').should('be.visible');
    +        cy.findByText('Your password must be 15-72 characters long.').should('be.visible');
     
             cy.apiLogout();
     
    @@ -64,9 +65,9 @@ describe('Authentication', () => {
             cy.findByText('Create account').click();
     
             // * Assert the error is what is expected;
    -        cy.findByText('Your password must be 7-72 characters long.').should('be.visible');
    +        cy.findByText('Your password must be 15-72 characters long.').should('be.visible');
     
    -        cy.get('#input_password-input').clear().type('greaterthan7');
    +        cy.get('#input_password-input').clear().type('GreaterThan15Chr!');
     
             cy.findByText('Create account').click();
     
    @@ -77,20 +78,26 @@ describe('Authentication', () => {
         it('MM-T1773 - Minimum password length field resets to default after saving invalid value', () => {
             cy.visit('/admin_console/authentication/password');
     
    -        cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('10');
    +        cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('20');
     
             cy.uiSave();
     
             cy.reload();
     
    -        // * Ensure the limit 10 appears
    -        cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', '10');
    +        // * Ensure the limit 20 appears
    +        cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', '20');
             cy.findByPlaceholderText('E.g.: "5"').clear();
     
             cy.uiSave();
     
    -        // * Ensure the limit 10 appears
    -        cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', '5');
    +        // Reload to see the actual server state, since on FIPS builds saving
    +        // the webapp default of 5 is rejected (below the FIPS minimum of 14).
    +        cy.reload();
    +
    +        // * Ensure the field reflects the server's current minimum password length
    +        cy.apiGetConfig().then(({config: {PasswordSettings}}) => {
    +            cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', String(PasswordSettings.MinimumLength));
    +        });
         });
     
         it('MM-T1774 - Select all Password Requirements, verify help text and error on bad password', () => {
    @@ -112,12 +119,12 @@ describe('Authentication', () => {
     
             cy.get('#input_name').clear().type(`BestUsernameInTheWorld${getRandomId()}`);
     
    -        ['NOLOWERCASE123!', 'noupppercase123!', 'NoNumber!', 'NoSymbol123'].forEach((option) => {
    +        ['NOLOWERCASE12345!', 'nouppercase12345!', 'NoNumberHere!!!', 'NoSymbol1234567'].forEach((option) => {
                 cy.get('#input_password-input').clear().type(option);
                 cy.findByText('Create account').click();
     
                 // * Assert the error is what is expected;
    -            cy.findByText('Your password must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');
    +            cy.findByText('Your password must be 14-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');
             });
         });
     
    
  • e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts+10 7 modified
    @@ -10,7 +10,7 @@
     // Group: @channels @system_console @authentication
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
    -import {reUrl, getRandomId} from '../../../utils';
    +import {reUrl, getRandomId, newTestPassword} from '../../../utils';
     
     describe('Authentication', () => {
         let testUser;
    @@ -116,9 +116,10 @@ describe('Authentication', () => {
     
                 cy.apiUpdateConfig(newConfig);
     
    -            // * Ensure password has a minimum length of 8 and no password requirements are checked
    +            // * Ensure password has the default minimum length and no password requirements are checked
    +            // Note: default MinimumLength is 8 on non-FIPS builds and 14 on FIPS builds
                 cy.apiGetConfig().then(({config: {PasswordSettings}}) => {
    -                expect(PasswordSettings.MinimumLength).equal(8);
    +                expect(PasswordSettings.MinimumLength).to.be.oneOf([8, 14]);
                     expect(PasswordSettings.Lowercase).equal(false);
                     expect(PasswordSettings.Number).equal(false);
                     expect(PasswordSettings.Uppercase).equal(false);
    @@ -128,7 +129,9 @@ describe('Authentication', () => {
                 cy.visit('/admin_console/authentication/password');
                 cy.get('.admin-console__header').should('be.visible').and('have.text', 'Password');
     
    -            cy.findByTestId('passwordMinimumLengthinput').should('be.visible').and('have.value', '8');
    +            cy.findByTestId('passwordMinimumLengthinput').should('be.visible').invoke('val').then((val) => {
    +                expect(val).to.be.oneOf(['8', '14']);
    +            });
                 cy.findByText('At least one lowercase letter').siblings().should('not.be.checked');
                 cy.findByText('At least one uppercase letter').siblings().should('not.be.checked');
                 cy.findByText('At least one number').siblings().should('not.be.checked');
    @@ -146,7 +149,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             ['1user', 'te', 'user#1', 'user!1'].forEach((option) => {
                 cy.get('#input_name').clear().type(option);
    @@ -178,7 +181,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             cy.get('#input_name').clear().type(`Test${getRandomId()}`);
     
    @@ -240,7 +243,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             cy.get('#input_name').clear().type(`Test${getRandomId()}`);
     
    
  • e2e-tests/cypress/tests/integration/channels/autocomplete/helpers.ts+2 1 modified
    @@ -3,6 +3,7 @@
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
     import {getAdminAccount} from '../../../support/env';
    +import {newTestPassword} from '../../../utils';
     
     export type SimpleUser = Pick<Cypress.UserProfile, 'username' | 'first_name' | 'last_name' | 'nickname' | 'password' | 'email'>;
     
    @@ -224,7 +225,7 @@ function createChannel(channelType: string, teamId: string, userToAdd: Cypress.U
     function generatePrefixedUser(user: Omit<SimpleUser, 'password' | 'email'>, prefix: string) {
         return {
             username: withPrefix(user.username, prefix),
    -        password: 'passwd',
    +        password: newTestPassword(),
             first_name: withPrefix(user.first_name, prefix),
             last_name: withPrefix(user.last_name, prefix),
             email: createEmail(user.username, prefix),
    
  • e2e-tests/cypress/tests/integration/channels/enterprise/auth_sso/authentication_spec.ts+2 2 modified
    @@ -13,7 +13,7 @@
     import {Team} from '@mattermost/types/teams';
     
     import * as TIMEOUTS from '../../../../fixtures/timeouts';
    -import {getRandomId} from '../../../../utils';
    +import {getRandomId, newTestPassword} from '../../../../utils';
     
     describe('Authentication', () => {
         const restrictCreationToDomains = 'mattermost.com, test.com';
    @@ -54,7 +54,7 @@ describe('Authentication', () => {
     
             cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
     
    -        cy.get('#input_password-input').type('Test123456!');
    +        cy.get('#input_password-input').type(newTestPassword());
     
             cy.get('#input_name').clear().type(`Test${getRandomId()}`);
     
    
  • e2e-tests/cypress/tests/integration/channels/enterprise/cloud/billing/notify_admin_spec.ts+7 6 modified
    @@ -11,6 +11,7 @@
     // Group: @channels @cloud_only @cloud_trial
     
     import {getAdminAccount} from '../../../../../support/env';
    +import {newTestPassword} from '../../../../../utils';
     
     const admin = getAdminAccount();
     
    @@ -291,7 +292,7 @@ function testTrialNotifications(subscription, limits) {
         cy.then(() => {
             myAllProfessionalUsers.forEach((user) => {
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 cy.wait(['@subscription', '@products']);
                 createTrialNotificationForProfessionalFeatures();
    @@ -302,7 +303,7 @@ function testTrialNotifications(subscription, limits) {
         cy.then(() => {
             myAllEnterpriseUsers.forEach((user) => {
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 cy.wait(['@subscription', '@products']);
                 createTrialNotificationForEnterpriseFeatures();
    @@ -345,7 +346,7 @@ function testFilesNotifications(subscription: Subscription, limits: Limits) {
         cy.then(() => {
             myAllProfessionalUsers.forEach((user) => {
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 cy.wait(['@subscription', '@products']);
                 createFilesNotificationForProfessionalFeatures();
    @@ -393,7 +394,7 @@ function testUpgradeNotifications(subscription, limits) {
             myMessageLimitUsers.forEach((user) => {
                 cy.clearCookies();
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 cy.wait(['@subscription', '@products']);
                 createMessageLimitNotification();
    @@ -405,7 +406,7 @@ function testUpgradeNotifications(subscription, limits) {
             myUnlimitedTeamsUsers.forEach((user) => {
                 cy.clearCookies();
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 cy.wait(['@subscription', '@products']);
                 creatNewTeamNotification();
    @@ -417,7 +418,7 @@ function testUpgradeNotifications(subscription, limits) {
             myUserGroupsUsers.forEach((user) => {
                 cy.clearCookies();
                 simulateSubscription(subscription, limits);
    -            cy.apiLogin({...user, password: 'passwd'});
    +            cy.apiLogin({...user, password: newTestPassword()});
                 cy.visit(`/${myTeam.name}/channels/${myChannel.name}`);
                 userGroupsNotification();
             });
    
  • e2e-tests/cypress/tests/integration/channels/enterprise/elasticsearch_autocomplete/helpers.ts+12 11 modified
    @@ -6,6 +6,7 @@ import {ChainableT} from 'tests/types';
     
     import * as TIMEOUTS from '../../../../fixtures/timeouts';
     import {getAdminAccount} from '../../../../support/env';
    +import {newTestPassword} from '../../../../utils';
     import {SimpleUser} from '../../autocomplete/helpers';
     
     const admin = getAdminAccount();
    @@ -116,87 +117,87 @@ export function getTestUsers(): Record<string, SimpleUser> {
         return {
             ironman: {
                 username: withTimestamp('ironman', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Tony',
                 last_name: 'Stark',
                 email: createEmail('ironman', reverseTimeStamp),
                 nickname: withTimestamp('protoncannon', reverseTimeStamp),
             },
             hulk: {
                 username: withTimestamp('hulk', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Bruce',
                 last_name: 'Banner',
                 email: createEmail('hulk', reverseTimeStamp),
                 nickname: withTimestamp('gammaray', reverseTimeStamp),
             },
             hawkeye: {
                 username: withTimestamp('hawkeye', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Clint',
                 last_name: 'Barton',
                 email: createEmail('hawkeye', reverseTimeStamp),
                 nickname: withTimestamp('ronin', reverseTimeStamp),
             },
             deadpool: {
                 username: withTimestamp('deadpool', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Wade',
                 last_name: 'Wilson',
                 email: createEmail('deadpool', reverseTimeStamp),
                 nickname: withTimestamp('merc', reverseTimeStamp),
             },
             captainamerica: {
                 username: withTimestamp('captainamerica', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Steve',
                 last_name: 'Rogers',
                 email: createEmail('captainamerica', reverseTimeStamp),
                 nickname: withTimestamp('professional', reverseTimeStamp),
             },
             doctorstrange: {
                 username: withTimestamp('doctorstrange', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Stephen',
                 last_name: 'Strange',
                 email: createEmail('doctorstrange', reverseTimeStamp),
                 nickname: withTimestamp('sorcerersupreme', reverseTimeStamp),
             },
             thor: {
                 username: withTimestamp('thor', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Thor',
                 last_name: 'Odinson',
                 email: createEmail('thor', reverseTimeStamp),
                 nickname: withTimestamp('mjolnir', reverseTimeStamp),
             },
             loki: {
                 username: withTimestamp('loki', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Loki',
                 last_name: 'Odinson',
                 email: createEmail('loki', reverseTimeStamp),
                 nickname: withTimestamp('trickster', reverseTimeStamp),
             },
             dot: {
                 username: withTimestamp('dot.dot', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'z1First',
                 last_name: 'z1Last',
                 email: createEmail('dot', reverseTimeStamp),
                 nickname: 'z1Nick',
             },
             dash: {
                 username: withTimestamp('dash-dash', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'z2First',
                 last_name: 'z2Last',
                 email: createEmail('dash', reverseTimeStamp),
                 nickname: 'z2Nick',
             },
             underscore: {
                 username: withTimestamp('under_score', reverseTimeStamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'z3First',
                 last_name: 'z3Last',
                 email: createEmail('underscore', reverseTimeStamp),
    
  • e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_experience_ui_spec.ts+2 1 modified
    @@ -15,6 +15,7 @@
      */
     
     import * as TIMEOUTS from '../../../../fixtures/timeouts';
    +import {newTestPassword} from '../../../../utils';
     
     function demoteGuestUser(guestUser) {
         // # Demote user as guest user before each test
    @@ -226,7 +227,7 @@ describe('Guest Account - Guest User Experience', () => {
     
             // # Login with guest user credentials and check the error message
             cy.get('#input_loginId').type(guestUser.username);
    -        cy.get('#input_password-input').type('passwd');
    +        cy.get('#input_password-input').type(newTestPassword());
             cy.get('#saveSetting').should('not.be.disabled').click();
     
             // * Verify if guest account is deactivated
    
  • e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/member_invitation_ui_spec.ts+3 3 modified
    @@ -13,7 +13,7 @@
      * Note: This test requires Enterprise license to be uploaded
      */
     
    -import {getRandomId, stubClipboard} from '../../../../utils';
    +import {getRandomId, stubClipboard, newTestPassword} from '../../../../utils';
     import {getAdminAccount} from '../../../../support/env';
     import * as TIMEOUTS from '../../../../fixtures/timeouts';
     
    @@ -105,7 +105,7 @@ describe('Guest Account - Member Invitation Flow', () => {
             const email = `${username}@mattermost.com`;
             cy.get('#input_email').type(email);
             cy.get('#input_name').type(username);
    -        cy.get('#input_password-input').type('Testing123');
    +        cy.get('#input_password-input').type(newTestPassword());
             cy.findByText('Create Account').click();
     
             // * Verify if user is added to the invited team
    @@ -136,7 +136,7 @@ describe('Guest Account - Member Invitation Flow', () => {
     
                 // # Login as user
                 cy.get('#input_loginId').type(testUser.username);
    -            cy.get('#input_password-input').type('passwd');
    +            cy.get('#input_password-input').type(newTestPassword());
                 cy.get('#saveSetting').should('not.be.disabled').click();
     
                 // * Verify if user is added to the invited team
    
  • e2e-tests/cypress/tests/integration/channels/notifications/users_with_same_firstname_channel_mentions_spec.js+2 2 modified
    @@ -11,7 +11,7 @@
     // Group: @channels @notifications
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
    -import {getRandomId} from '../../../utils';
    +import {getRandomId, newTestPassword} from '../../../utils';
     
     describe('Notifications', () => {
         let testTeam;
    @@ -104,7 +104,7 @@ describe('Notifications', () => {
             return {
                 email: `${username}${randomId}@sample.mattermost.com`,
                 username,
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: `First${randomId}`,
                 last_name: `Last${randomId}`,
                 nickname: `Nickname${randomId}`,
    
  • e2e-tests/cypress/tests/integration/channels/onboarding/existing_email_adress_spec.js+3 3 modified
    @@ -11,7 +11,7 @@
     // Group: @channels @onboarding
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
    -import {getRandomId} from '../../../utils';
    +import {getRandomId, newTestPassword} from '../../../utils';
     
     const uniqueUserId = getRandomId();
     
    @@ -48,7 +48,7 @@ describe('Cloud Onboarding', () => {
     
         it('MM-T403 Email address already exists', () => {
             // # Signup a new user with an email address and user generated in signupWithEmail
    -        signupWithEmail('unique.' + uniqueUserId, 'unique1pw');
    +        signupWithEmail('unique.' + uniqueUserId, newTestPassword());
     
             // * Verify there is Logout Button
             cy.contains('Logout').should('be.visible');
    @@ -61,7 +61,7 @@ describe('Cloud Onboarding', () => {
     
             // # Logout and signup another user with the same email but different username and password
             cy.apiLogout();
    -        signupWithEmail('unique-2', 'unique2pw');
    +        signupWithEmail('unique-2', newTestPassword());
     
             // * Error message displays below the Create Account button that says "An account with that email already exists"
             cy.findByText('An account with that email already exists.').should('be.visible');
    
  • e2e-tests/cypress/tests/integration/channels/onboarding/invalidate_pending_email_invitations_spec.js+2 2 modified
    @@ -12,7 +12,7 @@
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
     import {getAdminAccount} from '../../../support/env';
    -import {getRandomId} from '../../../utils';
    +import {getRandomId, newTestPassword} from '../../../utils';
     import {inviteUserByEmail, verifyEmailInviteAndVisitLink, signupAndVerifyTutorial} from '../team_settings/helpers';
     
     describe('Onboarding', () => {
    @@ -23,7 +23,7 @@ describe('Onboarding', () => {
         const emailOne = `${usernameOne}@sample.mattermost.com`;
         const emailTwo = `${usernameTwo}@sample.mattermost.com`;
         const emailThree = `${usernameThree}@sample.mattermost.com`;
    -    const password = 'passwd';
    +    const password = newTestPassword();
     
         let testTeam;
         let siteName;
    
  • e2e-tests/cypress/tests/integration/channels/plugins/link_tooltip_spec.js+10 2 modified
    @@ -17,10 +17,18 @@ describe('Link tooltips', () => {
             cy.shouldNotRunOnCloudEdition();
             cy.shouldHavePluginUploadEnabled();
     
    -        // # Set plugin settings
    +        // # Set plugin settings, including demo plugin defaults so that
    +        // OnConfigurationChange can create the demo user with a valid email
             const newSettings = {
                 PluginSettings: {
                     Enable: true,
    +                Plugins: {
    +                    [demoPlugin.id]: {
    +                        channelname: 'demo_plugin',
    +                        username: 'demo_plugin',
    +                        lastname: 'Plugin User',
    +                    },
    +                },
                 },
                 ServiceSettings: {
                     EnableGifPicker: true,
    @@ -47,7 +55,7 @@ describe('Link tooltips', () => {
             cy.uiWaitUntilMessagePostedIncludes(url);
     
             // # Hover over the plugin link
    -        cy.findByText(url).should('exist').focus();
    +        cy.getLastPost().findByText(url).should('exist').focus();
     
             // * Check tooltip has appeared
             cy.findByText('This is a custom tooltip from the Demo Plugin').should('be.visible');
    
  • e2e-tests/cypress/tests/integration/channels/search_autocomplete/renaming_spec.js+3 2 modified
    @@ -12,6 +12,7 @@
     
     import * as TIMEOUTS from '../../../fixtures/timeouts';
     import {withTimestamp, createEmail} from '../enterprise/elasticsearch_autocomplete/helpers';
    +import {newTestPassword} from '../../../utils';
     
     describe('Autocomplete without Elasticsearch - Renaming', () => {
         const timestamp = Date.now();
    @@ -33,7 +34,7 @@ describe('Autocomplete without Elasticsearch - Renaming', () => {
         it('renamed user appears in message input box', () => {
             const spiderman = {
                 username: withTimestamp('spiderman', timestamp),
    -            password: 'passwd',
    +            password: newTestPassword(),
                 first_name: 'Peter',
                 last_name: 'Parker',
                 email: createEmail('spiderman', timestamp),
    @@ -87,7 +88,7 @@ describe('Autocomplete without Elasticsearch - Renaming', () => {
             before(() => {
                 const punisher = {
                     username: withTimestamp('punisher', timestamp),
    -                password: 'passwd',
    +                password: newTestPassword(),
                     first_name: 'Frank',
                     last_name: 'Castle',
                     email: createEmail('punisher', timestamp),
    
  • e2e-tests/cypress/tests/integration/channels/signin_authentication/forgot_password_spec.js+2 1 modified
    @@ -12,6 +12,7 @@
     
     import {
         getPasswordResetEmailTemplate,
    +    newTestPassword,
         reUrl,
         verifyEmailBody,
     } from '../../../utils';
    @@ -34,7 +35,7 @@ describe('Signin/Authentication', () => {
         });
     
         it('MM-T407 - Sign In Forgot password - Email address has account on server', () => {
    -        const newPassword = 'newpasswd';
    +        const newPassword = newTestPassword();
     
             // # Visit town-square
             cy.visit(`/${teamName.name}/channels/town-square`);
    
  • e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js+1 1 modified
    @@ -74,7 +74,7 @@ describe('Signup Email page', () => {
             cy.findByText('You can use lowercase letters, numbers, periods, dashes, and underscores.').should('be.visible');
     
             cy.get('#input_password-input').should('be.visible').and('have.attr', 'placeholder', 'Choose a Password');
    -        cy.findByText('Your password must be 5-72 characters long.').should('be.visible');
    +        cy.findByText('Your password must be 14-72 characters long.').should('be.visible');
     
             cy.get('#saveSetting').scrollIntoView().should('be.visible');
             cy.get('#saveSetting').should('contain', 'Create Account');
    
  • e2e-tests/cypress/tests/integration/channels/team_settings/closed_team_invite_by_email_spec.js+2 1 modified
    @@ -15,6 +15,7 @@ import * as TIMEOUTS from '../../../fixtures/timeouts';
     import {
         getJoinEmailTemplate,
         getRandomId,
    +    newTestPassword,
         reUrl,
         verifyEmailBody,
     } from '../../../utils';
    @@ -24,7 +25,7 @@ describe('Team Settings', () => {
         const randomId = getRandomId();
         const username = `user${randomId}`;
         const email = `user${randomId}@sample.mattermost.com`;
    -    const password = 'passwd';
    +    const password = newTestPassword();
     
         let testTeam;
         let siteName;
    
  • e2e-tests/cypress/tests/integration/channels/team_settings/join_closed_team_with_not_allowed_email_spec.js+3 3 modified
    @@ -10,7 +10,7 @@
     // Stage: @prod
     // Group: @channels @team_settings
     
    -import {getRandomId, stubClipboard} from '../../../utils';
    +import {getRandomId, stubClipboard, newTestPassword} from '../../../utils';
     import * as TIMEOUTS from '../../../fixtures/timeouts';
     
     describe('Team Settings', () => {
    @@ -79,7 +79,7 @@ describe('Team Settings', () => {
     
                 const email = `user${randomId}@sample.gmail.com`;
                 const username = `user${randomId}`;
    -            const password = 'passwd';
    +            const password = newTestPassword();
                 const errorMessage = `The following email addresses do not belong to an accepted domain: ${emailDomain}. Please contact your System Administrator for details.`;
     
                 // # Type email, username and password
    @@ -127,7 +127,7 @@ describe('Team Settings', () => {
             });
     
             // # Create a new user
    -        cy.apiCreateUser({user: {email: `user${randomId}@sample.gmail.com`, username: `user${randomId}`, password: 'passwd'}}).then(({user}) => {
    +        cy.apiCreateUser({user: {email: `user${randomId}@sample.gmail.com`, username: `user${randomId}`, password: newTestPassword()}}).then(({user}) => {
                 // # Create a second team
                 cy.apiCreateTeam('other-team', 'Other Team').then(({team: otherTeam}) => {
                     // # Add user to the other team
    
  • e2e-tests/cypress/tests/support/api/cloud_default_config.json+1 1 modified
    @@ -141,7 +141,7 @@
             "IosMinVersion": ""
         },
         "PasswordSettings": {
    -        "MinimumLength": 5,
    +        "MinimumLength": 14,
             "Lowercase": false,
             "Number": false,
             "Uppercase": false,
    
  • e2e-tests/cypress/tests/support/api/on_prem_default_config.json+1 1 modified
    @@ -183,7 +183,7 @@
             "AdvancedLoggingJSON": {}
         },
         "PasswordSettings": {
    -        "MinimumLength": 5,
    +        "MinimumLength": 14,
             "Lowercase": false,
             "Number": false,
             "Uppercase": false,
    
  • e2e-tests/cypress/tests/support/api/plugin.js+29 12 modified
    @@ -56,38 +56,55 @@ Cypress.Commands.add('apiUploadPlugin', (filename) => {
     
     Cypress.Commands.add('apiUploadAndEnablePlugin', ({filename, url, id, version}) => {
         return cy.apiGetPluginStatus(id, version).then((data) => {
    -        // # If already active, then only return the data
    -        if (data.isActive) {
    +        // # Only short-circuit when version is specified, so we know the exact
    +        // installed version matches the desired artifact. Without a version,
    +        // any installed version would match and we'd silently skip the install.
    +        if (version && data.isActive) {
                 cy.log(`${id}: Plugin is active.`);
                 return cy.wrap(data);
             }
     
    -        // # If already installed, then only enable the plugin
    -        if (data.isInstalled) {
    +        if (version && data.isInstalled) {
                 cy.log(`${id}: Plugin is inactive. Only going to enable.`);
                 return cy.apiEnablePluginById(id).then(() => {
                     cy.wait(TIMEOUTS.ONE_SEC);
                     return cy.wrap(data);
                 });
             }
     
    +        // # Remove any old version of the plugin before installing the new one
    +        const removeOldVersion = () => {
    +            return cy.apiGetPluginStatus(id).then((anyVersionData) => {
    +                if (anyVersionData.isInstalled || anyVersionData.isActive) {
    +                    cy.log(`${id}: Removing old version before installing new one.`);
    +                    return cy.apiRemovePluginById(id);
    +                }
    +
    +                return cy.wrap(null);
    +            });
    +        };
    +
             if (url) {
                 // # Upload plugin by URL then enable
                 cy.log(`${id}: Plugin is to be uploaded via URL and then enable.`);
    -            return cy.apiInstallPluginFromUrl(url).then(() => {
    -                cy.wait(TIMEOUTS.FIVE_SEC);
    -                return cy.apiEnablePluginById(id).then(() => {
    -                    cy.wait(TIMEOUTS.ONE_SEC);
    -                    return cy.wrap({isInstalled: true, isActive: true});
    +            return removeOldVersion().then(() => {
    +                return cy.apiInstallPluginFromUrl(url).then(() => {
    +                    cy.wait(TIMEOUTS.FIVE_SEC);
    +                    return cy.apiEnablePluginById(id).then(() => {
    +                        cy.wait(TIMEOUTS.ONE_SEC);
    +                        return cy.wrap({isInstalled: true, isActive: true});
    +                    });
                     });
                 });
             }
     
             // # Upload plugin by file then enable
             cy.log(`${id}: Plugin is to be uploaded by filename and then enable.`);
    -        return cy.apiUploadPlugin(filename).then(() => {
    -            return cy.apiEnablePluginById(id).then(() => {
    -                return cy.wrap({isInstalled: true, isActive: true});
    +        return removeOldVersion().then(() => {
    +            return cy.apiUploadPlugin(filename).then(() => {
    +                return cy.apiEnablePluginById(id).then(() => {
    +                    return cy.wrap({isInstalled: true, isActive: true});
    +                });
                 });
             });
         });
    
  • e2e-tests/cypress/tests/support/api/user.ts+2 2 modified
    @@ -5,7 +5,7 @@ import {UserAccessToken, UserProfile} from '@mattermost/types/users';
     import authenticator from 'authenticator';
     import {ChainableT} from 'tests/types';
     
    -import {getRandomId} from '../../utils';
    +import {getRandomId, newTestPassword} from '../../utils';
     import {getAdminAccount} from '../env';
     
     import {buildQueryString} from './helpers';
    @@ -377,7 +377,7 @@ function generateRandomUser(prefix = 'user', createAt = 0): Partial<UserProfile>
         return {
             email: `${prefix}${randomId}@sample.mattermost.com`,
             username: `${prefix}${randomId}`,
    -        password: 'passwd',
    +        password: newTestPassword(),
             first_name: `First${randomId}`,
             last_name: `Last${randomId}`,
             nickname: `Nickname${randomId}`,
    
  • e2e-tests/cypress/tests/utils/index.js+6 0 modified
    @@ -63,6 +63,12 @@ export function rgbArrayToString(rgbArr) {
         return `rgb(${rgbArr[0]}, ${rgbArr[1]}, ${rgbArr[2]})`;
     }
     
    +// Returns a FIPS-compliant test password (>= 14 chars with complexity).
    +// Static for now but could generate unique passwords if requirements change.
    +export function newTestPassword() {
    +    return 'Passwd4Testing!';
    +}
    +
     export const reUrl = /(https?:\/\/[^ ]*)/;
     
     const userAgent = () => window.navigator.userAgent;
    
  • e2e-tests/cypress/tests/utils/plugins.js+3 3 modified
    @@ -23,9 +23,9 @@ export const agendaPlugin = {
     
     export const demoPlugin = {
         id: 'com.mattermost.demo-plugin',
    -    version: '0.10.0',
    -    url: 'https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.10.0/com.mattermost.demo-plugin-0.10.0.tar.gz',
    -    filename: 'com.mattermost.demo-plugin-0.10.0.tar.gz',
    +    version: '0.11.0',
    +    url: 'https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.11.0/mattermost-plugin-demo-v0.11.0.tar.gz',
    +    filename: 'mattermost-plugin-demo-v0.11.0.tar.gz',
     };
     
     export const demoPluginOld = {
    
  • e2e-tests/playwright/lib/src/index.ts+1 1 modified
    @@ -6,7 +6,7 @@ export {testConfig} from './test_config';
     export {baseGlobalSetup} from './global_setup';
     export {TestBrowser} from './browser_context';
     export {getBlobFromAsset, getFileFromAsset} from './file';
    -export {duration, wait} from './util';
    +export {duration, wait, newTestPassword} from './util';
     
     export {
         ChannelsPage,
    
  • e2e-tests/playwright/lib/src/server/default_config.ts+2 2 modified
    @@ -50,7 +50,7 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
                 EnableDiagnostics: false,
             },
             PasswordSettings: {
    -            MinimumLength: 5,
    +            MinimumLength: 14,
                 Lowercase: false,
                 Number: false,
                 Uppercase: false,
    @@ -289,7 +289,7 @@ const defaultServerConfig: AdminConfig = {
             Certificate: '',
         },
         PasswordSettings: {
    -        MinimumLength: 8,
    +        MinimumLength: 14,
             Lowercase: false,
             Number: false,
             Uppercase: false,
    
  • e2e-tests/playwright/lib/src/server/user.ts+2 2 modified
    @@ -5,7 +5,7 @@ import {Client4} from '@mattermost/client';
     import {UserProfile, UserTimezone} from '@mattermost/types/users';
     import {DateTime} from 'luxon';
     
    -import {getRandomId} from '@/util';
    +import {getRandomId, newTestPassword} from '@/util';
     import {testConfig} from '@/test_config';
     import {REMOTE_USERS_HOUR_LIMIT_END_OF_THE_DAY, REMOTE_USERS_HOUR_LIMIT_BEGINNING_OF_THE_DAY} from '@/constant';
     
    @@ -24,7 +24,7 @@ export async function createRandomUser(prefix = 'user') {
         const user = {
             email: `${prefix}${randomId}@sample.mattermost.com`,
             username: `${prefix}${randomId}`,
    -        password: 'passwd',
    +        password: newTestPassword(),
             first_name: `First${randomId}`,
             last_name: `Last${randomId}`,
             nickname: `Nickname${randomId}`,
    
  • e2e-tests/playwright/lib/src/ui/pages/signup.ts+1 1 modified
    @@ -46,7 +46,7 @@ export default class SignupPage {
             this.usernameError = page.locator(
                 'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.',
             );
    -        this.passwordError = page.locator('text=Must be 5-72 characters long.');
    +        this.passwordError = page.locator('text=/Must be \\d+-72 characters long\\./');
     
             const newsletterBlock = page.locator('.check-input');
             this.newsLetterCheckBox = newsletterBlock.getByRole('checkbox', {name: 'newsletter checkbox'});
    
  • e2e-tests/playwright/lib/src/util.ts+6 0 modified
    @@ -56,6 +56,12 @@ export async function getRandomId(length = 7): Promise<string> {
     // It should not be used for testing.
     export const defaultTeam = {name: 'ad-1', displayName: 'eligendi', type: 'O'};
     
    +// Returns a FIPS-compliant test password (>= 14 chars with complexity).
    +// Static for now but could generate unique passwords if requirements change.
    +export function newTestPassword(): string {
    +    return 'Passwd4Testing!';
    +}
    +
     export const illegalRe = /[/?<>\\:*|":&();]/g;
     export const simpleEmailRe = /\S+@\S+\.\S+/;
     
    
  • e2e-tests/playwright/specs/functional/system_console/system_users/actions.spec.ts+0 202 removed
    @@ -1,202 +0,0 @@
    -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    -// See LICENSE.txt for license information.
    -
    -import {type PlaywrightExtended, expect, test} from '@mattermost/playwright-lib';
    -
    -/**
    - * Setup a new random user, and search for it such that it's the first row in the list
    - * @param pw
    - * @param pages
    - * @returns A function to get the refreshed user, and the System Console page for navigation
    - */
    -async function setupAndGetRandomUser(pw: PlaywrightExtended) {
    -    const {adminUser, adminClient} = await pw.initSetup();
    -
    -    if (!adminUser) {
    -        throw new Error('Failed to create admin user');
    -    }
    -
    -    // # Log in as admin
    -    const {systemConsolePage} = await pw.testBrowser.login(adminUser);
    -
    -    // # Create a random user to edit for
    -    const user = await adminClient.createUser(await pw.random.user(), '', '');
    -    const team = await adminClient.createTeam(await pw.random.team());
    -    await adminClient.addToTeam(team.id, user.id);
    -
    -    // # Visit system console
    -    await systemConsolePage.goto();
    -    await systemConsolePage.toBeVisible();
    -
    -    // # Go to Users section
    -    await systemConsolePage.sidebar.goToItem('Users');
    -    await systemConsolePage.systemUsers.toBeVisible();
    -
    -    // # Search for user-1
    -    await systemConsolePage.systemUsers.enterSearchText(user.email);
    -    const userRow = await systemConsolePage.systemUsers.getNthRow(1);
    -    await userRow.getByText(user.email).waitFor();
    -    const innerText = await userRow.innerText();
    -    expect(innerText).toContain(user.email);
    -
    -    return {getUser: () => adminClient.getUser(user.id), systemConsolePage};
    -}
    -
    -test('MM-T5520-1 should activate and deactivate users', async ({pw}) => {
    -    const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
    -
    -    // # Open menu and deactivate the user
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const deactivate = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Deactivate');
    -    await deactivate.click();
    -
    -    // # Press confirm on the modal
    -    await systemConsolePage.confirmModal.confirm();
    -
    -    // * Verify user is deactivated
    -    const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
    -    await firstRow.getByText('Deactivated').waitFor();
    -    expect(await firstRow.innerText()).toContain('Deactivated');
    -    expect((await getUser()).delete_at).toBeGreaterThan(0);
    -
    -    // # Open menu and reactivate the user
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const activate = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Activate');
    -    await activate.click();
    -
    -    // * Verify user is activated
    -    await firstRow.getByText('Member').waitFor();
    -    expect(await firstRow.innerText()).toContain('Member');
    -});
    -
    -test('MM-T5520-2 should change user roles', async ({pw}) => {
    -    const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
    -
    -    // # Open menu and click Manage roles
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    let manageRoles = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage roles');
    -    await manageRoles.click();
    -
    -    // # Change to System Admin and click Save
    -    const systemAdmin = systemConsolePage.page.locator('input[name="systemadmin"]');
    -    await systemAdmin.waitFor();
    -    await systemAdmin.click();
    -    systemConsolePage.saveRoleChange();
    -
    -    // * Verify that the modal closed and no error showed
    -    await systemAdmin.waitFor({state: 'detached'});
    -
    -    // * Verify that the role was updated
    -    const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
    -    expect(await firstRow.innerText()).toContain('System Admin');
    -    expect((await getUser()).roles).toContain('system_admin');
    -
    -    // # Open menu and click Manage roles
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    manageRoles = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage roles');
    -    await manageRoles.click();
    -
    -    // # Change to Member and click Save
    -    const systemMember = systemConsolePage.page.locator('input[name="systemmember"]');
    -    await systemMember.waitFor();
    -    await systemMember.click();
    -    await systemConsolePage.saveRoleChange();
    -
    -    // * Verify that the modal closed and no error showed
    -    await systemMember.waitFor({state: 'detached'});
    -
    -    // * Verify that the role was updated
    -    expect(await firstRow.innerText()).toContain('Member');
    -    expect((await getUser()).roles).toContain('system_user');
    -});
    -
    -test('MM-T5520-3 should be able to manage teams', async ({pw}) => {
    -    const {systemConsolePage} = await setupAndGetRandomUser(pw);
    -
    -    // # Open menu and click Manage teams
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const manageTeams = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage teams');
    -    await manageTeams.click();
    -
    -    // # Click Make Team Admin
    -    const team = systemConsolePage.page.locator('div.manage-teams__team');
    -    const teamDropdown = team.locator('div.MenuWrapper');
    -    await teamDropdown.click();
    -    const makeTeamAdmin = teamDropdown.getByText('Make Team Admin');
    -    await makeTeamAdmin.click();
    -
    -    // * Verify role is updated
    -    expect(await team.innerText()).toContain('Team Admin');
    -
    -    // # Change back to Team Member
    -    await teamDropdown.click();
    -    const makeTeamMember = teamDropdown.getByText('Make Team Member');
    -    await makeTeamMember.click();
    -
    -    // * Verify role is updated
    -    expect(await team.innerText()).toContain('Team Member');
    -
    -    // # Click Remove From Team
    -    await teamDropdown.click();
    -    const removeFromTeam = teamDropdown.getByText('Remove From Team');
    -    await removeFromTeam.click();
    -
    -    // * The team should be detached
    -    await team.waitFor({state: 'detached'});
    -    expect(team).not.toBeVisible();
    -});
    -
    -test('MM-T5520-4 should reset the users password', async ({pw}) => {
    -    const {systemConsolePage} = await setupAndGetRandomUser(pw);
    -
    -    // # Open menu and click Reset Password
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const resetPassword = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Reset password');
    -    await resetPassword.click();
    -
    -    // # Enter a random password and click Save
    -    const passwordInput = systemConsolePage.page.locator('input[type="password"]');
    -    await passwordInput.fill(await pw.random.id());
    -    await systemConsolePage.clickResetButton();
    -
    -    // * Verify that the modal closed and no error showed
    -    await passwordInput.waitFor({state: 'detached'});
    -});
    -
    -test('MM-T5520-5 should change the users email', async ({pw}) => {
    -    const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
    -    const newEmail = `${await pw.random.id()}@example.com`;
    -
    -    // # Open menu and click Update Email
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const updateEmail = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Update email');
    -    await updateEmail.click();
    -
    -    // # Enter new email and click Update
    -    const emailInput = systemConsolePage.page.locator('input[type="email"]');
    -    await emailInput.fill(newEmail);
    -    await systemConsolePage.clickUpdateEmailButton();
    -
    -    // * Verify that the modal closed
    -    await emailInput.waitFor({state: 'detached'});
    -
    -    // * Verify that the email updated
    -    const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
    -    expect(await firstRow.innerText()).toContain(newEmail);
    -    expect((await getUser()).email).toEqual(newEmail);
    -});
    -
    -test('MM-T5520-6 should revoke sessions', async ({pw}) => {
    -    const {systemConsolePage} = await setupAndGetRandomUser(pw);
    -
    -    // # Open menu and revoke sessions
    -    await systemConsolePage.systemUsers.actionMenuButtons[0].click();
    -    const removeSessions = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Revoke sessions');
    -    await removeSessions.click();
    -
    -    // # Press confirm on the modal
    -    await systemConsolePage.confirmModal.confirm();
    -
    -    const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
    -    expect(await firstRow.innerHTML()).not.toContain('class="error"');
    -});
    
  • .github/workflows/mmctl-test-template.yml+3 0 modified
    @@ -60,6 +60,8 @@ jobs:
               make prepackaged-plugins PLUGIN_PACKAGES=mattermost-plugin-jira-v3.2.5
     
           - name: Run docker compose
    +        env:
    +          POSTGRES_PASSWORD: ${{ inputs.fips-enabled && 'mostest-fips-test' || 'mostest' }}
             run: |
               cd server/build
               docker compose --ansi never run --rm start_dependencies
    @@ -81,6 +83,7 @@ jobs:
               docker run --net ghactions_mm-test \
                 --ulimit nofile=8096:8096 \
                 --env-file=server/build/dotenv/test.env \
    +            --env TEST_DATABASE_POSTGRESQL_DSN="${{ inputs.datasource }}" \
                 --env MM_SQLSETTINGS_DATASOURCE="${{ inputs.datasource }}" \
                 --env MMCTL_TESTFLAGS="$TESTFLAGS" \
                 --env FIPS_ENABLED="${{ inputs.fips-enabled }}" \
    
  • .github/workflows/server-ci.yml+53 10 modified
    @@ -15,6 +15,7 @@ on:
           - "e2e-tests/**"
           - ".github/workflows/server-ci.yml"
           - ".github/workflows/server-test-template.yml"
    +      - ".github/workflows/server-test-merge-template.yml"
           - ".github/workflows/mmctl-test-template.yml"
           - "!server/build/Dockerfile.buildenv"
           - "!server/build/Dockerfile.buildenv-fips"
    @@ -29,13 +30,20 @@ jobs:
         runs-on: ubuntu-22.04
         outputs:
           version: ${{ steps.calculate.outputs.GO_VERSION }}
    +      gomod-changed: ${{ steps.changed-files.outputs.any_changed }}
         steps:
           - name: Checkout mattermost project
             uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
           - name: Calculate version
             id: calculate
             working-directory: server/
             run: echo GO_VERSION=$(cat .go-version) >> "${GITHUB_OUTPUT}"
    +      - name: Check for go.mod changes
    +        id: changed-files
    +        uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
    +        with:
    +          files: |
    +            **/go.mod
       check-mocks:
         name: Check mocks
         needs: go
    @@ -199,32 +207,67 @@ jobs:
           logsartifact: postgres-binary-server-test-logs
           go-version: ${{ needs.go.outputs.version }}
           fips-enabled: false
    +  # -- Sharded into 4 parallel runners for ~88% wall-time improvement --
       test-postgres-normal:
    -    name: Postgres
    +    name: Postgres (shard ${{ matrix.shard }})
         needs: go
    +    strategy:
    +      fail-fast: false # Let all shards complete so we get full test results
    +      matrix:
    +        shard: [0, 1, 2, 3]
         uses: ./.github/workflows/server-test-template.yml
         secrets: inherit
         with:
    -      name: Postgres
    +      name: "Postgres (shard ${{ matrix.shard }})"
           datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
           drivername: postgres
    -      logsartifact: postgres-server-test-logs
    +      # Each shard gets a unique artifact name so they don't collide
    +      logsartifact: "postgres-server-test-logs-shard-${{ matrix.shard }}"
           go-version: ${{ needs.go.outputs.version }}
           fips-enabled: false
    +      shard-index: ${{ matrix.shard }}
    +      shard-total: 4
    +  # -- Merge test results (handles both single-run and future sharded runs) --
    +  merge-postgres-test-results:
    +    name: Merge Postgres Test Results
    +    needs: test-postgres-normal
    +    if: always()
    +    uses: ./.github/workflows/server-test-merge-template.yml
    +    with:
    +      artifact-pattern: postgres-server-test-logs-shard-*
    +      artifact-name: postgres-server-test-logs
    +      save-timing-cache: true
    +
       test-postgres-normal-fips:
    -    # Skip FIPS testing for forks, which won't have docker login credentials.
    -    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
    -    name: Postgres (FIPS)
    +    # Always run on pushes to master/release branches.
    +    # For PRs, run when the branch name contains "fips" or any go.mod was changed.
    +    if: github.event_name == 'push' || contains(github.head_ref, 'fips') || needs.go.outputs.gomod-changed == 'true'
    +    name: Postgres FIPS (shard ${{ matrix.shard }})
         needs: go
    +    strategy:
    +      fail-fast: false
    +      matrix:
    +        shard: [0, 1, 2, 3]
         uses: ./.github/workflows/server-test-template.yml
         secrets: inherit
         with:
    -      name: Postgres
    -      datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
    +      name: "Postgres FIPS (shard ${{ matrix.shard }})"
    +      datasource: postgres://mmuser:mostest-fips-test@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
           drivername: postgres
    -      logsartifact: postgres-server-test-logs
    +      logsartifact: "postgres-server-fips-test-logs-shard-${{ matrix.shard }}"
           go-version: ${{ needs.go.outputs.version }}
           fips-enabled: true
    +      shard-index: ${{ matrix.shard }}
    +      shard-total: 4
    +  merge-postgres-fips-test-results:
    +    name: Merge Postgres FIPS Test Results
    +    needs: test-postgres-normal-fips
    +    if: needs.test-postgres-normal-fips.result != 'skipped'
    +    uses: ./.github/workflows/server-test-merge-template.yml
    +    with:
    +      artifact-pattern: postgres-server-fips-test-logs-shard-*
    +      artifact-name: postgres-server-fips-test-logs
    +
       test-coverage:
         name: Generate Test Coverage
         # Disabled: Running out of memory and causing spurious failures.
    @@ -262,7 +305,7 @@ jobs:
         secrets: inherit
         with:
           name: mmctl
    -      datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
    +      datasource: postgres://mmuser:mostest-fips-test@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
           drivername: postgres
           logsartifact: mmctl-test-logs
           go-version: ${{ needs.go.outputs.version }}
    
  • .github/workflows/server-test-merge-template.yml+89 0 added
    @@ -0,0 +1,89 @@
    +name: Server Test Merge Template
    +
    +on:
    +  workflow_call:
    +    inputs:
    +      artifact-pattern:
    +        description: "Glob pattern to download shard artifacts"
    +        required: true
    +        type: string
    +      artifact-name:
    +        description: "Name for the merged output artifact"
    +        required: true
    +        type: string
    +      save-timing-cache:
    +        description: "Whether to save timing cache for future shard balancing"
    +        required: false
    +        type: boolean
    +        default: false
    +
    +jobs:
    +  merge:
    +    name: Merge
    +    if: always()
    +    runs-on: ubuntu-22.04
    +    steps:
    +      - name: Download all shard artifacts
    +        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
    +        with:
    +          pattern: ${{ inputs.artifact-pattern }}
    +          path: shards
    +
    +      - name: Merge JUnit reports
    +        run: |
    +          python3 -c "
    +          import glob, sys
    +          from xml.etree import ElementTree as ET
    +
    +          root = ET.Element('testsuites')
    +          for path in sorted(glob.glob('shards/*/report.xml')):
    +              tree = ET.parse(path)
    +              r = tree.getroot()
    +              if r.tag == 'testsuites':
    +                  root.extend(r)
    +              else:
    +                  root.append(r)
    +
    +          ET.ElementTree(root).write('merged-report.xml', xml_declaration=True, encoding='UTF-8')
    +          "
    +
    +      - name: Prepare merged artifact
    +        run: |
    +          mkdir -p merged
    +          cp merged-report.xml merged/report.xml
    +          for dir in shards/*/; do
    +            if [[ -f "${dir}test-name" ]]; then
    +              cp "${dir}test-name" merged/test-name
    +              cp "${dir}pr-number" merged/pr-number
    +              break
    +            fi
    +          done
    +
    +      - name: Upload merged test logs
    +        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
    +        with:
    +          name: ${{ inputs.artifact-name }}
    +          path: merged/
    +
    +      - name: Prepare timing cache
    +        if: inputs.save-timing-cache
    +        id: timing-prep
    +        run: |
    +          mkdir -p server
    +          if [[ -f merged-report.xml && $(stat -c%s merged-report.xml) -gt 1024 ]]; then
    +            cp merged-report.xml server/prev-report.xml
    +            cat shards/*/gotestsum.json > server/prev-gotestsum.json 2>/dev/null || true
    +            echo "has_timing=true" >> "$GITHUB_OUTPUT"
    +          else
    +            echo "Skipping timing cache — merged report too small or missing"
    +            echo "has_timing=false" >> "$GITHUB_OUTPUT"
    +          fi
    +
    +      - name: Save test timing cache
    +        if: inputs.save-timing-cache && steps.timing-prep.outputs.has_timing == 'true' && github.ref_name == github.event.repository.default_branch
    +        uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
    +        with:
    +          path: |
    +            server/prev-report.xml
    +            server/prev-gotestsum.json
    +          key: server-test-timing-master-${{ github.run_id }}
    
  • .github/workflows/server-test-template.yml+98 1 modified
    @@ -29,6 +29,15 @@ on:
             required: false
             default: false
             type: boolean
    +      # -- Test sharding inputs (leave defaults for non-sharded callers) --
    +      shard-index:
    +        required: false
    +        type: number
    +        default: -1 # -1 = no sharding; run all tests
    +      shard-total:
    +        required: false
    +        type: number
    +        default: 1
     
     permissions:
       id-token: write
    @@ -52,6 +61,20 @@ jobs:
     
           - name: Checkout mattermost project
             uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    +
    +      - name: Restore test timing data
    +        if: inputs.shard-total > 1
    +        id: timing-cache
    +        uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
    +        with:
    +          path: |
    +            server/prev-report.xml
    +            server/prev-gotestsum.json
    +          # Always restore from master — timing is only saved on the default
    +          # branch and is stable enough for shard balancing.
    +          key: server-test-timing-master
    +          restore-keys: |
    +            server-test-timing-
           - name: Setup BUILD_IMAGE
             id: build
             run: |
    @@ -69,6 +92,8 @@ jobs:
               echo "${{ github.event.pull_request.number }}" > server/pr-number
     
           - name: Run docker compose
    +        env:
    +          POSTGRES_PASSWORD: ${{ inputs.fips-enabled && 'mostest-fips-test' || 'mostest' }}
             run: |
               cd server/build
               docker compose --ansi never run --rm start_dependencies
    @@ -78,13 +103,81 @@ jobs:
               docker compose --ansi never exec -T minio sh -c 'mkdir -p /data/mattermost-test';
               docker compose --ansi never ps
     
    +      # ── Test-level sharding ────────────────────────────────────────────
    +      # When shard-total > 1, we split tests across N parallel runners.
    +      #
    +      # Two-tier splitting strategy:
    +      #   - "Light" packages (< 5 min): assigned whole to a shard
    +      #   - "Heavy" packages (≥ 5 min, e.g. api4, app): individual tests
    +      #     are distributed across shards using -run regex filters
    +      #
    +      # See server/scripts/shard-split.js for the full algorithm.
    +      # ─────────────────────────────────────────────────────────────────────
    +
    +      - name: Setup Go for test discovery
    +        if: inputs.shard-total > 1
    +        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
    +        with:
    +          go-version: ${{ inputs.go-version }}
    +
    +      - name: Split tests across shards
    +        if: inputs.shard-total > 1
    +        id: test_split
    +        working-directory: server
    +        env:
    +          SHARD_INDEX: ${{ inputs.shard-index }}
    +          SHARD_TOTAL: ${{ inputs.shard-total }}
    +        run: |
    +          set -euo pipefail
    +
    +          # ── List all test packages ──
    +          echo "::group::Listing test packages"
    +          TE_PKGS=$(find ./public/ ./ -name '*_test.go' -not -path './enterprise/*' -not -path './cmd/mmctl/*' 2>/dev/null \
    +            | sed 's|/[^/]*$||' | sort -u \
    +            | sed 's|^\./|github.com/mattermost/mattermost/server/v8/|' \
    +            | sed 's|github.com/mattermost/mattermost/server/v8/public/|github.com/mattermost/mattermost/server/public/|')
    +          EE_PKGS=$(find ./enterprise/ -name '*_test.go' 2>/dev/null \
    +            | sed 's|/[^/]*$||' | sort -u \
    +            | sed 's|^\./|github.com/mattermost/mattermost/server/v8/|')
    +          ALL_PKGS=$(printf '%s\n%s' "$TE_PKGS" "$EE_PKGS" | grep -v '^$' | sort -u)
    +          TOTAL_PKGS=$(echo "$ALL_PKGS" | wc -l)
    +          echo "Found $TOTAL_PKGS test packages"
    +          echo "::endgroup::"
    +
    +          if [[ "$TOTAL_PKGS" -eq 0 ]]; then
    +            echo "WARNING: No test packages found"
    +            echo "has_packages=false" >> "$GITHUB_OUTPUT"
    +            exit 0
    +          fi
    +
    +          echo "$ALL_PKGS" > all-packages.txt
    +
    +          # ── Run shard solver ──
    +          node scripts/shard-split.js
    +
    +          echo "has_packages=true" >> "$GITHUB_OUTPUT"
    +
           - name: Run Tests
             env:
               BUILD_IMAGE: ${{ steps.build.outputs.BUILD_IMAGE }}
             run: |
               if [[ ${{ github.ref_name }} == 'master' && ${{ inputs.fullyparallel }} != true ]]; then
                 export RACE_MODE="-race"
               fi
    +
    +          TEST_TARGET="test-server${RACE_MODE}"
    +          BUILD_NUMBER="${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}"
    +          DOCKER_CMD="make ${TEST_TARGET}"
    +
    +          # When sharding is active, use the multi-run wrapper script.
    +          # run-shard-tests.sh detects heavy runs itself and falls back to
    +          # light-only mode when shard-heavy-runs.txt is absent or empty.
    +          if [[ "${{ inputs.shard-total }}" -gt 1 && -f server/shard-te-packages.txt ]]; then
    +            cp server/scripts/run-shard-tests.sh server/run-shard-tests.sh
    +            chmod +x server/run-shard-tests.sh
    +            DOCKER_CMD="/mattermost/server/run-shard-tests.sh"
    +          fi
    +
               docker run --net ghactions_mm-test \
                 --ulimit nofile=8096:8096 \
                 --env-file=server/build/dotenv/test.env \
    @@ -94,10 +187,13 @@ jobs:
                 --env ENABLE_FULLY_PARALLEL_TESTS="${{ inputs.fullyparallel }}" \
                 --env ENABLE_COVERAGE="${{ inputs.enablecoverage }}" \
                 --env FIPS_ENABLED="${{ inputs.fips-enabled }}" \
    +            --env RACE_MODE \
    +            --env TEST_TARGET \
    +            --env BUILD_NUMBER \
                 -v $PWD:/mattermost \
                 -w /mattermost/server \
                 $BUILD_IMAGE \
    -            make test-server$RACE_MODE BUILD_NUMBER=$GITHUB_HEAD_REF-$GITHUB_RUN_ID
    +            $DOCKER_CMD
           - name: Upload coverage to Codecov
             if: ${{ inputs.enablecoverage }}
             uses: codecov/codecov-action@v5
    @@ -122,3 +218,4 @@ jobs:
                 server/cover.out
                 server/test-name
                 server/pr-number
    +
    
  • .gitignore+4 0 modified
    @@ -163,3 +163,7 @@ docker-compose.override.yaml
     **/CLAUDE.local.md
     CLAUDE.md
     .cursorrules
    +.cursor/
    +server/prev-report.xml
    +server/prev-gotestsum.json
    +server/shard-*.txt
    
  • server/build/docker-compose.common.yml+1 1 modified
    @@ -6,7 +6,7 @@ services:
           - mm-test
         environment:
           POSTGRES_USER: mmuser
    -      POSTGRES_PASSWORD: mostest
    +      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mostest}
           POSTGRES_DB: mattermost_test
           POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256"
         command: postgres -c 'config_file=/etc/postgresql/postgresql.conf'
    
  • server/build/release.mk+4 4 modified
    @@ -14,10 +14,10 @@ ifeq ($(FIPS_ENABLED),true)
     	@echo Verifying Build Linux amd64 for FIPS
     	$(GO) version -m $(GOBIN)/$(MM_BIN_NAME) | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mattermost binary missing GOEXPERIMENT=systemcrypto" && exit 1)
     	$(GO) version -m $(GOBIN)/$(MM_BIN_NAME) | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mattermost binary missing -tags=requirefips" && exit 1)
    -	$(GO) tool nm $(GOBIN)/$(MM_BIN_NAME) | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1)
    +	$(GO) tool nm $(GOBIN)/$(MM_BIN_NAME) | grep -qE "func_go_openssl_OpenSSL_version|_mkcgo_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1)
     	$(GO) version -m $(GOBIN)/$(MMCTL_BIN_NAME) | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mmctl binary missing GOEXPERIMENT=systemcrypto" && exit 1)
     	$(GO) version -m $(GOBIN)/$(MMCTL_BIN_NAME) | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mmctl binary missing -tags=requirefips" && exit 1)
    -	$(GO) tool nm $(GOBIN)/$(MMCTL_BIN_NAME) | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1)
    +	$(GO) tool nm $(GOBIN)/$(MMCTL_BIN_NAME) | grep -qE "func_go_openssl_OpenSSL_version|_mkcgo_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1)
     endif
     
     build-linux-arm64: setup-go-work
    @@ -70,10 +70,10 @@ ifeq ($(FIPS_ENABLED),true)
     	@echo Verifying Build Linux amd64 for FIPS
     	$(GO) version -m $(GOBIN)/mattermost | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mattermost binary missing GOEXPERIMENT=systemcrypto" && exit 1)
     	$(GO) version -m $(GOBIN)/mattermost | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mattermost binary missing -tags=requirefips" && exit 1)
    -	$(GO) tool nm $(GOBIN)/mattermost | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1)
    +	$(GO) tool nm $(GOBIN)/mattermost | grep -qE "func_go_openssl_OpenSSL_version|_mkcgo_OpenSSL_version" || (echo "ERROR: FIPS mattermost binary missing OpenSSL integration" && exit 1)
     	$(GO) version -m $(GOBIN)/mmctl | grep -q "GOEXPERIMENT=systemcrypto" || (echo "ERROR: FIPS mmctl binary missing GOEXPERIMENT=systemcrypto" && exit 1)
     	$(GO) version -m $(GOBIN)/mmctl | grep "\-tags" | grep -q "requirefips" || (echo "ERROR: FIPS mmctl binary missing -tags=requirefips" && exit 1)
    -	$(GO) tool nm $(GOBIN)/mmctl | grep -q "func_go_openssl_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1)
    +	$(GO) tool nm $(GOBIN)/mmctl | grep -qE "func_go_openssl_OpenSSL_version|_mkcgo_OpenSSL_version" || (echo "ERROR: FIPS mmctl binary missing OpenSSL integration" && exit 1)
     endif
     ifeq ($(FIPS_ENABLED),true)
     	@echo Skipping Build Linux arm64 for FIPS
    
  • server/channels/api4/apitestlib.go+17 17 modified
    @@ -201,13 +201,6 @@ func setupTestHelper(tb testing.TB, dbStore store.Store, sqlSettings *model.SqlS
     
     		*cfg.TeamSettings.EnableOpenServer = true
     
    -		// Disable strict password requirements for test
    -		*cfg.PasswordSettings.MinimumLength = 5
    -		*cfg.PasswordSettings.Lowercase = false
    -		*cfg.PasswordSettings.Uppercase = false
    -		*cfg.PasswordSettings.Symbol = false
    -		*cfg.PasswordSettings.Number = false
    -
     		*cfg.ServiceSettings.ListenAddress = "localhost:0"
     	})
     
    @@ -471,37 +464,42 @@ func (th *TestHelper) InitLogin(tb testing.TB) *TestHelper {
     	th.waitForConnectivity(tb)
     
     	th.SystemAdminUser = th.CreateUser(tb)
    +	systemAdminPassword := th.SystemAdminUser.Password
     	_, appErr := th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
     	require.Nil(tb, appErr)
     	th.SystemAdminUser, appErr = th.App.GetUser(th.SystemAdminUser.Id)
     	require.Nil(tb, appErr)
     
     	th.SystemManagerUser = th.CreateUser(tb)
    +	systemManagerPassword := th.SystemManagerUser.Password
     	_, appErr = th.App.UpdateUserRoles(th.Context, th.SystemManagerUser.Id, model.SystemUserRoleId+" "+model.SystemManagerRoleId, false)
     	require.Nil(tb, appErr)
     	th.SystemManagerUser, appErr = th.App.GetUser(th.SystemManagerUser.Id)
     	require.Nil(tb, appErr)
     
     	th.TeamAdminUser = th.CreateUser(tb)
    +	teamAdminPassword := th.TeamAdminUser.Password
     	_, appErr = th.App.UpdateUserRoles(th.Context, th.TeamAdminUser.Id, model.SystemUserRoleId, false)
     	require.Nil(tb, appErr)
     	th.TeamAdminUser, appErr = th.App.GetUser(th.TeamAdminUser.Id)
     	require.Nil(tb, appErr)
     
     	th.BasicUser = th.CreateUser(tb)
    +	basicUserPassword := th.BasicUser.Password
     	th.BasicUser, appErr = th.App.GetUser(th.BasicUser.Id)
     	require.Nil(tb, appErr)
     
     	th.BasicUser2 = th.CreateUser(tb)
    +	basicUser2Password := th.BasicUser2.Password
     	th.BasicUser2, appErr = th.App.GetUser(th.BasicUser2.Id)
     	require.Nil(tb, appErr)
     
    -	// restore non hashed password for login
    -	th.SystemAdminUser.Password = "Pa$$word11"
    -	th.TeamAdminUser.Password = "Pa$$word11"
    -	th.BasicUser.Password = "Pa$$word11"
    -	th.BasicUser2.Password = "Pa$$word11"
    -	th.SystemManagerUser.Password = "Pa$$word11"
    +	// restore non-hashed password for login
    +	th.SystemAdminUser.Password = systemAdminPassword
    +	th.SystemManagerUser.Password = systemManagerPassword
    +	th.TeamAdminUser.Password = teamAdminPassword
    +	th.BasicUser.Password = basicUserPassword
    +	th.BasicUser2.Password = basicUser2Password
     
     	var wg sync.WaitGroup
     	wg.Add(2)
    @@ -693,13 +691,13 @@ func (th *TestHelper) CreateUserWithClient(tb testing.TB, client *model.Client4)
     		Nickname:  "nn_" + id,
     		FirstName: "f_" + id,
     		LastName:  "l_" + id,
    -		Password:  "Pa$$word11",
    +		Password:  model.NewTestPassword(),
     	}
     
     	ruser, _, err := client.CreateUser(context.Background(), user)
     	require.NoError(tb, err)
     
    -	ruser.Password = "Pa$$word11"
    +	ruser.Password = user.Password
     	_, err = th.App.Srv().Store().User().VerifyEmail(ruser.Id, ruser.Email)
     	if err != nil {
     		return nil
    @@ -729,11 +727,12 @@ func (th *TestHelper) CreateGuestAndClient(tb testing.TB) (*model.User, *model.C
     	id := model.NewId()
     
     	// create a guest user and add it to the basic team and public/private channels
    +	password := model.NewTestPassword()
     	guest, cgErr := th.App.CreateGuest(th.Context, &model.User{
     		Email:         "test_guest" + id + "@sample.com",
     		Username:      "guest_" + id,
     		Nickname:      "guest_" + id,
    -		Password:      "Password1",
    +		Password:      password,
     		EmailVerified: true,
     	})
     	require.Nil(tb, cgErr)
    @@ -745,9 +744,10 @@ func (th *TestHelper) CreateGuestAndClient(tb testing.TB) (*model.User, *model.C
     
     	// create a client and login the guest
     	guestClient := th.CreateClient()
    -	_, _, err := guestClient.Login(context.Background(), guest.Email, "Password1")
    +	_, _, err := guestClient.Login(context.Background(), guest.Email, password)
     	require.NoError(tb, err)
     
    +	guest.Password = password
     	return guest, guestClient
     }
     
    
  • server/channels/api4/bot_test.go+3 3 modified
    @@ -1519,7 +1519,7 @@ func TestConvertBotToUser(t *testing.T) {
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
    -	user, resp, err := th.Client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer("password")}, false)
    +	user, resp, err := th.Client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer(model.NewTestPassword())}, false)
     	require.Error(t, err)
     	CheckForbiddenStatus(t, resp)
     	require.Nil(t, user)
    @@ -1538,7 +1538,7 @@ func TestConvertBotToUser(t *testing.T) {
     		CheckBadRequestStatus(t, resp)
     		require.Nil(t, user)
     
    -		user, _, err = client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer("password")}, false)
    +		user, _, err = client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer(model.NewTestPassword())}, false)
     		require.NoError(t, err)
     		require.NotNil(t, user)
     		require.Equal(t, bot.UserId, user.Id)
    @@ -1555,7 +1555,7 @@ func TestConvertBotToUser(t *testing.T) {
     		require.NoError(t, err)
     		CheckCreatedStatus(t, resp)
     
    -		user, _, err = client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer("password")}, true)
    +		user, _, err = client.ConvertBotToUser(context.Background(), bot.UserId, &model.UserPatch{Password: model.NewPointer(model.NewTestPassword())}, true)
     		require.NoError(t, err)
     		require.NotNil(t, user)
     		require.Equal(t, bot.UserId, user.Id)
    
  • server/channels/api4/channel_bookmark_test.go+3 3 modified
    @@ -612,7 +612,7 @@ func TestEditChannelBookmark(t *testing.T) {
     
     		// create a client for basic user 2
     		client2 := th.CreateClient()
    -		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, "Pa$$word11")
    +		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, th.BasicUser2.Password)
     		require.NoError(t, lErr)
     
     		ucb, resp, err := client2.UpdateChannelBookmark(context.Background(), cb.ChannelId, cb.Id, patch)
    @@ -1013,7 +1013,7 @@ func TestUpdateChannelBookmarkSortOrder(t *testing.T) {
     
     		// create a client for basic user 2
     		client2 := th.CreateClient()
    -		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, "Pa$$word11")
    +		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, th.BasicUser2.Password)
     		require.NoError(t, lErr)
     
     		bookmarks, resp, err := client2.UpdateChannelBookmarkSortOrder(context.Background(), th.BasicChannel.Id, cb.Id, 0)
    @@ -1387,7 +1387,7 @@ func TestDeleteChannelBookmark(t *testing.T) {
     
     		// create a client for basic user 2
     		client2 := th.CreateClient()
    -		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, "Pa$$word11")
    +		_, _, lErr := client2.Login(context.Background(), th.BasicUser2.Username, th.BasicUser2.Password)
     		require.NoError(t, lErr)
     
     		dbm, resp, err := client2.DeleteChannelBookmark(context.Background(), th.BasicChannel.Id, cb.Id)
    
  • server/channels/api4/channel_category_test.go+40 27 modified
    @@ -107,10 +107,11 @@ func TestCreateCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to create a category for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -127,7 +128,7 @@ func TestCreateCategoryForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Now remove the user from the team
    @@ -494,10 +495,11 @@ func TestUpdateCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to update a category for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -514,7 +516,7 @@ func TestUpdateCategoryForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories to have valid category IDs
    @@ -643,10 +645,11 @@ func TestUpdateCategoriesForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to update categories for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -663,7 +666,7 @@ func TestUpdateCategoriesForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories to have valid category IDs
    @@ -711,15 +714,16 @@ func TestGetCategoriesForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user doesn't have permission", func(t *testing.T) {
     		// Create a new user that's not on the team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Attempt to get categories for the basic user
    @@ -730,10 +734,11 @@ func TestGetCategoriesForTeamForUser(t *testing.T) {
     
     	t.Run("should return error for a team the user is not a member of", func(t *testing.T) {
     		// Create a new user and team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -747,7 +752,7 @@ func TestGetCategoriesForTeamForUser(t *testing.T) {
     
     		// Log in as the new user
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Attempt to get categories for a team the user is not a member of
    @@ -797,15 +802,16 @@ func TestGetCategoryOrderForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user doesn't have permission", func(t *testing.T) {
     		// Create a new user that's not on the team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Attempt to get order for the basic user
    @@ -835,10 +841,11 @@ func TestGetCategoryOrderForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to get category order for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -855,7 +862,7 @@ func TestGetCategoryOrderForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Verify the user can access categories initially
    @@ -904,15 +911,16 @@ func TestUpdateCategoryOrderForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user doesn't have permission", func(t *testing.T) {
     		// Create a new user that's not on the team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories for basic user to try to update
    @@ -992,10 +1000,11 @@ func TestUpdateCategoryOrderForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to update category order for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -1012,7 +1021,7 @@ func TestUpdateCategoryOrderForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories to have a valid order
    @@ -1055,15 +1064,16 @@ func TestGetCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user doesn't have permission", func(t *testing.T) {
     		// Create a new user that's not on the team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories for basic user to get a valid category ID
    @@ -1130,10 +1140,11 @@ func TestGetCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to get category for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -1150,7 +1161,7 @@ func TestGetCategoryForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories to have valid category IDs
    @@ -1470,15 +1481,16 @@ func TestDeleteCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user doesn't have permission", func(t *testing.T) {
     		// Create a new user that's not on the team
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Get categories for basic user to get a valid category ID
    @@ -1551,10 +1563,11 @@ func TestDeleteCategoryForTeamForUser(t *testing.T) {
     
     	t.Run("should return error when user tries to delete a category for a team they're not a member of", func(t *testing.T) {
     		// Create a user
    +		password := model.NewTestPassword()
     		user, appErr := th.App.CreateUser(th.Context, &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: "user_" + model.NewId(),
    -			Password: "password",
    +			Password: password,
     		})
     		require.Nil(t, appErr)
     
    @@ -1571,7 +1584,7 @@ func TestDeleteCategoryForTeamForUser(t *testing.T) {
     
     		// Create a client and log in
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "password")
    +		_, _, err := client.Login(context.Background(), user.Email, password)
     		require.NoError(t, err)
     
     		// Create a custom category
    @@ -1598,7 +1611,7 @@ func TestDeleteCategoryForTeamForUser(t *testing.T) {
     }
     
     func setupUserForSubtest(t *testing.T, th *TestHelper) (*model.User, *model.Client4) {
    -	password := "password"
    +	password := model.NewTestPassword()
     	user, appErr := th.App.CreateUser(th.Context, &model.User{
     		Email:    th.GenerateTestEmail(),
     		Username: "user_" + model.NewId(),
    
  • server/channels/api4/channel_test.go+13 9 modified
    @@ -1418,17 +1418,18 @@ func TestCreateDirectChannelAsGuest(t *testing.T) {
     	th.App.Srv().SetLicense(model.NewTestLicense())
     
     	id := model.NewId()
    +	guestPassword := model.NewTestPassword()
     	guest := &model.User{
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      guestPassword,
     		EmailVerified: true,
     	}
     	guest, appErr := th.App.CreateGuest(th.Context, guest)
     	require.Nil(t, appErr)
     
    -	_, _, err := client.Login(context.Background(), guest.Username, "Password1")
    +	_, _, err := client.Login(context.Background(), guest.Username, guestPassword)
     	require.NoError(t, err)
     
     	t.Run("Try to created DM with not visible user", func(t *testing.T) {
    @@ -1556,17 +1557,18 @@ func TestCreateGroupChannelAsGuest(t *testing.T) {
     	th.App.Srv().SetLicense(model.NewTestLicense())
     
     	id := model.NewId()
    +	guestPassword := model.NewTestPassword()
     	guest := &model.User{
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      guestPassword,
     		EmailVerified: true,
     	}
     	guest, appErr := th.App.CreateGuest(th.Context, guest)
     	require.Nil(t, appErr)
     
    -	_, _, err := client.Login(context.Background(), guest.Username, "Password1")
    +	_, _, err := client.Login(context.Background(), guest.Username, guestPassword)
     	require.NoError(t, err)
     
     	var resp *model.Response
    @@ -2000,18 +2002,19 @@ func TestGetPublicChannelsByIdsForTeam(t *testing.T) {
     		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.AllowEmailAccounts = true })
     
     		id := model.NewId()
    +		guestPassword := model.NewTestPassword()
     		guest := &model.User{
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      guestPassword,
     			EmailVerified: true,
     		}
     		guest, appErr := th.App.CreateGuest(th.Context, guest)
     		require.Nil(t, appErr)
     
     		guestClient := th.CreateClient()
    -		_, _, err := guestClient.Login(context.Background(), guest.Username, "Password1")
    +		_, _, err := guestClient.Login(context.Background(), guest.Username, guestPassword)
     		require.NoError(t, err)
     		t.Cleanup(func() {
     			_, lErr := guestClient.Logout(context.Background())
    @@ -3997,7 +4000,7 @@ func TestUpdateChannelMemberSchemeRoles(t *testing.T) {
     		Nickname:      "nn_" + id,
     		FirstName:     "f_" + id,
     		LastName:      "l_" + id,
    -		Password:      "Pa$$word11",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     	guest, appError := th.App.CreateGuest(th.Context, guest)
    @@ -5097,11 +5100,12 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
     	th.App.Srv().SetLicense(model.NewTestLicense())
     
     	id := model.NewId()
    +	guestPassword := model.NewTestPassword()
     	guest := &model.User{
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      guestPassword,
     		EmailVerified: true,
     	}
     	guest, appErr := th.App.CreateGuest(th.Context, guest)
    @@ -5151,7 +5155,7 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
     	gc2, _, err := th.SystemAdminClient.CreateGroupChannel(context.Background(), []string{th.BasicUser.Id, th.BasicUser2.Id, u1.Id})
     	require.NoError(t, err)
     
    -	_, _, err = th.Client.Login(context.Background(), guest.Username, "Password1")
    +	_, _, err = th.Client.Login(context.Background(), guest.Username, guestPassword)
     	require.NoError(t, err)
     
     	for _, tc := range []struct {
    
  • server/channels/api4/file_test.go+1 1 modified
    @@ -867,7 +867,7 @@ func TestGetFile(t *testing.T) {
     		CheckOKStatus(t, response)
     
     		reviewerClient := th.CreateClient()
    -		_, response, err = reviewerClient.Login(context.Background(), reviewer.Email, "Pa$$word11")
    +		_, response, err = reviewerClient.Login(context.Background(), reviewer.Email, reviewer.Password)
     		require.NoError(t, err)
     		CheckOKStatus(t, response)
     
    
  • server/channels/api4/image_test.go+2 2 modified
    @@ -45,12 +45,12 @@ func TestGetImage(t *testing.T) {
     
     	t.Run("atmos/camo", func(t *testing.T) {
     		imageURL := "http://foo.bar/baz.gif"
    -		proxiedURL := "https://proxy.foo.bar/004afe2ef382eb5f30c4490f793f8a8c5b33d8a2/687474703a2f2f666f6f2e6261722f62617a2e676966"
    +		proxiedURL := "https://proxy.foo.bar/83d4d9ac78b76ce425ea67038826df867c62cc5c/687474703a2f2f666f6f2e6261722f62617a2e676966"
     
     		th.App.UpdateConfig(func(cfg *model.Config) {
     			cfg.ImageProxySettings.Enable = model.NewPointer(true)
     			cfg.ImageProxySettings.ImageProxyType = model.NewPointer("atmos/camo")
    -			cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewPointer("foo")
    +			cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewPointer("7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac")
     			cfg.ImageProxySettings.RemoteImageProxyURL = model.NewPointer("https://proxy.foo.bar")
     		})
     
    
  • server/channels/api4/post_test.go+2 2 modified
    @@ -1199,7 +1199,7 @@ func TestCreatePostPublic(t *testing.T) {
     
     	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
     
    -	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
     
     	ruser, _, err := client.CreateUser(context.Background(), &user)
     	require.NoError(t, err)
    @@ -1257,7 +1257,7 @@ func TestCreatePostAll(t *testing.T) {
     
     	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
     
    -	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
     
     	directChannel, _ := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
     
    
  • server/channels/api4/remote_cluster_test.go+4 5 modified
    @@ -188,7 +188,7 @@ func TestCreateRemoteCluster(t *testing.T) {
     			DefaultTeamId: model.NewId(),
     			Token:         model.NewId(),
     		},
    -		Password: "mysupersecret",
    +		Password: model.NewTestPassword(),
     	}
     
     	t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
    @@ -296,7 +296,7 @@ func TestRemoteClusterAcceptinvite(t *testing.T) {
     	rcAcceptInvite := &model.RemoteClusterAcceptInvite{
     		Name:          "remotecluster",
     		Invite:        "myinvitecode",
    -		Password:      "mysupersecret",
    +		Password:      model.NewTestPassword(),
     		DefaultTeamId: "",
     	}
     
    @@ -319,8 +319,7 @@ func TestRemoteClusterAcceptinvite(t *testing.T) {
     		SiteURL:  "http://localhost:8065",
     		Token:    "token",
     	}
    -	password := "mysupersecret"
    -	encrypted, err := invite.Encrypt(password)
    +	encrypted, err := invite.Encrypt(rcAcceptInvite.Password)
     	require.NoError(t, err)
     	encoded := base64.URLEncoding.EncodeToString(encrypted)
     	rcAcceptInvite.Invite = encoded
    @@ -390,7 +389,7 @@ func TestRemoteClusterAcceptinvite(t *testing.T) {
     
     func TestGenerateRemoteClusterInvite(t *testing.T) {
     	mainHelper.Parallel(t)
    -	password := "mysupersecret"
    +	password := model.NewTestPassword()
     
     	newRC := &model.RemoteCluster{
     		Name:    "remotecluster",
    
  • server/channels/api4/shared_channel_metadata_test.go+1 1 modified
    @@ -455,7 +455,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) {
     		remoteUserFromClusterB := &model.User{
     			Email:         "remote-user-b@example.com",
     			Username:      "remoteuserb" + model.NewId()[:4],
    -			Password:      "password123",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     			RemoteId:      &clusterB.RemoteId,
     		}
    
  • server/channels/api4/team_test.go+4 3 modified
    @@ -3014,15 +3014,16 @@ func TestAddTeamMembersDomainConstrained(t *testing.T) {
     	require.NoError(t, err)
     
     	// create two users on allowed domains
    +	password := model.NewTestPassword()
     	user1, _, err := client.CreateUser(context.Background(), &model.User{
     		Email:    "user@domain1.com",
    -		Password: "Pa$$word11",
    +		Password: password,
     		Username: GenerateTestUsername(),
     	})
     	require.NoError(t, err)
     	user2, _, err := client.CreateUser(context.Background(), &model.User{
     		Email:    "user@domain2.com",
    -		Password: "Pa$$word11",
    +		Password: password,
     		Username: GenerateTestUsername(),
     	})
     	require.NoError(t, err)
    @@ -3511,7 +3512,7 @@ func TestUpdateTeamMemberSchemeRoles(t *testing.T) {
     		Nickname:      "nn_" + id,
     		FirstName:     "f_" + id,
     		LastName:      "l_" + id,
    -		Password:      "Pa$$word11",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     	guest, appError := th.App.CreateGuest(th.Context, guest)
    
  • server/channels/api4/user_test.go+104 93 modified
    @@ -44,7 +44,7 @@ func TestCreateUser(t *testing.T) {
     		Id:       model.NewId(),
     		Email:    th.GenerateTestEmail(),
     		Nickname: "Corey Hulen",
    -		Password: "hello1",
    +		Password: model.NewTestPassword(),
     		Username: GenerateTestUsername(),
     	}
     	_, resp, err := th.Client.CreateUser(context.Background(), &user)
    @@ -54,7 +54,7 @@ func TestCreateUser(t *testing.T) {
     	user = model.User{
     		Email:          th.GenerateTestEmail(),
     		Nickname:       "Corey Hulen",
    -		Password:       "hello1",
    +		Password:       model.NewTestPassword(),
     		Username:       GenerateTestUsername(),
     		Roles:          model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     		EmailVerified:  true,
    @@ -88,7 +88,7 @@ func TestCreateUser(t *testing.T) {
     
     	ruser.Id = ""
     	ruser.Username = GenerateTestUsername()
    -	ruser.Password = "passwd1"
    +	ruser.Password = model.NewTestPassword()
     	_, resp, err = th.Client.CreateUser(context.Background(), ruser)
     	CheckErrorID(t, err, "app.user.save.email_exists.app_error")
     	CheckBadRequestStatus(t, resp)
    @@ -113,7 +113,7 @@ func TestCreateUser(t *testing.T) {
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableUserCreation = false })
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user2 := &model.User{Email: th.GenerateTestEmail(), Password: "Password1", Username: GenerateTestUsername(), EmailVerified: true}
    +		user2 := &model.User{Email: th.GenerateTestEmail(), Password: model.NewTestPassword(), Username: GenerateTestUsername(), EmailVerified: true}
     		ruser2, _, err2 := client.CreateUser(context.Background(), user2)
     		require.NoError(t, err2)
     		// Creating a user as sysadmin should verify the user with the EmailVerified flag.
    @@ -126,13 +126,13 @@ func TestCreateUser(t *testing.T) {
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
     		email := th.GenerateTestEmail()
    -		user2 := &model.User{Email: email, Password: "Password1", Username: GenerateTestUsername(), EmailVerified: true}
    +		user2 := &model.User{Email: email, Password: model.NewTestPassword(), Username: GenerateTestUsername(), EmailVerified: true}
     		_, _, err = client.CreateUser(context.Background(), user2)
     		require.NoError(t, err)
     		_, appErr := th.App.GetUserByUsername(user2.Username)
     		require.Nil(t, appErr)
     
    -		user3 := &model.User{Email: fmt.Sprintf(" %s  ", email), Password: "Password1", Username: GenerateTestUsername(), EmailVerified: true}
    +		user3 := &model.User{Email: fmt.Sprintf(" %s  ", email), Password: model.NewTestPassword(), Username: GenerateTestUsername(), EmailVerified: true}
     		_, resp, err = client.CreateUser(context.Background(), user3)
     		require.Error(t, err)
     		CheckBadRequestStatus(t, resp)
    @@ -146,7 +146,7 @@ func TestCreateUser(t *testing.T) {
     			Id:            model.NewId(),
     			RemoteId:      model.NewPointer(model.NewId()),
     			Email:         email,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			Username:      GenerateTestUsername(),
     			EmailVerified: true,
     		}
    @@ -174,7 +174,7 @@ func TestCreateUserPasswordValidation(t *testing.T) {
     
     	ruser := model.User{
     		Nickname:      "Corey Hulen",
    -		Password:      "hello1",
    +		Password:      model.NewTestPassword(),
     		Roles:         model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     		EmailVerified: true,
     	}
    @@ -185,9 +185,9 @@ func TestCreateUserPasswordValidation(t *testing.T) {
     		ExpectedError string
     	}{
     		"Short": {
    -			Password: strings.Repeat("x", 5),
    +			Password: strings.Repeat("x", model.PasswordFIPSMinimumLength),
     			Settings: &model.PasswordSettings{
    -				MinimumLength: model.NewPointer(5),
    +				MinimumLength: model.NewPointer(model.PasswordFIPSMinimumLength),
     				Lowercase:     model.NewPointer(false),
     				Uppercase:     model.NewPointer(false),
     				Number:        model.NewPointer(false),
    @@ -206,7 +206,7 @@ func TestCreateUserPasswordValidation(t *testing.T) {
     		"TooShort": {
     			Password: strings.Repeat("x", 2),
     			Settings: &model.PasswordSettings{
    -				MinimumLength: model.NewPointer(5),
    +				MinimumLength: model.NewPointer(model.PasswordFIPSMinimumLength),
     				Lowercase:     model.NewPointer(false),
     				Uppercase:     model.NewPointer(false),
     				Number:        model.NewPointer(false),
    @@ -275,7 +275,7 @@ func TestCreateUserPasswordValidation(t *testing.T) {
     			ExpectedError: "model.user.is_valid.pwd_uppercase_number_symbol.app_error",
     		},
     		"Everything": {
    -			Password: "asdASD!@#123",
    +			Password: "asdASDasd!@#123",
     			Settings: &model.PasswordSettings{
     				Lowercase: model.NewPointer(true),
     				Uppercase: model.NewPointer(true),
    @@ -313,10 +313,9 @@ func TestCreateUserAudit(t *testing.T) {
     	th := SetupWithServerOptions(t, options)
     
     	email := th.GenerateTestEmail()
    -	password := "this_is_the_password"
     	user := model.User{
     		Email:    email,
    -		Password: password,
    +		Password: model.NewTestPassword(),
     		Username: GenerateTestUsername(),
     	}
     	_, resp, err := th.Client.CreateUser(context.Background(), &user)
    @@ -334,7 +333,7 @@ func TestCreateUserAudit(t *testing.T) {
     	require.NotEmpty(t, data)
     
     	require.Contains(t, string(data), email)
    -	require.NotContains(t, string(data), password)
    +	require.NotContains(t, string(data), user.Password)
     }
     
     func TestUserLoginAudit(t *testing.T) {
    @@ -397,15 +396,15 @@ func TestCreateUserInputFilter(t *testing.T) {
     		})
     
     		th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.com", Password: "Password1", Username: GenerateTestUsername()}
    +			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.com", Password: model.NewTestPassword(), Username: GenerateTestUsername()}
     			u, _, err := client.CreateUser(context.Background(), user) // we need the returned created user to use its Id for deletion.
     			require.NoError(t, err)
     			_, err = client.PermanentDeleteUser(context.Background(), u.Id)
     			require.NoError(t, err)
     		}, "ValidUser")
     
     		th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername()}
    +			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: model.NewTestPassword(), Username: GenerateTestUsername()}
     			_, resp, err := client.CreateUser(context.Background(), user)
     			require.Error(t, err)
     			CheckBadRequestStatus(t, resp)
    @@ -439,7 +438,7 @@ func TestCreateUserInputFilter(t *testing.T) {
     		})
     
     		th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername(), AuthService: "ldap"}
    +			user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: model.NewTestPassword(), Username: GenerateTestUsername(), AuthService: "ldap"}
     			_, resp, err := th.Client.CreateUser(context.Background(), user)
     			require.Error(t, err)
     			CheckBadRequestStatus(t, resp)
    @@ -456,7 +455,7 @@ func TestCreateUserInputFilter(t *testing.T) {
     
     		th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
     			emailAddr := "foobar+testinvalidrole@mattermost.com"
    -			user := &model.User{Email: emailAddr, Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"}
    +			user := &model.User{Email: emailAddr, Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: "system_user system_admin"}
     			_, _, err := client.CreateUser(context.Background(), user)
     			require.NoError(t, err)
     			ruser, appErr := th.App.GetUserByEmail(emailAddr)
    @@ -472,7 +471,7 @@ func TestCreateUserInputFilter(t *testing.T) {
     			*cfg.TeamSettings.EnableOpenServer = true
     			*cfg.TeamSettings.EnableUserCreation = true
     		})
    -		user := &model.User{Id: "AAAAAAAAAAAAAAAAAAAAAAAAAA", Email: "foobar+testinvalidid@mattermost.com", Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"}
    +		user := &model.User{Id: "AAAAAAAAAAAAAAAAAAAAAAAAAA", Email: "foobar+testinvalidid@mattermost.com", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: "system_user system_admin"}
     		_, resp, err := client.CreateUser(context.Background(), user)
     		require.Error(t, err)
     		CheckBadRequestStatus(t, resp)
    @@ -484,7 +483,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("CreateWithTokenHappyPath", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
     			model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
    @@ -510,7 +509,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	})
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
     			model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
    @@ -536,7 +535,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	}, "CreateWithTokenHappyPath")
     
     	t.Run("NoToken", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
     			model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
    @@ -553,7 +552,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	})
     
     	t.Run("TokenExpired", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     		timeNow := time.Now()
     		past49Hours := timeNow.Add(-49*time.Hour).UnixNano() / int64(time.Millisecond)
     		token := model.NewToken(
    @@ -574,7 +573,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	})
     
     	t.Run("WrongToken", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		_, resp, err := th.Client.CreateUserWithToken(context.Background(), &user, "wrong")
     		require.Error(t, err)
    @@ -588,7 +587,7 @@ func TestCreateUserWithToken(t *testing.T) {
     			th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableUserCreation = enableUserCreation })
     		}()
     
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
    @@ -611,7 +610,7 @@ func TestCreateUserWithToken(t *testing.T) {
     		enableUserCreation := th.App.Config().TeamSettings.EnableUserCreation
     		defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableUserCreation = enableUserCreation })
     
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
    @@ -632,7 +631,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	}, "EnableUserCreationDisable")
     
     	t.Run("EnableOpenServerDisable", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
    @@ -661,7 +660,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	})
     
     	t.Run("Validate inviter user has permissions on channels he is inviting", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
     		channelIdWithoutPermissions := th.BasicPrivateChannel2.Id
     		channelIds := th.BasicChannel.Id + " " + channelIdWithoutPermissions
     		token := model.NewToken(
    @@ -700,7 +699,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	})
     
     	t.Run("Validate inviterUser permissions on channels he is inviting, when inviting guests", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Guest User", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Guest User", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
     		channelIdWithoutPermissions := th.BasicPrivateChannel2.Id
     		channelIds := th.BasicChannel.Id + " " + channelIdWithoutPermissions
     		token := model.NewToken(
    @@ -749,7 +748,7 @@ func TestCreateUserWebSocketEvent(t *testing.T) {
     		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.AllowEmailAccounts = true })
     
     		id := model.NewId()
    -		guestPassword := "Pa$$word11"
    +		guestPassword := model.NewTestPassword()
     		guest := &model.User{
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
    @@ -775,7 +774,7 @@ func TestCreateUserWebSocketEvent(t *testing.T) {
     		guestWSClient := th.CreateConnectedWebSocketClientWithClient(t, guestClient)
     		userWSClient := th.CreateConnectedWebSocketClient(t)
     
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		inviteId := th.BasicTeam.InviteId
     
    @@ -813,7 +812,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("CreateWithInviteIdHappyPath", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		inviteId := th.BasicTeam.InviteId
     
    @@ -828,7 +827,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     		CheckUserSanitization(t, ruser)
     	})
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		inviteId := th.BasicTeam.InviteId
     
    @@ -844,7 +843,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	}, "CreateWithInviteIdHappyPath")
     
     	t.Run("GroupConstrainedTeam", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		th.BasicTeam.GroupConstrained = model.NewPointer(true)
     		team, appErr := th.App.UpdateTeam(th.BasicTeam)
    @@ -863,7 +862,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	})
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		th.BasicTeam.GroupConstrained = model.NewPointer(true)
     		team, appErr := th.App.UpdateTeam(th.BasicTeam)
    @@ -882,7 +881,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	}, "GroupConstrainedTeam")
     
     	t.Run("WrongInviteId", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		inviteId := model.NewId()
     
    @@ -893,15 +892,15 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	})
     
     	t.Run("NoInviteId", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		_, _, err := th.Client.CreateUserWithInviteId(context.Background(), &user, "")
     		require.Error(t, err)
     		assert.ErrorContains(t, err, "invite ID is required")
     	})
     
     	t.Run("ExpiredInviteId", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		inviteId := th.BasicTeam.InviteId
     
    @@ -915,7 +914,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	})
     
     	t.Run("EnableUserCreationDisable", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		enableUserCreation := th.App.Config().TeamSettings.EnableUserCreation
     		defer func() {
    @@ -932,7 +931,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     		CheckErrorID(t, err, "api.user.create_user.signup_email_disabled.app_error")
     	})
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		enableUserCreation := th.App.Config().TeamSettings.EnableUserCreation
     		defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableUserCreation = enableUserCreation })
    @@ -947,7 +946,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	}, "EnableUserCreationDisable")
     
     	t.Run("EnableOpenServerDisable", func(t *testing.T) {
    -		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +		user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     		enableOpenServer := th.App.Config().TeamSettings.EnableOpenServer
     		defer func() {
    @@ -1268,7 +1267,7 @@ func TestGetUserByEmail(t *testing.T) {
     	userWithSlash, _, err := th.SystemAdminClient.CreateUser(context.Background(), &model.User{
     		Email:    "email/with/slashes@example.com",
     		Username: GenerateTestUsername(),
    -		Password: "Pa$$word11",
    +		Password: model.NewTestPassword(),
     	})
     	require.NoError(t, err)
     
    @@ -2287,7 +2286,7 @@ func TestPatchUser(t *testing.T) {
     	})
     
     	patch := &model.UserPatch{}
    -	patch.Password = model.NewPointer("testpassword")
    +	patch.Password = model.NewPointer(model.NewTestPassword())
     	patch.Nickname = model.NewPointer("Joram Wilander")
     	patch.FirstName = model.NewPointer("Joram")
     	patch.LastName = model.NewPointer("Wilander")
    @@ -2436,7 +2435,7 @@ func TestUserUnicodeNames(t *testing.T) {
     			FirstName: "Andrew\u202e",
     			LastName:  "\ufeffWiggin",
     			Nickname:  "Ender\u2028 Wiggin",
    -			Password:  "hello1",
    +			Password:  model.NewTestPassword(),
     			Username:  "\ufeffwiggin77",
     			Roles:     model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     		}
    @@ -2533,7 +2532,7 @@ func TestUpdateUserAuth(t *testing.T) {
     	_, err = th.App.Srv().Store().User().VerifyEmail(user2.Id, user2.Email)
     	require.NoError(t, err)
     
    -	_, _, err = th.SystemAdminClient.Login(context.Background(), user2.Email, "Pa$$word11")
    +	_, _, err = th.SystemAdminClient.Login(context.Background(), user2.Email, user2.Password)
     	require.NoError(t, err)
     
     	userAuth.AuthData = user.AuthData
    @@ -2872,7 +2871,7 @@ func TestUpdateUserActive(t *testing.T) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     		user, err := th.App.CreateGuest(th.Context, guest)
    @@ -2899,7 +2898,7 @@ func TestUpdateUserActive(t *testing.T) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     		user, appErr := th.App.CreateGuest(th.Context, guest)
    @@ -2921,7 +2920,7 @@ func TestUpdateUserActive(t *testing.T) {
     		ldapUser := &model.User{
     			Email:         "ldapuser@mattermost-customer.com",
     			Username:      "ldapuser",
    -			Password:      "Password123",
    +			Password:      model.NewTestPassword(),
     			AuthService:   model.UserAuthServiceLdap,
     			EmailVerified: true,
     		}
    @@ -3106,7 +3105,7 @@ func TestGetUsersWithoutTeam(t *testing.T) {
     	user, _, err := th.Client.CreateUser(context.Background(), &model.User{
     		Username: "a000000000" + model.NewId(),
     		Email:    "success+" + model.NewId() + "@simulator.amazonses.com",
    -		Password: "Password1",
    +		Password: model.NewTestPassword(),
     	})
     	require.NoError(t, err)
     	th.LinkUserToTeam(t, user, th.BasicTeam)
    @@ -3118,7 +3117,7 @@ func TestGetUsersWithoutTeam(t *testing.T) {
     	user2, _, err := th.Client.CreateUser(context.Background(), &model.User{
     		Username: "a000000001" + model.NewId(),
     		Email:    "success+" + model.NewId() + "@simulator.amazonses.com",
    -		Password: "Password1",
    +		Password: model.NewTestPassword(),
     	})
     	require.NoError(t, err)
     	defer func() {
    @@ -3628,15 +3627,16 @@ func TestUpdateUserPassword(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	password := "newpassword1"
    +	password := model.NewTestPassword()
     	_, err := th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, th.BasicUser.Password, password)
     	require.NoError(t, err)
     
     	resp, err := th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, password, "")
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
    -	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, password, "junk")
    +	newInvalidPassword := "junk"
    +	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, password, newInvalidPassword)
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
    @@ -3648,7 +3648,7 @@ func TestUpdateUserPassword(t *testing.T) {
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
    -	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, "junk", password)
    +	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, model.NewTestPassword(), password)
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
    @@ -3672,20 +3672,22 @@ func TestUpdateUserPassword(t *testing.T) {
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.MaximumLoginAttempts = 2 })
     
     	// Fail twice
    -	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, "badpwd", "newpwd")
    +	badPassword := model.NewTestPassword()
    +	newPassword := model.NewTestPassword()
    +	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, badPassword, newPassword)
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
    -	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, "badpwd", "newpwd")
    +	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, badPassword, newPassword)
     	require.Error(t, err)
     	CheckBadRequestStatus(t, resp)
     
     	// Should fail because account is locked out
    -	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, th.BasicUser.Password, "newpwd")
    +	resp, err = th.Client.UpdateUserPassword(context.Background(), th.BasicUser.Id, th.BasicUser.Password, newPassword)
     	CheckErrorID(t, err, "api.user.check_user_login_attempts.too_many.app_error")
     	CheckUnauthorizedStatus(t, resp)
     
     	// System admin can update another user's password
    -	adminSetPassword := "pwdsetbyadmin"
    +	adminSetPassword := model.NewTestPassword()
     	_, err = th.SystemAdminClient.UpdateUserPassword(context.Background(), th.BasicUser.Id, "", adminSetPassword)
     	require.NoError(t, err)
     
    @@ -4139,7 +4141,7 @@ func TestVerifyUserEmail(t *testing.T) {
     	th := Setup(t)
     
     	email := th.GenerateTestEmail()
    -	user := model.User{Email: email, Nickname: "Darth Vader", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
    +	user := model.User{Email: email, Nickname: "Darth Vader", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId}
     
     	ruser, _, _ := th.Client.CreateUser(context.Background(), &user)
     
    @@ -4324,24 +4326,26 @@ func TestLogin(t *testing.T) {
     		botUser, _, err := th.SystemAdminClient.GetUser(context.Background(), bot.UserId, "")
     		require.NoError(t, err)
     
    -		_, err = th.SystemAdminClient.UpdateUserPassword(context.Background(), bot.UserId, "", "password")
    +		botPassword := model.NewTestPassword()
    +		_, err = th.SystemAdminClient.UpdateUserPassword(context.Background(), bot.UserId, "", botPassword)
     		require.NoError(t, err)
     
    -		_, _, err = th.Client.Login(context.Background(), botUser.Email, "password")
    +		_, _, err = th.Client.Login(context.Background(), botUser.Email, botPassword)
     		CheckErrorID(t, err, "api.user.login.bot_login_forbidden.app_error")
     	})
     
     	t.Run("remote user login rejected", func(t *testing.T) {
     		email := th.GenerateTestEmail()
    -		user := model.User{Email: email, Nickname: "Darth Vader", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId, RemoteId: model.NewPointer("remote-id")}
    +		remoteUserPassword := model.NewTestPassword()
    +		user := model.User{Email: email, Nickname: "Darth Vader", Password: remoteUserPassword, Username: GenerateTestUsername(), Roles: model.SystemAdminRoleId + " " + model.SystemUserRoleId, RemoteId: model.NewPointer("remote-id")}
     		ruser, appErr := th.App.CreateUser(th.Context, &user)
     		require.Nil(t, appErr)
     
     		// remote user cannot reset password
    -		_, err := th.SystemAdminClient.UpdateUserPassword(context.Background(), ruser.Id, "", "password")
    +		_, err := th.SystemAdminClient.UpdateUserPassword(context.Background(), ruser.Id, "", model.NewTestPassword())
     		require.Error(t, err)
     
    -		_, _, err = th.Client.Login(context.Background(), ruser.Email, "hello1")
    +		_, _, err = th.Client.Login(context.Background(), ruser.Email, remoteUserPassword)
     		CheckErrorID(t, err, "api.user.login.remote_users.login.error")
     	})
     
    @@ -4657,7 +4661,7 @@ func TestSwitchAccount(t *testing.T) {
     				CurrentService: model.UserAuthServiceGitlab,
     				NewService:     model.UserAuthServiceEmail,
     				Email:          th.BasicUser.Email,
    -				NewPassword:    "NewPassword123!",
    +				NewPassword:    model.NewTestPassword(),
     			}
     
     			_, resp, err := th.Client.SwitchAccountType(context.Background(), sr)
    @@ -6019,14 +6023,15 @@ func TestGetUsersByStatus(t *testing.T) {
     	}, false)
     	require.Nil(t, appErr, "failed to create channel")
     
    +	userPassword := model.NewTestPassword()
     	createUserWithStatus := func(username string, status string) *model.User {
     		id := model.NewId()
     
     		user, err := th.App.CreateUser(th.Context, &model.User{
     			Email:    "success+" + id + "@simulator.amazonses.com",
     			Username: "un_" + username + "_" + id,
     			Nickname: "nn_" + id,
    -			Password: "Password1",
    +			Password: userPassword,
     		})
     		require.Nil(t, err, "failed to create user")
     
    @@ -6053,7 +6058,7 @@ func TestGetUsersByStatus(t *testing.T) {
     	dndUser2 := createUserWithStatus("dnd2", model.StatusDnd)
     
     	client := th.CreateClient()
    -	_, _, err := client.Login(context.Background(), onlineUser2.Username, "Password1")
    +	_, _, err := client.Login(context.Background(), onlineUser2.Username, userPassword)
     	require.NoError(t, err)
     
     	t.Run("sorting by status then alphabetical", func(t *testing.T) {
    @@ -6152,28 +6157,30 @@ func TestLoginErrorMessage(t *testing.T) {
     	_, err := th.Client.Logout(context.Background())
     	require.NoError(t, err)
     
    +	wrongPassword := model.NewTestPassword()
    +
     	// Email and Username enabled
     	th.App.UpdateConfig(func(cfg *model.Config) {
     		*cfg.EmailSettings.EnableSignInWithEmail = true
     		*cfg.EmailSettings.EnableSignInWithUsername = true
     	})
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_email_username")
     
     	// Email enabled
     	th.App.UpdateConfig(func(cfg *model.Config) {
     		*cfg.EmailSettings.EnableSignInWithEmail = true
     		*cfg.EmailSettings.EnableSignInWithUsername = false
     	})
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_email")
     
     	// Username enabled
     	th.App.UpdateConfig(func(cfg *model.Config) {
     		*cfg.EmailSettings.EnableSignInWithEmail = false
     		*cfg.EmailSettings.EnableSignInWithUsername = true
     	})
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_username")
     
     	// SAML/SSO enabled
    @@ -6197,7 +6204,7 @@ func TestLoginErrorMessage(t *testing.T) {
     		*cfg.SamlSettings.PositionAttribute = ""
     		*cfg.SamlSettings.LocaleAttribute = ""
     	})
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_sso")
     }
     
    @@ -6212,15 +6219,16 @@ func TestLoginLockout(t *testing.T) {
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.MaximumLoginAttempts = 3 })
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true })
     
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	wrongPassword := model.NewTestPassword()
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_email_username")
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_email_username")
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.login.invalid_credentials_email_username")
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.check_user_login_attempts.too_many.app_error")
    -	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, "wrong")
    +	_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, wrongPassword)
     	CheckErrorID(t, err, "api.user.check_user_login_attempts.too_many.app_error")
     
     	// Check if lock is active
    @@ -6388,8 +6396,9 @@ func TestVerifyUserEmailWithoutToken(t *testing.T) {
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
     		email := th.GenerateTestEmail()
    -		user := model.User{Email: email, Nickname: "Darth Vader", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    -		ruser, _, _ := th.Client.CreateUser(context.Background(), &user)
    +		user := model.User{Email: email, Nickname: "Darth Vader", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +		ruser, _, err := th.Client.CreateUser(context.Background(), &user)
    +		require.NoError(t, err)
     
     		vuser, _, err := client.VerifyUserEmailWithoutToken(context.Background(), ruser.Id)
     		require.NoError(t, err)
    @@ -6402,8 +6411,9 @@ func TestVerifyUserEmailWithoutToken(t *testing.T) {
     		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true })
     
     		email := th.GenerateTestEmail()
    -		user := model.User{Email: email, Nickname: "Test User", Password: "password123", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    -		ruser, _, _ := th.Client.CreateUser(context.Background(), &user)
    +		user := model.User{Email: email, Nickname: "Test User", Password: model.NewTestPassword(), Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
    +		ruser, _, err := th.Client.CreateUser(context.Background(), &user)
    +		require.NoError(t, err)
     
     		// Set some NotifyProps to ensure we have data to verify is preserved
     		ruser.NotifyProps = map[string]string{
    @@ -6418,7 +6428,7 @@ func TestVerifyUserEmailWithoutToken(t *testing.T) {
     		// Set up MFA secret for the user
     		secret, appErr := th.App.GenerateMfaSecret(ruser.Id)
     		require.Nil(t, appErr)
    -		err := th.Server.Store().User().UpdateMfaSecret(ruser.Id, secret.Secret)
    +		err = th.Server.Store().User().UpdateMfaSecret(ruser.Id, secret.Secret)
     		require.NoError(t, err)
     
     		// Verify the user has a password hash and MFA secret in the database
    @@ -6658,7 +6668,7 @@ func TestConvertUserToBot(t *testing.T) {
     	require.Nil(t, bot)
     
     	th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
    -		user := model.User{Email: th.GenerateTestEmail(), Username: GenerateTestUsername(), Password: "password"}
    +		user := model.User{Email: th.GenerateTestEmail(), Username: GenerateTestUsername(), Password: model.NewTestPassword()}
     
     		ruser, resp, err := client.CreateUser(context.Background(), &user)
     		require.NoError(t, err)
    @@ -6760,7 +6770,7 @@ func TestUpdatePassword(t *testing.T) {
     	th := Setup(t)
     
     	t.Run("Forbidden when request performed by system user on a system admin", func(t *testing.T) {
    -		res, err := th.Client.UpdatePassword(context.Background(), th.SystemAdminUser.Id, "Pa$$word11", "foobar")
    +		res, err := th.Client.UpdatePassword(context.Background(), th.SystemAdminUser.Id, th.SystemAdminUser.Password, model.NewTestPassword())
     		require.Error(t, err)
     		CheckForbiddenStatus(t, res)
     	})
    @@ -6769,16 +6779,16 @@ func TestUpdatePassword(t *testing.T) {
     		th.AddPermissionToRole(t, model.PermissionSysconsoleWriteUserManagementUsers.Id, model.SystemUserRoleId)
     		defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleWriteUserManagementUsers.Id, model.SystemUserRoleId)
     
    -		res, _ := th.Client.UpdatePassword(context.Background(), th.TeamAdminUser.Id, "Pa$$word11", "foobar")
    +		res, _ := th.Client.UpdatePassword(context.Background(), th.TeamAdminUser.Id, th.TeamAdminUser.Password, model.NewTestPassword())
     		CheckOKStatus(t, res)
     
    -		res, err := th.Client.UpdatePassword(context.Background(), th.SystemAdminUser.Id, "Pa$$word11", "foobar")
    +		res, err := th.Client.UpdatePassword(context.Background(), th.SystemAdminUser.Id, th.SystemAdminUser.Password, model.NewTestPassword())
     		require.Error(t, err)
     		CheckForbiddenStatus(t, res)
     	})
     
     	t.Run("OK when request performed by system admin, even if requested user is system admin", func(t *testing.T) {
    -		res, _ := th.SystemAdminClient.UpdatePassword(context.Background(), th.SystemAdminUser.Id, "Pa$$word11", "foobar")
    +		res, _ := th.SystemAdminClient.UpdatePassword(context.Background(), th.SystemAdminUser.Id, th.SystemAdminUser.Password, model.NewTestPassword())
     		CheckOKStatus(t, res)
     	})
     }
    @@ -6796,7 +6806,7 @@ func TestUpdatePasswordAudit(t *testing.T) {
     	options := []app.Option{app.WithLicense(model.NewTestLicense("advanced_logging"))}
     	th := SetupWithServerOptions(t, options)
     
    -	password := "this_is_the_password"
    +	password := model.NewTestPassword()
     	th.LoginBasic(t)
     	resp, err := th.Client.UpdatePassword(context.Background(), th.BasicUser.Id, th.BasicUser.Password, password)
     	require.NoError(t, err)
    @@ -8290,7 +8300,7 @@ func TestGetUsersWithInvalidEmails(t *testing.T) {
     	user := model.User{
     		Email:    "ben@invalid.mattermost.com",
     		Nickname: "Ben Cooke",
    -		Password: "hello1",
    +		Password: model.NewTestPassword(),
     		Username: GenerateTestUsername(),
     		Roles:    model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     	}
    @@ -9208,6 +9218,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     	th.SetupLdapConfig()
     
     	th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
    +	wrongPassword := model.NewTestPassword()
     
     	t.Run("Reset password failed attempts for regular user", func(t *testing.T) {
     		client := th.CreateClient()
    @@ -9219,7 +9230,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     		user := th.CreateUser(t)
     
     		for i := 0; i < *maxAttempts; i++ {
    -			_, _, err := client.Login(context.Background(), user.Email, "wrongpassword")
    +			_, _, err := client.Login(context.Background(), user.Email, wrongPassword)
     			require.Error(t, err)
     		}
     
    @@ -9298,7 +9309,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     		user := th.CreateUser(t)
     
     		for i := 0; i < *maxAttempts; i++ {
    -			_, _, err := client.Login(context.Background(), user.Email, "wrongpassword")
    +			_, _, err := client.Login(context.Background(), user.Email, wrongPassword)
     			require.Error(t, err)
     		}
     
    @@ -9330,7 +9341,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     		user := th.CreateUser(t)
     
     		for i := 0; i < *maxAttempts; i++ {
    -			_, _, err := client.Login(context.Background(), user.Email, "wrongpassword")
    +			_, _, err := client.Login(context.Background(), user.Email, wrongPassword)
     			require.Error(t, err)
     		}
     
    @@ -9366,7 +9377,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     		require.Nil(t, appErr)
     
     		for i := 0; i < *maxAttempts; i++ {
    -			_, _, err := client.Login(context.Background(), sysadmin.Email, "wrongpassword")
    +			_, _, err := client.Login(context.Background(), sysadmin.Email, wrongPassword)
     			require.Error(t, err)
     		}
     
    @@ -9398,7 +9409,7 @@ func TestResetPasswordFailedAttempts(t *testing.T) {
     		require.Nil(t, appErr)
     
     		for i := 0; i < *maxAttempts; i++ {
    -			_, _, err := client.Login(context.Background(), sysadmin.Email, "wrongpassword")
    +			_, _, err := client.Login(context.Background(), sysadmin.Email, wrongPassword)
     			require.Error(t, err)
     		}
     
    
  • server/channels/api4/websocket_test.go+4 4 modified
    @@ -286,14 +286,14 @@ func TestWebSocketStatuses(t *testing.T) {
     	team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewRandomTeamName() + "a", Email: "test@nowhere.com", Type: model.TeamOpen}
     	rteam, _, _ := client.CreateTeam(context.Background(), &team)
     
    -	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
    +	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: model.NewTestPassword()}
     	ruser, _, err := client.CreateUser(context.Background(), &user)
     	require.NoError(t, err)
     	th.LinkUserToTeam(t, ruser, rteam)
     	_, err = th.App.Srv().Store().User().VerifyEmail(ruser.Id, ruser.Email)
     	require.NoError(t, err)
     
    -	user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
    +	user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: model.NewTestPassword()}
     	ruser2, _, err := client.CreateUser(context.Background(), &user2)
     	require.NoError(t, err)
     	th.LinkUserToTeam(t, ruser2, rteam)
    @@ -587,7 +587,7 @@ func TestWebSocketMFAEnforcement(t *testing.T) {
     
     		// Login user (this should work for initial authentication)
     		client := th.CreateClient()
    -		_, _, err := client.Login(context.Background(), user.Email, "Pa$$word11")
    +		_, _, err := client.Login(context.Background(), user.Email, user.Password)
     		require.NoError(t, err)
     
     		// Create WebSocket client - initial connection succeeds, but subsequent API requests require completed MFA
    @@ -633,7 +633,7 @@ func TestWebSocketMFAEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:    th.GenerateTestEmail(),
     			Username: model.NewUsername(),
    -			Password: "password123",
    +			Password: model.NewTestPassword(),
     		}
     		ruser, _, err := th.Client.CreateUser(context.Background(), user)
     		require.NoError(t, err)
    
  • server/channels/app/app_test.go+3 3 modified
    @@ -63,9 +63,9 @@ func TestUnitUpdateConfig(t *testing.T) {
     
     	require.False(t, th.App.IsConfigReadOnly())
     
    -	var called int32
    +	var called atomic.Int32
     	th.App.AddConfigListener(func(old, current *model.Config) {
    -		atomic.AddInt32(&called, 1)
    +		called.Add(1)
     		assert.Equal(t, prev, *old.ServiceSettings.SiteURL)
     		assert.Equal(t, "http://foo.com", *current.ServiceSettings.SiteURL)
     	})
    @@ -75,7 +75,7 @@ func TestUnitUpdateConfig(t *testing.T) {
     	})
     
     	// callback should be called once
    -	assert.Equal(t, int32(1), atomic.LoadInt32(&called))
    +	assert.Equal(t, int32(1), called.Load())
     }
     
     func TestDoAdvancedPermissionsMigration(t *testing.T) {
    
  • server/channels/app/authentication_test.go+23 15 modified
    @@ -76,7 +76,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
     		*cfg.ServiceSettings.EnableMultifactorAuthentication = true
     	})
     
    -	password := "newpassword1"
    +	password := model.NewTestPassword()
     	appErr := th.App.UpdatePassword(th.Context, th.BasicUser, password)
     	require.Nil(t, appErr)
     
    @@ -107,7 +107,7 @@ func TestCheckPasswordAndAllCriteria(t *testing.T) {
     		}{
     			{
     				name:          "should not breach max. login attempts when password is wrong",
    -				password:      "wrong password",
    +				password:      model.NewTestPassword(),
     				expectedErrID: "api.user.check_user_password.invalid.app_error",
     			},
     			{
    @@ -186,6 +186,9 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
     	require.Nil(t, appErr)
     	user.AuthData = &authData
     
    +	validPassword := model.NewTestPassword()
    +	wrongPassword := model.NewTestPassword()
    +
     	testCases := []struct {
     		name          string
     		password      string
    @@ -194,26 +197,26 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
     	}{
     		{
     			name:          "valid password",
    -			password:      "password",
    +			password:      validPassword,
     			expectedErrID: "",
     			mockDoLogin: func() {
    -				mockLdap.Mock.On("DoLogin", th.Context, authData, "password").Return(user, nil)
    +				mockLdap.Mock.On("DoLogin", th.Context, authData, validPassword).Return(user, nil)
     			},
     		},
     		{
     			name:          "invalid password",
    -			password:      "wrongpassword",
    +			password:      wrongPassword,
     			expectedErrID: "api.user.check_user_password.invalid.app_error",
     			mockDoLogin: func() {
    -				mockLdap.Mock.On("DoLogin", th.Context, authData, "wrongpassword").Return(nil, &model.AppError{Id: "ent.ldap.do_login.invalid_password.app_error"})
    +				mockLdap.Mock.On("DoLogin", th.Context, authData, wrongPassword).Return(nil, &model.AppError{Id: "ent.ldap.do_login.invalid_password.app_error"})
     			},
     		},
     		{
     			name:          "too many login attempts",
    -			password:      "wrongpassword",
    +			password:      wrongPassword,
     			expectedErrID: "api.user.check_user_login_attempts.too_many_ldap.app_error",
     			mockDoLogin: func() {
    -				mockLdap.Mock.On("DoLogin", th.Context, authData, "wrongpassword").Return(nil, &model.AppError{Id: "ent.ldap.do_login.invalid_password.app_error"}).Once()
    +				mockLdap.Mock.On("DoLogin", th.Context, authData, wrongPassword).Return(nil, &model.AppError{Id: "ent.ldap.do_login.invalid_password.app_error"}).Once()
     			},
     		},
     	}
    @@ -231,7 +234,7 @@ func TestCheckLdapUserPasswordAndAllCriteria(t *testing.T) {
     			// Simulate failed login attempts if necessary
     			if tc.expectedErrID == "api.user.check_user_login_attempts.too_many_ldap.app_error" {
     				for range maxFailedLoginAttempts - 1 {
    -					_, appErr = th.App.checkLdapUserPasswordAndAllCriteria(th.Context, ldapUser, "wrongpassword", "")
    +					_, appErr = th.App.checkLdapUserPasswordAndAllCriteria(th.Context, ldapUser, wrongPassword, "")
     					require.NotNil(t, appErr)
     					require.Equal(t, "ent.ldap.do_login.invalid_password.app_error", appErr.Id)
     				}
    @@ -291,6 +294,9 @@ func TestCheckLdapUserPasswordConcurrency(t *testing.T) {
     	require.Nil(t, appErr)
     	user.AuthData = &authData
     
    +	wrongPassword := model.NewTestPassword()
    +	validPassword := model.NewTestPassword()
    +
     	t.Run("validate concurrent failed attempts to bypass checks", func(t *testing.T) {
     		testCases := []struct {
     			name                 string
    @@ -301,14 +307,14 @@ func TestCheckLdapUserPasswordConcurrency(t *testing.T) {
     		}{
     			{
     				name:                 "should not breach max. login attempts when password is wrong",
    -				password:             "wrong password",
    +				password:             wrongPassword,
     				mfaToken:             "",
     				doLoginExpectedErrID: "ent.ldap.do_login.invalid_password.app_error",
     				expectedErrID:        "ent.ldap.do_login.invalid_password.app_error",
     			},
     			{
     				name:                 "should not breach max. login attempts when MFA is wrong",
    -				password:             "password",
    +				password:             validPassword,
     				mfaToken:             "123456",
     				doLoginExpectedErrID: "",
     				expectedErrID:        "api.user.check_user_mfa.bad_code.app_error",
    @@ -368,7 +374,7 @@ func TestCheckLdapUserPasswordConcurrency(t *testing.T) {
     func TestCheckUserPassword(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
    -	pwd := "testPassword123$"
    +	pwd := model.NewTestPassword()
     	pwdBcryptBytes, err := bcrypt.GenerateFromPassword([]byte(pwd), 10)
     	require.NoError(t, err)
     	pwdBcrypt := string(pwdBcryptBytes)
    @@ -404,10 +410,12 @@ func TestCheckUserPassword(t *testing.T) {
     		require.Nil(t, err)
     	})
     
    +	wrongPassword := model.NewTestPassword()
    +
     	t.Run("invalid password", func(t *testing.T) {
     		user := createUserWithHash(pwdPBKDF2)
     
    -		err := th.App.checkUserPassword(user, "wrongpassword", false)
    +		err := th.App.checkUserPassword(user, wrongPassword, false)
     		require.NotNil(t, err)
     		require.Equal(t, "api.user.check_user_password.invalid.app_error", err.Id)
     
    @@ -437,7 +445,7 @@ func TestCheckUserPassword(t *testing.T) {
     	t.Run("password migration fails with invalid password", func(t *testing.T) {
     		user := createUserWithHash(pwdBcrypt)
     
    -		err := th.App.checkUserPassword(user, "wrongpassword", false)
    +		err := th.App.checkUserPassword(user, wrongPassword, false)
     		require.NotNil(t, err)
     		require.Equal(t, "api.user.check_user_password.invalid.app_error", err.Id)
     
    @@ -500,7 +508,7 @@ func TestCheckUserPassword(t *testing.T) {
     func TestMigratePassword(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
    -	pwd := "testPassword123$"
    +	pwd := model.NewTestPassword()
     	pwdBcryptBytes, err := bcrypt.GenerateFromPassword([]byte(pwd), 10)
     	require.NoError(t, err)
     	pwdBcrypt := string(pwdBcryptBytes)
    
  • server/channels/app/bot_test.go+5 5 modified
    @@ -718,7 +718,7 @@ func TestNotifySysadminsBotOwnerDisabled(t *testing.T) {
     	sysadmin1 := model.User{
     		Email:    "sys1@example.com",
     		Nickname: "nn_sysadmin1",
    -		Password: "hello1",
    +		Password: model.NewTestPassword(),
     		Username: "un_sysadmin1",
     		Roles:    model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     	}
    @@ -730,7 +730,7 @@ func TestNotifySysadminsBotOwnerDisabled(t *testing.T) {
     	sysadmin2 := model.User{
     		Email:    "sys2@example.com",
     		Nickname: "nn_sysadmin2",
    -		Password: "hello1",
    +		Password: model.NewTestPassword(),
     		Username: "un_sysadmin2",
     		Roles:    model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     	}
    @@ -744,7 +744,7 @@ func TestNotifySysadminsBotOwnerDisabled(t *testing.T) {
     		Email:    "user1@example.com",
     		Username: "user1_disabled",
     		Nickname: "user1",
    -		Password: "Password1",
    +		Password: model.NewTestPassword(),
     	})
     	require.Nil(t, err, "failed to create user")
     
    @@ -753,7 +753,7 @@ func TestNotifySysadminsBotOwnerDisabled(t *testing.T) {
     		Email:    "user2@example.com",
     		Username: "user2_disabled",
     		Nickname: "user2",
    -		Password: "Password1",
    +		Password: model.NewTestPassword(),
     	})
     	require.Nil(t, err, "failed to create user")
     
    @@ -902,7 +902,7 @@ func TestConvertUserToBot(t *testing.T) {
     		oauthUser := &model.User{
     			Email:         "oauth_user@example.com",
     			Username:      "oauth_user",
    -			Password:      "password",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    
  • server/channels/app/channel_test.go+14 14 modified
    @@ -1377,7 +1377,7 @@ func TestGetChannelMembersTimezones(t *testing.T) {
     	_, appErr = th.App.UpdateUser(th.Context, user2, false)
     	require.Nil(t, appErr)
     
    -	user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser, appErr := th.App.CreateUser(th.Context, &user3)
     	require.Nil(t, appErr)
     
    @@ -1391,7 +1391,7 @@ func TestGetChannelMembersTimezones(t *testing.T) {
     	_, appErr = th.App.UpdateUser(th.Context, ruser, false)
     	require.Nil(t, appErr)
     
    -	user4 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user4 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser, _ = th.App.CreateUser(th.Context, &user4)
     	_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
     	require.NotNil(t, appErr, "user should not be able to join the channel without being in the team.")
    @@ -1534,7 +1534,7 @@ func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("from guest to user", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1548,7 +1548,7 @@ func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from user to guest", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1562,7 +1562,7 @@ func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from user to admin", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1576,7 +1576,7 @@ func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from guest to guest plus custom", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1593,7 +1593,7 @@ func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("a guest cant have user role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1612,7 +1612,7 @@ func TestUpdateChannelMemberRolesRequireUser(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("empty roles string requires user or guest scheme role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1632,7 +1632,7 @@ func TestUpdateChannelMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("admin role requires user or guest scheme role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1647,7 +1647,7 @@ func TestUpdateChannelMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("valid user and admin roles update succeeds", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1664,7 +1664,7 @@ func TestUpdateChannelMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("removing admin role while keeping user role succeeds", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1917,7 +1917,7 @@ func TestAddUserToChannel(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser1, _ := th.App.CreateUser(th.Context, &user1)
     	defer func() {
     		appErr := th.App.PermanentDeleteUser(th.Context, &user1)
    @@ -1957,7 +1957,7 @@ func TestAddUserToChannel(t *testing.T) {
     	require.Nil(t, appErr)
     	require.False(t, cm1.SchemeAdmin)
     
    -	user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser2, _ := th.App.CreateUser(th.Context, &user2)
     	defer func() {
     		appErr = th.App.PermanentDeleteUser(th.Context, &user2)
    @@ -2011,7 +2011,7 @@ func TestRemoveUserFromChannel(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser, _ := th.App.CreateUser(th.Context, &user)
     	defer func() {
     		appErr := th.App.PermanentDeleteUser(th.Context, ruser)
    
  • server/channels/app/email/email_batching_test.go+3 3 modified
    @@ -126,9 +126,9 @@ func TestCheckPendingNotifications(t *testing.T) {
     	}})
     	require.NoError(t, nErr)
     
    -	var wasCalled int32
    +	var wasCalled atomic.Int32
     	job.checkPendingNotifications(time.Unix(10050, 0), func(string, []*batchedNotification) {
    -		atomic.StoreInt32(&wasCalled, int32(1))
    +		wasCalled.Store(int32(1))
     	})
     
     	// A hack to check whether the handler was called.
    @@ -138,7 +138,7 @@ func TestCheckPendingNotifications(t *testing.T) {
     	// We do a check outside the email handler, because otherwise, failing from
     	// inside the handler doesn't let the .Go() function exit cleanly, and it gets
     	// stuck during server shutdown, trying to wait for the goroutine to exit
    -	require.Equal(t, int32(0), atomic.LoadInt32(&wasCalled), "email handler should not have been called")
    +	require.Equal(t, int32(0), wasCalled.Load(), "email handler should not have been called")
     
     	require.Nil(t, job.pendingNotifications[th.BasicUser.Id])
     	require.Empty(t, job.pendingNotifications[th.BasicUser.Id], "should've remove queued post since user acted")
    
  • server/channels/app/email/helper_test.go+1 7 modified
    @@ -88,12 +88,6 @@ func setupTestHelper(s store.Store, tb testing.TB) *TestHelper {
     	*config.TeamSettings.MaxUsersPerTeam = 50
     	*config.RateLimitSettings.Enable = false
     	*config.TeamSettings.EnableOpenServer = true
    -	// Disable strict password requirements for test
    -	*config.PasswordSettings.MinimumLength = 5
    -	*config.PasswordSettings.Lowercase = false
    -	*config.PasswordSettings.Uppercase = false
    -	*config.PasswordSettings.Symbol = false
    -	*config.PasswordSettings.Number = false
     	_, _, err = configStore.Set(config)
     	require.NoError(tb, err)
     
    @@ -247,7 +241,7 @@ func (th *TestHelper) CreateUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     
    
  • server/channels/app/helper_test.go+1 10 modified
    @@ -168,15 +168,6 @@ func setupTestHelper(dbStore store.Store, sqlStore *sqlstore.SqlStore, sqlSettin
     
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true })
     
    -	// Disable strict password requirements for test
    -	th.App.UpdateConfig(func(cfg *model.Config) {
    -		*cfg.PasswordSettings.MinimumLength = 5
    -		*cfg.PasswordSettings.Lowercase = false
    -		*cfg.PasswordSettings.Uppercase = false
    -		*cfg.PasswordSettings.Symbol = false
    -		*cfg.PasswordSettings.Number = false
    -	})
    -
     	tb.Cleanup(func() {
     		if th.IncludeCacheLayer {
     			// Clean all the caches
    @@ -381,7 +372,7 @@ func (th *TestHelper) CreateUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     
    
  • server/channels/app/import_functions_test.go+1 1 modified
    @@ -946,7 +946,7 @@ func TestImportImportUser(t *testing.T) {
     		assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
     
     		// Check Password and AuthData together.
    -		data.Password = model.NewPointer("PasswordTest")
    +		data.Password = model.NewPointer(model.NewTestPassword())
     		appErr = th.App.importUser(th.Context, &data, false)
     		require.NotNil(t, appErr, "Should have failed to import invalid user.")
     
    
  • server/channels/app/notification_email_test.go+2 2 modified
    @@ -762,7 +762,7 @@ func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     		Locale:        "en",
     	}
    @@ -879,7 +879,7 @@ func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     		Locale:        "en",
     	}
    
  • server/channels/app/notification_push_test.go+1 1 modified
    @@ -1748,7 +1748,7 @@ func BenchmarkPushNotificationThroughput(b *testing.B) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     		sess1 := &model.Session{
    
  • server/channels/app/password/hashers/fips_default.go+9 0 added
    @@ -0,0 +1,9 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +//go:build !requirefips
    +
    +package hashers
    +
    +// fipsMinKeyLength is 0 in non-FIPS builds, meaning no minimum is enforced.
    +const fipsMinKeyLength = 0
    
  • server/channels/app/password/hashers/fips.go+12 0 added
    @@ -0,0 +1,12 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +//go:build requirefips
    +
    +package hashers
    +
    +import "github.com/mattermost/mattermost/server/public/model"
    +
    +// fipsMinKeyLength is the minimum PBKDF2 key length enforced by the OpenSSL 3.x
    +// FIPS provider (NIST SP 800-132: PBKDF password must be at least 14 bytes).
    +const fipsMinKeyLength = model.PasswordFIPSMinimumLength
    
  • server/channels/app/password/hashers/hashers_dev.go+9 3 modified
    @@ -11,10 +11,10 @@ import "testing"
     // alternative. This should only be set via SetTestHasher and only in test code.
     var testHasher PasswordHasher
     
    -// getLatestHasher returns the hasher to use for password operations.
    +// GetLatestHasher returns the hasher to use for password operations.
     // In non-production builds, if a test hasher has been set via SetTestHasher,
     // it will be returned instead of the production latestHasher.
    -func getLatestHasher() PasswordHasher {
    +func GetLatestHasher() PasswordHasher {
     	if testHasher != nil {
     		return testHasher
     	}
    @@ -42,13 +42,19 @@ func SetTestHasher(h PasswordHasher) {
     	testHasher = h
     }
     
    +// fastTestHasherWorkFactor is the minimum iteration count that the OpenSSL 3.x
    +// FIPS provider accepts for PBKDF2 (NIST SP 800-132 floor). Using anything
    +// lower causes PKCS5_PBKDF2_HMAC to fail with "invalid iteration count" in
    +// FIPS mode. This is still ~600x faster than the production default (600000).
    +const fastTestHasherWorkFactor = 1000
    +
     // FastTestHasher returns a PBKDF2 hasher configured with minimal work factor
     // for use in tests while still producing valid password hashes that can be
     // verified.
     //
     // This function is only available in non-production builds.
     func FastTestHasher() PasswordHasher {
    -	h, err := NewPBKDF2(1, defaultKeyLength)
    +	h, err := NewPBKDF2(fastTestHasherWorkFactor, defaultKeyLength)
     	if err != nil {
     		panic("failed to create fast test hasher: " + err.Error())
     	}
    
  • server/channels/app/password/hashers/hashers_dev_test.go+15 14 modified
    @@ -16,7 +16,7 @@ func TestSetTestHasher(t *testing.T) {
     	SetTestHasher(nil)
     
     	// Hash should work with nil testHasher (uses latestHasher)
    -	hash1, err := Hash("password")
    +	hash1, err := Hash("T3stP@ssw0rd!xYz")
     	require.NoError(t, err)
     	require.NotEmpty(t, hash1)
     
    @@ -26,56 +26,57 @@ func TestSetTestHasher(t *testing.T) {
     	defer SetTestHasher(nil)
     
     	// Hash should now use the fast test hasher
    -	hash2, err := Hash("password")
    +	hash2, err := Hash("T3stP@ssw0rd!xYz")
     	require.NoError(t, err)
     	require.NotEmpty(t, hash2)
     
     	// Verify the hash was generated with different parameters
    -	// The fast hasher uses work factor 1, so the hash should contain w=1
    -	require.Contains(t, hash2, "w=1")
    +	// The fast hasher uses the FIPS-minimum work factor.
    +	require.Contains(t, hash2, ",w=1000,")
     
     	// Verify the password can be verified against the hash
     	hasher, phc, err := GetHasherFromPHCString(hash2)
     	require.NoError(t, err)
    -	require.NoError(t, hasher.CompareHashAndPassword(phc, "password"))
    -	require.Error(t, hasher.CompareHashAndPassword(phc, "wrongpassword"))
    +	require.NoError(t, hasher.CompareHashAndPassword(phc, "T3stP@ssw0rd!xYz"))
    +	require.Error(t, hasher.CompareHashAndPassword(phc, "Wr0ngP@ssw0rd!!"))
     }
     
     func TestFastTestHasher(t *testing.T) {
     	hasher := FastTestHasher()
     	require.NotNil(t, hasher)
     
    -	// Verify it's a PBKDF2 hasher with work factor 1
    +	// Verify it's a PBKDF2 hasher with the FIPS-minimum work factor.
     	pbkdf2Hasher, ok := hasher.(PBKDF2)
     	require.True(t, ok, "FastTestHasher should return a PBKDF2 hasher")
    -	require.Equal(t, 1, pbkdf2Hasher.workFactor)
    +	require.Equal(t, fastTestHasherWorkFactor, pbkdf2Hasher.workFactor)
     
     	// Test that it produces valid hashes
    -	hash, err := hasher.Hash("testpassword")
    +	testPassword := "T3stP@ssw0rd!xYz"
    +	hash, err := hasher.Hash(testPassword)
     	require.NoError(t, err)
    -	require.Contains(t, hash, "w=1")
    +	require.Contains(t, hash, ",w=1000,")
     
     	// Verify the hash can be validated
     	parsedHasher, phc, err := GetHasherFromPHCString(hash)
     	require.NoError(t, err)
    -	require.NoError(t, parsedHasher.CompareHashAndPassword(phc, "testpassword"))
    +	require.NoError(t, parsedHasher.CompareHashAndPassword(phc, testPassword))
     }
     
     func TestGetLatestHasher(t *testing.T) {
     	// Ensure testHasher starts as nil
     	SetTestHasher(nil)
     
     	// Without test hasher, should return latestHasher
    -	require.Equal(t, latestHasher, getLatestHasher())
    +	require.Equal(t, latestHasher, GetLatestHasher())
     
     	// Set a fast test hasher
     	fastHasher := FastTestHasher()
     	SetTestHasher(fastHasher)
     	defer SetTestHasher(nil)
     
     	// With test hasher set, should return the test hasher
    -	require.Equal(t, fastHasher, getLatestHasher())
    -	require.NotEqual(t, latestHasher, getLatestHasher())
    +	require.Equal(t, fastHasher, GetLatestHasher())
    +	require.NotEqual(t, latestHasher, GetLatestHasher())
     }
     
     func BenchmarkFastTestHasher(b *testing.B) {
    
  • server/channels/app/password/hashers/hashers.go+9 8 modified
    @@ -44,6 +44,7 @@ import (
     	"fmt"
     	"strings"
     
    +	"github.com/mattermost/mattermost/server/public/model"
     	"github.com/mattermost/mattermost/server/v8/channels/app/password/phcparser"
     )
     
    @@ -74,18 +75,18 @@ type PasswordHasher interface {
     }
     
     const (
    -	// Maximum password length for all password hashers
    -	PasswordMaxLengthBytes = 72
    +	// Maximum password length for all password hashers.
    +	PasswordMaxLengthBytes = model.UserPasswordMaxLength
     )
     
     var (
     	// latestHasher is the hasher currently in use.
     	// Any password hashed with a different hasher must be migrated to this one.
     	latestHasher PasswordHasher = DefaultPBKDF2()
     
    -	// ErrPasswordTooLong is the error returned when the provided password is
    -	// longer than [PasswordMaxLengthBytes].
    -	ErrPasswordTooLong = fmt.Errorf("password too long; maximum length in bytes: %d", PasswordMaxLengthBytes)
    +	// ErrPasswordTooLong wraps [model.ErrPasswordTooLong] so that errors.Is
    +	// matches both the hashers-level and model-level sentinel.
    +	ErrPasswordTooLong = fmt.Errorf("hashers: %w", model.ErrPasswordTooLong)
     
     	// ErrMismatchedHashAndPassword is the error returned when the provided
     	// password does not match the stored hash
    @@ -135,17 +136,17 @@ func GetHasherFromPHCString(phcString string) (PasswordHasher, phcparser.PHC, er
     
     // Hash hashes the provided password with the latest hashing method.
     func Hash(password string) (string, error) {
    -	return getLatestHasher().Hash(password)
    +	return GetLatestHasher().Hash(password)
     }
     
     // CompareHashAndPassword compares the parsed [phcparser.PHC] and the provided
     // password using the latest hashing method.
     func CompareHashAndPassword(phc phcparser.PHC, password string) error {
    -	return getLatestHasher().CompareHashAndPassword(phc, password)
    +	return GetLatestHasher().CompareHashAndPassword(phc, password)
     }
     
     // IsLatestHasher verifies that the provided hasher is the latest one. This
     // function is useful for identifying stored hashes that require a migration.
     func IsLatestHasher(hasher PasswordHasher) bool {
    -	return getLatestHasher() == hasher
    +	return GetLatestHasher() == hasher
     }
    
  • server/channels/app/password/hashers/hashers_production.go+2 2 modified
    @@ -5,8 +5,8 @@
     
     package hashers
     
    -// getLatestHasher returns the hasher to use for password operations.
    +// GetLatestHasher returns the hasher to use for password operations.
     // In production builds, this always returns the latestHasher.
    -func getLatestHasher() PasswordHasher {
    +func GetLatestHasher() PasswordHasher {
     	return latestHasher
     }
    
  • server/channels/app/password/hashers/pbkdf2.go+8 0 modified
    @@ -199,6 +199,14 @@ func (p PBKDF2) CompareHashAndPassword(hash phcparser.PHC, password string) erro
     		return ErrPasswordTooLong
     	}
     
    +	// Under FIPS, the OpenSSL PBKDF2 implementation requires keys of at least
    +	// fipsMinKeyLength bytes. A password shorter than this can never match a
    +	// stored hash, so return ErrMismatchedHashAndPassword directly rather than
    +	// letting PBKDF2 fail with a generic crypto error.
    +	if fipsMinKeyLength > 0 && len(password) < fipsMinKeyLength {
    +		return ErrMismatchedHashAndPassword
    +	}
    +
     	// Validate parameters
     	if !p.IsPHCValid(hash) {
     		return fmt.Errorf("the stored password does not comply with the PBKDF2 parser's PHC serialization")
    
  • server/channels/app/password/hashers/pbkdf2_test.go+22 17 modified
    @@ -11,6 +11,7 @@ import (
     	"strings"
     	"testing"
     
    +	"github.com/mattermost/mattermost/server/public/model"
     	"github.com/mattermost/mattermost/server/v8/channels/app/password/phcparser"
     	"github.com/stretchr/testify/require"
     )
    @@ -56,38 +57,42 @@ func TestPBKDF2CompareHashAndPassword(t *testing.T) {
     		storedPwd   string
     		inputPwd    string
     		expectedErr error
    +		skipFIPS    bool
     	}{
     		{
    -
    -			"empty password",
    -			"",
    -			"",
    -			nil,
    +			testName:    "empty password",
    +			storedPwd:   "",
    +			inputPwd:    "",
    +			expectedErr: nil,
    +			skipFIPS:    true,
     		},
     		{
    -			"same password",
    -			"one password",
    -			"one password",
    -			nil,
    +			testName:    "same password",
    +			storedPwd:   "one password!!!",
    +			inputPwd:    "one password!!!",
    +			expectedErr: nil,
     		},
     		{
    -			"different password",
    -			"one password",
    -			"another password",
    -			ErrMismatchedHashAndPassword,
    +			testName:    "different password",
    +			storedPwd:   "one password!!!",
    +			inputPwd:    "another password",
    +			expectedErr: ErrMismatchedHashAndPassword,
     		},
     		{
    -			"password too long",
    -			"stored password",
    -			string(passwordTooLong),
    -			ErrPasswordTooLong,
    +			testName:    "password too long",
    +			storedPwd:   "stored password",
    +			inputPwd:    string(passwordTooLong),
    +			expectedErr: ErrPasswordTooLong,
     		},
     	}
     
     	hasher := DefaultPBKDF2()
     
     	for _, tc := range testCases {
     		t.Run(tc.testName, func(t *testing.T) {
    +			if tc.skipFIPS && model.FIPSEnabled {
    +				t.Skip("skipping under FIPS: PBKDF2 requires keys >= 14 bytes")
    +			}
     			storedPHCStr, err := hasher.Hash(tc.storedPwd)
     			require.NoError(t, err)
     
    
  • server/channels/app/platform/config_test.go+2 5 modified
    @@ -181,12 +181,9 @@ func TestIsFirstUserAccountThunderingHerd(t *testing.T) {
     
     			var wg sync.WaitGroup
     			for i := 0; i < te.concurrentRequest; i++ {
    -				wg.Add(1)
    -
    -				go func() {
    -					defer wg.Done()
    +				wg.Go(func() {
     					require.Equal(t, te.result, th.Service.IsFirstUserAccount())
    -				}()
    +				})
     			}
     
     			wg.Wait()
    
  • server/channels/app/platform/helper_test.go+2 11 modified
    @@ -186,15 +186,6 @@ func setupTestHelper(dbStore store.Store, dbSettings *model.SqlSettings, enterpr
     		*cfg.TeamSettings.EnableOpenServer = true
     	})
     
    -	// Disable strict password requirements for test
    -	th.Service.UpdateConfig(func(cfg *model.Config) {
    -		*cfg.PasswordSettings.MinimumLength = 5
    -		*cfg.PasswordSettings.Lowercase = false
    -		*cfg.PasswordSettings.Uppercase = false
    -		*cfg.PasswordSettings.Symbol = false
    -		*cfg.PasswordSettings.Number = false
    -	})
    -
     	if enterprise {
     		th.Service.SetLicense(model.NewTestLicense())
     	} else {
    @@ -251,7 +242,7 @@ func (th *TestHelper) CreateUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     		Roles:         model.SystemUserRoleId,
     	}
    @@ -269,7 +260,7 @@ func (th *TestHelper) CreateAdmin(tb testing.TB) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     		Roles:         model.SystemAdminRoleId + " " + model.SystemUserRoleId,
     	}
    
  • server/channels/app/platform/web_conn.go+2 4 modified
    @@ -407,11 +407,9 @@ func (wc *WebConn) SetSession(v *model.Session) {
     // is ready to send/receive messages.
     func (wc *WebConn) Pump() {
     	var wg sync.WaitGroup
    -	wg.Add(1)
    -	go func() {
    -		defer wg.Done()
    +	wg.Go(func() {
     		wc.writePump()
    -	}()
    +	})
     
     	wg.Add(1)
     	go wc.pluginPostedConsumer(&wg)
    
  • server/channels/app/plugin_api_test.go+17 17 modified
    @@ -219,7 +219,7 @@ func TestPluginAPIGetUserPreferences(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -253,7 +253,7 @@ func TestPluginAPIDeleteUserPreferences(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -274,7 +274,7 @@ func TestPluginAPIDeleteUserPreferences(t *testing.T) {
     
     	user2, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user2" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -315,7 +315,7 @@ func TestPluginAPIUpdateUserPreferences(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -367,7 +367,7 @@ func TestPluginAPIGetUsers(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -378,7 +378,7 @@ func TestPluginAPIGetUsers(t *testing.T) {
     
     	user2, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user2" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -389,7 +389,7 @@ func TestPluginAPIGetUsers(t *testing.T) {
     
     	user3, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user3" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -400,7 +400,7 @@ func TestPluginAPIGetUsers(t *testing.T) {
     
     	user4, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user4" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -467,7 +467,7 @@ func TestPluginAPIGetUsersByIds(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -478,7 +478,7 @@ func TestPluginAPIGetUsersByIds(t *testing.T) {
     
     	user2, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user2" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -489,7 +489,7 @@ func TestPluginAPIGetUsersByIds(t *testing.T) {
     
     	user3, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user3" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -535,7 +535,7 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
     
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user1" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -546,7 +546,7 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
     
     	user2, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user2" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -557,7 +557,7 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
     
     	user3, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user3" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -568,7 +568,7 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
     
     	user4, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     		Username: "user4" + model.NewId(),
     	})
     	require.Nil(t, err)
    @@ -662,7 +662,7 @@ func TestPluginAPIUserCustomStatus(t *testing.T) {
     	user1, err := th.App.CreateUser(th.Context, &model.User{
     		Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
     		Username: "user_" + model.NewId(),
    -		Password: "password",
    +		Password: model.NewTestPassword(),
     	})
     	require.Nil(t, err)
     	defer func() {
    @@ -2242,7 +2242,7 @@ func TestAPIMetrics(t *testing.T) {
     			Email:       model.NewId() + "success+test@example.com",
     			Nickname:    "Darth Vader1",
     			Username:    "vader" + model.NewId(),
    -			Password:    "passwd1",
    +			Password:    model.NewTestPassword(),
     			AuthService: "",
     		}
     		_, appErr := th.App.CreateUser(th.Context, user1)
    
  • server/channels/app/plugin_hooks_test.go+7 7 modified
    @@ -718,7 +718,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	err := th.App.UpdatePassword(th.Context, th.BasicUser, "hunter2")
    +	err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
     	assert.Nil(t, err, "Error updating user password: %s", err)
     	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
     		[]string{
    @@ -757,7 +757,7 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	err := th.App.UpdatePassword(th.Context, th.BasicUser, "hunter2")
    +	err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
     
     	assert.Nil(t, err, "Error updating user password: %s", err)
     
    @@ -799,7 +799,7 @@ func TestUserHasLoggedIn(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	err := th.App.UpdatePassword(th.Context, th.BasicUser, "hunter2")
    +	err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
     
     	assert.Nil(t, err, "Error updating user password: %s", err)
     
    @@ -876,7 +876,7 @@ func TestUserHasBeenDeactivated(t *testing.T) {
     		Email:    "success+test@example.com",
     		Nickname: "testnickname",
     		Username: "testusername",
    -		Password: "testpassword",
    +		Password: model.NewTestPassword(),
     	}
     
     	_, err := th.App.CreateUser(th.Context, user)
    @@ -925,7 +925,7 @@ func TestUserHasBeenCreated(t *testing.T) {
     		Email:    "success+test@example.com",
     		Nickname: "testnickname",
     		Username: "testusername",
    -		Password: "testpassword",
    +		Password: model.NewTestPassword(),
     	}
     	_, err := th.App.CreateUser(th.Context, user)
     	require.Nil(t, err)
    @@ -1113,7 +1113,7 @@ func TestActiveHooks(t *testing.T) {
     			Email:    "success+test@example.com",
     			Nickname: "testnickname",
     			Username: "testusername",
    -			Password: "testpassword",
    +			Password: model.NewTestPassword(),
     		}
     		_, appErr := th.App.CreateUser(th.Context, user1)
     		require.Nil(t, appErr)
    @@ -1218,7 +1218,7 @@ func TestHookMetrics(t *testing.T) {
     			Email:       "success+test@example.com",
     			Nickname:    "testnickname",
     			Username:    "testusername",
    -			Password:    "testpassword",
    +			Password:    model.NewTestPassword(),
     			AuthService: "",
     		}
     		_, appErr := th.App.CreateUser(th.Context, user1)
    
  • server/channels/app/post_metadata_test.go+1 1 modified
    @@ -853,7 +853,7 @@ func TestPreparePostForClientWithImageProxy(t *testing.T) {
     			*cfg.ImageProxySettings.Enable = true
     			*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
     			*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
    -			*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
    +			*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
     		})
     
     		th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
    
  • server/channels/app/post_test.go+15 17 modified
    @@ -168,9 +168,7 @@ func TestCreatePostDeduplicate(t *testing.T) {
     
     		// Launch a goroutine to make the first CreatePost call that will get delayed
     		// by the plugin above.
    -		wg.Add(1)
    -		go func() {
    -			defer wg.Done()
    +		wg.Go(func() {
     			var appErr *model.AppError
     			post, appErr = th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
     				UserId:        th.BasicUser.Id,
    @@ -180,7 +178,7 @@ func TestCreatePostDeduplicate(t *testing.T) {
     			}, session.Id, true)
     			require.Nil(t, appErr)
     			require.Equal(t, post.Message, "plugin delayed")
    -		}()
    +		})
     
     		// Give the goroutine above a chance to start and get delayed by the plugin.
     		time.Sleep(2 * time.Second)
    @@ -734,6 +732,8 @@ func TestImageProxy(t *testing.T) {
     
     	th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
     
    +	testHMACKey := model.NewTestPassword()
    +
     	for name, tc := range map[string]struct {
     		ProxyType              string
     		ProxyURL               string
    @@ -745,31 +745,31 @@ func TestImageProxy(t *testing.T) {
     		"atmos/camo": {
     			ProxyType:              model.ImageProxyTypeAtmosCamo,
     			ProxyURL:               "https://127.0.0.1",
    -			ProxyOptions:           "foo",
    +			ProxyOptions:           testHMACKey,
     			ImageURL:               "http://mydomain.com/myimage",
     			ProxiedRemovedImageURL: "http://mydomain.com/myimage",
     			ProxiedImageURL:        "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
     		},
     		"atmos/camo_SameSite": {
     			ProxyType:              model.ImageProxyTypeAtmosCamo,
     			ProxyURL:               "https://127.0.0.1",
    -			ProxyOptions:           "foo",
    +			ProxyOptions:           testHMACKey,
     			ImageURL:               "http://mymattermost.com/myimage",
     			ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
     			ProxiedImageURL:        "http://mymattermost.com/myimage",
     		},
     		"atmos/camo_PathOnly": {
     			ProxyType:              model.ImageProxyTypeAtmosCamo,
     			ProxyURL:               "https://127.0.0.1",
    -			ProxyOptions:           "foo",
    +			ProxyOptions:           testHMACKey,
     			ImageURL:               "/myimage",
     			ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
     			ProxiedImageURL:        "http://mymattermost.com/myimage",
     		},
     		"atmos/camo_EmptyImageURL": {
     			ProxyType:              model.ImageProxyTypeAtmosCamo,
     			ProxyURL:               "https://127.0.0.1",
    -			ProxyOptions:           "foo",
    +			ProxyOptions:           testHMACKey,
     			ImageURL:               "",
     			ProxiedRemovedImageURL: "",
     			ProxiedImageURL:        "",
    @@ -956,7 +956,7 @@ func TestCreatePost(t *testing.T) {
     			*cfg.ImageProxySettings.Enable = true
     			*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
     			*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
    -			*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
    +			*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
     		})
     
     		th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
    @@ -1361,7 +1361,7 @@ func TestPatchPost(t *testing.T) {
     			*cfg.ImageProxySettings.Enable = true
     			*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
     			*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
    -			*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
    +			*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
     		})
     
     		th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
    @@ -1816,7 +1816,7 @@ func TestUpdatePost(t *testing.T) {
     			*cfg.ImageProxySettings.Enable = true
     			*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
     			*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
    -			*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
    +			*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
     		})
     
     		th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
    @@ -3054,7 +3054,7 @@ func TestFillInPostProps(t *testing.T) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     		guest, err := th.App.CreateGuest(th.Context, guest)
    @@ -3088,7 +3088,7 @@ func TestFillInPostProps(t *testing.T) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      "un_" + id,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     		guest, err := th.App.CreateGuest(th.Context, guest)
    @@ -3536,12 +3536,10 @@ func TestCollapsedThreadFetch(t *testing.T) {
     
     		// we introduce a race to trigger an unexpected error from the db side.
     		var wg sync.WaitGroup
    -		wg.Add(1)
    -		go func() {
    -			defer wg.Done()
    +		wg.Go(func() {
     			err := th.Server.Store().Post().PermanentDeleteByUser(th.Context, user1.Id)
     			require.NoError(t, err)
    -		}()
    +		})
     
     		require.NotPanics(t, func() {
     			// We're only testing that this doesn't panic, not checking the error
    
  • server/channels/app/shared_channel_global_user_sync_self_referential_test.go+24 24 modified
    @@ -453,7 +453,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		// - Cursor updates only when sync is successful
     		EnsureCleanState(t, th, ss)
     
    -		var syncAttempts int32
    +		var syncAttempts atomic.Int32
     		var failureMode atomic.Bool
     		failureMode.Store(false)
     		var syncHandler *SelfReferentialSyncHandler
    @@ -462,7 +462,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     			switch r.URL.Path {
     			case "/api/v4/remotecluster/msg":
    -				atomic.AddInt32(&syncAttempts, 1)
    +				syncAttempts.Add(1)
     
     				if failureMode.Load() {
     					w.WriteHeader(http.StatusInternalServerError)
    @@ -520,7 +520,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     
     		// Wait for first sync
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) > 0
    +			return syncAttempts.Load() > 0
     		}, 5*time.Second, 100*time.Millisecond, "Should have attempted sync")
     
     		// Verify cursor was updated
    @@ -539,13 +539,13 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		require.NoError(t, err)
     
     		// Second sync - should fail
    -		initialAttempts := atomic.LoadInt32(&syncAttempts)
    +		initialAttempts := syncAttempts.Load()
     		err = service.HandleSyncAllUsersForTesting(selfCluster)
     		require.NoError(t, err) // The method itself shouldn't error, just the remote call
     
     		// Wait for failed sync attempt
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) > initialAttempts
    +			return syncAttempts.Load() > initialAttempts
     		}, 5*time.Second, 100*time.Millisecond, "Should have attempted sync")
     
     		// Verify cursor was NOT updated on failure
    @@ -557,13 +557,13 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		failureMode.Store(false)
     
     		// Third sync - should succeed and update cursor
    -		preSuccessAttempts := atomic.LoadInt32(&syncAttempts)
    +		preSuccessAttempts := syncAttempts.Load()
     		err = service.HandleSyncAllUsersForTesting(selfCluster)
     		require.NoError(t, err)
     
     		// Wait for successful sync
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) > preSuccessAttempts
    +			return syncAttempts.Load() > preSuccessAttempts
     		}, 5*time.Second, 100*time.Millisecond, "Should have attempted sync")
     
     		// Verify cursor was updated after successful sync
    @@ -579,12 +579,12 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		// - Ensures cursor is only updated when flag is enabled
     		EnsureCleanState(t, th, ss)
     
    -		var syncMessageCount int32
    +		var syncMessageCount atomic.Int32
     
     		// Create test HTTP server
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				atomic.AddInt32(&syncMessageCount, 1)
    +				syncMessageCount.Add(1)
     			}
     			writeOKResponse(w)
     		}))
    @@ -622,13 +622,13 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		err = th.App.ReloadConfig()
     		require.NoError(t, err)
     
    -		atomic.StoreInt32(&syncMessageCount, 0)
    +		syncMessageCount.Store(0)
     		err = service.HandleSyncAllUsersForTesting(selfCluster)
     		require.NoError(t, err)
     
     		// Verify no sync messages were sent
     		require.Never(t, func() bool {
    -			return atomic.LoadInt32(&syncMessageCount) > 0
    +			return syncMessageCount.Load() > 0
     		}, 2*time.Second, 100*time.Millisecond, "No sync should occur with feature flag disabled")
     
     		// Verify cursor was not updated
    @@ -645,13 +645,13 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		err = th.App.ReloadConfig()
     		require.NoError(t, err)
     
    -		atomic.StoreInt32(&syncMessageCount, 0)
    +		syncMessageCount.Store(0)
     		err = service.HandleSyncAllUsersForTesting(selfCluster)
     		require.NoError(t, err)
     
     		// Verify sync messages were sent
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncMessageCount) > 0
    +			return syncMessageCount.Load() > 0
     		}, 5*time.Second, 100*time.Millisecond, "Sync should occur with feature flag enabled")
     
     		// Verify cursor was updated
    @@ -667,13 +667,13 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		// - Tests cursor updates in both scenarios
     		EnsureCleanState(t, th, ss)
     
    -		var syncMessageCount int32
    +		var syncMessageCount atomic.Int32
     		var connectionOpenSyncOccurred atomic.Bool
     
     		// Create test HTTP server
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				atomic.AddInt32(&syncMessageCount, 1)
    +				syncMessageCount.Add(1)
     
     				// Parse message to check if it's a user sync
     				bodyBytes, _ := io.ReadAll(r.Body)
    @@ -724,12 +724,12 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     
     		// Verify no automatic sync occurs within a reasonable time
     		require.Never(t, func() bool {
    -			return connectionOpenSyncOccurred.Load() || atomic.LoadInt32(&syncMessageCount) > 0
    +			return connectionOpenSyncOccurred.Load() || syncMessageCount.Load() > 0
     		}, 2*time.Second, 100*time.Millisecond, "No automatic sync should occur when config is disabled")
     
     		// Test 2: Connection open with sync enabled
     		// Reset counters
    -		atomic.StoreInt32(&syncMessageCount, 0)
    +		syncMessageCount.Store(0)
     		connectionOpenSyncOccurred.Store(false)
     
     		// Enable config option
    @@ -767,7 +767,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		}, 5*time.Second, 100*time.Millisecond, "Automatic sync should occur when config is enabled")
     
     		// Verify sync occurred
    -		assert.Greater(t, atomic.LoadInt32(&syncMessageCount), int32(0), "Should have sync messages when config enabled")
    +		assert.Greater(t, syncMessageCount.Load(), int32(0), "Should have sync messages when config enabled")
     
     		// Verify cursor was updated
     		updatedCluster, err2 := ss.RemoteCluster().Get(selfCluster2.RemoteId, true)
    @@ -849,7 +849,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		// - No partial data should be persisted
     		EnsureCleanState(t, th, ss)
     
    -		var syncAttempts int32
    +		var syncAttempts atomic.Int32
     		var serverOnline atomic.Bool
     		serverOnline.Store(true)
     
    @@ -862,9 +862,9 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     			}
     
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				atomic.AddInt32(&syncAttempts, 1)
    +				syncAttempts.Add(1)
     				// On second attempt, go offline
    -				if atomic.LoadInt32(&syncAttempts) >= 2 {
    +				if syncAttempts.Load() >= 2 {
     					serverOnline.Store(false)
     					w.WriteHeader(http.StatusServiceUnavailable)
     					return
    @@ -903,7 +903,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     
     		// Wait for first sync
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) >= 1
    +			return syncAttempts.Load() >= 1
     		}, 5*time.Second, 100*time.Millisecond)
     
     		// Get cursor after first sync
    @@ -926,7 +926,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     
     		// Wait for second sync attempt
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) >= 2
    +			return syncAttempts.Load() >= 2
     		}, 5*time.Second, 100*time.Millisecond)
     
     		// Verify cursor was not updated after failed sync
    @@ -1192,7 +1192,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
     		syncedUserOnB := &model.User{
     			Email:    model.NewId() + "@example.com",
     			Username: originalUser.Username + "_" + clusterB.Name, // Munged username
    -			Password: "password",
    +			Password: model.NewTestPassword(),
     			RemoteId: &clusterB.RemoteId, // This would be A's cluster ID on the actual B server
     			UpdateAt: model.GetMillis(),
     		}
    
  • server/channels/app/shared_channel_membership_sync_self_referential_test.go+49 49 modified
    @@ -65,12 +65,12 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// The test ensures that sync messages are sent asynchronously after a minimum delay for both add and remove operations.
     		EnsureCleanState(t, th, ss)
     		// Track sync messages received
    -		var syncMessageCount int32
    +		var syncMessageCount atomic.Int32
     		var syncHandler *SelfReferentialSyncHandler
     
     		// Create a test HTTP server that acts as the "remote" cluster
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    -			atomic.AddInt32(&syncMessageCount, 1)
    +			syncMessageCount.Add(1)
     			if syncHandler != nil {
     				syncHandler.HandleRequest(w, r)
     			} else {
    @@ -144,7 +144,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for async sync with more generous timeout (minimum delay is 2 seconds + async task processing)
     		require.Eventually(t, func() bool {
    -			count := atomic.LoadInt32(&syncMessageCount)
    +			count := syncMessageCount.Load()
     			return count > 0
     		}, 15*time.Second, 200*time.Millisecond, "Should have received at least one sync message via automatic sync")
     
    @@ -161,7 +161,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// Reset sync counter and wait for background tasks to settle
     		var initialCount int32
     		require.Eventually(t, func() bool {
    -			initialCount = atomic.LoadInt32(&syncMessageCount)
    +			initialCount = syncMessageCount.Load()
     			return !service.HasPendingTasksForTesting()
     		}, 5*time.Second, 100*time.Millisecond, "Background tasks should settle before removal test")
     
    @@ -171,7 +171,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for removal sync with increased timeout
     		require.Eventually(t, func() bool {
    -			count := atomic.LoadInt32(&syncMessageCount)
    +			count := syncMessageCount.Load()
     			return count > initialCount
     		}, 20*time.Second, 200*time.Millisecond, "Should have received sync message for user removal")
     
    @@ -533,7 +533,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// 2. No members are synced during failure mode
     		// 3. Once the server recovers, sync completes successfully
     		EnsureCleanState(t, th, ss)
    -		var syncAttempts int32
    +		var syncAttempts atomic.Int32
     		var failureMode atomic.Bool
     		failureMode.Store(true)
     		var successfulSyncs []string
    @@ -542,7 +542,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				atomic.AddInt32(&syncAttempts, 1)
    +				syncAttempts.Add(1)
     
     				if failureMode.Load() {
     					w.WriteHeader(http.StatusInternalServerError)
    @@ -623,11 +623,11 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for first sync attempt with more robust checking
     		require.Eventually(t, func() bool {
    -			attempts := atomic.LoadInt32(&syncAttempts)
    +			attempts := syncAttempts.Load()
     			return attempts > 0
     		}, 15*time.Second, 100*time.Millisecond, "Should have attempted sync during failure mode")
     
    -		initialAttempts := atomic.LoadInt32(&syncAttempts)
    +		initialAttempts := syncAttempts.Load()
     		assert.Greater(t, initialAttempts, int32(0), "Should have attempted sync")
     		assert.Empty(t, successfulSyncs, "No successful syncs during failure mode")
     
    @@ -644,7 +644,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		}, 15*time.Second, 100*time.Millisecond, "Should have successful sync after recovery")
     
     		// Verify recovery
    -		finalAttempts := atomic.LoadInt32(&syncAttempts)
    +		finalAttempts := syncAttempts.Load()
     		assert.Greater(t, finalAttempts, initialAttempts, "Should have retried after recovery")
     	})
     	t.Run("Test 5: Manual sync with cursor management", func(t *testing.T) {
    @@ -654,9 +654,9 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// 3. Verifies all operations are properly synced and cursor is updated correctly
     		// 4. Validates that the LastMembersSyncAt cursor advances after each sync operation
     		EnsureCleanState(t, th, ss)
    -		var totalSyncMessages int32
    -		var addOperations int32
    -		var removeOperations int32
    +		var totalSyncMessages atomic.Int32
    +		var addOperations atomic.Int32
    +		var removeOperations atomic.Int32
     		var selfCluster *model.RemoteCluster
     
     		// Create sync handler
    @@ -680,9 +680,9 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     						// Count membership changes from the unified field
     						for _, change := range syncMsg.MembershipChanges {
     							if change.IsAdd {
    -								atomic.AddInt32(&addOperations, 1)
    +								addOperations.Add(1)
     							} else {
    -								atomic.AddInt32(&removeOperations, 1)
    +								removeOperations.Add(1)
     							}
     						}
     					}
    @@ -767,10 +767,10 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for initial sync to complete
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&addOperations) >= 10
    +			return addOperations.Load() >= 10
     		}, 10*time.Second, 100*time.Millisecond, "Should sync all initial users")
     
    -		initialAdds := atomic.LoadInt32(&addOperations)
    +		initialAdds := addOperations.Load()
     		assert.GreaterOrEqual(t, initialAdds, int32(10), "Should sync all initial users")
     
     		// Verify cursor was updated after initial sync
    @@ -802,15 +802,15 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		}
     
     		// Sync mixed changes
    -		previousMessages := atomic.LoadInt32(&totalSyncMessages)
    +		previousMessages := totalSyncMessages.Load()
     
     		err = service.SyncAllChannelMembers(channel.Id, selfCluster.RemoteId, nil)
     		require.NoError(t, err)
     
     		// Wait for mixed changes sync to complete
     		require.Eventually(t, func() bool {
    -			messages := atomic.LoadInt32(&totalSyncMessages)
    -			removes := atomic.LoadInt32(&removeOperations)
    +			messages := totalSyncMessages.Load()
    +			removes := removeOperations.Load()
     			return messages > previousMessages && removes >= 3
     		}, 10*time.Second, 100*time.Millisecond, "Should sync mixed changes")
     
    @@ -830,9 +830,9 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		expectedMembers := 10 - 3 + 5 + 1 // initial - removed + added + system admin
     		assert.Equal(t, expectedMembers, len(members), "Should have correct final member count")
     
    -		finalMessages := atomic.LoadInt32(&totalSyncMessages)
    -		finalAdds := atomic.LoadInt32(&addOperations)
    -		finalRemoves := atomic.LoadInt32(&removeOperations)
    +		finalMessages := totalSyncMessages.Load()
    +		finalAdds := addOperations.Load()
    +		finalRemoves := removeOperations.Load()
     
     		assert.Greater(t, finalMessages, int32(0), "Should have sync messages")
     		assert.Greater(t, finalAdds, int32(0), "Should have add operations")
    @@ -844,7 +844,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// 2. Changes from one cluster propagate through our server to other clusters
     		// 3. Removals sync to all clusters
     		EnsureCleanState(t, th, ss)
    -		var totalSyncMessages int32
    +		var totalSyncMessages atomic.Int32
     		var syncMessagesPerCluster = make(map[string]*int32)
     
     		// Create multiple test HTTP servers to simulate different remote clusters
    @@ -991,7 +991,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// This simulates cluster-2 receiving a membership change and propagating it
     
     		// Reset counters
    -		atomic.StoreInt32(&totalSyncMessages, 0)
    +		totalSyncMessages.Store(0)
     		for _, countPtr := range syncMessagesPerCluster {
     			atomic.StoreInt32(countPtr, 0)
     		}
    @@ -1052,7 +1052,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// Part 3: Test removal syncing to all clusters
     
     		// Reset counters
    -		atomic.StoreInt32(&totalSyncMessages, 0)
    +		totalSyncMessages.Store(0)
     		for _, countPtr := range syncMessagesPerCluster {
     			atomic.StoreInt32(countPtr, 0)
     		}
    @@ -1092,7 +1092,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// 2. When the feature flag is enabled, sync messages should be sent as expected
     		// This ensures that the feature can be safely disabled in production without triggering unintended syncs
     		EnsureCleanState(t, th, ss)
    -		var syncMessageCount int32
    +		var syncMessageCount atomic.Int32
     
     		// Disable feature flag from the beginning to prevent any automatic sync
     		os.Setenv("MM_FEATUREFLAGS_ENABLESHAREDCHANNELMEMBERSYNC", "false")
    @@ -1102,7 +1102,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// Create test HTTP server that counts sync messages
     		testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				atomic.AddInt32(&syncMessageCount, 1)
    +				syncMessageCount.Add(1)
     			}
     			writeOKResponse(w)
     		}))
    @@ -1162,27 +1162,27 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     			require.Nil(t, appErr)
     		}
     
    -		atomic.StoreInt32(&syncMessageCount, 0)
    +		syncMessageCount.Store(0)
     		err = service.SyncAllChannelMembers(channel.Id, selfCluster.RemoteId, nil)
     		require.NoError(t, err)
     
     		// Verify no sync messages were sent
     		require.Never(t, func() bool {
    -			return atomic.LoadInt32(&syncMessageCount) > 0
    +			return syncMessageCount.Load() > 0
     		}, 2*time.Second, 100*time.Millisecond, "No sync should occur with feature flag disabled")
     
     		// Test 2: Sync with feature flag enabled
     		th.App.UpdateConfig(func(cfg *model.Config) {
     			cfg.FeatureFlags.EnableSharedChannelsMemberSync = true
     		})
     
    -		atomic.StoreInt32(&syncMessageCount, 0)
    +		syncMessageCount.Store(0)
     		err = service.SyncAllChannelMembers(channel.Id, selfCluster.RemoteId, nil)
     		require.NoError(t, err)
     
     		// Verify sync messages were sent
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncMessageCount) > 0
    +			return syncMessageCount.Load() > 0
     		}, 5*time.Second, 100*time.Millisecond, "Sync should occur with feature flag enabled")
     	})
     	t.Run("Test 8: Sync Task After Connection Becomes Available", func(t *testing.T) {
    @@ -1301,7 +1301,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		// 5. No partial data is persisted from the failed sync
     		EnsureCleanState(t, th, ss)
     
    -		var syncAttempts int32
    +		var syncAttempts atomic.Int32
     		var serverOnline atomic.Bool
     		serverOnline.Store(true)
     		var syncHandler *SelfReferentialSyncHandler
    @@ -1315,7 +1315,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     			}
     
     			if r.URL.Path == "/api/v4/remotecluster/msg" {
    -				currentAttempt := atomic.AddInt32(&syncAttempts, 1)
    +				currentAttempt := syncAttempts.Add(1)
     				// On second sync cycle, go offline (allow first full sync to complete)
     				if currentAttempt > 2 {
     					serverOnline.Store(false)
    @@ -1392,7 +1392,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for first sync with more generous timeout
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) >= 1
    +			return syncAttempts.Load() >= 1
     		}, 15*time.Second, 200*time.Millisecond, "Should complete first sync")
     
     		// Wait for cursor to be updated after first sync
    @@ -1421,7 +1421,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for second sync attempt with more generous timeout
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&syncAttempts) >= 2
    +			return syncAttempts.Load() >= 2
     		}, 20*time.Second, 200*time.Millisecond, "Should attempt second sync")
     
     		// Wait for any cursor updates to complete and verify cursor was not updated
    @@ -1451,7 +1451,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     		var mu sync.Mutex
     		var syncHandler *SelfReferentialSyncHandler
     		var testServer *httptest.Server
    -		var totalSyncMessages int32
    +		var totalSyncMessages atomic.Int32
     
     		// Create users
     		user1 := th.CreateUser(t)
    @@ -1522,7 +1522,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     								for _, change := range syncMsg.MembershipChanges {
     									if change.IsAdd {
     										syncedChannelUsers[channelId] = append(syncedChannelUsers[channelId], change.UserId)
    -										atomic.AddInt32(&totalSyncMessages, 1)
    +										totalSyncMessages.Add(1)
     									}
     								}
     							}
    @@ -1577,7 +1577,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Ensure the sync handler is ready by waiting for the first message
     		require.Eventually(t, func() bool {
    -			return atomic.LoadInt32(&totalSyncMessages) > 0
    +			return totalSyncMessages.Load() > 0
     		}, 10*time.Second, 50*time.Millisecond, "Expected at least one sync message to be sent")
     
     		// Calculate expected number of sync messages
    @@ -1589,7 +1589,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     		// Wait for all sync messages to be processed with detailed debugging
     		require.Eventually(t, func() bool {
    -			currentMessages := atomic.LoadInt32(&totalSyncMessages)
    +			currentMessages := totalSyncMessages.Load()
     
     			mu.Lock()
     			channelCount := len(syncedChannelUsers)
    @@ -1607,7 +1607,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     			return currentMessages >= expectedSyncMessages
     		}, 30*time.Second, 200*time.Millisecond,
    -			fmt.Sprintf("Expected %d sync messages, but got %d", expectedSyncMessages, atomic.LoadInt32(&totalSyncMessages)))
    +			fmt.Sprintf("Expected %d sync messages, but got %d", expectedSyncMessages, totalSyncMessages.Load()))
     
     		// Verify we have complete data for all channels
     		require.Eventually(t, func() bool {
    @@ -1691,7 +1691,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     	// 	EnsureCleanState(t, th, ss)
     	// 	var syncMessages []model.SyncMsg
     	// 	var mu sync.Mutex
    -	// 	var syncMessageCount int32
    +	// 	var syncMessageCount atomic.Int32
     	// 	var selfCluster *model.RemoteCluster
     
     	// 	// Create sync handler
    @@ -1700,7 +1700,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     	// 	// Create test HTTP server that tracks sync messages
     	// 	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     	// 		if r.URL.Path == "/api/v4/remotecluster/msg" {
    -	// 			atomic.AddInt32(&syncMessageCount, 1)
    +	// 			syncMessageCount.Add(1)
     
     	// 			// Read body once
     	// 			bodyBytes, readErr := io.ReadAll(r.Body)
    @@ -1800,7 +1800,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     	// 	// Wait for initial sync to complete
     	// 	require.Eventually(t, func() bool {
    -	// 		count := atomic.LoadInt32(&syncMessageCount)
    +	// 		count := syncMessageCount.Load()
     	// 		return count > 0
     	// 	}, 15*time.Second, 200*time.Millisecond, "Should have initial sync messages")
     
    @@ -1847,7 +1847,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     	// 	// Phase 5: Conflict resolution sync
     	// 	// Reset message tracking for conflict resolution phase
    -	// 	atomic.StoreInt32(&syncMessageCount, 0)
    +	// 	syncMessageCount.Store(0)
     	// 	mu.Lock()
     	// 	syncMessages = []model.SyncMsg{}
     	// 	mu.Unlock()
    @@ -1857,7 +1857,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     
     	// 	// Wait for conflict resolution sync to complete
     	// 	require.Eventually(t, func() bool {
    -	// 		count := atomic.LoadInt32(&syncMessageCount)
    +	// 		count := syncMessageCount.Load()
     	// 		return count > 0
     	// 	}, 20*time.Second, 200*time.Millisecond, "Should receive conflict resolution sync messages")
     
    @@ -1901,7 +1901,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     	// 	require.Nil(t, appErr)
     
     	// 	// Reset and sync this new user
    -	// 	atomic.StoreInt32(&syncMessageCount, 0)
    +	// 	syncMessageCount.Store(0)
     	// 	mu.Lock()
     	// 	syncMessages = []model.SyncMsg{}
     	// 	mu.Unlock()
    @@ -1926,7 +1926,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     	// 	}, 15*time.Second, 200*time.Millisecond, "New user should be synced correctly after conflict resolution")
     
     	// 	// Phase 9: Verify efficiency - no redundant syncs for existing members
    -	// 	atomic.StoreInt32(&syncMessageCount, 0)
    +	// 	syncMessageCount.Store(0)
     	// 	mu.Lock()
     	// 	syncMessages = []model.SyncMsg{}
     	// 	mu.Unlock()
    @@ -1938,7 +1938,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
     	// 	// Wait for sync completion and verify minimal activity
     	// 	// Give time for any sync to complete, then check the final count
     	// 	require.Eventually(t, func() bool {
    -	// 		finalCount := atomic.LoadInt32(&syncMessageCount)
    +	// 		finalCount := syncMessageCount.Load()
     	// 		// Should have minimal activity since all members are already synced
     	// 		return finalCount <= 1
     	// 	}, 10*time.Second, 200*time.Millisecond, "Should have minimal sync activity for already-synced members")
    
  • server/channels/app/shared_channel_sync_self_referential_utils_test.go+4 5 modified
    @@ -41,7 +41,7 @@ type SelfReferentialSyncHandler struct {
     	t                *testing.T
     	service          *sharedchannel.Service
     	selfCluster      *model.RemoteCluster
    -	syncMessageCount *int32
    +	syncMessageCount *atomic.Int32
     	SimulateUnshared bool // When true, always return ErrChannelIsNotShared for sync messages
     
     	// Callbacks for capturing sync data
    @@ -52,12 +52,11 @@ type SelfReferentialSyncHandler struct {
     
     // NewSelfReferentialSyncHandler creates a new handler for processing sync messages in tests
     func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service, selfCluster *model.RemoteCluster) *SelfReferentialSyncHandler {
    -	count := int32(0)
     	return &SelfReferentialSyncHandler{
     		t:                t,
     		service:          service,
     		selfCluster:      selfCluster,
    -		syncMessageCount: &count,
    +		syncMessageCount: &atomic.Int32{},
     	}
     }
     
    @@ -69,7 +68,7 @@ func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service,
     func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
     	switch r.URL.Path {
     	case "/api/v4/remotecluster/msg":
    -		currentCall := atomic.AddInt32(h.syncMessageCount, 1)
    +		currentCall := h.syncMessageCount.Add(1)
     
     		// Read and process the sync message
     		body, _ := io.ReadAll(r.Body)
    @@ -162,7 +161,7 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
     
     // GetSyncMessageCount returns the current count of sync messages received
     func (h *SelfReferentialSyncHandler) GetSyncMessageCount() int32 {
    -	return atomic.LoadInt32(h.syncMessageCount)
    +	return h.syncMessageCount.Load()
     }
     
     // EnsureCleanState ensures a clean test state by removing all shared channels, remote clusters,
    
  • server/channels/app/slashcommands/auto_constants.go+2 2 modified
    @@ -9,15 +9,15 @@ import (
     )
     
     const (
    -	UserPassword         = "Usr@MMTest123"
    +	UserPassword         = "Usr@MMTest12345"
     	ChannelType          = model.ChannelTypeOpen
     	BTestTeamDisplayName = "TestTeam"
     	BTestTeamName        = "z-z-testdomaina"
     	BTestTeamEmail       = "test@nowhere.com"
     	BTestTeamType        = model.TeamOpen
     	BTestUserName        = "Mr. Testing Tester"
     	BTestUserEmail       = "success+ttester@simulator.amazonses.com"
    -	BTestUserPassword    = "passwd"
    +	BTestUserPassword    = "Passwd+Us3r1234"
     )
     
     var (
    
  • server/channels/app/slashcommands/helper_test.go+1 10 modified
    @@ -129,15 +129,6 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
     
     	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true })
     
    -	// Disable strict password requirements for test
    -	th.App.UpdateConfig(func(cfg *model.Config) {
    -		*cfg.PasswordSettings.MinimumLength = 5
    -		*cfg.PasswordSettings.Lowercase = false
    -		*cfg.PasswordSettings.Uppercase = false
    -		*cfg.PasswordSettings.Symbol = false
    -		*cfg.PasswordSettings.Number = false
    -	})
    -
     	tb.Cleanup(func() {
     		if th.IncludeCacheLayer {
     			// Clean all the caches
    @@ -249,7 +240,7 @@ func (th *TestHelper) createUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     
    
  • server/channels/app/support_packet.go+4 10 modified
    @@ -38,10 +38,7 @@ func (a *App) GenerateSupportPacket(rctx request.CTX, options *model.SupportPack
     		mut       sync.Mutex // Protects warnings and fileDatas
     	)
     
    -	wg.Add(1)
    -	go func() {
    -		defer wg.Done()
    -
    +	wg.Go(func() {
     		for name, fn := range functions {
     			fileData, err := fn(rctx)
     			mut.Lock()
    @@ -82,14 +79,11 @@ func (a *App) GenerateSupportPacket(rctx request.CTX, options *model.SupportPack
     			}
     		}
     		mut.Unlock()
    -	}()
    +	})
     
     	// Run the cluster generation in a separate goroutine as CPU profile generation and file upload can take a long time
     	if cluster := a.Cluster(); cluster != nil && *a.Config().ClusterSettings.Enable {
    -		wg.Add(1)
    -		go func() {
    -			defer wg.Done()
    -
    +		wg.Go(func() {
     			files, err := cluster.GenerateSupportPacket(rctx, options)
     			mut.Lock()
     			if err != nil {
    @@ -101,7 +95,7 @@ func (a *App) GenerateSupportPacket(rctx request.CTX, options *model.SupportPack
     				fileDatas = append(fileDatas, node...)
     			}
     			mut.Unlock()
    -		}()
    +		})
     	}
     
     	wg.Wait()
    
  • server/channels/app/teams/helper_test.go+0 6 modified
    @@ -61,12 +61,6 @@ func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *Test
     	*config.TeamSettings.MaxUsersPerTeam = 50
     	*config.RateLimitSettings.Enable = false
     	*config.TeamSettings.EnableOpenServer = true
    -	// Disable strict password requirements for test
    -	*config.PasswordSettings.MinimumLength = 5
    -	*config.PasswordSettings.Lowercase = false
    -	*config.PasswordSettings.Uppercase = false
    -	*config.PasswordSettings.Symbol = false
    -	*config.PasswordSettings.Number = false
     	_, _, err = configStore.Set(config)
     	require.NoError(tb, err)
     
    
  • server/channels/app/teams/teams_test.go+7 7 modified
    @@ -93,7 +93,7 @@ func TestJoinUserToTeam(t *testing.T) {
     	th.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = &one })
     
     	t.Run("new join", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser := th.CreateUser(&user)
     		defer th.DeleteUser(&user)
     
    @@ -103,7 +103,7 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("join when you are a member", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser := th.CreateUser(&user)
     		defer th.DeleteUser(&user)
     
    @@ -116,7 +116,7 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("re-join after leaving", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser := th.CreateUser(&user)
     		defer th.DeleteUser(&user)
     
    @@ -131,9 +131,9 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("new join with limit problem", func(t *testing.T) {
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1 := th.CreateUser(&user1)
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2 := th.CreateUser(&user2)
     
     		defer th.DeleteUser(&user1)
    @@ -147,10 +147,10 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("re-join after leaving with limit problem", func(t *testing.T) {
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1 := th.CreateUser(&user1)
     
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2 := th.CreateUser(&user2)
     
     		defer th.DeleteUser(&user1)
    
  • server/channels/app/team_test.go+37 37 modified
    @@ -84,7 +84,7 @@ func TestAddUserToTeam(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("add user", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -100,7 +100,7 @@ func TestAddUserToTeam(t *testing.T) {
     		_, err := th.App.UpdateTeam(th.BasicTeam)
     		require.Nil(t, err, "Should update the team")
     
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -116,7 +116,7 @@ func TestAddUserToTeam(t *testing.T) {
     		_, err := th.App.UpdateTeam(th.BasicTeam)
     		require.Nil(t, err, "Should update the team")
     
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, err := th.App.CreateUser(th.Context, &user)
     		require.Nil(t, err, "Error creating user: %s", err)
     		defer func() {
    @@ -156,7 +156,7 @@ func TestAddUserToTeam(t *testing.T) {
     		_, err := th.App.UpdateTeam(th.BasicTeam)
     		require.Nil(t, err, "Should update the team")
     
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -173,13 +173,13 @@ func TestAddUserToTeam(t *testing.T) {
     		_, err := th.App.UpdateTeam(th.BasicTeam)
     		require.Nil(t, err, "Should update the team")
     
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1, _ := th.App.CreateUser(th.Context, &user1)
     
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2, _ := th.App.CreateUser(th.Context, &user2)
     
    -		user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser3, _ := th.App.CreateUser(th.Context, &user3)
     
     		defer func() {
    @@ -226,7 +226,7 @@ func TestAddUserToTeamByToken(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     	ruser, _ := th.App.CreateUser(th.Context, &user)
     	rguest := th.CreateGuest(t)
     
    @@ -445,7 +445,7 @@ func TestAddUserToTeamByToken(t *testing.T) {
     			_, _ = th.App.UpdateTeam(th.BasicTeam)
     		}()
     
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -572,7 +572,7 @@ func TestAddUserToTeamByTeamId(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("add user", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		err := th.App.AddUserToTeamByTeamId(th.Context, th.BasicTeam.Id, ruser)
    @@ -588,7 +588,7 @@ func TestAddUserToTeamByTeamId(t *testing.T) {
     			_, _ = th.App.UpdateTeam(th.BasicTeam)
     		}()
     
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -1027,7 +1027,7 @@ func TestJoinUserToTeam(t *testing.T) {
     	th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = &one })
     
     	t.Run("new join", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user)
    @@ -1039,9 +1039,9 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("new join with limit problem", func(t *testing.T) {
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1, _ := th.App.CreateUser(th.Context, &user1)
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2, _ := th.App.CreateUser(th.Context, &user2)
     
     		defer func() {
    @@ -1061,10 +1061,10 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("re-join after leaving with limit problem", func(t *testing.T) {
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1, _ := th.App.CreateUser(th.Context, &user1)
     
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2, _ := th.App.CreateUser(th.Context, &user2)
     
     		defer func() {
    @@ -1088,7 +1088,7 @@ func TestJoinUserToTeam(t *testing.T) {
     	})
     
     	t.Run("new join with correct scheme_admin value from group syncable", func(t *testing.T) {
    -		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser1, _ := th.App.CreateUser(th.Context, &user1)
     		defer func() {
     			appErr := th.App.PermanentDeleteUser(th.Context, &user1)
    @@ -1115,7 +1115,7 @@ func TestJoinUserToTeam(t *testing.T) {
     		require.Nil(t, appErr)
     		require.False(t, tm1.SchemeAdmin)
     
    -		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser2, _ := th.App.CreateUser(th.Context, &user2)
     		defer func() {
     			appErr = th.App.PermanentDeleteUser(th.Context, &user2)
    @@ -1285,7 +1285,7 @@ func TestGetTeamMembers(t *testing.T) {
     		user := model.User{
     			Email:    strings.ToLower(model.NewId()) + "success+test@example.com",
     			Username: fmt.Sprintf("user%v", i),
    -			Password: "passwd1",
    +			Password: model.NewTestPassword(),
     			DeleteAt: int64(rand.Intn(2)),
     		}
     		ruser, err := th.App.CreateUser(th.Context, &user)
    @@ -1453,7 +1453,7 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("from guest to user", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1464,7 +1464,7 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from user to guest", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1475,7 +1475,7 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from user to admin", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1486,7 +1486,7 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("from guest to guest plus custom", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1500,7 +1500,7 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
     	})
     
     	t.Run("a guest cant have user role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateGuest(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1516,7 +1516,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	th := Setup(t).InitBasic(t)
     
     	t.Run("empty roles string requires user or guest scheme role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1533,7 +1533,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("admin role requires user or guest scheme role", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1545,7 +1545,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("valid user and admin roles update succeeds", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1559,7 +1559,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("removing admin role while keeping user role succeeds", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1575,7 +1575,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("team_post_all alone should fail", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1587,7 +1587,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("team_post_all_public alone should fail", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1599,7 +1599,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("system_post_all alone should fail", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1611,7 +1611,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("system_user_manager alone should fail", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1623,7 +1623,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("multiple non-scheme-managed roles without user scheme should fail", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1635,7 +1635,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("team_post_all with team_user should succeed", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1648,7 +1648,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("team_post_all_public with team_user should succeed", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1661,7 +1661,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("multiple explicit roles with team_user should succeed", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    @@ -1675,7 +1675,7 @@ func TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
     	})
     
     	t.Run("explicit role with admin should succeed", func(t *testing.T) {
    -		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		ruser, _ := th.App.CreateUser(th.Context, &user)
     
     		_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
    
  • server/channels/app/user_agent.go+2 3 modified
    @@ -116,9 +116,8 @@ var versionPrefixes = []string{
     
     func getBrowserVersion(ua *uasurfer.UserAgent, userAgentString string) string {
     	for _, prefix := range versionPrefixes {
    -		if index := strings.Index(userAgentString, prefix); index != -1 {
    -			afterPrefix := userAgentString[index+len(prefix):]
    -			if fields := strings.Fields(afterPrefix); len(fields) > 0 {
    +		if _, after, ok := strings.Cut(userAgentString, prefix); ok {
    +			if fields := strings.Fields(after); len(fields) > 0 {
     				// MM-55320: limitStringLength prevents potential DOS caused by filling an unbounded string with junk data
     				return limitStringLength(fields[0], maxUserAgentVersionLength)
     			}
    
  • server/channels/app/user_limits_test.go+8 8 modified
    @@ -280,7 +280,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestCreateUserOrGuest@example.com",
     			Username:      "username_123",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -311,7 +311,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCount@example.com",
     			Username:      "seat_test_user",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -349,7 +349,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCount@example.com",
     			Username:      "seat_test_user",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -377,7 +377,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCount@example.com",
     			Username:      "seat_test_user",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -396,7 +396,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCount@example.com",
     			Username:      "seat_test_user",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -418,7 +418,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCount@example.com",
     			Username:      "seat_test_user",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -449,7 +449,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCountGuest@example.com",
     			Username:      "seat_test_guest",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -474,7 +474,7 @@ func TestCreateUserOrGuestSeatCountEnforcement(t *testing.T) {
     		user := &model.User{
     			Email:         "TestSeatCountGuest@example.com",
     			Username:      "seat_test_guest",
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    
  • server/channels/app/users/helper_test.go+1 7 modified
    @@ -59,12 +59,6 @@ func setupTestHelper(s store.Store, _ bool, tb testing.TB) *TestHelper {
     	*config.TeamSettings.MaxUsersPerTeam = 50
     	*config.RateLimitSettings.Enable = false
     	*config.TeamSettings.EnableOpenServer = true
    -	// Disable strict password requirements for test
    -	*config.PasswordSettings.MinimumLength = 5
    -	*config.PasswordSettings.Lowercase = false
    -	*config.PasswordSettings.Uppercase = false
    -	*config.PasswordSettings.Symbol = false
    -	*config.PasswordSettings.Number = false
     	_, _, err = configStore.Set(config)
     	require.NoError(tb, err)
     
    @@ -129,7 +123,7 @@ func (th *TestHelper) CreateUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     
    
  • server/channels/app/users/password_test.go+4 4 modified
    @@ -20,9 +20,9 @@ func TestIsPasswordValidWithSettings(t *testing.T) {
     		ExpectedError string
     	}{
     		"Short": {
    -			Password: strings.Repeat("x", 3),
    +			Password: strings.Repeat("x", model.PasswordFIPSMinimumLength),
     			Settings: &model.PasswordSettings{
    -				MinimumLength: model.NewPointer(3),
    +				MinimumLength: model.NewPointer(model.PasswordFIPSMinimumLength),
     				Lowercase:     model.NewPointer(false),
     				Uppercase:     model.NewPointer(false),
     				Number:        model.NewPointer(false),
    @@ -41,7 +41,7 @@ func TestIsPasswordValidWithSettings(t *testing.T) {
     		"TooShort": {
     			Password: strings.Repeat("x", 2),
     			Settings: &model.PasswordSettings{
    -				MinimumLength: model.NewPointer(3),
    +				MinimumLength: model.NewPointer(model.PasswordFIPSMinimumLength),
     				Lowercase:     model.NewPointer(false),
     				Uppercase:     model.NewPointer(false),
     				Number:        model.NewPointer(false),
    @@ -110,7 +110,7 @@ func TestIsPasswordValidWithSettings(t *testing.T) {
     			ExpectedError: "model.user.is_valid.pwd_uppercase_number_symbol.app_error",
     		},
     		"Everything": {
    -			Password: "asdASD!@#123",
    +			Password: "asdASDasd!@#123",
     			Settings: &model.PasswordSettings{
     				Lowercase: model.NewPointer(true),
     				Uppercase: model.NewPointer(true),
    
  • server/channels/app/user_test.go+24 24 modified
    @@ -293,7 +293,7 @@ func TestCreateUser(t *testing.T) {
     			Email:         "success+" + id + "@simulator.amazonses.com",
     			Username:      *group.Name,
     			Nickname:      "nn_" + id,
    -			Password:      "Password1",
    +			Password:      model.NewTestPassword(),
     			EmailVerified: true,
     		}
     
    @@ -337,7 +337,7 @@ func TestCreateUser(t *testing.T) {
     			Email:       model.NewId() + "success+test@example.com",
     			Nickname:    "Darth Vader",
     			Username:    "vader" + model.NewId(),
    -			Password:    "passwd12345",
    +			Password:    model.NewTestPassword(),
     			AuthService: "",
     		}
     		_, err := th.App.CreateUser(th.Context, user)
    @@ -771,7 +771,7 @@ func TestGetUsersByStatus(t *testing.T) {
     			Email:    "success+" + id + "@simulator.amazonses.com",
     			Username: "un_" + username + "_" + id,
     			Nickname: "nn_" + id,
    -			Password: "Password1",
    +			Password: model.NewTestPassword(),
     		})
     		require.Nil(t, err, "failed to create user: %v", err)
     
    @@ -1003,7 +1003,7 @@ func TestCreateUserWithInviteId(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     
     	t.Run("should create a user", func(t *testing.T) {
     		u, err := th.App.CreateUserWithInviteId(th.Context, &user, th.BasicTeam.InviteId, "")
    @@ -1034,7 +1034,7 @@ func TestCreateUserWithToken(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
     
    -	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +	user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     
     	t.Run("invalid token", func(t *testing.T) {
     		_, err := th.App.CreateUserWithToken(th.Context, &user, &model.Token{Token: "123"})
    @@ -1098,7 +1098,7 @@ func TestCreateUserWithToken(t *testing.T) {
     
     	t.Run("valid regular user request", func(t *testing.T) {
     		invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
    -		u := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		u := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		token := model.NewToken(
     			model.TokenTypeTeamInvitation,
     			model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail}),
    @@ -1125,7 +1125,7 @@ func TestCreateUserWithToken(t *testing.T) {
     		)
     
     		require.NoError(t, th.App.Srv().Store().Token().Save(token))
    -		guest := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		guest := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
     		require.Nil(t, err, "Should add user to the team. err=%v", err)
     
    @@ -1164,7 +1164,7 @@ func TestCreateUserWithToken(t *testing.T) {
     			Email:       forbiddenInvitationEmail,
     			Nickname:    "Darth Vader",
     			Username:    "vader" + model.NewId(),
    -			Password:    "passwd1",
    +			Password:    model.NewTestPassword(),
     			AuthService: "",
     		}
     		newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, forbiddenDomainToken)
    @@ -1210,7 +1210,7 @@ func TestCreateUserWithToken(t *testing.T) {
     			Email:       invitationEmail,
     			Nickname:    "Darth Vader",
     			Username:    "vader" + model.NewId(),
    -			Password:    "passwd1",
    +			Password:    model.NewTestPassword(),
     			AuthService: "",
     		}
     		newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
    @@ -1245,7 +1245,7 @@ func TestCreateUserWithToken(t *testing.T) {
     		)
     		require.NoError(t, th.App.Srv().Store().Token().Save(token))
     
    -		guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
     		require.Nil(t, err, "Should create guest user successfully")
     		require.True(t, newGuest.IsGuest())
    @@ -1294,7 +1294,7 @@ func TestCreateUserWithToken(t *testing.T) {
     		)
     		require.NoError(t, th.App.Srv().Store().Token().Save(token))
     
    -		guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
     		require.Nil(t, err)
     
    @@ -1328,7 +1328,7 @@ func TestCreateUserWithToken(t *testing.T) {
     		)
     		require.NoError(t, th.App.Srv().Store().Token().Save(token))
     
    -		regularUser := model.User{Email: invitationEmail, Nickname: "Regular User", Username: "regular" + model.NewId(), Password: "passwd1", AuthService: ""}
    +		regularUser := model.User{Email: invitationEmail, Nickname: "Regular User", Username: "regular" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
     		newUser, err := th.App.CreateUserWithToken(th.Context, &regularUser, token)
     		require.Nil(t, err)
     		require.False(t, newUser.IsGuest())
    @@ -1459,7 +1459,7 @@ func TestPasswordRecovery(t *testing.T) {
     		assert.Equal(t, th.BasicUser.Id, tokenData.UserID)
     		assert.Equal(t, th.BasicUser.Email, tokenData.Email)
     
    -		err = th.App.ResetPasswordFromToken(th.Context, token.Token, "abcdefgh")
    +		err = th.App.ResetPasswordFromToken(th.Context, token.Token, model.NewTestPassword())
     		assert.Nil(t, err)
     	})
     
    @@ -1475,23 +1475,23 @@ func TestPasswordRecovery(t *testing.T) {
     		_, err = th.App.UpdateUser(th.Context, th.BasicUser, false)
     		assert.Nil(t, err)
     
    -		err = th.App.ResetPasswordFromToken(th.Context, token.Token, "abcdefgh")
    +		err = th.App.ResetPasswordFromToken(th.Context, token.Token, model.NewTestPassword())
     		assert.NotNil(t, err)
     	})
     
     	t.Run("non-expired token", func(t *testing.T) {
     		token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
     		assert.Nil(t, err)
     
    -		err = th.App.resetPasswordFromToken(th.Context, token.Token, "abcdefgh", model.GetMillis())
    +		err = th.App.resetPasswordFromToken(th.Context, token.Token, model.NewTestPassword(), model.GetMillis())
     		assert.Nil(t, err)
     	})
     
     	t.Run("expired token", func(t *testing.T) {
     		token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
     		assert.Nil(t, err)
     
    -		err = th.App.resetPasswordFromToken(th.Context, token.Token, "abcdefgh", model.GetMillisForTime(time.Now().Add(25*time.Hour)))
    +		err = th.App.resetPasswordFromToken(th.Context, token.Token, model.NewTestPassword(), model.GetMillisForTime(time.Now().Add(25*time.Hour)))
     		assert.NotNil(t, err)
     	})
     }
    @@ -1558,7 +1558,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		th.Context.Session().UserId = th.BasicUser2.Id
     		th.Context.Session().Id = session.Id
     
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     
     		session, err = th.App.GetSession(session.Token)
    @@ -1570,7 +1570,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		require.Nil(t, session2)
     
     		// Cleanup
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     		th.Context.Session().UserId = ""
     		th.Context.Session().Id = ""
    @@ -1596,7 +1596,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		th.Context.Session().UserId = th.BasicUser2.Id
     		th.Context.Session().Id = session.Id
     
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     
     		session, err = th.App.GetSession(session.Token)
    @@ -1608,7 +1608,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		require.False(t, session2.IsExpired())
     
     		// Cleanup
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     		th.Context.Session().UserId = ""
     		th.Context.Session().Id = ""
    @@ -1631,7 +1631,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		})
     		require.Nil(t, err)
     
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     
     		session, err = th.App.GetSession(session.Token)
    @@ -1643,7 +1643,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		require.Nil(t, session2)
     
     		// Cleanup
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     	})
     
    @@ -1664,7 +1664,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		})
     		require.Nil(t, err)
     
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password2")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     
     		session, err = th.App.GetSession(session.Token)
    @@ -1676,7 +1676,7 @@ func TestPasswordChangeSessionTermination(t *testing.T) {
     		require.False(t, session2.IsExpired())
     
     		// Cleanup
    -		err = th.App.UpdatePassword(th.Context, th.BasicUser2, "Password1")
    +		err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
     		require.Nil(t, err)
     	})
     }
    
  • server/channels/jobs/helper_test.go+1 1 modified
    @@ -225,7 +225,7 @@ func (th *TestHelper) CreateUserOrGuest(tb testing.TB, guest bool) *model.User {
     		Email:         "success+" + id + "@simulator.amazonses.com",
     		Username:      "un_" + id,
     		Nickname:      "nn_" + id,
    -		Password:      "Password1",
    +		Password:      model.NewTestPassword(),
     		EmailVerified: true,
     	}
     
    
  • server/channels/jobs/migrations/worker.go+3 3 modified
    @@ -28,7 +28,7 @@ type Worker struct {
     	jobServer *jobs.JobServer
     	logger    mlog.LoggerIFace
     	store     store.Store
    -	closed    int32
    +	closed    atomic.Int32
     }
     
     func MakeWorker(jobServer *jobs.JobServer, store store.Store) *Worker {
    @@ -48,7 +48,7 @@ func MakeWorker(jobServer *jobs.JobServer, store store.Store) *Worker {
     
     func (worker *Worker) Run() {
     	// Set to open if closed before. We are not bothered about multiple opens.
    -	if atomic.CompareAndSwapInt32(&worker.closed, 1, 0) {
    +	if worker.closed.CompareAndSwap(1, 0) {
     		worker.stop = make(chan struct{})
     	}
     	worker.logger.Debug("Worker started")
    @@ -71,7 +71,7 @@ func (worker *Worker) Run() {
     
     func (worker *Worker) Stop() {
     	// Set to close, and if already closed before, then return.
    -	if !atomic.CompareAndSwapInt32(&worker.closed, 0, 1) {
    +	if !worker.closed.CompareAndSwap(0, 1) {
     		return
     	}
     	worker.logger.Debug("Worker stopping")
    
  • server/channels/store/searchtest/helper.go+2 2 modified
    @@ -165,7 +165,7 @@ func (th *SearchTestHelper) makeEmail() string {
     func (th *SearchTestHelper) createUser(username, nickname, firstName, lastName string) (*model.User, error) {
     	return th.Store.User().Save(th.Context, &model.User{
     		Username:  username,
    -		Password:  username,
    +		Password:  model.NewTestPassword(),
     		Nickname:  nickname,
     		FirstName: firstName,
     		LastName:  lastName,
    @@ -176,7 +176,7 @@ func (th *SearchTestHelper) createUser(username, nickname, firstName, lastName s
     func (th *SearchTestHelper) createGuest(username, nickname, firstName, lastName string) (*model.User, error) {
     	return th.Store.User().Save(th.Context, &model.User{
     		Username:  username,
    -		Password:  username,
    +		Password:  model.NewTestPassword(),
     		Nickname:  nickname,
     		FirstName: firstName,
     		LastName:  lastName,
    
  • server/channels/store/sqlstore/user_store.go+2 1 modified
    @@ -21,6 +21,7 @@ import (
     	"github.com/mattermost/mattermost/server/public/model"
     	"github.com/mattermost/mattermost/server/public/shared/mlog"
     	"github.com/mattermost/mattermost/server/public/shared/request"
    +	"github.com/mattermost/mattermost/server/v8/channels/app/password/hashers"
     	"github.com/mattermost/mattermost/server/v8/channels/store"
     	"github.com/mattermost/mattermost/server/v8/einterfaces"
     )
    @@ -177,7 +178,7 @@ func (us SqlUserStore) Save(rctx request.CTX, user *model.User) (*model.User, er
     		return nil, store.NewErrInvalidInput("User", "id", user.Id)
     	}
     
    -	if err := user.PreSave(); err != nil {
    +	if err := user.PreSave(hashers.GetLatestHasher()); err != nil {
     		return nil, err
     	}
     	if err := user.IsValid(); err != nil {
    
  • server/channels/store/storetest/channel_store_categories.go+4 10 modified
    @@ -150,13 +150,9 @@ func testCreateInitialSidebarCategories(t *testing.T, rctx request.CTX, ss store
     		var wg sync.WaitGroup
     
     		for range 10 {
    -			wg.Add(1)
    -
    -			go func() {
    -				defer wg.Done()
    -
    +			wg.Go(func() {
     				_, _ = ss.Channel().CreateInitialSidebarCategories(rctx, userID, team.Id)
    -			}()
    +			})
     		}
     
     		wg.Wait()
    @@ -2294,14 +2290,12 @@ func doTestSidebarCategoryConcurrentAccess(t *testing.T, rctx request.CTX, ss st
     	var wg sync.WaitGroup
     
     	for i := range numGoroutines {
    -		wg.Add(1)
     		// Run GetSidebarCategoriesForTeamForUser
    -		go func() {
    -			defer wg.Done()
    +		wg.Go(func() {
     			categories, getErr := ss.Channel().GetSidebarCategoriesForTeamForUser(userID, team.Id)
     			require.NoError(t, getErr)
     			require.NotEmpty(t, categories.Categories)
    -		}()
    +		})
     
     		// Run UpdateSidebarCategories with different update patterns
     		wg.Add(1)
    
  • server/channels/store/storetest/channel_store.go+15 0 modified
    @@ -43,6 +43,20 @@ type SqlXExecutor interface {
     	Select(dest any, query string, args ...any) error
     }
     
    +// cleanupChannelStoreData purges all channel-related data written by TestChannelStore
    +// sub-tests. The integrity tests (TestCheck*) do full-table scans and fail if any
    +// orphaned rows remain. A blanket purge is safe: no FK constraints are enforced in the
    +// schema, and every test suite creates its own data independently.
    +func cleanupChannelStoreData(t *testing.T, s SqlStore) {
    +	t.Helper()
    +	db := s.GetMaster()
    +	db.Exec(`DELETE FROM Threads`)
    +	db.Exec(`DELETE FROM ChannelMemberHistory`)
    +	db.Exec(`DELETE FROM ChannelMembers`)
    +	db.Exec(`DELETE FROM Channels`)
    +	db.Exec(`DELETE FROM TeamMembers`)
    +}
    +
     func cleanupChannels(t *testing.T, rctx request.CTX, ss store.Store) {
     	list, err := ss.Channel().GetAllChannels(0, 100000, store.ChannelSearchOpts{IncludeDeleted: true})
     	require.NoError(t, err, "error cleaning all channels", err)
    @@ -68,6 +82,7 @@ func channelMemberToJSON(t *testing.T, cm *model.ChannelMember) string {
     
     func TestChannelStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
     	createDefaultRoles(ss)
    +	t.Cleanup(func() { cleanupChannelStoreData(t, s) })
     
     	t.Run("Save", func(t *testing.T) { testChannelStoreSave(t, rctx, ss) })
     	t.Run("SaveDirectChannel", func(t *testing.T) { testChannelStoreSaveDirectChannel(t, rctx, ss, s) })
    
  • server/channels/store/storetest/user_store.go+23 23 modified
    @@ -2371,7 +2371,7 @@ func testUserStoreUpdatePassword(t *testing.T, rctx request.CTX, ss store.Store)
     	_, err = hashers.Hash(strings.Repeat("1234567890", 8))
     	require.ErrorIs(t, err, hashers.ErrPasswordTooLong)
     
    -	hashedPassword, err := hashers.Hash("newpwd")
    +	hashedPassword, err := hashers.Hash(model.NewTestPassword())
     	require.NoError(t, err)
     
     	err = ss.User().UpdatePassword(u1.Id, hashedPassword)
    @@ -5110,7 +5110,7 @@ func testUserStoreGetTeamGroupUsers(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     		})
     		require.NoError(t, userErr)
     		require.NotNil(t, user)
    @@ -5231,7 +5231,7 @@ func testUserStoreGetChannelGroupUsers(t *testing.T, rctx request.CTX, ss store.
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     		})
     		require.NoError(t, userErr)
     		require.NotNil(t, user)
    @@ -5342,7 +5342,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5388,7 +5388,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user system_admin",
     		})
     		require.NoError(t, err)
    @@ -5433,7 +5433,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5454,7 +5454,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5484,7 +5484,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5529,7 +5529,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest custom_role",
     		})
     		require.NoError(t, err)
    @@ -5574,7 +5574,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5602,7 +5602,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, rctx request.CTX, ss store.St
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5657,7 +5657,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5701,7 +5701,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user system_admin",
     		})
     		require.NoError(t, err)
    @@ -5744,7 +5744,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5763,7 +5763,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5791,7 +5791,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5834,7 +5834,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user custom_role",
     		})
     		require.NoError(t, err)
    @@ -5877,7 +5877,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5905,7 +5905,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, rctx request.CTX, ss store.Sto
     			Nickname:  "nn_" + id,
     			FirstName: "f_" + id,
     			LastName:  "l_" + id,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    @@ -5958,7 +5958,7 @@ func testDeactivateGuests(t *testing.T, rctx request.CTX, ss store.Store) {
     			Nickname:  "nn_" + guest1Random,
     			FirstName: "f_" + guest1Random,
     			LastName:  "l_" + guest1Random,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5971,7 +5971,7 @@ func testDeactivateGuests(t *testing.T, rctx request.CTX, ss store.Store) {
     			Nickname:  "nn_" + guest2Random,
     			FirstName: "f_" + guest2Random,
     			LastName:  "l_" + guest2Random,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     		})
     		require.NoError(t, err)
    @@ -5984,7 +5984,7 @@ func testDeactivateGuests(t *testing.T, rctx request.CTX, ss store.Store) {
     			Nickname:  "nn_" + guest3Random,
     			FirstName: "f_" + guest3Random,
     			LastName:  "l_" + guest3Random,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_guest",
     			DeleteAt:  10,
     		})
    @@ -5998,7 +5998,7 @@ func testDeactivateGuests(t *testing.T, rctx request.CTX, ss store.Store) {
     			Nickname:  "nn_" + regularUserRandom,
     			FirstName: "f_" + regularUserRandom,
     			LastName:  "l_" + regularUserRandom,
    -			Password:  "Password1",
    +			Password:  model.NewTestPassword(),
     			Roles:     "system_user",
     		})
     		require.NoError(t, err)
    
  • server/channels/web/webhook_test.go+3 2 modified
    @@ -30,10 +30,11 @@ func TestIncomingWebhook(t *testing.T) {
     
     	url := apiClient.URL + "/hooks/" + hook.Id
     
    -	tooLongText := ""
    +	var tooLongTextBuilder strings.Builder
     	for range 8200 {
    -		tooLongText += "a"
    +		tooLongTextBuilder.WriteString("a")
     	}
    +	tooLongText := tooLongTextBuilder.String()
     
     	t.Run("WebhookBasics", func(t *testing.T) {
     		payload := "payload={\"text\": \"test text\"}"
    
  • server/channels/web/web_test.go+2 11 modified
    @@ -115,15 +115,6 @@ func setupTestHelper(tb testing.TB, includeCacheLayer bool, options []app.Option
     	require.NoError(tb, err)
     	a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
     
    -	// Disable strict password requirements for test
    -	a.UpdateConfig(func(cfg *model.Config) {
    -		*cfg.PasswordSettings.MinimumLength = 5
    -		*cfg.PasswordSettings.Lowercase = false
    -		*cfg.PasswordSettings.Uppercase = false
    -		*cfg.PasswordSettings.Symbol = false
    -		*cfg.PasswordSettings.Number = false
    -	})
    -
     	web := New(s)
     	URL = fmt.Sprintf("http://localhost:%v", s.ListenAddr.Port)
     	apiClient = model.NewAPIv4Client(URL)
    @@ -172,10 +163,10 @@ func (th *TestHelper) InitBasic(tb testing.TB) *TestHelper {
     	tb.Helper()
     
     	var appErr *model.AppError
    -	th.SystemAdminUser, appErr = th.App.CreateUser(th.Context, &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", EmailVerified: true, Roles: model.SystemAdminRoleId})
    +	th.SystemAdminUser, appErr = th.App.CreateUser(th.Context, &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: model.NewTestPassword(), EmailVerified: true, Roles: model.SystemAdminRoleId})
     	require.Nil(tb, appErr)
     
    -	th.BasicUser, appErr = th.App.CreateUser(th.Context, &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", EmailVerified: true, Roles: model.SystemUserRoleId})
    +	th.BasicUser, appErr = th.App.CreateUser(th.Context, &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: model.NewTestPassword(), EmailVerified: true, Roles: model.SystemUserRoleId})
     	require.Nil(tb, appErr)
     
     	th.BasicTeam, appErr = th.App.CreateTeam(th.Context, &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: th.BasicUser.Email, Type: model.TeamOpen})
    
  • server/cmd/mattermost/commands/cmdtestlib.go+0 7 modified
    @@ -94,13 +94,6 @@ func (h *testHelper) SetConfig(config *model.Config) {
     		config.SqlSettings = *mainHelper.GetSQLSettings()
     	}
     
    -	// Disable strict password requirements for test
    -	*config.PasswordSettings.MinimumLength = 5
    -	*config.PasswordSettings.Lowercase = false
    -	*config.PasswordSettings.Uppercase = false
    -	*config.PasswordSettings.Symbol = false
    -	*config.PasswordSettings.Number = false
    -
     	h.config = config
     
     	buf, err := json.Marshal(config)
    
  • server/cmd/mmctl/commands/user_e2e_test.go+5 5 modified
    @@ -218,7 +218,7 @@ func (s *MmctlE2ETestSuite) TestListUserCmd() {
     	for range 10 {
     		userData := model.User{
     			Username: "fakeuser" + model.NewRandomString(10),
    -			Password: "Pa$$word11",
    +			Password: model.NewTestPassword(),
     			Email:    s.th.GenerateTestEmail(),
     		}
     		usr, err := s.th.App.CreateUser(s.th.Context, &userData)
    @@ -231,7 +231,7 @@ func (s *MmctlE2ETestSuite) TestListUserCmd() {
     	for range 2 {
     		userData := model.User{
     			Username: "fakeuser" + model.NewRandomString(10),
    -			Password: "Pa$$word11",
    +			Password: model.NewTestPassword(),
     			Email:    s.th.GenerateTestEmail(),
     			DeleteAt: model.GetMillis(),
     		}
    @@ -297,7 +297,7 @@ func (s *MmctlE2ETestSuite) TestListUserCmd() {
     	for range 10 {
     		userData := model.User{
     			Username: "teamuser" + model.NewRandomString(10),
    -			Password: "Pa$$word11",
    +			Password: model.NewTestPassword(),
     			Email:    s.th.GenerateTestEmail(),
     		}
     		usr, err := s.th.App.CreateUser(s.th.Context, &userData)
    @@ -330,7 +330,7 @@ func (s *MmctlE2ETestSuite) TestListUserCmd() {
     	for range 10 {
     		userData := model.User{
     			Username: "inactiveteamuser" + model.NewRandomString(10),
    -			Password: "Pa$$word11",
    +			Password: model.NewTestPassword(),
     			Email:    s.th.GenerateTestEmail(),
     			DeleteAt: model.GetMillis(),
     		}
    @@ -971,7 +971,7 @@ func (s *MmctlE2ETestSuite) TestDeleteAllUserCmd() {
     		for range 10 {
     			userData := model.User{
     				Username: "fakeuser" + model.NewRandomString(10),
    -				Password: "Pa$$word11",
    +				Password: model.NewTestPassword(),
     				Email:    s.th.GenerateTestEmail(),
     			}
     			_, err := s.th.App.CreateUser(s.th.Context, &userData)
    
  • server/enterprise/elasticsearch/common/test_helpers.go+2 1 modified
    @@ -8,6 +8,7 @@ import (
     	"testing"
     
     	"github.com/mattermost/mattermost/server/public/model"
    +	"github.com/mattermost/mattermost/server/v8/channels/app/password/hashers"
     	"github.com/stretchr/testify/assert"
     )
     
    @@ -44,7 +45,7 @@ func createUser(username, nickname, firstName, lastName string) *model.User {
     		FirstName: firstName,
     		LastName:  lastName,
     	}
    -	if err := user.PreSave(); err != nil {
    +	if err := user.PreSave(hashers.GetLatestHasher()); err != nil {
     		return nil
     	}
     
    
  • server/enterprise/elasticsearch/elasticsearch/elasticsearch.go+29 29 modified
    @@ -38,7 +38,7 @@ var (
     type ElasticsearchInterfaceImpl struct {
     	client      *elastic.TypedClient
     	mutex       sync.RWMutex
    -	ready       int32
    +	ready       atomic.Int32
     	version     int
     	fullVersion string
     	plugins     []string
    @@ -69,7 +69,7 @@ func (es *ElasticsearchInterfaceImpl) IsEnabled() bool {
     }
     
     func (es *ElasticsearchInterfaceImpl) IsActive() bool {
    -	return *es.Platform.Config().ElasticsearchSettings.EnableIndexing && atomic.LoadInt32(&es.ready) == 1
    +	return *es.Platform.Config().ElasticsearchSettings.EnableIndexing && es.ready.Load() == 1
     }
     
     func (es *ElasticsearchInterfaceImpl) IsIndexingEnabled() bool {
    @@ -96,7 +96,7 @@ func (es *ElasticsearchInterfaceImpl) Start() *model.AppError {
     	es.mutex.Lock()
     	defer es.mutex.Unlock()
     
    -	if atomic.LoadInt32(&es.ready) != 0 {
    +	if es.ready.Load() != 0 {
     		// Elasticsearch is already started. We don't return an error
     		// because "Test Connection" already re-initializes the client. So this
     		// can be a valid scenario.
    @@ -196,7 +196,7 @@ func (es *ElasticsearchInterfaceImpl) Start() *model.AppError {
     		return model.NewAppError("Elasticsearch.start", "ent.elasticsearch.create_template_file_info_if_not_exists.template_create_failed", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError).Wrap(err)
     	}
     
    -	atomic.StoreInt32(&es.ready, 1)
    +	es.ready.Store(1)
     
     	return nil
     }
    @@ -205,7 +205,7 @@ func (es *ElasticsearchInterfaceImpl) Stop() *model.AppError {
     	es.mutex.Lock()
     	defer es.mutex.Unlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.start", "ent.elasticsearch.stop.already_stopped.app_error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -218,7 +218,7 @@ func (es *ElasticsearchInterfaceImpl) Stop() *model.AppError {
     		es.bulkProcessor = nil
     	}
     
    -	atomic.StoreInt32(&es.ready, 0)
    +	es.ready.Store(0)
     
     	return nil
     }
    @@ -239,7 +239,7 @@ func (es *ElasticsearchInterfaceImpl) IndexPost(post *model.Post, teamId string)
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.IndexPost", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -301,7 +301,7 @@ func (es *ElasticsearchInterfaceImpl) SearchPosts(channels model.ChannelList, se
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return []string{}, nil, model.NewAppError("Elasticsearch.SearchPosts", "ent.elasticsearch.search_posts.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -625,7 +625,7 @@ func (es *ElasticsearchInterfaceImpl) DeletePost(post *model.Post) *model.AppErr
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeletePost", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -647,7 +647,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteChannelPosts(rctx request.CTX, chann
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteChannelPosts", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -683,7 +683,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteUserPosts(rctx request.CTX, userID s
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteUserPosts", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -742,7 +742,7 @@ func (es *ElasticsearchInterfaceImpl) IndexChannel(rctx request.CTX, channel *mo
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.IndexChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -787,7 +787,7 @@ func (es *ElasticsearchInterfaceImpl) SyncBulkIndexChannels(rctx request.CTX, ch
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.SyncBulkIndexChannels", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -826,7 +826,7 @@ func (es *ElasticsearchInterfaceImpl) SearchChannels(teamId, userID string, term
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return []string{}, model.NewAppError("Elasticsearch.SearchChannels", "ent.elasticsearch.search_channels.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -929,7 +929,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteChannel(channel *model.Channel) *mod
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -960,7 +960,7 @@ func (es *ElasticsearchInterfaceImpl) IndexUser(rctx request.CTX, user *model.Us
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.IndexUser", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1002,7 +1002,7 @@ func (es *ElasticsearchInterfaceImpl) autocompleteUsers(contextCategory string,
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return nil, model.NewAppError("Elasticsearch.autocompleteUsers", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1115,7 +1115,7 @@ func (es *ElasticsearchInterfaceImpl) autocompleteUsersNotInChannel(teamId, chan
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return nil, model.NewAppError("Elasticsearch.autocompleteUsersNotInChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1278,7 +1278,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteUser(user *model.User) *model.AppErr
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteUser", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1325,7 +1325,7 @@ func (es *ElasticsearchInterfaceImpl) TestConfig(rctx request.CTX, cfg *model.Co
     	}
     
     	// Resetting the state.
    -	if atomic.CompareAndSwapInt32(&es.ready, 0, 1) {
    +	if es.ready.CompareAndSwap(0, 1) {
     		// Re-assign the client.
     		// This is necessary in case elasticsearch was started
     		// after server start.
    @@ -1345,7 +1345,7 @@ func (es *ElasticsearchInterfaceImpl) PurgeIndexes(rctx request.CTX) *model.AppE
     		return model.NewAppError("Elasticsearch.PurgeIndexes", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.PurgeIndexes", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1395,7 +1395,7 @@ func (es *ElasticsearchInterfaceImpl) PurgeIndexList(rctx request.CTX, indexes [
     		return model.NewAppError("Elasticsearch.PurgeIndexList", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.PurgeIndexList", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1458,7 +1458,7 @@ func (es *ElasticsearchInterfaceImpl) DataRetentionDeleteIndexes(rctx request.CT
     		return model.NewAppError("Elasticsearch.DataRetentionDeleteIndexes", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DataRetentionDeleteIndexes", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1487,7 +1487,7 @@ func (es *ElasticsearchInterfaceImpl) IndexFile(file *model.FileInfo, channelId
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.IndexFile", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1528,7 +1528,7 @@ func (es *ElasticsearchInterfaceImpl) SearchFiles(channels model.ChannelList, se
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return []string{}, model.NewAppError("Elasticsearch.SearchPosts", "ent.elasticsearch.search_files.disabled", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1769,7 +1769,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteFile(fileID string) *model.AppError
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteFile", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1800,7 +1800,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteUserFiles(rctx request.CTX, userID s
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1832,7 +1832,7 @@ func (es *ElasticsearchInterfaceImpl) DeletePostFiles(rctx request.CTX, postID s
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1863,7 +1863,7 @@ func (es *ElasticsearchInterfaceImpl) DeleteFilesBatch(rctx request.CTX, endTime
     	es.mutex.RLock()
     	defer es.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&es.ready) == 0 {
    +	if es.ready.Load() == 0 {
     		return model.NewAppError("Elasticsearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError)
     	}
     
    
  • server/enterprise/elasticsearch/opensearch/opensearch.go+29 29 modified
    @@ -40,7 +40,7 @@ var (
     type OpensearchInterfaceImpl struct {
     	client      *opensearchapi.Client
     	mutex       sync.RWMutex
    -	ready       int32
    +	ready       atomic.Int32
     	version     int
     	fullVersion string
     	plugins     []string
    @@ -71,7 +71,7 @@ func (os *OpensearchInterfaceImpl) IsEnabled() bool {
     }
     
     func (os *OpensearchInterfaceImpl) IsActive() bool {
    -	return *os.Platform.Config().ElasticsearchSettings.EnableIndexing && atomic.LoadInt32(&os.ready) == 1
    +	return *os.Platform.Config().ElasticsearchSettings.EnableIndexing && os.ready.Load() == 1
     }
     
     func (os *OpensearchInterfaceImpl) IsIndexingEnabled() bool {
    @@ -98,7 +98,7 @@ func (os *OpensearchInterfaceImpl) Start() *model.AppError {
     	os.mutex.Lock()
     	defer os.mutex.Unlock()
     
    -	if atomic.LoadInt32(&os.ready) != 0 {
    +	if os.ready.Load() != 0 {
     		// Elasticsearch is already started. We don't return an error
     		// because "Test Connection" already re-initializes the client. So this
     		// can be a valid scenario.
    @@ -205,7 +205,7 @@ func (os *OpensearchInterfaceImpl) Start() *model.AppError {
     		return model.NewAppError("Opensearch.start", "ent.elasticsearch.create_template_file_info_if_not_exists.template_create_failed", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError).Wrap(err)
     	}
     
    -	atomic.StoreInt32(&os.ready, 1)
    +	os.ready.Store(1)
     
     	return nil
     }
    @@ -214,7 +214,7 @@ func (os *OpensearchInterfaceImpl) Stop() *model.AppError {
     	os.mutex.Lock()
     	defer os.mutex.Unlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.start", "ent.elasticsearch.stop.already_stopped.app_error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -227,7 +227,7 @@ func (os *OpensearchInterfaceImpl) Stop() *model.AppError {
     	}
     
     	os.client = nil
    -	atomic.StoreInt32(&os.ready, 0)
    +	os.ready.Store(0)
     
     	return nil
     }
    @@ -248,7 +248,7 @@ func (os *OpensearchInterfaceImpl) IndexPost(post *model.Post, teamId string) *m
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.IndexPost", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -318,7 +318,7 @@ func (os *OpensearchInterfaceImpl) SearchPosts(channels model.ChannelList, searc
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return []string{}, nil, model.NewAppError("Opensearch.SearchPosts", "ent.elasticsearch.search_posts.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -676,7 +676,7 @@ func (os *OpensearchInterfaceImpl) DeletePost(post *model.Post) *model.AppError
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeletePost", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -698,7 +698,7 @@ func (os *OpensearchInterfaceImpl) DeleteChannelPosts(rctx request.CTX, channelI
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteChannelPosts", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -739,7 +739,7 @@ func (os *OpensearchInterfaceImpl) DeleteUserPosts(rctx request.CTX, userID stri
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteUserPosts", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -805,7 +805,7 @@ func (os *OpensearchInterfaceImpl) IndexChannel(rctx request.CTX, channel *model
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.IndexChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -856,7 +856,7 @@ func (os *OpensearchInterfaceImpl) SyncBulkIndexChannels(rctx request.CTX, chann
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.SyncBulkIndexChannels", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -895,7 +895,7 @@ func (os *OpensearchInterfaceImpl) SearchChannels(teamId, userID string, term st
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return []string{}, model.NewAppError("Opensearch.SearchChannels", "ent.elasticsearch.search_channels.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1002,7 +1002,7 @@ func (os *OpensearchInterfaceImpl) DeleteChannel(channel *model.Channel) *model.
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1035,7 +1035,7 @@ func (os *OpensearchInterfaceImpl) IndexUser(rctx request.CTX, user *model.User,
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.IndexUser", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1083,7 +1083,7 @@ func (os *OpensearchInterfaceImpl) autocompleteUsers(contextCategory string, cat
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return nil, model.NewAppError("Opensearch.autocompleteUsers", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1202,7 +1202,7 @@ func (os *OpensearchInterfaceImpl) autocompleteUsersNotInChannel(teamId, channel
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return nil, model.NewAppError("Opensearch.autocompleteUsersNotInChannel", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1371,7 +1371,7 @@ func (os *OpensearchInterfaceImpl) DeleteUser(user *model.User) *model.AppError
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteUser", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1420,7 +1420,7 @@ func (os *OpensearchInterfaceImpl) TestConfig(rctx request.CTX, cfg *model.Confi
     	}
     
     	// Resetting the state.
    -	if atomic.CompareAndSwapInt32(&os.ready, 0, 1) {
    +	if os.ready.CompareAndSwap(0, 1) {
     		// Re-assign the client.
     		// This is necessary in case opensearch was started
     		// after server start.
    @@ -1440,7 +1440,7 @@ func (os *OpensearchInterfaceImpl) PurgeIndexes(rctx request.CTX) *model.AppErro
     		return model.NewAppError("Opensearch.PurgeIndexes", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.PurgeIndexes", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1493,7 +1493,7 @@ func (os *OpensearchInterfaceImpl) PurgeIndexList(rctx request.CTX, indexes []st
     		return model.NewAppError("Opensearch.PurgeIndexList", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.PurgeIndexList", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1558,7 +1558,7 @@ func (os *OpensearchInterfaceImpl) DataRetentionDeleteIndexes(rctx request.CTX,
     		return model.NewAppError("Opensearch.DataRetentionDeleteIndexes", "ent.elasticsearch.test_config.license.error", nil, "", http.StatusNotImplemented)
     	}
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DataRetentionDeleteIndexes", "ent.elasticsearch.generic.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1591,7 +1591,7 @@ func (os *OpensearchInterfaceImpl) IndexFile(file *model.FileInfo, channelId str
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.IndexFile", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1638,7 +1638,7 @@ func (os *OpensearchInterfaceImpl) SearchFiles(channels model.ChannelList, searc
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return []string{}, model.NewAppError("Opensearch.SearchPosts", "ent.elasticsearch.search_files.disabled", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1885,7 +1885,7 @@ func (os *OpensearchInterfaceImpl) DeleteFile(fileID string) *model.AppError {
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteFile", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1918,7 +1918,7 @@ func (os *OpensearchInterfaceImpl) DeleteUserFiles(rctx request.CTX, userID stri
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1955,7 +1955,7 @@ func (os *OpensearchInterfaceImpl) DeletePostFiles(rctx request.CTX, postID stri
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    @@ -1991,7 +1991,7 @@ func (os *OpensearchInterfaceImpl) DeleteFilesBatch(rctx request.CTX, endTime, l
     	os.mutex.RLock()
     	defer os.mutex.RUnlock()
     
    -	if atomic.LoadInt32(&os.ready) == 0 {
    +	if os.ready.Load() == 0 {
     		return model.NewAppError("Opensearch.DeleteFilesBatch", "ent.elasticsearch.not_started.error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError)
     	}
     
    
  • server/enterprise/metrics/metrics.go+3 3 modified
    @@ -2205,12 +2205,12 @@ func extractDBCluster(driver, connectionString string) (string, error) {
     		return "", err
     	}
     
    -	clusterEnd := strings.Index(host, ".")
    -	if clusterEnd == -1 {
    +	cluster, _, found := strings.Cut(host, ".")
    +	if !found {
     		return host, nil
     	}
     
    -	return host[:clusterEnd], nil
    +	return cluster, nil
     }
     
     func extractHost(driver, connectionString string) (string, error) {
    
  • server/go.mod+6 12 modified
    @@ -1,6 +1,6 @@
     module github.com/mattermost/mattermost/server/v8
     
    -go 1.24.13
    +go 1.25.8
     
     require (
     	code.sajari.com/docconv/v2 v2.0.0-pre.4
    @@ -145,7 +145,7 @@ require (
     	github.com/hashicorp/yamux v0.1.2 // indirect
     	github.com/inconshreveable/mousetrap v1.1.0 // indirect
     	github.com/isacikgoz/fuzzy v0.2.0 // indirect
    -	github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 // indirect
    +	github.com/jaytaylor/html2text v0.0.0-20260303211410-1a4bdc82ecec // indirect
     	github.com/jonboulle/clockwork v0.5.0 // indirect
     	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
     	github.com/klauspost/pgzip v1.2.6 // indirect
    @@ -166,7 +166,10 @@ require (
     	github.com/ncruces/go-strftime v1.0.0 // indirect
     	github.com/nwaples/rardecode/v2 v2.2.1 // indirect
     	github.com/oklog/run v1.2.0 // indirect
    -	github.com/olekukonko/tablewriter v1.1.0 // indirect
    +	github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
    +	github.com/olekukonko/errors v1.2.0 // indirect
    +	github.com/olekukonko/ll v0.1.8 // indirect
    +	github.com/olekukonko/tablewriter v1.1.4 // indirect
     	github.com/otiai10/gosseract/v2 v2.4.1 // indirect
     	github.com/pborman/uuid v1.2.1 // indirect
     	github.com/pelletier/go-toml v1.9.5 // indirect
    @@ -221,14 +224,5 @@ require (
     	modernc.org/sqlite v1.39.1 // indirect
     )
     
    -// Prevent tablewriter from being upgraded because the downstream dependency
    -// code.sajari.com/docconv/v2 has an indirect dependency on jaytaylor/html2text via
    -// advancedlogic/GoOse. jaytaylor/html2text does not have a go.mod file which makes
    -// it bump to the latest version always. Tablewriter has made breaking changes to its
    -// latest release.
    -// There is a proposed PR to fix this for GoOse we should monitor:
    -// https://github.com/advancedlogic/GoOse/pull/77
    -replace github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5
    -
     // See MM-66167, MM-68222 for more details.
     replace github.com/vmihailenco/msgpack/v5 => github.com/mattermost/msgpack/v5 v5.0.0-20260408165622-cadfad56a815
    
  • server/go.sum+15 6 modified
    @@ -131,8 +131,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
     github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
     github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
     github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
    -github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
    -github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
    +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
    +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
    +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
    +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
     github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
     github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE=
     github.com/corpix/uarand v0.2.0/go.mod h1:/3Z1QIqWkDIhf6XWn/08/uMHoQ8JUoTIKc2iPchBOmM=
    @@ -341,8 +343,8 @@ github.com/isacikgoz/fuzzy v0.2.0/go.mod h1:VEYn1Gfwj4lMg+FTH603LmQni/zTrhxKv7nT
     github.com/isacikgoz/prompt v0.1.0 h1:fv6jBpM0TNjypC66XuyyzD67dAerIjPzxVAj5WQwn/8=
     github.com/isacikgoz/prompt v0.1.0/go.mod h1:4wlyaxU1qSotYuMZm8vcy1/tGGMfCU1wMjOnXZc58z0=
     github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
    -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
    -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
    +github.com/jaytaylor/html2text v0.0.0-20260303211410-1a4bdc82ecec h1:DrV+GDNKHeHyfqEZaoxQoHlWcgTBiaJ8ZUyNyd5vvkY=
    +github.com/jaytaylor/html2text v0.0.0-20260303211410-1a4bdc82ecec/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
     github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
     github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
     github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
    @@ -470,8 +472,15 @@ github.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+r
     github.com/nwaples/rardecode/v2 v2.2.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
     github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
     github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
    -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
    -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
    +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
    +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
    +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
    +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
    +github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
    +github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
    +github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
    +github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
    +github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
     github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
     github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
     github.com/opensearch-project/opensearch-go/v4 v4.5.0 h1:26XckmmF6MhlXt91Bu1yY6R51jy1Ns/C3XgIfvyeTRo=
    
  • server/.go-version+1 1 modified
    @@ -1 +1 @@
    -1.24.13
    +1.25.8
    
  • server/i18n/en.json+4 0 modified
    @@ -9812,6 +9812,10 @@
         "id": "model.config.is_valid.atmos_camo_image_proxy_options.app_error",
         "translation": "Invalid RemoteImageProxyOptions for atmos/camo. Must be set to your shared key."
       },
    +  {
    +    "id": "model.config.is_valid.atmos_camo_image_proxy_options_length.app_error",
    +    "translation": "Invalid RemoteImageProxyOptions for atmos/camo: HMAC key must be at least {{.MinLength}} bytes for FIPS compliance."
    +  },
       {
         "id": "model.config.is_valid.atmos_camo_image_proxy_url.app_error",
         "translation": "Invalid RemoteImageProxyURL for atmos/camo. Must be set to your shared key."
    
  • server/Makefile+2 2 modified
    @@ -322,7 +322,7 @@ golang-versions: ## Install Golang versions used for compatibility testing (e.g.
     	export GO_COMPATIBILITY_TEST_VERSIONS="${GO_COMPATIBILITY_TEST_VERSIONS}"
     
     golangci-lint: setup-go-work ## Run golangci-lint on codebase
    -	$(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0
    +	$(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
     ifeq ($(BUILD_ENTERPRISE_READY),true)
     	$(GOBIN)/golangci-lint run ./... ./public/... $(BUILD_ENTERPRISE_DIR)/...
     else
    @@ -432,7 +432,7 @@ endif
     check-style: plugin-checker vet golangci-lint ## Runs style/lint checks
     
     gotestsum:
    -	$(GO) install gotest.tools/gotestsum@v1.11.0
    +	$(GO) install gotest.tools/gotestsum@v1.13.0
     
     test-compile: setup-go-work gotestsum ## Compile tests.
     	@echo COMPILE TESTS
    
  • server/platform/services/remotecluster/send_test.go+13 13 modified
    @@ -37,8 +37,8 @@ func TestBroadcastMsg(t *testing.T) {
     	disablePing = true
     
     	t.Run("No error", func(t *testing.T) {
    -		var countCallbacks int32
    -		var countWebReq int32
    +		var countCallbacks atomic.Int32
    +		var countWebReq atomic.Int32
     		merr := merror.New()
     
     		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    @@ -53,7 +53,7 @@ func TestBroadcastMsg(t *testing.T) {
     				w.Write(b)
     			}()
     
    -			atomic.AddInt32(&countWebReq, 1)
    +			countWebReq.Add(1)
     
     			var frame model.RemoteClusterFrame
     			jsonErr := json.NewDecoder(r.Body).Decode(&frame)
    @@ -102,7 +102,7 @@ func TestBroadcastMsg(t *testing.T) {
     
     		err = service.BroadcastMsg(ctx, msg, func(msg model.RemoteClusterMsg, remote *model.RemoteCluster, resp *Response, err error) {
     			defer wg.Done()
    -			atomic.AddInt32(&countCallbacks, 1)
    +			countCallbacks.Add(1)
     
     			if err != nil {
     				merr.Append(err)
    @@ -127,10 +127,10 @@ func TestBroadcastMsg(t *testing.T) {
     
     		assert.NoError(t, merr.ErrorOrNil())
     
    -		assert.Equal(t, int32(NumRemotes), atomic.LoadInt32(&countCallbacks))
    -		assert.Equal(t, int32(NumRemotes), atomic.LoadInt32(&countWebReq))
    +		assert.Equal(t, int32(NumRemotes), countCallbacks.Load())
    +		assert.Equal(t, int32(NumRemotes), countWebReq.Load())
     		t.Logf("%d callbacks counted;  %d web requests counted;  %d expected",
    -			atomic.LoadInt32(&countCallbacks), atomic.LoadInt32(&countWebReq), NumRemotes)
    +			countCallbacks.Load(), countWebReq.Load(), NumRemotes)
     	})
     
     	t.Run("HTTP error", func(t *testing.T) {
    @@ -150,24 +150,24 @@ func TestBroadcastMsg(t *testing.T) {
     		defer service.Shutdown()
     
     		msg := makeRemoteClusterMsg(msgId, NoteContent)
    -		var countCallbacks int32
    -		var countErrors int32
    +		var countCallbacks atomic.Int32
    +		var countErrors atomic.Int32
     		wg := &sync.WaitGroup{}
     		wg.Add(NumRemotes)
     
     		err = service.BroadcastMsg(context.Background(), msg, func(msg model.RemoteClusterMsg, remote *model.RemoteCluster, resp *Response, err error) {
     			defer wg.Done()
    -			atomic.AddInt32(&countCallbacks, 1)
    +			countCallbacks.Add(1)
     			if err != nil {
    -				atomic.AddInt32(&countErrors, 1)
    +				countErrors.Add(1)
     			}
     		})
     		assert.NoError(t, err)
     
     		wg.Wait()
     
    -		assert.Equal(t, int32(NumRemotes), atomic.LoadInt32(&countCallbacks))
    -		assert.Equal(t, int32(NumRemotes), atomic.LoadInt32(&countErrors))
    +		assert.Equal(t, int32(NumRemotes), countCallbacks.Load())
    +		assert.Equal(t, int32(NumRemotes), countErrors.Load())
     	})
     }
     
    
  • server/platform/services/remotecluster/service_test.go+10 10 modified
    @@ -14,18 +14,18 @@ import (
     )
     
     func TestService_AddTopicListener(t *testing.T) {
    -	var count int32
    +	var count atomic.Int32
     
     	l1 := func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *Response) error {
    -		atomic.AddInt32(&count, 1)
    +		count.Add(1)
     		return nil
     	}
     	l2 := func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *Response) error {
    -		atomic.AddInt32(&count, 1)
    +		count.Add(1)
     		return nil
     	}
     	l3 := func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *Response) error {
    -		atomic.AddInt32(&count, 1)
    +		count.Add(1)
     		return nil
     	}
     
    @@ -47,26 +47,26 @@ func TestService_AddTopicListener(t *testing.T) {
     	msg2 := model.RemoteClusterMsg{Topic: "different"}
     
     	service.ReceiveIncomingMsg(rc, msg1)
    -	assert.Equal(t, int32(2), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(2), count.Load())
     
     	service.ReceiveIncomingMsg(rc, msg2)
    -	assert.Equal(t, int32(3), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(3), count.Load())
     
     	service.RemoveTopicListener(l1id)
     	service.ReceiveIncomingMsg(rc, msg1)
    -	assert.Equal(t, int32(4), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(4), count.Load())
     
     	service.RemoveTopicListener(l2id)
     	service.ReceiveIncomingMsg(rc, msg1)
    -	assert.Equal(t, int32(4), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(4), count.Load())
     
     	service.ReceiveIncomingMsg(rc, msg2)
    -	assert.Equal(t, int32(5), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(5), count.Load())
     
     	service.RemoveTopicListener(l3id)
     	service.ReceiveIncomingMsg(rc, msg1)
     	service.ReceiveIncomingMsg(rc, msg2)
    -	assert.Equal(t, int32(5), atomic.LoadInt32(&count))
    +	assert.Equal(t, int32(5), count.Load())
     
     	listeners = service.getTopicListeners("test")
     	assert.Empty(t, listeners)
    
  • server/public/go.mod+1 2 modified
    @@ -1,6 +1,6 @@
     module github.com/mattermost/mattermost/server/public
     
    -go 1.24.13
    +go 1.25.8
     
     require (
     	github.com/blang/semver/v4 v4.0.0
    @@ -18,7 +18,6 @@ require (
     	github.com/mattermost/gosaml2 v0.10.0
     	github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956
     	github.com/mattermost/logr/v2 v2.0.22
    -	github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d
     	github.com/nicksnyder/go-i18n/v2 v2.6.0
     	github.com/pborman/uuid v1.2.1
     	github.com/pkg/errors v0.9.1
    
  • server/public/go.sum+0 2 modified
    @@ -122,8 +122,6 @@ github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb
     github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=
     github.com/mattermost/logr/v2 v2.0.22 h1:npFkXlkAWR9J8payh8ftPcCZvLbHSI125mAM5/r/lP4=
     github.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s=
    -github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d h1:etRyN6FNd6fc7BGZ8X+XB2u/5Hb2HNz5/K53YZNvfrs=
    -github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d/go.mod h1:HILhsra+xY4SNEFhuPbobH3I8a0aeXJcTJ6RWPX85nI=
     github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
     github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
     github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
    
  • server/public/model/config.go+20 5 modified
    @@ -44,8 +44,9 @@ const (
     	MinioSecretKey = "miniosecretkey"
     	MinioBucket    = "mattermost-test"
     
    -	PasswordMaximumLength = 72
    -	PasswordMinimumLength = 5
    +	PasswordMaximumLength     = 72
    +	PasswordMinimumLength     = 5
    +	PasswordFIPSMinimumLength = 14
     
     	ServiceGitlab = "gitlab"
     
    @@ -1749,7 +1750,11 @@ type PasswordSettings struct {
     
     func (s *PasswordSettings) SetDefaults() {
     	if s.MinimumLength == nil {
    -		s.MinimumLength = NewPointer(8)
    +		if FIPSEnabled {
    +			s.MinimumLength = NewPointer(PasswordFIPSMinimumLength)
    +		} else {
    +			s.MinimumLength = NewPointer(8)
    +		}
     	}
     
     	if s.Lowercase == nil {
    @@ -4185,8 +4190,12 @@ func (o *Config) IsValid() *AppError {
     		}
     	}
     
    -	if *o.PasswordSettings.MinimumLength < PasswordMinimumLength || *o.PasswordSettings.MinimumLength > PasswordMaximumLength {
    -		return NewAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]any{"MinLength": PasswordMinimumLength, "MaxLength": PasswordMaximumLength}, "", http.StatusBadRequest)
    +	minPasswordLength := PasswordMinimumLength
    +	if FIPSEnabled {
    +		minPasswordLength = PasswordFIPSMinimumLength
    +	}
    +	if *o.PasswordSettings.MinimumLength < minPasswordLength || *o.PasswordSettings.MinimumLength > PasswordMaximumLength {
    +		return NewAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]any{"MinLength": minPasswordLength, "MaxLength": PasswordMaximumLength}, "", http.StatusBadRequest)
     	}
     
     	if appErr := o.RateLimitSettings.isValid(); appErr != nil {
    @@ -4935,6 +4944,12 @@ func (s *ImageProxySettings) isValid() *AppError {
     			if *s.RemoteImageProxyOptions == "" {
     				return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_options.app_error", nil, "", http.StatusBadRequest)
     			}
    +
    +			// RemoteImageProxyOptions is used as the HMAC key for URL signing,
    +			// so it is subject to the same FIPS minimum key length as passwords.
    +			if FIPSEnabled && len(*s.RemoteImageProxyOptions) < PasswordFIPSMinimumLength {
    +				return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_options_length.app_error", map[string]any{"MinLength": PasswordFIPSMinimumLength}, "", http.StatusBadRequest)
    +			}
     		default:
     			return NewAppError("Config.IsValid", "model.config.is_valid.image_proxy_type.app_error", nil, "", http.StatusBadRequest)
     		}
    
  • server/public/model/config_test.go+11 1 modified
    @@ -973,6 +973,8 @@ func TestImageProxySettingsSetDefaults(t *testing.T) {
     }
     
     func TestImageProxySettingsIsValid(t *testing.T) {
    +	testHMACKey := NewTestPassword()
    +
     	for _, test := range []struct {
     		Name                    string
     		Enable                  bool
    @@ -1013,7 +1015,7 @@ func TestImageProxySettingsIsValid(t *testing.T) {
     			Enable:                  true,
     			ImageProxyType:          ImageProxyTypeAtmosCamo,
     			RemoteImageProxyURL:     "someurl",
    -			RemoteImageProxyOptions: "someoptions",
    +			RemoteImageProxyOptions: testHMACKey,
     			ExpectError:             false,
     		},
     		{
    @@ -1032,6 +1034,14 @@ func TestImageProxySettingsIsValid(t *testing.T) {
     			RemoteImageProxyOptions: "",
     			ExpectError:             true,
     		},
    +		{
    +			Name:                    "atmos/camo, short options under FIPS",
    +			Enable:                  true,
    +			ImageProxyType:          ImageProxyTypeAtmosCamo,
    +			RemoteImageProxyURL:     "someurl",
    +			RemoteImageProxyOptions: "foo",
    +			ExpectError:             FIPSEnabled,
    +		},
     	} {
     		t.Run(test.Name, func(t *testing.T) {
     			ips := &ImageProxySettings{
    
  • server/public/model/fips_default.go+8 0 added
    @@ -0,0 +1,8 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +//go:build !requirefips
    +
    +package model
    +
    +const FIPSEnabled = false
    
  • server/public/model/fips.go+8 0 added
    @@ -0,0 +1,8 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +//go:build requirefips
    +
    +package model
    +
    +const FIPSEnabled = true
    
  • server/public/model/push_notification.go+3 5 modified
    @@ -95,10 +95,8 @@ func (pn *PushNotification) DeepCopy() *PushNotification {
     }
     
     func (pn *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
    -	index := strings.Index(deviceId, ":")
    -
    -	if index > -1 {
    -		pn.Platform = deviceId[:index]
    -		pn.DeviceId = deviceId[index+1:]
    +	if platform, id, ok := strings.Cut(deviceId, ":"); ok {
    +		pn.Platform = platform
    +		pn.DeviceId = id
     	}
     }
    
  • server/public/model/remote_cluster_test.go+23 17 modified
    @@ -132,29 +132,35 @@ func TestRemoteClusterInviteEncryption(t *testing.T) {
     		badDecrypt bool
     		password   string
     		invite     RemoteClusterInvite
    +		skipFIPS   bool
     	}{
    -		{name: "empty password", badDecrypt: false, password: "", invite: makeInvite("https://example.com:8065")},
    +		{name: "empty password", badDecrypt: false, password: "", invite: makeInvite("https://example.com:8065"), skipFIPS: true},
     		{name: "good password", badDecrypt: false, password: "Ultra secret password!", invite: makeInvite("https://example.com:8065")},
     		{name: "bad decrypt", badDecrypt: true, password: "correct horse battery staple", invite: makeInvite("https://example.com:8065")},
     	}
     
     	for _, tt := range testData {
    -		encrypted, err := tt.invite.Encrypt(tt.password)
    -		require.NoError(t, err)
    -
    -		invite := RemoteClusterInvite{}
    -		if tt.badDecrypt {
    -			buf := make([]byte, len(encrypted))
    -			_, err = io.ReadFull(rand.Reader, buf)
    -			assert.NoError(t, err)
    -
    -			err = invite.Decrypt(buf, tt.password)
    -			require.Error(t, err)
    -		} else {
    -			err = invite.Decrypt(encrypted, tt.password)
    +		t.Run(tt.name, func(t *testing.T) {
    +			if tt.skipFIPS && FIPSEnabled {
    +				t.Skip("skipping under FIPS: encryption requires keys >= 14 bytes")
    +			}
    +			encrypted, err := tt.invite.Encrypt(tt.password)
     			require.NoError(t, err)
    -			assert.Equal(t, tt.invite, invite)
    -		}
    +
    +			invite := RemoteClusterInvite{}
    +			if tt.badDecrypt {
    +				buf := make([]byte, len(encrypted))
    +				_, err = io.ReadFull(rand.Reader, buf)
    +				assert.NoError(t, err)
    +
    +				err = invite.Decrypt(buf, tt.password)
    +				require.Error(t, err)
    +			} else {
    +				err = invite.Decrypt(encrypted, tt.password)
    +				require.NoError(t, err)
    +				assert.Equal(t, tt.invite, invite)
    +			}
    +		})
     	}
     }
     
    @@ -168,7 +174,7 @@ func TestRemoteClusterInviteBackwardCompatibility(t *testing.T) {
     		Version:        2, // Old version using scrypt
     	}
     
    -	password := "test password"
    +	password := NewTestPassword()
     
     	// Encrypt with old method (scrypt)
     	encrypted, err := oldInvite.Encrypt(password)
    
  • server/public/model/user.go+13 7 modified
    @@ -6,6 +6,7 @@ package model
     import (
     	"crypto/sha256"
     	"encoding/json"
    +	"errors"
     	"fmt"
     	"net/http"
     	"regexp"
    @@ -15,13 +16,10 @@ import (
     	"time"
     	"unicode/utf8"
     
    -	"github.com/pkg/errors"
    -
     	"golang.org/x/text/language"
     
     	"github.com/mattermost/mattermost/server/public/shared/mlog"
     	"github.com/mattermost/mattermost/server/public/shared/timezones"
    -	"github.com/mattermost/mattermost/server/v8/channels/app/password/hashers"
     )
     
     const (
    @@ -72,6 +70,14 @@ const (
     	UserAuthServiceMagicLink = "magic_link"
     )
     
    +// ErrPasswordTooLong is returned when the password exceeds
    +// UserPasswordMaxLength bytes.
    +var ErrPasswordTooLong = fmt.Errorf("password too long; maximum length in bytes: %d", UserPasswordMaxLength)
    +
    +type UserPasswordHasher interface {
    +	Hash(password string) (string, error)
    +}
    +
     //msgp:tuple User
     
     // User contains the details about the user.
    @@ -464,7 +470,7 @@ func NormalizeEmail(email string) string {
     // PreSave will set the Id and Username if missing.  It will also fill
     // in the CreateAt, UpdateAt times.  It will also hash the password.  It should
     // be run before saving the user to the db.
    -func (u *User) PreSave() *AppError {
    +func (u *User) PreSave(hasher UserPasswordHasher) *AppError {
     	if u.Id == "" {
     		u.Id = NewId()
     	}
    @@ -511,13 +517,13 @@ func (u *User) PreSave() *AppError {
     	}
     
     	if u.Password != "" {
    -		hashed, err := hashers.Hash(u.Password)
    -		if errors.Is(err, hashers.ErrPasswordTooLong) {
    +		hashed, err := hasher.Hash(u.Password)
    +		if errors.Is(err, ErrPasswordTooLong) {
     			return NewAppError("User.PreSave", "model.user.pre_save.password_too_long.app_error",
     				nil, "user_id="+u.Id, http.StatusBadRequest).Wrap(err)
     		} else if err != nil {
     			return NewAppError("User.PreSave", "model.user.pre_save.password_hash.app_error",
    -				nil, "user_id="+u.Id, http.StatusBadRequest).Wrap(err)
    +				nil, "user_id="+u.Id, http.StatusInternalServerError).Wrap(err)
     		}
     		u.Password = hashed
     	}
    
  • server/public/model/user_test.go+17 4 modified
    @@ -14,7 +14,6 @@ import (
     
     	"github.com/mattermost/mattermost/server/public/shared/mlog"
     	"github.com/mattermost/mattermost/server/public/shared/timezones"
    -	"github.com/mattermost/mattermost/server/v8/channels/app/password/hashers"
     )
     
     func TestUserAuditable(t *testing.T) {
    @@ -197,10 +196,19 @@ func TestUserDeepCopy(t *testing.T) {
     	assert.Equal(t, id, copyUser.Id)
     }
     
    +type stubHasherFunc func(password string) (string, error)
    +
    +func (f stubHasherFunc) Hash(password string) (string, error) { return f(password) }
    +
     func TestUserPreSave(t *testing.T) {
    +	hasher := stubHasherFunc(func(password string) (string, error) {
    +		return "hashed_" + password, nil
    +	})
    +
     	user := User{Password: "test"}
    -	err := user.PreSave()
    +	err := user.PreSave(hasher)
     	require.Nil(t, err)
    +	assert.Equal(t, "hashed_test", user.Password)
     	user.Etag(true, true)
     	assert.NotNil(t, user.Timezone, "Timezone is nil")
     	assert.Equal(t, user.Timezone["useAutomaticTimezone"], "true", "Timezone is not set to default")
    @@ -218,9 +226,14 @@ func TestUserPreSave(t *testing.T) {
     }
     
     func TestUserPreSavePwdTooLong(t *testing.T) {
    +	hasher := stubHasherFunc(func(password string) (string, error) {
    +		return "", ErrPasswordTooLong
    +	})
    +
     	user := User{Password: strings.Repeat("1234567890", 8)}
    -	err := user.PreSave()
    -	assert.ErrorIs(t, err, hashers.ErrPasswordTooLong)
    +	err := user.PreSave(hasher)
    +	require.NotNil(t, err)
    +	assert.Equal(t, "model.user.pre_save.password_too_long.app_error", err.Id)
     }
     
     func TestUserPreUpdate(t *testing.T) {
    
  • server/public/model/utils.go+38 0 modified
    @@ -406,6 +406,44 @@ func NewRandomString(length int) string {
     	return encoding.EncodeToString(data)[:length]
     }
     
    +// NewTestPassword generates a password that meets complexity requirements
    +// (uppercase, lowercase, number, special character) with a minimum length of 14.
    +// The passwords are not cryptographically random. Use only in tests.
    +func NewTestPassword() string {
    +	const (
    +		lowers   = LowercaseLetters
    +		uppers   = UppercaseLetters
    +		digits   = NUMBERS
    +		specials = "!%^&*(),."
    +		all      = lowers + uppers + digits + specials
    +		minLen   = PasswordFIPSMinimumLength
    +	)
    +
    +	// Read all randomness in one call for performance.
    +	// We need minLen bytes for character selection + minLen bytes for shuffle indices.
    +	entropy := make([]byte, 2*minLen)
    +	if _, err := rand.Read(entropy); err != nil {
    +		panic(err)
    +	}
    +
    +	pw := make([]byte, minLen)
    +	pw[0] = uppers[int(entropy[0])%len(uppers)]
    +	pw[1] = lowers[int(entropy[1])%len(lowers)]
    +	pw[2] = digits[int(entropy[2])%len(digits)]
    +	pw[3] = specials[int(entropy[3])%len(specials)]
    +	for i := 4; i < minLen; i++ {
    +		pw[i] = all[int(entropy[i])%len(all)]
    +	}
    +
    +	// Shuffle to avoid predictable prefix using remaining entropy.
    +	for i := len(pw) - 1; i > 0; i-- {
    +		j := int(entropy[minLen+i]) % (i + 1)
    +		pw[i], pw[j] = pw[j], pw[i]
    +	}
    +
    +	return string(pw)
    +}
    +
     // GetMillis is a convenience method to get milliseconds since epoch.
     func GetMillis() int64 {
     	return GetMillisForTime(time.Now())
    
  • server/public/model/utils_test.go+6 0 modified
    @@ -33,6 +33,12 @@ func TestRandomString(t *testing.T) {
     	}
     }
     
    +func BenchmarkNewTestPassword(b *testing.B) {
    +	for range b.N {
    +		NewTestPassword()
    +	}
    +}
    +
     func TestGetMillisForTime(t *testing.T) {
     	thisTimeMillis := int64(1471219200000)
     	thisTime := time.Date(2016, time.August, 15, 0, 0, 0, 0, time.UTC)
    
  • server/public/pluginapi/cluster/job_test.go+4 8 modified
    @@ -326,12 +326,10 @@ func TestSchedule(t *testing.T) {
     		var wg sync.WaitGroup
     		for i := range 3 {
     			job := jobs[i]
    -			wg.Add(1)
    -			go func() {
    -				defer wg.Done()
    +			wg.Go(func() {
     				err := job.Close()
     				require.NoError(t, err)
    -			}()
    +			})
     		}
     		wg.Wait()
     
    @@ -386,12 +384,10 @@ func TestSchedule(t *testing.T) {
     		var wg sync.WaitGroup
     		for i := range 3 {
     			job := jobs[i]
    -			wg.Add(1)
    -			go func() {
    -				defer wg.Done()
    +			wg.Go(func() {
     				err := job.Close()
     				require.NoError(t, err)
    -			}()
    +			})
     		}
     		wg.Wait()
     
    
  • server/public/pluginapi/kv_memory_test.go+4 8 modified
    @@ -136,13 +136,11 @@ func TestMemoryStoreSet(t *testing.T) {
     		var wg sync.WaitGroup
     		const n = 100
     		for i := range n {
    -			wg.Add(1)
    -			go func() {
    -				defer wg.Done()
    +			wg.Go(func() {
     				ok, err := store.Set(fmt.Sprintf("k_%d", i), []byte("value"))
     				require.NoError(t, err)
     				require.True(t, ok)
    -			}()
    +			})
     		}
     
     		wg.Wait()
    @@ -189,12 +187,10 @@ func TestMemoryStoreSetAtomicWithRetries(t *testing.T) {
     		var wg sync.WaitGroup
     		const n = 10
     		for i := range n {
    -			wg.Add(1)
    -			go func() {
    -				defer wg.Done()
    +			wg.Go(func() {
     				err := store.SetAtomicWithRetries("key", func(oldValue []byte) (any, error) { return fmt.Sprintf("k_%d", i), nil })
     				require.NoError(t, err)
    -			}()
    +			})
     		}
     
     		wg.Wait()
    
  • server/public/plugin/client_rpc.go+4 8 modified
    @@ -239,23 +239,19 @@ type Z_OnActivateReturns struct {
     
     func (g *hooksRPCClient) OnActivate() error {
     	muxId := g.muxBroker.NextId()
    -	g.doneWg.Add(1)
    -	go func() {
    -		defer g.doneWg.Done()
    +	g.doneWg.Go(func() {
     		g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{
     			impl:      g.apiImpl,
     			muxBroker: g.muxBroker,
     		})
    -	}()
    +	})
     
     	nextID := g.muxBroker.NextId()
    -	g.doneWg.Add(1)
    -	go func() {
    -		defer g.doneWg.Done()
    +	g.doneWg.Go(func() {
     		g.muxBroker.AcceptAndServe(nextID, &dbRPCServer{
     			dbImpl: g.driver,
     		})
    -	}()
    +	})
     
     	_args := &Z_OnActivateArgs{
     		APIMuxId:    muxId,
    
  • server/scripts/run-shard-tests.sh+116 0 added
    @@ -0,0 +1,116 @@
    +#!/bin/bash
    +set -uo pipefail
    +
    +# run-shard-tests.sh — Multi-run test wrapper for sharded CI
    +#
    +# When a shard has both "light" packages (run whole) and "heavy" package
    +# splits (run with -run regex), we need multiple gotestsum invocations.
    +# The Makefile's test-server target only supports a single invocation,
    +# so this script calls gotestsum directly.
    +#
    +# Each invocation produces its own JUnit XML and JSON log files, which
    +# are merged at the end into the standard report.xml and gotestsum.json
    +# that the CI pipeline expects.
    +#
    +# Input files (in working directory, written by shard-split.js):
    +#   shard-te-packages.txt   — space-separated TE packages
    +#   shard-ee-packages.txt   — space-separated EE packages
    +#   shard-heavy-runs.txt    — one line per heavy run: "pkg REGEX"
    +#
    +# Environment variables (set by CI):
    +#   RACE_MODE          — "-race" on master, empty on PRs
    +#   ENABLE_COVERAGE    — "true" to enable coverage profiling
    +
    +GOBIN="$(pwd)/bin"
    +
    +# Set up build prerequisites (go.work, gotestsum, go versions)
    +# These are normally done by make test-server-pre.
    +make setup-go-work gotestsum golang-versions
    +
    +GOFLAGS_BASE="-buildvcs=false -timeout=90m"
    +RACE_FLAG="${RACE_MODE:-}"
    +
    +RUN_IDX=0
    +FAILURES=0
    +
    +# run_gotestsum PACKAGES [RUN_REGEX]
    +#   $1 = space-separated package list
    +#   $2 = optional -run regex (passed directly to go test)
    +run_gotestsum() {
    +  local junitfile="report-${RUN_IDX}.xml"
    +  local jsonfile="gotestsum-${RUN_IDX}.json"
    +  local run_flag=""
    +  if [[ -n "${2:-}" ]]; then run_flag="-run $2"; fi
    +
    +  local coverage_flag=""
    +  if [[ "${ENABLE_COVERAGE:-false}" == "true" ]]; then
    +    coverage_flag="-coverprofile=cover-${RUN_IDX}.out -covermode=atomic"
    +  fi
    +
    +  RUN_IDX=$((RUN_IDX + 1))
    +
    +  GOTESTSUM_JUNITFILE="$junitfile" GOTESTSUM_JSONFILE="$jsonfile" \
    +    "$GOBIN/gotestsum" --format "${GOTESTSUM_FORMAT:-testname}" --rerun-fails=3 --packages="$1" \
    +    -- $GOFLAGS_BASE $RACE_FLAG $coverage_flag $run_flag \
    +    || FAILURES=$((FAILURES + 1))
    +}
    +
    +# ── Read shard assignments ──
    +SHARD_TE=""
    +SHARD_EE=""
    +HEAVY_RUNS=""
    +
    +if [[ -f shard-te-packages.txt ]]; then
    +  SHARD_TE=$(cat shard-te-packages.txt)
    +fi
    +if [[ -f shard-ee-packages.txt ]]; then
    +  SHARD_EE=$(cat shard-ee-packages.txt)
    +fi
    +if [[ -f shard-heavy-runs.txt && -s shard-heavy-runs.txt ]]; then
    +  HEAVY_RUNS=$(cat shard-heavy-runs.txt)
    +fi
    +
    +# ── Run light packages (single invocation, no -run filter) ──
    +ALL_LIGHT="${SHARD_TE} ${SHARD_EE}"
    +ALL_LIGHT="${ALL_LIGHT## }"
    +ALL_LIGHT="${ALL_LIGHT%% }"
    +if [[ -n "$ALL_LIGHT" ]]; then
    +  LIGHT_COUNT=$(echo "$ALL_LIGHT" | wc -w)
    +  echo "Running $LIGHT_COUNT light packages..."
    +  run_gotestsum "$ALL_LIGHT"
    +fi
    +
    +# ── Run heavy package splits (one invocation per package subset) ──
    +if [[ -n "$HEAVY_RUNS" ]]; then
    +  while IFS= read -r line; do
    +    [[ -z "$line" ]] && continue
    +    PKG="${line%% *}"
    +    REGEX="${line#* }"
    +    SHORT_PKG="${PKG##*/}"
    +    TEST_COUNT=$(echo "$REGEX" | tr '|' '\n' | wc -l)
    +    echo "Running $TEST_COUNT tests from $SHORT_PKG..."
    +    run_gotestsum "$PKG" "$REGEX"
    +  done <<< "$HEAVY_RUNS"
    +fi
    +
    +# ── Merge results from all runs ──
    +echo "Merging results from $RUN_IDX gotestsum runs..."
    +
    +if ls report-*.xml 1>/dev/null 2>&1; then
    +  # Simple XML concatenation — the merge job uses junit-report-merger for proper merging
    +  head -1 report-0.xml > report.xml
    +  echo "<testsuites>" >> report.xml
    +  for f in report-*.xml; do
    +    grep -v "<?xml" "$f" | grep -v "^<testsuites" | grep -v "^</testsuites" >> report.xml || true
    +  done
    +  echo "</testsuites>" >> report.xml
    +fi
    +
    +cat gotestsum-*.json > gotestsum.json 2>/dev/null || true
    +
    +if [[ $FAILURES -gt 0 ]]; then
    +  echo "Shard complete: $RUN_IDX gotestsum runs, $FAILURES failed"
    +  exit 1
    +fi
    +
    +echo "Shard complete: $RUN_IDX gotestsum runs, all passed"
    
  • server/scripts/shard-split.js+242 0 added
    @@ -0,0 +1,242 @@
    +#!/usr/bin/env node
    +/**
    + * shard-split.js — Test shard assignment solver
    + *
    + * Splits Go test packages across N parallel CI runners using timing data
    + * from previous runs. Uses a two-tier strategy:
    + *
    + *   1. "Light" packages (< HEAVY_MS total runtime): assigned whole to a shard
    + *   2. "Heavy" packages (>= HEAVY_MS): individual tests distributed across
    + *      shards using -run regex filters
    + *
    + * Timing data sources (in priority order):
    + *   - gotestsum.json (JSONL): per-test elapsed times from previous run
    + *   - prev-report.xml (JUnit XML): package-level timing (fallback)
    + *   - Round-robin: when no timing data exists at all
    + *
    + * Assignment algorithm: greedy bin-packing (sort by duration desc, assign
    + * each item to the shard with lowest current load). Simple and effective
    + * for our distribution where 2 packages dominate 84% of runtime.
    + *
    + * Environment variables:
    + *   SHARD_INDEX  — this runner's index (0-based)
    + *   SHARD_TOTAL  — total number of shards
    + *
    + * Input files (in working directory):
    + *   all-packages.txt      — newline-separated list of all test packages
    + *   prev-gotestsum.json   — (optional) JSONL timing data from previous run
    + *   prev-report.xml       — (optional) JUnit XML from previous run
    + *
    + * Output files (in working directory):
    + *   shard-te-packages.txt   — space-separated TE packages for this shard
    + *   shard-ee-packages.txt   — space-separated EE packages for this shard
    + *   shard-heavy-runs.txt    — heavy package runs, one per line: "pkg REGEX"
    + */
    +
    +const fs = require("node:fs");
    +const { execSync } = require("node:child_process");
    +
    +const SHARD_INDEX = parseInt(process.env.SHARD_INDEX);
    +const SHARD_TOTAL = parseInt(process.env.SHARD_TOTAL);
    +const HEAVY_MS = 300000; // 5 min: packages above this get test-level splitting
    +// Only api4 (~38 min) and app (~15 min) exceed this threshold.
    +// Packages like sqlstore (~3 min) stay whole to preserve test isolation —
    +// their integrity tests scan the entire database and break if split across
    +// shards where other tests leave data behind.
    +
    +if (isNaN(SHARD_INDEX) || isNaN(SHARD_TOTAL) || SHARD_TOTAL < 1) {
    +  console.error("ERROR: SHARD_INDEX and SHARD_TOTAL must be set");
    +  process.exit(1);
    +}
    +
    +const allPkgs = fs.readFileSync("all-packages.txt", "utf8").trim().split("\n").filter(Boolean);
    +if (allPkgs.length === 0) {
    +  console.error("WARNING: No test packages found in all-packages.txt");
    +  process.exit(0);
    +}
    +
    +const pkgTimes = {};
    +const testTimes = {}; // "pkg::TestName" -> ms
    +
    +// ── Parse gotestsum.json (JSONL) for per-test timing ──
    +// Each line is a JSON event; we want "pass" events with Elapsed times.
    +if (fs.existsSync("prev-gotestsum.json")) {
    +  console.log("::group::Parsing gotestsum.json timing data");
    +  const lines = fs.readFileSync("prev-gotestsum.json", "utf8").split("\n");
    +  for (const line of lines) {
    +    if (!line.includes('"pass"')) continue;
    +    try {
    +      const d = JSON.parse(line);
    +      if (!d.Test || !d.Package) continue;
    +      const elapsed = Math.round((d.Elapsed || 0) * 1000);
    +      // Aggregate package time from test pass events
    +      pkgTimes[d.Package] = (pkgTimes[d.Package] || 0) + elapsed;
    +      // Top-level test name (use max elapsed for parent vs subtests)
    +      const top = d.Test.split("/")[0];
    +      const key = d.Package + "::" + top;
    +      testTimes[key] = Math.max(testTimes[key] || 0, elapsed);
    +    } catch (e) {
    +      // Skip malformed lines
    +    }
    +  }
    +  console.log(
    +    `gotestsum.json: ${Object.keys(pkgTimes).length} packages, ${Object.keys(testTimes).length} tests`
    +  );
    +  console.log("::endgroup::");
    +}
    +
    +// ── Fallback: parse JUnit XML for package-level timing ──
    +if (Object.keys(pkgTimes).length === 0 && fs.existsSync("prev-report.xml")) {
    +  console.log("::group::Parsing JUnit XML timing data (fallback)");
    +  const xml = fs.readFileSync("prev-report.xml", "utf8");
    +  for (const m of xml.matchAll(/<testsuite[^>]*>/g)) {
    +    const name = m[0].match(/name="([^"]+)"/)?.[1];
    +    const time = m[0].match(/\btime="([^"]+)"/)?.[1];
    +    if (name && time) {
    +      pkgTimes[name] = (pkgTimes[name] || 0) + Math.round(parseFloat(time) * 1000);
    +    }
    +  }
    +  console.log(`JUnit XML: ${Object.keys(pkgTimes).length} packages (no per-test data)`);
    +  console.log("::endgroup::");
    +}
    +
    +const hasTimingData = Object.keys(pkgTimes).length > 0;
    +const hasTestTiming = Object.keys(testTimes).length > 0;
    +
    +// ── Identify heavy packages ──
    +// Only split at test level if we have per-test timing data
    +const heavyPkgs = new Set();
    +if (hasTestTiming) {
    +  for (const [pkg, ms] of Object.entries(pkgTimes)) {
    +    if (ms > HEAVY_MS) heavyPkgs.add(pkg);
    +  }
    +}
    +if (heavyPkgs.size > 0) {
    +  console.log("Heavy packages (test-level splitting):");
    +  for (const p of heavyPkgs) {
    +    console.log(`  ${(pkgTimes[p] / 1000).toFixed(0)}s  ${p.split("/").pop()}`);
    +  }
    +}
    +
    +// ── Build work items ──
    +// Each item is either a whole package ("P") or a single test from a heavy package ("T")
    +const items = [];
    +for (const pkg of allPkgs) {
    +  if (heavyPkgs.has(pkg)) {
    +    // Split into individual test items
    +    const tests = Object.entries(testTimes)
    +      .filter(([k]) => k.startsWith(pkg + "::"))
    +      .map(([k, ms]) => ({ ms, type: "T", pkg, test: k.split("::")[1] }));
    +    if (tests.length > 0) {
    +      items.push(...tests);
    +    } else {
    +      // Shouldn't happen, but fall back to whole package
    +      items.push({ ms: pkgTimes[pkg] || 1, type: "P", pkg });
    +    }
    +  } else {
    +    items.push({ ms: pkgTimes[pkg] || 1, type: "P", pkg });
    +  }
    +}
    +// ── Discover new/renamed tests in heavy packages ──
    +// Tests not in the timing cache won't appear in any shard's -run regex,
    +// silently skipping them. Discover current test names at runtime and
    +// assign any cache-missing tests to the least-loaded shard.
    +if (heavyPkgs.size > 0) {
    +  console.log("::group::Discovering new tests in heavy packages");
    +  for (const pkg of heavyPkgs) {
    +    const cachedTests = new Set(
    +      Object.keys(testTimes)
    +        .filter((k) => k.startsWith(pkg + "::"))
    +        .map((k) => k.split("::")[1])
    +    );
    +    try {
    +      const out = execSync(`go test -list '.*' ${pkg} 2>/dev/null`, {
    +        encoding: "utf8",
    +        timeout: 60000,
    +      });
    +      const currentTests = out
    +        .split("\n")
    +        .map((l) => l.trim())
    +        .filter((l) => /^Test[A-Z]/.test(l));
    +      let newCount = 0;
    +      for (const t of currentTests) {
    +        if (!cachedTests.has(t)) {
    +          // Assign a small default duration so it gets picked up
    +          items.push({ ms: 1000, type: "T", pkg, test: t });
    +          newCount++;
    +        }
    +      }
    +      if (newCount > 0) {
    +        console.log(`  ${pkg.split("/").pop()}: ${newCount} new test(s) not in cache`);
    +      }
    +    } catch (e) {
    +      console.log(`  ${pkg.split("/").pop()}: go test -list failed, skipping discovery`);
    +    }
    +  }
    +  console.log("::endgroup::");
    +}
    +
    +// Sort descending by duration for greedy bin-packing
    +items.sort((a, b) => b.ms - a.ms);
    +
    +// ── Greedy bin-packing assignment ──
    +const shards = Array.from({ length: SHARD_TOTAL }, () => ({
    +  load: 0,
    +  whole: [],
    +  heavy: {},
    +}));
    +
    +if (!hasTimingData) {
    +  // Round-robin fallback when no timing data exists
    +  console.log("No timing data — using round-robin");
    +  allPkgs.forEach((pkg, i) => {
    +    shards[i % SHARD_TOTAL].whole.push(pkg);
    +  });
    +} else {
    +  for (const item of items) {
    +    // Find shard with minimum current load
    +    const min = shards.reduce((m, s, i) => (s.load < shards[m].load ? i : m), 0);
    +    shards[min].load += item.ms;
    +    if (item.type === "P") {
    +      shards[min].whole.push(item.pkg);
    +    } else {
    +      if (!shards[min].heavy[item.pkg]) shards[min].heavy[item.pkg] = [];
    +      shards[min].heavy[item.pkg].push(item.test);
    +    }
    +  }
    +}
    +
    +// ── Report shard assignments ──
    +console.log("::group::Shard assignment");
    +for (let i = 0; i < SHARD_TOTAL; i++) {
    +  const s = shards[i];
    +  const hRuns = Object.keys(s.heavy).length;
    +  const hTests = Object.values(s.heavy).reduce((n, a) => n + a.length, 0);
    +  const marker = i === SHARD_INDEX ? " ← THIS SHARD" : "";
    +  console.log(
    +    `Shard ${i}: ${(s.load / 1000).toFixed(1)}s | ${s.whole.length} pkgs` +
    +      (hRuns > 0 ? `, ${hRuns} heavy splits (${hTests} tests)` : "") +
    +      marker
    +  );
    +}
    +console.log("::endgroup::");
    +
    +// ── Write output for this shard ──
    +const myShard = shards[SHARD_INDEX];
    +const te = myShard.whole.filter((p) => !p.includes("/enterprise/")).join(" ");
    +const ee = myShard.whole.filter((p) => p.includes("/enterprise/")).join(" ");
    +
    +fs.writeFileSync("shard-te-packages.txt", te);
    +fs.writeFileSync("shard-ee-packages.txt", ee);
    +
    +// Heavy package runs: one line per run as "pkg REGEX"
    +const heavyRuns = Object.entries(myShard.heavy).map(([pkg, tests]) => {
    +  const regex = tests.map((t) => "^" + t + "$").join("|");
    +  return pkg + " " + regex;
    +});
    +fs.writeFileSync("shard-heavy-runs.txt", heavyRuns.join("\n"));
    +
    +console.log(
    +  `Light packages: ${myShard.whole.length} (${te.split(" ").filter(Boolean).length} TE, ${ee.split(" ").filter(Boolean).length} EE)`
    +);
    +console.log(`Heavy package runs: ${heavyRuns.length}`);
    
  • server/scripts/shard-split.test.js+333 0 added
    @@ -0,0 +1,333 @@
    +const { describe, it, beforeEach, afterEach } = require("node:test");
    +const assert = require("node:assert/strict");
    +const fs = require("node:fs");
    +const path = require("node:path");
    +const { execFileSync } = require("node:child_process");
    +const os = require("node:os");
    +
    +const SCRIPT = path.join(__dirname, "shard-split.js");
    +const TESTDATA = path.join(__dirname, "testdata");
    +
    +/**
    + * Helper: run shard-split.js in a temp directory with given inputs.
    + * Returns the output files and stdout.
    + */
    +function runSolver({ packages, shardIndex, shardTotal, gotestsumJson, prevReportXml }) {
    +  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "shard-test-"));
    +  try {
    +    fs.writeFileSync(path.join(tmpDir, "all-packages.txt"), packages.join("\n"));
    +
    +    if (gotestsumJson) {
    +      fs.writeFileSync(path.join(tmpDir, "prev-gotestsum.json"), gotestsumJson);
    +    }
    +    if (prevReportXml) {
    +      fs.writeFileSync(path.join(tmpDir, "prev-report.xml"), prevReportXml);
    +    }
    +
    +    const stdout = execFileSync("node", [SCRIPT], {
    +      cwd: tmpDir,
    +      env: {
    +        ...process.env,
    +        SHARD_INDEX: String(shardIndex),
    +        SHARD_TOTAL: String(shardTotal),
    +      },
    +      encoding: "utf8",
    +    });
    +
    +    const te = fs.readFileSync(path.join(tmpDir, "shard-te-packages.txt"), "utf8");
    +    const ee = fs.readFileSync(path.join(tmpDir, "shard-ee-packages.txt"), "utf8");
    +    const heavy = fs.readFileSync(path.join(tmpDir, "shard-heavy-runs.txt"), "utf8");
    +
    +    return { te, ee, heavy, stdout };
    +  } finally {
    +    fs.rmSync(tmpDir, { recursive: true, force: true });
    +  }
    +}
    +
    +describe("shard-split.js", () => {
    +  describe("round-robin fallback (no timing data)", () => {
    +    it("distributes packages evenly across shards", () => {
    +      const packages = [
    +        "github.com/mattermost/mattermost/server/v8/channels/api4",
    +        "github.com/mattermost/mattermost/server/v8/channels/app",
    +        "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore",
    +        "github.com/mattermost/mattermost/server/v8/config",
    +      ];
    +
    +      // Collect assignments from all shards
    +      const allTe = [];
    +      for (let i = 0; i < 2; i++) {
    +        const result = runSolver({ packages, shardIndex: i, shardTotal: 2 });
    +        allTe.push(...result.te.split(" ").filter(Boolean));
    +      }
    +
    +      // All packages should be assigned exactly once
    +      assert.equal(allTe.sort().join("\n"), packages.sort().join("\n"));
    +    });
    +
    +    it("uses round-robin when no timing files exist", () => {
    +      const packages = ["pkg/a", "pkg/b", "pkg/c", "pkg/d", "pkg/e"];
    +      const r0 = runSolver({ packages, shardIndex: 0, shardTotal: 2 });
    +      const r1 = runSolver({ packages, shardIndex: 1, shardTotal: 2 });
    +
    +      assert.ok(r0.stdout.includes("round-robin"), "Should mention round-robin in output");
    +      // No heavy runs
    +      assert.equal(r0.heavy.trim(), "");
    +      assert.equal(r1.heavy.trim(), "");
    +    });
    +  });
    +
    +  describe("timing-based balancing", () => {
    +    it("balances shards using gotestsum.json timing data", () => {
    +      const gotestsumJson = fs.readFileSync(
    +        path.join(TESTDATA, "sample-gotestsum.json"),
    +        "utf8"
    +      );
    +      const packages = [
    +        "github.com/mattermost/mattermost/server/v8/channels/api4",
    +        "github.com/mattermost/mattermost/server/v8/channels/app",
    +        "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore",
    +        "github.com/mattermost/mattermost/server/v8/config",
    +        "github.com/mattermost/mattermost/server/v8/enterprise/elasticsearch",
    +        "github.com/mattermost/mattermost/server/v8/enterprise/compliance",
    +        "github.com/mattermost/mattermost/server/public/model",
    +      ];
    +
    +      // Run for all 4 shards and check that loads are somewhat balanced
    +      const loads = [];
    +      const allAssigned = new Set();
    +
    +      for (let i = 0; i < 4; i++) {
    +        const result = runSolver({
    +          packages,
    +          shardIndex: i,
    +          shardTotal: 4,
    +          gotestsumJson,
    +        });
    +
    +        // Track all assigned packages and tests
    +        const tePkgs = result.te.split(" ").filter(Boolean);
    +        const eePkgs = result.ee.split(" ").filter(Boolean);
    +        tePkgs.forEach((p) => allAssigned.add(p));
    +        eePkgs.forEach((p) => allAssigned.add(p));
    +
    +        // Parse heavy runs
    +        if (result.heavy.trim()) {
    +          result.heavy
    +            .trim()
    +            .split("\n")
    +            .forEach((line) => {
    +              const pkg = line.split(" ")[0];
    +              allAssigned.add(pkg);
    +            });
    +        }
    +      }
    +
    +      // Every package should be covered
    +      for (const pkg of packages) {
    +        assert.ok(
    +          allAssigned.has(pkg),
    +          `Package ${pkg} should be assigned to some shard`
    +        );
    +      }
    +    });
    +
    +    it("does not produce empty shards with sample data", () => {
    +      const gotestsumJson = fs.readFileSync(
    +        path.join(TESTDATA, "sample-gotestsum.json"),
    +        "utf8"
    +      );
    +      const packages = [
    +        "github.com/mattermost/mattermost/server/v8/channels/api4",
    +        "github.com/mattermost/mattermost/server/v8/channels/app",
    +        "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore",
    +        "github.com/mattermost/mattermost/server/v8/config",
    +      ];
    +
    +      for (let i = 0; i < 4; i++) {
    +        const result = runSolver({
    +          packages,
    +          shardIndex: i,
    +          shardTotal: 4,
    +          gotestsumJson,
    +        });
    +        const hasWork =
    +          result.te.trim() !== "" ||
    +          result.ee.trim() !== "" ||
    +          result.heavy.trim() !== "";
    +        assert.ok(hasWork, `Shard ${i} should have some work assigned`);
    +      }
    +    });
    +  });
    +
    +  describe("heavy package splitting", () => {
    +    it("splits packages over HEAVY_MS threshold into individual tests", () => {
    +      // Create timing data where api4 is very heavy (> 300s = 300000ms)
    +      const lines = [];
    +      // api4: 6 tests totaling 452.2s (> 300s threshold)
    +      for (const [test, elapsed] of [
    +        ["TestGetChannel", 145.2],
    +        ["TestCreatePost", 98.1],
    +        ["TestUpdateChannel", 72.5],
    +        ["TestDeleteChannel", 58.3],
    +        ["TestGetChannelMembers", 45.7],
    +        ["TestSearchChannels", 32.4],
    +      ]) {
    +        lines.push(
    +          JSON.stringify({
    +            Time: "2025-03-20T10:00:00Z",
    +            Action: "pass",
    +            Package: "github.com/mattermost/mattermost/server/v8/channels/api4",
    +            Test: test,
    +            Elapsed: elapsed,
    +          })
    +        );
    +      }
    +      // config: 2 tests totaling 8s (< 120s, stays whole)
    +      for (const [test, elapsed] of [
    +        ["TestConfigStore", 5.0],
    +        ["TestConfigMigrate", 3.0],
    +      ]) {
    +        lines.push(
    +          JSON.stringify({
    +            Time: "2025-03-20T10:00:00Z",
    +            Action: "pass",
    +            Package: "github.com/mattermost/mattermost/server/v8/config",
    +            Test: test,
    +            Elapsed: elapsed,
    +          })
    +        );
    +      }
    +
    +      const gotestsumJson = lines.join("\n");
    +      const packages = [
    +        "github.com/mattermost/mattermost/server/v8/channels/api4",
    +        "github.com/mattermost/mattermost/server/v8/config",
    +      ];
    +
    +      // With 2 shards, api4 tests should be split across shards
    +      let heavyFound = false;
    +      const allHeavyTests = [];
    +
    +      for (let i = 0; i < 2; i++) {
    +        const result = runSolver({
    +          packages,
    +          shardIndex: i,
    +          shardTotal: 2,
    +          gotestsumJson,
    +        });
    +
    +        if (result.heavy.trim()) {
    +          heavyFound = true;
    +          // Parse heavy runs to extract test names
    +          for (const line of result.heavy.trim().split("\n")) {
    +            const parts = line.split(" ");
    +            assert.equal(
    +              parts[0],
    +              "github.com/mattermost/mattermost/server/v8/channels/api4",
    +              "Heavy package should be api4"
    +            );
    +            // Regex is like "^TestGetChannel$|^TestCreatePost$"
    +            const tests = parts[1].split("|").map((r) => r.replace(/[\^$]/g, ""));
    +            allHeavyTests.push(...tests);
    +          }
    +        }
    +      }
    +
    +      assert.ok(heavyFound, "Should have heavy package splits for api4");
    +      // All api4 tests should be distributed
    +      const expectedTests = [
    +        "TestGetChannel",
    +        "TestCreatePost",
    +        "TestUpdateChannel",
    +        "TestDeleteChannel",
    +        "TestGetChannelMembers",
    +        "TestSearchChannels",
    +      ];
    +      assert.deepEqual(
    +        allHeavyTests.sort(),
    +        expectedTests.sort(),
    +        "All api4 tests should be distributed across shards"
    +      );
    +    });
    +
    +    it("keeps light packages whole even with timing data", () => {
    +      const gotestsumJson = [
    +        '{"Action":"pass","Package":"pkg/light","Test":"TestA","Elapsed":5.0}',
    +        '{"Action":"pass","Package":"pkg/light","Test":"TestB","Elapsed":3.0}',
    +      ].join("\n");
    +
    +      const result = runSolver({
    +        packages: ["pkg/light"],
    +        shardIndex: 0,
    +        shardTotal: 2,
    +        gotestsumJson,
    +      });
    +
    +      // Light package should be assigned whole, not split
    +      assert.equal(result.heavy.trim(), "", "Light package should not be in heavy runs");
    +      assert.ok(
    +        result.te.includes("pkg/light"),
    +        "Light package should be in TE packages"
    +      );
    +    });
    +  });
    +
    +  describe("JUnit XML fallback", () => {
    +    it("uses JUnit XML when gotestsum.json is missing", () => {
    +      const prevReportXml = `<?xml version="1.0" encoding="UTF-8"?>
    +<testsuites>
    +  <testsuite name="pkg/fast" time="10.0" tests="5">
    +    <testcase name="TestA" time="5.0"/>
    +    <testcase name="TestB" time="5.0"/>
    +  </testsuite>
    +  <testsuite name="pkg/slow" time="50.0" tests="3">
    +    <testcase name="TestX" time="25.0"/>
    +    <testcase name="TestY" time="25.0"/>
    +  </testsuite>
    +</testsuites>`;
    +
    +      const result = runSolver({
    +        packages: ["pkg/fast", "pkg/slow"],
    +        shardIndex: 0,
    +        shardTotal: 2,
    +        prevReportXml,
    +      });
    +
    +      assert.ok(
    +        result.stdout.includes("JUnit XML"),
    +        "Should indicate using JUnit XML fallback"
    +      );
    +      // No heavy splits with XML-only data (no per-test timing)
    +      assert.equal(result.heavy.trim(), "", "Should not split packages without per-test timing");
    +    });
    +  });
    +
    +  describe("enterprise package separation", () => {
    +    it("separates enterprise packages into EE output", () => {
    +      const packages = [
    +        "github.com/mattermost/mattermost/server/v8/channels/app",
    +        "github.com/mattermost/mattermost/server/v8/enterprise/compliance",
    +      ];
    +
    +      const result = runSolver({
    +        packages,
    +        shardIndex: 0,
    +        shardTotal: 1,
    +      });
    +
    +      assert.ok(
    +        result.te.includes("channels/app"),
    +        "TE should include non-enterprise packages"
    +      );
    +      assert.ok(
    +        result.ee.includes("enterprise/compliance"),
    +        "EE should include enterprise packages"
    +      );
    +      assert.ok(
    +        !result.te.includes("enterprise"),
    +        "TE should not include enterprise packages"
    +      );
    +    });
    +  });
    +});
    
  • server/scripts/testdata/sample-gotestsum.json+22 0 added
    @@ -0,0 +1,22 @@
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestGetChannel","Elapsed":45.2}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestCreatePost","Elapsed":38.1}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestUpdateChannel","Elapsed":22.5}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestDeleteChannel","Elapsed":18.3}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestGetChannelMembers","Elapsed":15.7}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestSearchChannels","Elapsed":12.4}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/app","Test":"TestCreateUser","Elapsed":25.6}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/app","Test":"TestUpdateUser","Elapsed":20.3}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/app","Test":"TestDeleteUser","Elapsed":15.8}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/app","Test":"TestGetUser","Elapsed":10.2}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/app","Test":"TestSearchUsers","Elapsed":8.5}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore","Test":"TestChannelStore","Elapsed":35.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore","Test":"TestPostStore","Elapsed":28.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore","Test":"TestUserStore","Elapsed":22.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/config","Test":"TestConfigStore","Elapsed":5.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/config","Test":"TestConfigMigrate","Elapsed":3.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/enterprise/elasticsearch","Test":"TestSearchPosts","Elapsed":8.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/enterprise/elasticsearch","Test":"TestIndexPosts","Elapsed":6.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/v8/enterprise/compliance","Test":"TestExportCompliance","Elapsed":4.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/public/model","Test":"TestModelValidation","Elapsed":2.0}
    +{"Time":"2025-03-20T10:00:00Z","Action":"pass","Package":"github.com/mattermost/mattermost/server/public/model","Test":"TestModelSerialization","Elapsed":1.5}
    +{"Time":"2025-03-20T10:00:00Z","Action":"output","Package":"github.com/mattermost/mattermost/server/v8/channels/api4","Test":"TestGetChannel","Output":"--- PASS: TestGetChannel (45.20s)\n"}
    
  • webapp/channels/src/components/plugin_link_tooltip/plugin_link_tooltip.scss+5 0 modified
    @@ -2,6 +2,11 @@
     
     .plugin-link-tooltip-floating-overlay {
         z-index: variables.$z-index-popover;
    +    pointer-events: none;
    +
    +    > * {
    +        pointer-events: auto;
    +    }
     }
     
     // This is as per UX guidelines what the container of the
    
  • webapp/channels/src/components/post_markdown/index.test.ts+15 1 modified
    @@ -5,7 +5,21 @@ import {TestHelper} from 'utils/test_helper';
     
     import type {GlobalState} from 'types/store';
     
    -import {makeGetMentionKeysForPost} from './index';
    +import {hasPluginTooltips, makeGetMentionKeysForPost} from './index';
    +
    +describe('hasPluginTooltips', () => {
    +    it('should be false when LinkTooltip is an empty array', () => {
    +        expect(hasPluginTooltips([])).toBe(false);
    +    });
    +
    +    it('should be false when LinkTooltip is undefined', () => {
    +        expect(hasPluginTooltips(undefined)).toBe(false);
    +    });
    +
    +    it('should be true when LinkTooltip has registered components', () => {
    +        expect(hasPluginTooltips([{id: 'test', pluginId: 'com.example', component: () => null}])).toBe(true);
    +    });
    +});
     
     describe('makeGetMentionKeysForPost', () => {
         const channel = TestHelper.getChannelMock({});
    
  • webapp/channels/src/components/post_markdown/index.ts+5 1 modified
    @@ -56,6 +56,10 @@ export function makeGetMentionKeysForPost(): (
         );
     }
     
    +export function hasPluginTooltips(linkTooltip?: unknown[]): boolean {
    +    return Boolean(linkTooltip?.length);
    +}
    +
     function makeMapStateToProps() {
         const getMentionKeysForPost = makeGetMentionKeysForPost();
     
    @@ -73,7 +77,7 @@ function makeMapStateToProps() {
                 channel,
                 currentTeam,
                 pluginHooks: state.plugins.components.MessageWillFormat,
    -            hasPluginTooltips: Boolean(state.plugins.components.LinkTooltip),
    +            hasPluginTooltips: hasPluginTooltips(state.plugins.components.LinkTooltip),
                 isUserCanManageMembers: channel && canManageMembers(state, channel),
                 mentionKeys: getMentionKeysForPost(state, ownProps.post, channel),
                 highlightKeys: getHighlightWithoutNotificationKeys(state),
    
a07e4b05ff82

Added nil checks (#35755) (#36134)

https://github.com/mattermost/mattermostHarshil SharmaApr 16, 2026Fixed in 11.4.5via llm-release-walk
3 files changed · +250 7
  • server/channels/app/channel.go+6 6 modified
    @@ -1580,6 +1580,12 @@ func (a *App) DeleteChannel(rctx request.CTX, channel *model.Channel, userID str
     		return err
     	}
     
    +	deleteAt := model.GetMillis()
    +
    +	if err := a.Srv().Store().Channel().Delete(channel.Id, deleteAt); err != nil {
    +		return model.NewAppError("DeleteChannel", "app.channel.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +	}
    +
     	if user != nil {
     		T := i18n.GetUserTranslations(user.Locale)
     
    @@ -1635,12 +1641,6 @@ func (a *App) DeleteChannel(rctx request.CTX, channel *model.Channel, userID str
     		return model.NewAppError("DeleteChannel", "app.post_persistent_notification.delete_by_channel.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     	}
     
    -	deleteAt := model.GetMillis()
    -
    -	if err := a.Srv().Store().Channel().Delete(channel.Id, deleteAt); err != nil {
    -		return model.NewAppError("DeleteChannel", "app.channel.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    -	}
    -
     	a.Srv().Platform().InvalidateCacheForChannel(channel)
     
     	var message *model.WebSocketEvent
    
  • server/channels/app/post_persistent_notification.go+26 1 modified
    @@ -163,13 +163,25 @@ func (a *App) forEachPersistentNotificationPost(posts []*model.Post, fn func(pos
     		return err
     	}
     
    +	var postsForPersistentNotificationCleanup []*model.Post
    +
     	for _, post := range posts {
     		channel := channelsMap[post.ChannelId]
    +		if channel == nil {
    +			postsForPersistentNotificationCleanup = append(postsForPersistentNotificationCleanup, post)
    +			continue
    +		}
    +
     		team := teamsMap[channel.TeamId]
     		// GMs and DMs don't belong to any team
     		if channel.IsGroupOrDirect() {
     			team = &model.Team{}
    +		} else if team == nil {
    +			// cleanup persistent notification for posts with missing teams when they are not DM or GM
    +			postsForPersistentNotificationCleanup = append(postsForPersistentNotificationCleanup, post)
    +			continue
     		}
    +
     		profileMap := channelProfileMap[channel.Id]
     
     		// Ensure the sender is always in the profile map: for example, system admins can post
    @@ -210,6 +222,14 @@ func (a *App) forEachPersistentNotificationPost(posts []*model.Post, fn func(pos
     		}
     	}
     
    +	if len(postsForPersistentNotificationCleanup) > 0 {
    +		for _, post := range postsForPersistentNotificationCleanup {
    +			if appErr := a.DeletePersistentNotification(request.EmptyContext(a.Log()), post); appErr != nil {
    +				a.Log().Warn("Failed to delete persistent notification for post", mlog.String("post_id", post.Id), mlog.String("channel_id", post.ChannelId), mlog.Err(appErr))
    +			}
    +		}
    +	}
    +
     	return nil
     }
     
    @@ -219,9 +239,14 @@ func (a *App) persistentNotificationsAuxiliaryData(channelsMap map[string]*model
     	channelKeywords := make(map[string]MentionKeywords, len(channelsMap))
     	channelNotifyProps := make(map[string]map[string]model.StringMap, len(channelsMap))
     	for _, c := range channelsMap {
    +		team := teamsMap[c.TeamId]
    +		if team == nil && !c.IsGroupOrDirect() {
    +			continue
    +		}
    +
     		// In DM, notifications can't be send to any 3rd person.
     		if c.Type != model.ChannelTypeDirect {
    -			groups, err := a.getGroupsAllowedForReferenceInChannel(c, teamsMap[c.TeamId])
    +			groups, err := a.getGroupsAllowedForReferenceInChannel(c, team)
     			if err != nil {
     				return nil, nil, nil, nil, errors.Wrapf(err, "failed to get profiles for channel %s", c.Id)
     			}
    
  • server/channels/app/post_persistent_notification_test.go+218 0 modified
    @@ -192,6 +192,224 @@ func TestDeletePersistentNotification(t *testing.T) {
     	})
     }
     
    +func TestForEachPersistentNotificationPost(t *testing.T) {
    +	mainHelper.Parallel(t)
    +
    +	t.Run("should cleanup posts whose channel no longer exists", func(t *testing.T) {
    +		th := SetupWithStoreMock(t)
    +
    +		user1 := &model.User{Id: "uid1", Username: "user-1"}
    +		profileMap := map[string]*model.User{user1.Id: user1}
    +		team := &model.Team{Id: "tid"}
    +		channel := &model.Channel{Id: "chid", TeamId: team.Id, Type: model.ChannelTypeOpen}
    +
    +		// post1 belongs to an existing channel; post2 belongs to a deleted/missing channel
    +		post1 := &model.Post{Id: "pid1", ChannelId: channel.Id, Message: "hello @user-1", UserId: user1.Id}
    +		post2 := &model.Post{Id: "pid2", ChannelId: "deleted-channel-id", Message: "hello", UserId: user1.Id}
    +
    +		mockStore := th.App.Srv().Store().(*storemocks.Store)
    +
    +		mockChannel := storemocks.ChannelStore{}
    +		mockStore.On("Channel").Return(&mockChannel)
    +		// Only return channel for post1; post2's channel is missing
    +		mockChannel.On("GetChannelsByIds", mock.Anything, mock.Anything).Return([]*model.Channel{channel}, nil)
    +		mockChannel.On("GetAllChannelMembersNotifyPropsForChannel", mock.Anything, mock.Anything).Return(map[string]model.StringMap{}, nil)
    +
    +		mockTeam := storemocks.TeamStore{}
    +		mockStore.On("Team").Return(&mockTeam)
    +		mockTeam.On("GetMany", mock.Anything).Return([]*model.Team{team}, nil)
    +
    +		mockUser := storemocks.UserStore{}
    +		mockStore.On("User").Return(&mockUser)
    +		mockUser.On("GetAllProfilesInChannel", mock.Anything, mock.Anything, mock.Anything).Return(profileMap, nil)
    +
    +		mockGroup := storemocks.GroupStore{}
    +		mockStore.On("Group").Return(&mockGroup)
    +		mockGroup.On("GetGroups", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Group{}, nil)
    +
    +		// DeletePersistentNotification mocks - the cleanup path calls GetSingle then Delete
    +		mockPostPersistentNotification := storemocks.PostPersistentNotificationStore{}
    +		mockStore.On("PostPersistentNotification").Return(&mockPostPersistentNotification)
    +		mockPostPersistentNotification.On("GetSingle", post2.Id).Return(&model.PostPersistentNotifications{PostId: post2.Id}, nil)
    +		mockPostPersistentNotification.On("Delete", []string{post2.Id}).Return(nil)
    +
    +		th.App.Srv().SetLicense(getLicWithSkuShortName(model.LicenseShortSkuProfessional))
    +		cfg := th.App.Config()
    +		*cfg.ServiceSettings.PostPriority = true
    +		*cfg.ServiceSettings.AllowPersistentNotifications = true
    +
    +		fnCalled := []string{}
    +		err := th.App.forEachPersistentNotificationPost([]*model.Post{post1, post2}, func(post *model.Post, _ *model.Channel, _ *model.Team, _ *MentionResults, _ model.UserMap, _ map[string]map[string]model.StringMap) error {
    +			fnCalled = append(fnCalled, post.Id)
    +			return nil
    +		})
    +		require.NoError(t, err)
    +
    +		// The callback should only be called for post1 (valid channel)
    +		assert.Equal(t, []string{"pid1"}, fnCalled)
    +		// post2 persistent notification should have been cleaned up
    +		mockPostPersistentNotification.AssertCalled(t, "Delete", []string{post2.Id})
    +	})
    +
    +	t.Run("should cleanup posts whose team no longer exists", func(t *testing.T) {
    +		th := SetupWithStoreMock(t)
    +
    +		user1 := &model.User{Id: "uid1", Username: "user-1"}
    +		user2 := &model.User{Id: "uid2", Username: "user-2"}
    +		profileMap := map[string]*model.User{user1.Id: user1, user2.Id: user2}
    +		team := &model.Team{Id: "tid"}
    +		channel := &model.Channel{Id: "chid", TeamId: team.Id, Type: model.ChannelTypeOpen}
    +		// channelWithMissingTeam has a TeamId that won't be in teamsMap
    +		channelWithMissingTeam := &model.Channel{Id: "chid2", TeamId: "deleted-team-id", Type: model.ChannelTypeOpen}
    +
    +		post1 := &model.Post{Id: "pid1", ChannelId: channel.Id, Message: "hello @user-1", UserId: user2.Id}
    +		post2 := &model.Post{Id: "pid2", ChannelId: channelWithMissingTeam.Id, Message: "hello @user-1", UserId: user2.Id}
    +
    +		mockStore := th.App.Srv().Store().(*storemocks.Store)
    +
    +		mockChannel := storemocks.ChannelStore{}
    +		mockStore.On("Channel").Return(&mockChannel)
    +		// Both channels exist, but only one team exists
    +		mockChannel.On("GetChannelsByIds", mock.Anything, mock.Anything).Return([]*model.Channel{channel, channelWithMissingTeam}, nil)
    +		mockChannel.On("GetAllChannelMembersNotifyPropsForChannel", mock.Anything, mock.Anything).Return(map[string]model.StringMap{}, nil)
    +
    +		mockTeam := storemocks.TeamStore{}
    +		mockStore.On("Team").Return(&mockTeam)
    +		// Only return the team for channel, not for channelWithMissingTeam
    +		mockTeam.On("GetMany", mock.Anything).Return([]*model.Team{team}, nil)
    +
    +		mockUser := storemocks.UserStore{}
    +		mockStore.On("User").Return(&mockUser)
    +		mockUser.On("GetAllProfilesInChannel", mock.Anything, mock.Anything, mock.Anything).Return(profileMap, nil)
    +
    +		mockGroup := storemocks.GroupStore{}
    +		mockStore.On("Group").Return(&mockGroup)
    +		mockGroup.On("GetGroups", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Group{}, nil)
    +
    +		mockPostPersistentNotification := storemocks.PostPersistentNotificationStore{}
    +		mockStore.On("PostPersistentNotification").Return(&mockPostPersistentNotification)
    +		mockPostPersistentNotification.On("GetSingle", post2.Id).Return(&model.PostPersistentNotifications{PostId: post2.Id}, nil)
    +		mockPostPersistentNotification.On("Delete", []string{post2.Id}).Return(nil)
    +
    +		th.App.Srv().SetLicense(getLicWithSkuShortName(model.LicenseShortSkuProfessional))
    +		cfg := th.App.Config()
    +		*cfg.ServiceSettings.PostPriority = true
    +		*cfg.ServiceSettings.AllowPersistentNotifications = true
    +
    +		fnCalled := []string{}
    +		err := th.App.forEachPersistentNotificationPost([]*model.Post{post1, post2}, func(post *model.Post, _ *model.Channel, _ *model.Team, _ *MentionResults, _ model.UserMap, _ map[string]map[string]model.StringMap) error {
    +			fnCalled = append(fnCalled, post.Id)
    +			return nil
    +		})
    +		require.NoError(t, err)
    +
    +		// The callback should only be called for post1 (valid team)
    +		assert.Equal(t, []string{"pid1"}, fnCalled)
    +		// post2 persistent notification should have been cleaned up due to missing team
    +		mockPostPersistentNotification.AssertCalled(t, "Delete", []string{post2.Id})
    +	})
    +
    +	t.Run("should not cleanup DM posts that have no team", func(t *testing.T) {
    +		th := SetupWithStoreMock(t)
    +
    +		user1 := &model.User{Id: "uid1", Username: "user-1"}
    +		user2 := &model.User{Id: "uid2", Username: "user-2"}
    +		profileMap := map[string]*model.User{user1.Id: user1, user2.Id: user2}
    +		dmChannel := &model.Channel{Id: "dm-chid", TeamId: "", Type: model.ChannelTypeDirect, Name: model.GetDMNameFromIds(user1.Id, user2.Id)}
    +
    +		post1 := &model.Post{Id: "pid1", ChannelId: dmChannel.Id, Message: "hello", UserId: user1.Id}
    +
    +		mockStore := th.App.Srv().Store().(*storemocks.Store)
    +
    +		mockChannel := storemocks.ChannelStore{}
    +		mockStore.On("Channel").Return(&mockChannel)
    +		mockChannel.On("GetChannelsByIds", mock.Anything, mock.Anything).Return([]*model.Channel{dmChannel}, nil)
    +
    +		mockTeam := storemocks.TeamStore{}
    +		mockStore.On("Team").Return(&mockTeam)
    +		mockTeam.On("GetMany", mock.Anything).Return([]*model.Team{}, nil)
    +
    +		mockUser := storemocks.UserStore{}
    +		mockStore.On("User").Return(&mockUser)
    +		mockUser.On("GetAllProfilesInChannel", mock.Anything, mock.Anything, mock.Anything).Return(profileMap, nil)
    +
    +		mockGroup := storemocks.GroupStore{}
    +		mockStore.On("Group").Return(&mockGroup)
    +		mockGroup.On("GetGroups", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Group{}, nil)
    +
    +		mockPostPersistentNotification := storemocks.PostPersistentNotificationStore{}
    +		mockStore.On("PostPersistentNotification").Return(&mockPostPersistentNotification)
    +
    +		th.App.Srv().SetLicense(getLicWithSkuShortName(model.LicenseShortSkuProfessional))
    +		cfg := th.App.Config()
    +		*cfg.ServiceSettings.PostPriority = true
    +		*cfg.ServiceSettings.AllowPersistentNotifications = true
    +
    +		fnCalled := []string{}
    +		err := th.App.forEachPersistentNotificationPost([]*model.Post{post1}, func(post *model.Post, _ *model.Channel, _ *model.Team, _ *MentionResults, _ model.UserMap, _ map[string]map[string]model.StringMap) error {
    +			fnCalled = append(fnCalled, post.Id)
    +			return nil
    +		})
    +		require.NoError(t, err)
    +
    +		// The callback should be called for the DM post even though there's no team
    +		assert.Equal(t, []string{"pid1"}, fnCalled)
    +		// Delete should NOT have been called — DMs don't need a team
    +		mockPostPersistentNotification.AssertNotCalled(t, "Delete", mock.Anything)
    +	})
    +
    +	t.Run("should not cleanup GM posts that have no team", func(t *testing.T) {
    +		th := SetupWithStoreMock(t)
    +
    +		user1 := &model.User{Id: "uid1", Username: "user-1"}
    +		user2 := &model.User{Id: "uid2", Username: "user-2"}
    +		user3 := &model.User{Id: "uid3", Username: "user-3"}
    +		profileMap := map[string]*model.User{user1.Id: user1, user2.Id: user2, user3.Id: user3}
    +		gmChannel := &model.Channel{Id: "gm-chid", TeamId: "", Type: model.ChannelTypeGroup}
    +
    +		post1 := &model.Post{Id: "pid1", ChannelId: gmChannel.Id, Message: "hello @user-2", UserId: user1.Id}
    +
    +		mockStore := th.App.Srv().Store().(*storemocks.Store)
    +
    +		mockChannel := storemocks.ChannelStore{}
    +		mockStore.On("Channel").Return(&mockChannel)
    +		mockChannel.On("GetChannelsByIds", mock.Anything, mock.Anything).Return([]*model.Channel{gmChannel}, nil)
    +		mockChannel.On("GetAllChannelMembersNotifyPropsForChannel", mock.Anything, mock.Anything).Return(map[string]model.StringMap{}, nil)
    +
    +		mockTeam := storemocks.TeamStore{}
    +		mockStore.On("Team").Return(&mockTeam)
    +		mockTeam.On("GetMany", mock.Anything).Return([]*model.Team{}, nil)
    +
    +		mockUser := storemocks.UserStore{}
    +		mockStore.On("User").Return(&mockUser)
    +		mockUser.On("GetAllProfilesInChannel", mock.Anything, mock.Anything, mock.Anything).Return(profileMap, nil)
    +
    +		mockGroup := storemocks.GroupStore{}
    +		mockStore.On("Group").Return(&mockGroup)
    +		mockGroup.On("GetGroups", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Group{}, nil)
    +
    +		mockPostPersistentNotification := storemocks.PostPersistentNotificationStore{}
    +		mockStore.On("PostPersistentNotification").Return(&mockPostPersistentNotification)
    +
    +		th.App.Srv().SetLicense(getLicWithSkuShortName(model.LicenseShortSkuProfessional))
    +		cfg := th.App.Config()
    +		*cfg.ServiceSettings.PostPriority = true
    +		*cfg.ServiceSettings.AllowPersistentNotifications = true
    +
    +		fnCalled := []string{}
    +		err := th.App.forEachPersistentNotificationPost([]*model.Post{post1}, func(post *model.Post, _ *model.Channel, _ *model.Team, _ *MentionResults, _ model.UserMap, _ map[string]map[string]model.StringMap) error {
    +			fnCalled = append(fnCalled, post.Id)
    +			return nil
    +		})
    +		require.NoError(t, err)
    +
    +		// The callback should be called for the GM post even though there's no team
    +		assert.Equal(t, []string{"pid1"}, fnCalled)
    +		// Delete should NOT have been called — GMs don't need a team
    +		mockPostPersistentNotification.AssertNotCalled(t, "Delete", mock.Anything)
    +	})
    +}
    +
     func TestSendPersistentNotifications(t *testing.T) {
     	mainHelper.Parallel(t)
     	th := Setup(t).InitBasic(t)
    
667dffe31dbb
https://github.com/mattermost/mattermostFixed in 10.11.15via llm-release-walk
3b21498788a7
https://github.com/mattermost/mattermostFixed in 11.5.4via llm-release-walk
f6760151c4a7

Support Elasticsearch v9 (for v10.11) (#35925)

https://github.com/mattermost/mattermostJesse HallamApr 20, 2026Fixed in 10.11.15via release-tag
8 files changed · +182 11
  • .github/workflows/server-ci-template.yml+24 0 modified
    @@ -264,6 +264,30 @@ jobs:
           datasource: mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4&multiStatements=true&maxAllowedPacket=4194304
           drivername: mysql
           logsartifact: mysql-server-test-logs
    +  test-elasticsearch-v8:
    +    name: Elasticsearch v8 Compatibility
    +    needs: check-mattermost-vet
    +    uses: ./.github/workflows/server-test-template.yml
    +    secrets: inherit
    +    with:
    +      name: Elasticsearch v8 Compatibility
    +      datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
    +      drivername: postgres
    +      logsartifact: elasticsearch-v8-server-test-logs
    +      elasticsearch-version: "8.9.0"
    +      test-target: "test-server-elasticsearch"
    +  test-elasticsearch-v7:
    +    name: Elasticsearch v7 Compatibility
    +    needs: check-mattermost-vet
    +    uses: ./.github/workflows/server-test-template.yml
    +    secrets: inherit
    +    with:
    +      name: Elasticsearch v7 Compatibility
    +      datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
    +      drivername: postgres
    +      logsartifact: elasticsearch-v7-server-test-logs
    +      elasticsearch-version: "7.17.29"
    +      test-target: "test-server-elasticsearch"
       test-coverage:
         # Skip coverage generation for cherry-pick PRs into release branches.
         if: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }}
    
  • .github/workflows/server-test-template.yml+15 2 modified
    @@ -22,6 +22,14 @@ on:
             required: false
             type: boolean
             default: false
    +      elasticsearch-version:
    +        required: false
    +        type: string
    +        default: "9.0.0"
    +      test-target:
    +        required: false
    +        type: string
    +        default: "test-server"
           # -- Test sharding inputs (leave defaults for non-sharded callers) --
           shard-index:
             required: false
    @@ -75,6 +83,8 @@ jobs:
               echo "${{ inputs.name }}" > server/test-name
               echo "${{ github.event.pull_request.number }}" > server/pr-number
           - name: Run docker compose
    +        env:
    +          ELASTICSEARCH_VERSION: ${{ inputs.elasticsearch-version }}
             run: |
               cd server/build
               docker compose --ansi never run --rm start_dependencies
    @@ -143,11 +153,11 @@ jobs:
             env:
               BUILD_IMAGE: mattermost/mattermost-build-server:${{ steps.go.outputs.GO_VERSION }}
             run: |
    -          if [[ ${{ github.ref_name }} == 'master' && ${{ inputs.fullyparallel }} != true ]]; then
    +          if [[ ${{ github.ref_name }} == 'master' && ${{ inputs.fullyparallel }} != true && "${{ inputs.test-target }}" == "test-server" ]]; then
                 export RACE_MODE="-race"
               fi
     
    -          TEST_TARGET="test-server${RACE_MODE}"
    +          TEST_TARGET="${{ inputs.test-target }}${RACE_MODE}"
               BUILD_NUMBER="${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}"
               DOCKER_CMD="make ${TEST_TARGET}"
     
    @@ -186,8 +196,10 @@ jobs:
               disable_search: true
               files: server/cover.out
           - name: Stop docker compose
    +        if: ${{ always() }}
             run: |
               cd server/build
    +          docker compose --ansi never logs --no-color > ../../docker-compose.log 2>&1
               docker compose --ansi never stop
           - name: Archive logs
             if: ${{ always() }}
    @@ -200,4 +212,5 @@ jobs:
                 server/cover.out
                 server/test-name
                 server/pr-number
    +            docker-compose.log
     
    
  • server/build/docker-compose.common.yml+5 1 modified
    @@ -87,7 +87,11 @@ services:
           LDAP_DOMAIN: "mm.test.com"
           LDAP_ADMIN_PASSWORD: "mostest"
       elasticsearch:
    -    image: "mattermostdevelopment/mattermost-elasticsearch:8.9.0"
    +    build:
    +      context: .
    +      dockerfile: ./Dockerfile.elasticsearch
    +      args:
    +        ELASTICSEARCH_VERSION: ${ELASTICSEARCH_VERSION:-9.0.0}
         networks:
           - mm-test
         environment:
    
  • server/build/Dockerfile.elasticsearch+4 0 added
    @@ -0,0 +1,4 @@
    +ARG ELASTICSEARCH_VERSION=9.0.0
    +FROM docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION}
    +
    +RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch analysis-icu analysis-nori analysis-kuromoji analysis-smartcn
    
  • server/enterprise/elasticsearch/elasticsearch/check_version_test.go+109 0 added
    @@ -0,0 +1,109 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.enterprise for license information.
    +
    +package elasticsearch
    +
    +import (
    +	"fmt"
    +	"net/http"
    +	"net/http/httptest"
    +	"testing"
    +
    +	elastic "github.com/elastic/go-elasticsearch/v8"
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +)
    +
    +func newTestClient(t *testing.T, handler http.Handler) *elastic.TypedClient {
    +	t.Helper()
    +	ts := httptest.NewServer(handler)
    +	t.Cleanup(ts.Close)
    +
    +	client, err := elastic.NewTypedClient(elastic.Config{
    +		Addresses: []string{ts.URL},
    +	})
    +	require.NoError(t, err)
    +	return client
    +}
    +
    +func infoHandler(version string) http.HandlerFunc {
    +	return func(w http.ResponseWriter, r *http.Request) {
    +		w.Header().Set("Content-Type", "application/json")
    +		w.Header().Set("X-Elastic-Product", "Elasticsearch")
    +		fmt.Fprintf(w, `{"cluster_name":"test","version":{"number":%q,"build_flavor":"default","build_hash":"abc","build_date":"2024-01-01","build_snapshot":false,"build_type":"docker","lucene_version":"9.0.0","minimum_wire_compatibility_version":"7.0.0","minimum_index_compatibility_version":"7.0.0"}}`, version)
    +	}
    +}
    +
    +func TestCheckVersion(t *testing.T) {
    +	tests := []struct {
    +		name        string
    +		version     string
    +		wantVersion string
    +		wantMajor   int
    +		wantErrID   string
    +	}{
    +		{
    +			name:        "ES 8 is supported",
    +			version:     "8.9.0",
    +			wantVersion: "8.9.0",
    +			wantMajor:   8,
    +		},
    +		{
    +			name:        "ES 9 is supported",
    +			version:     "9.0.0",
    +			wantVersion: "9.0.0",
    +			wantMajor:   9,
    +		},
    +		{
    +			name:        "ES 7 is supported",
    +			version:     "7.17.0",
    +			wantVersion: "7.17.0",
    +			wantMajor:   7,
    +		},
    +		{
    +			name:      "ES 6 is too old",
    +			version:   "6.8.0",
    +			wantErrID: "ent.elasticsearch.min_version.app_error",
    +		},
    +		{
    +			name:      "ES 10 is too new",
    +			version:   "10.0.0",
    +			wantErrID: "ent.elasticsearch.max_version.app_error",
    +		},
    +		{
    +			name:      "invalid version string",
    +			version:   "invalid",
    +			wantErrID: "ent.elasticsearch.start.parse_server_version.app_error",
    +		},
    +	}
    +
    +	for _, tc := range tests {
    +		t.Run(tc.name, func(t *testing.T) {
    +			client := newTestClient(t, infoHandler(tc.version))
    +			version, major, appErr := checkVersion(client, nil)
    +			if tc.wantErrID != "" {
    +				require.NotNil(t, appErr)
    +				assert.Equal(t, tc.wantErrID, appErr.Id)
    +			} else {
    +				require.Nil(t, appErr)
    +				assert.Equal(t, tc.wantVersion, version)
    +				assert.Equal(t, tc.wantMajor, major)
    +			}
    +		})
    +	}
    +}
    +
    +func TestCheckVersionConnectionError(t *testing.T) {
    +	ts := httptest.NewServer(http.NotFoundHandler())
    +	ts.Close() // close immediately to force connection error
    +
    +	client, err := elastic.NewTypedClient(elastic.Config{
    +		Addresses:  []string{ts.URL},
    +		MaxRetries: 0,
    +	})
    +	require.NoError(t, err)
    +
    +	_, _, appErr := checkVersion(client, nil)
    +	require.NotNil(t, appErr)
    +	assert.Equal(t, "ent.elasticsearch.start.get_server_version.app_error", appErr.Id)
    +}
    
  • server/enterprise/elasticsearch/elasticsearch/elasticsearch.go+11 7 modified
    @@ -28,7 +28,8 @@ import (
     	"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/sortorder"
     )
     
    -const elasticsearchMaxVersion = 8
    +const elasticsearchMinVersion = 7
    +const elasticsearchMaxVersion = 9
     
     var (
     	purgeIndexListAllowedIndexes = []string{common.IndexBaseChannels}
    @@ -106,7 +107,7 @@ func (es *ElasticsearchInterfaceImpl) Start() *model.AppError {
     		return appErr
     	}
     
    -	version, major, appErr := checkMaxVersion(es.client, es.Platform.Config())
    +	version, major, appErr := checkVersion(es.client, es.Platform.Config())
     	if appErr != nil {
     		return appErr
     	}
    @@ -1245,7 +1246,7 @@ func (es *ElasticsearchInterfaceImpl) TestConfig(rctx request.CTX, cfg *model.Co
     		return appErr
     	}
     
    -	_, _, appErr = checkMaxVersion(client, cfg)
    +	_, _, appErr = checkVersion(client, cfg)
     	if appErr != nil {
     		return appErr
     	}
    @@ -1830,19 +1831,22 @@ func (es *ElasticsearchInterfaceImpl) DeleteFilesBatch(rctx request.CTX, endTime
     	return nil
     }
     
    -func checkMaxVersion(client *elastic.TypedClient, cfg *model.Config) (string, int, *model.AppError) {
    +func checkVersion(client *elastic.TypedClient, cfg *model.Config) (string, int, *model.AppError) {
     	resp, err := client.API.Core.Info().Do(context.Background())
     	if err != nil {
    -		return "", 0, model.NewAppError("Elasticsearch.checkMaxVersion", "ent.elasticsearch.start.get_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError).Wrap(err)
    +		return "", 0, model.NewAppError("Elasticsearch.checkVersion", "ent.elasticsearch.start.get_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError).Wrap(err)
     	}
     
     	major, _, _, esErr := common.GetVersionComponents(resp.Version.Int)
     	if esErr != nil {
    -		return "", 0, model.NewAppError("Elasticsearch.checkMaxVersion", "ent.elasticsearch.start.parse_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError).Wrap(err)
    +		return "", 0, model.NewAppError("Elasticsearch.checkVersion", "ent.elasticsearch.start.parse_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusInternalServerError).Wrap(esErr)
     	}
     
    +	if major < elasticsearchMinVersion {
    +		return "", 0, model.NewAppError("Elasticsearch.checkVersion", "ent.elasticsearch.min_version.app_error", map[string]any{"Version": major, "MinVersion": elasticsearchMinVersion, "Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusBadRequest)
    +	}
     	if major > elasticsearchMaxVersion {
    -		return "", 0, model.NewAppError("Elasticsearch.checkMaxVersion", "ent.elasticsearch.max_version.app_error", map[string]any{"Version": major, "MaxVersion": elasticsearchMaxVersion, "Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusBadRequest)
    +		return "", 0, model.NewAppError("Elasticsearch.checkVersion", "ent.elasticsearch.max_version.app_error", map[string]any{"Version": major, "MaxVersion": elasticsearchMaxVersion, "Backend": model.ElasticsearchSettingsESBackend}, "", http.StatusBadRequest)
     	}
     	return resp.Version.Int, major, nil
     }
    
  • server/i18n/en.json+4 0 modified
    @@ -8228,6 +8228,10 @@
         "id": "ent.elasticsearch.max_version.app_error",
         "translation": "{{.Backend}} version {{.Version}} is higher than max supported version of {{.MaxVersion}}"
       },
    +  {
    +    "id": "ent.elasticsearch.min_version.app_error",
    +    "translation": "{{.Backend}} version {{.Version}} is lower than min supported version of {{.MinVersion}}"
    +  },
       {
         "id": "ent.elasticsearch.not_started.error",
         "translation": "{{.Backend}} is not started"
    
  • server/Makefile+10 1 modified
    @@ -1,4 +1,4 @@
    -.PHONY: build package run stop run-client run-server run-node run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker update-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-quick test-server-race test-mmctl-unit test-mmctl-e2e test-mmctl test-mmctl-coverage mmctl-build mmctl-docs new-migration migrations-extract test-public mocks-public
    +.PHONY: build package run stop run-client run-server run-node run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker update-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-elasticsearch test-server-quick test-server-race test-mmctl-unit test-mmctl-e2e test-mmctl test-mmctl-coverage mmctl-build mmctl-docs new-migration migrations-extract test-public mocks-public
     
     ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
     
    @@ -476,6 +476,15 @@ test-server-ee: check-prereqs-enterprise start-docker gotestsum ## Runs EE tests
     	@echo Running only EE tests
     	$(GOBIN)/gotestsum --packages="$(EE_PACKAGES)" -- $(GOFLAGS) -timeout=20m
     
    +ES_PACKAGES=$(shell $(GO) list ./enterprise/elasticsearch/...)
    +
    +test-server-elasticsearch: export GOTESTSUM_FORMAT := $(GOTESTSUM_FORMAT)
    +test-server-elasticsearch: export GOTESTSUM_JUNITFILE := $(GOTESTSUM_JUNITFILE)
    +test-server-elasticsearch: export GOTESTSUM_JSONFILE := $(GOTESTSUM_JSONFILE)
    +test-server-elasticsearch: check-prereqs-enterprise start-docker gotestsum ## Runs Elasticsearch tests.
    +	@echo Running only Elasticsearch tests
    +	$(GOBIN)/gotestsum --rerun-fails=3 --packages="$(ES_PACKAGES)" -- $(GOFLAGS) -timeout=20m
    +
     test-server-quick: export GOTESTSUM_FORMAT := $(GOTESTSUM_FORMAT)
     test-server-quick: export GOTESTSUM_JUNITFILE := $(GOTESTSUM_JUNITFILE)
     test-server-quick: export GOTESTSUM_JSONFILE := $(GOTESTSUM_JSONFILE)
    
ffee10a61081

Bump Boards FIPS version to v9.2.4 (#36165) (#36168)

https://github.com/mattermost/mattermostMattermost BuildApr 17, 2026Fixed in 11.6.1via release-tag
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -183,7 +183,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.3.0
     ifeq ($(FIPS_ENABLED),true)
     	PLUGIN_PACKAGES  = mattermost-plugin-playbooks-v2.8.0%2Bc4449ac-fips
     	PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2%2B866e2dd-fips
    -	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2%2B4282c63-fips
    +	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4%2B5855fe1-fips
     endif
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
    
fff6ab3a5851

Bump Boards FIPS version to v9.2.4 (#36165) (#36169)

https://github.com/mattermost/mattermostMattermost BuildApr 17, 2026Fixed in 11.5.4via release-tag
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -176,7 +176,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.3.0
     ifeq ($(FIPS_ENABLED),true)
     	PLUGIN_PACKAGES  = mattermost-plugin-playbooks-v2.8.0%2Bc4449ac-fips
     	PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2%2B866e2dd-fips
    -	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2%2B4282c63-fips
    +	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4%2B5855fe1-fips
     endif
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
    

Vulnerability mechanics

Root cause

"Missing validation of the TIFF IFD offset in the image header before memory allocation in golang.org/x/image allows a crafted TIFF file to trigger excessive memory allocation."

Attack vector

An authenticated user with file upload or posting permissions can cause a denial of service by uploading a crafted TIFF file or posting a URL that serves one. The attacker crafts a TIFF image with a malicious IFD offset value in the header. When Mattermost's image processing pipeline (via `golang.org/x/image`) decodes this file, the missing validation on the IFD offset leads to an attempt to allocate an extremely large memory buffer, exhausting server memory (OOM). The attack requires only standard user-level permissions to upload files or post messages containing image URLs.

Affected code

The vulnerability resides in the `golang.org/x/image` library (specifically its TIFF decoder) which Mattermost uses to process uploaded image files. The advisory states that the TIFF IFD (Image File Directory) offset in the image header is not validated before memory allocation, allowing a crafted TIFF file to trigger excessive memory consumption. The patches update `golang.org/x/image` from versions `v0.27.0`/`v0.32.0` to `v0.38.0` across multiple release branches [patch_id=1693064][patch_id=1693062][patch_id=1693063][patch_id=1693061].

What the fix does

All four patches [patch_id=1693064][patch_id=1693062][patch_id=1693063][patch_id=1693061] update the `golang.org/x/image` dependency from vulnerable versions (`v0.27.0` or `v0.32.0`) to the fixed version `v0.38.0`. The upstream `golang.org/x/image` library in version `v0.38.0` includes a fix that validates the TIFF IFD offset before allocating memory, preventing the out-of-memory condition. No changes to Mattermost's own source code were needed; the fix was entirely in the updated dependency.

Preconditions

  • authAttacker must be an authenticated Mattermost user
  • inputAttacker must have file upload or message posting permissions
  • configServer must be running a vulnerable Mattermost version (11.6.x <= 11.6.0, 11.5.x <= 11.5.2/11.5.3, 11.4.x <= 11.4.4, 10.11.x <= 10.11.14)

Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.