github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/auth/auth_test.go (about)

     1  // spec package is introduced to avoid circular dependencies since this
     2  // particular test requires to depend on routing directly to expose the API and
     3  // the APP server.
     4  package auth_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/cozy/cozy-stack/model/instance"
    16  	"github.com/cozy/cozy-stack/model/instance/lifecycle"
    17  	"github.com/cozy/cozy-stack/model/oauth"
    18  	"github.com/cozy/cozy-stack/model/permission"
    19  	"github.com/cozy/cozy-stack/model/session"
    20  	"github.com/cozy/cozy-stack/model/stack"
    21  	"github.com/cozy/cozy-stack/pkg/assets/dynamic"
    22  	"github.com/cozy/cozy-stack/pkg/config/config"
    23  	"github.com/cozy/cozy-stack/pkg/consts"
    24  	"github.com/cozy/cozy-stack/pkg/couchdb"
    25  	"github.com/cozy/cozy-stack/pkg/crypto"
    26  	"github.com/cozy/cozy-stack/pkg/metadata"
    27  	"github.com/cozy/cozy-stack/tests/testutils"
    28  	"github.com/cozy/cozy-stack/web"
    29  	"github.com/cozy/cozy-stack/web/apps"
    30  	"github.com/cozy/cozy-stack/web/errors"
    31  	"github.com/cozy/cozy-stack/web/middlewares"
    32  	"github.com/gavv/httpexpect/v2"
    33  	"github.com/golang-jwt/jwt/v5"
    34  	"github.com/labstack/echo/v4"
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  const domain = "cozy.example.net"
    40  
    41  func TestAuth(t *testing.T) {
    42  	if testing.Short() {
    43  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    44  	}
    45  
    46  	var JWTSecret = []byte("foobar")
    47  
    48  	var sessionID string
    49  
    50  	var clientID string
    51  	var clientSecret string
    52  	var registrationToken string
    53  	var altClientID string
    54  	var altRegistrationToken string
    55  	var csrfToken string
    56  	var code string
    57  	var refreshToken string
    58  
    59  	config.UseTestFile(t)
    60  	conf := config.GetConfig()
    61  	conf.Assets = "../../assets"
    62  	conf.DeprecatedApps = config.DeprecatedAppsCfg{
    63  		Apps: []config.DeprecatedApp{
    64  			{
    65  				SoftwareID: "github.com/some-deprecated-app",
    66  				Name:       "some-deprecated-app",
    67  				StoreURLs: map[string]string{
    68  					// Must test "market://" url
    69  					"android": "market://some-market-url",
    70  					"iphone":  "https://some-basic-url",
    71  				},
    72  			},
    73  		},
    74  	}
    75  
    76  	conf.Authentication = make(map[string]interface{})
    77  	confAuth := make(map[string]interface{})
    78  	confAuth["jwt_secret"] = base64.StdEncoding.EncodeToString(JWTSecret)
    79  	conf.Authentication[config.DefaultInstanceContext] = confAuth
    80  
    81  	_ = web.LoadSupportedLocales()
    82  	testutils.NeedCouchdb(t)
    83  	setup := testutils.NewSetup(t, t.Name())
    84  
    85  	testInstance := setup.GetTestInstance(&lifecycle.Options{
    86  		Domain:        domain,
    87  		Email:         "test@spam.cozycloud.cc",
    88  		Passphrase:    "MyPassphrase",
    89  		KdfIterations: 5000,
    90  		Key:           "xxx",
    91  	})
    92  
    93  	ts := setup.GetTestServer("/test", fakeAPI, func(r *echo.Echo) *echo.Echo {
    94  		handler, err := web.CreateSubdomainProxy(r, &stack.Services{}, apps.Serve)
    95  		require.NoError(t, err, "Cant start subdomain proxy")
    96  		return handler
    97  	})
    98  	ts.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler
    99  
   100  	require.NoError(t, dynamic.InitDynamicAssetFS(config.FsURL().String()), "Could not init dynamic FS")
   101  
   102  	t.Run("InstanceBlocked", func(t *testing.T) {
   103  		e := testutils.CreateTestClient(t, ts.URL)
   104  
   105  		// Block the instance
   106  		testInstance.Blocked = true
   107  		require.NoError(t, instance.Update(testInstance))
   108  
   109  		e.GET("/auth/login").
   110  			WithHost(testInstance.Domain).
   111  			Expect().Status(http.StatusServiceUnavailable)
   112  
   113  		// Trying with a Accept: text/html header to simulate a browser
   114  		body := e.GET("/auth/login").
   115  			WithHost(testInstance.Domain).
   116  			WithHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").
   117  			Expect().Status(http.StatusServiceUnavailable).
   118  			Body()
   119  
   120  		body.Contains("<title>Cozy</title>")
   121  		body.Contains("Your Cozy has been blocked</h1>")
   122  
   123  		// Unblock the instance
   124  		testInstance.Blocked = false
   125  		_ = instance.Update(testInstance)
   126  	})
   127  
   128  	t.Run("IsLoggedInWhenNotLoggedIn", func(t *testing.T) {
   129  		e := testutils.CreateTestClient(t, ts.URL)
   130  
   131  		e.GET("/test").
   132  			WithHost(domain).
   133  			Expect().Status(200).
   134  			Text(httpexpect.ContentOpts{MediaType: "text/plain"}).
   135  			Equal("who_are_you")
   136  	})
   137  
   138  	t.Run("HomeWhenNotLoggedIn", func(t *testing.T) {
   139  		e := testutils.CreateTestClient(t, ts.URL)
   140  
   141  		e.GET("/").
   142  			WithHost(domain).
   143  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   144  			Expect().Status(303).
   145  			Header("Location").Equal("https://cozy.example.net/auth/login")
   146  	})
   147  
   148  	t.Run("HomeWhenNotLoggedInWithJWT", func(t *testing.T) {
   149  		e := testutils.CreateTestClient(t, ts.URL)
   150  
   151  		e.GET("/").WithQuery("jwt", "foobar").
   152  			WithHost(domain).
   153  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   154  			Expect().Status(303).
   155  			Header("Location").Equal("https://cozy.example.net/auth/login?jwt=foobar")
   156  	})
   157  
   158  	t.Run("ShowLoginPage", func(t *testing.T) {
   159  		e := testutils.CreateTestClient(t, ts.URL)
   160  
   161  		e.GET("/auth/login").
   162  			WithHost(domain).
   163  			Expect().Status(200).
   164  			Text(httpexpect.ContentOpts{MediaType: "text/html"}).
   165  			Contains("Log in")
   166  	})
   167  
   168  	t.Run("ShowLoginPageWithRedirectBadURL", func(t *testing.T) {
   169  		testsRedirect := []string{" ", "foo.bar", "ftp://sub." + domain + "/foo"}
   170  
   171  		for _, test := range testsRedirect {
   172  			t.Run(test, func(t *testing.T) {
   173  				e := testutils.CreateTestClient(t, ts.URL)
   174  
   175  				e.GET("/auth/login").WithQuery("redirect", test).
   176  					WithHost(domain).
   177  					Expect().Status(400).
   178  					Text(httpexpect.ContentOpts{MediaType: "text/plain"}).Contains("bad url")
   179  			})
   180  		}
   181  	})
   182  
   183  	t.Run("ShowLoginPageWithRedirectXSS", func(t *testing.T) {
   184  		e := testutils.CreateTestClient(t, ts.URL)
   185  
   186  		e.GET("/auth/login").WithQuery("redirect", "https://sub."+domain+"/<script>alert('foo')</script>").
   187  			WithHost(domain).
   188  			Expect().Status(200).
   189  			Text(httpexpect.ContentOpts{MediaType: "text/html"}).
   190  			Contains("%3Cscript%3Ealert%28%27foo%27%29%3C/script%3E").
   191  			NotContains("<script>")
   192  	})
   193  
   194  	t.Run("ShowLoginPageWithRedirectFragment", func(t *testing.T) {
   195  		e := testutils.CreateTestClient(t, ts.URL)
   196  
   197  		e.GET("/auth/login").WithQuery("redirect", "https://"+domain+"/auth/authorize#myfragment").
   198  			WithHost(domain).
   199  			Expect().Status(200).
   200  			Text(httpexpect.ContentOpts{MediaType: "text/html"}).
   201  			NotContains("myfragment").
   202  			Contains(`<input id="redirect" type="hidden" name="redirect" value="https://cozy.example.net/auth/authorize#=" />`)
   203  	})
   204  
   205  	t.Run("ShowLoginPageWithRedirectSuccess", func(t *testing.T) {
   206  		e := testutils.CreateTestClient(t, ts.URL)
   207  
   208  		e.GET("/auth/login").WithQuery("redirect", "https://sub."+domain+"/foo/bar?query=foo#myfragment").
   209  			WithHost(domain).
   210  			Expect().Status(200).
   211  			Text(httpexpect.ContentOpts{MediaType: "text/html"}).
   212  			Contains(`<input id="redirect" type="hidden" name="redirect" value="https://sub.cozy.example.net/foo/bar?query=foo#myfragment" />`)
   213  	})
   214  
   215  	t.Run("LoginWithoutCSRFToken", func(t *testing.T) {
   216  		e := testutils.CreateTestClient(t, ts.URL)
   217  
   218  		e.POST("/auth/login").WithFormField("passphrase", "MyPassphrase").
   219  			WithHost(domain).
   220  			Expect().Status(400)
   221  	})
   222  
   223  	t.Run("LoginWithBadPassphrase", func(t *testing.T) {
   224  		e := testutils.CreateTestClient(t, ts.URL)
   225  
   226  		token := getLoginCSRFToken(e)
   227  
   228  		e.POST("/auth/login").
   229  			WithHost(domain).
   230  			WithCookie("_csrf", token).
   231  			WithFormField("csrf_token", token).
   232  			WithFormField("passphrase", "Nope").
   233  			Expect().Status(401)
   234  	})
   235  
   236  	t.Run("LoginWithGoodPassphrase", func(t *testing.T) {
   237  		e := testutils.CreateTestClient(t, ts.URL)
   238  
   239  		token := getLoginCSRFToken(e)
   240  
   241  		res := e.POST("/auth/login").
   242  			WithHost(domain).
   243  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   244  			WithCookie("_csrf", token).
   245  			WithFormField("csrf_token", token).
   246  			WithFormField("passphrase", "MyPassphrase").
   247  			Expect().Status(303)
   248  
   249  		res.Header("Location").Equal("https://home.cozy.example.net/")
   250  		res.Cookies().Length().Equal(2)
   251  		res.Cookie("_csrf").Value().Equal(token)
   252  		res.Cookie(session.CookieName(testInstance)).Value().NotEmpty()
   253  
   254  		var results []*session.LoginEntry
   255  		err := couchdb.GetAllDocs(
   256  			testInstance,
   257  			consts.SessionsLogins,
   258  			&couchdb.AllDocsRequest{Limit: 100},
   259  			&results,
   260  		)
   261  		assert.NoError(t, err)
   262  		assert.Equal(t, 1, len(results))
   263  		assert.Equal(t, "Go-http-client/1.1", results[0].UA)
   264  		assert.Equal(t, "127.0.0.1", results[0].IP)
   265  		assert.False(t, results[0].CreatedAt.IsZero())
   266  	})
   267  
   268  	t.Run("LoginWithRedirect", func(t *testing.T) {
   269  		t.Run("success", func(t *testing.T) {
   270  			e := testutils.CreateTestClient(t, ts.URL)
   271  
   272  			token := getLoginCSRFToken(e)
   273  			e.POST("/auth/login").
   274  				WithHost(domain).
   275  				WithRedirectPolicy(httpexpect.DontFollowRedirects).
   276  				WithCookie("_csrf", token).
   277  				WithFormField("csrf_token", token).
   278  				WithFormField("passphrase", "MyPassphrase").
   279  				WithFormField("redirect", "https://sub."+domain+"/#myfragment").
   280  				Expect().Status(303).
   281  				Header("Location").Equal("https://sub.cozy.example.net/#myfragment")
   282  		})
   283  
   284  		t.Run("invalid redirect field", func(t *testing.T) {
   285  			e := testutils.CreateTestClient(t, ts.URL)
   286  
   287  			token := getLoginCSRFToken(e)
   288  			e.POST("/auth/login").
   289  				WithHost(domain).
   290  				WithRedirectPolicy(httpexpect.DontFollowRedirects).
   291  				WithCookie("_csrf", token).
   292  				WithFormField("csrf_token", token).
   293  				WithFormField("passphrase", "MyPassphrase").
   294  				WithFormField("redirect", "foo.bar").
   295  				Expect().Status(400)
   296  		})
   297  	})
   298  
   299  	t.Run("DelegatedJWTLoginWithRedirect", func(t *testing.T) {
   300  		e := testutils.CreateTestClient(t, ts.URL)
   301  
   302  		token := jwt.NewWithClaims(jwt.SigningMethodHS256, session.ExternalClaims{
   303  			RegisteredClaims: jwt.RegisteredClaims{
   304  				Subject:   "sruti",
   305  				IssuedAt:  jwt.NewNumericDate(time.Now()),
   306  				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
   307  			},
   308  			Name:  domain,
   309  			Email: "sruti@external.notmycozy.net",
   310  			Code:  "student",
   311  		})
   312  		signed, err := token.SignedString(JWTSecret)
   313  		require.NoError(t, err)
   314  
   315  		sessionID = e.GET("/auth/login").WithQuery("jwt", signed).
   316  			WithHost(domain).
   317  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   318  			Expect().Status(303).
   319  			Cookie(session.CookieName(testInstance)).Value().NotEmpty().Raw()
   320  	})
   321  
   322  	t.Run("IsLoggedInAfterLogin", func(t *testing.T) {
   323  		e := testutils.CreateTestClient(t, ts.URL)
   324  
   325  		e.GET("/test").
   326  			WithHost(domain).
   327  			WithCookie(session.CookieName(testInstance), sessionID).
   328  			Expect().Status(200).
   329  			Text(httpexpect.ContentOpts{MediaType: "text/plain"}).
   330  			Equal("logged_in")
   331  	})
   332  
   333  	t.Run("HomeWhenLoggedIn", func(t *testing.T) {
   334  		e := testutils.CreateTestClient(t, ts.URL)
   335  
   336  		e.GET("/").
   337  			WithHost(domain).
   338  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   339  			WithCookie(session.CookieName(testInstance), sessionID).
   340  			Expect().Status(303).
   341  			Header("Location").Equal("https://home.cozy.example.net/")
   342  	})
   343  
   344  	t.Run("RegisterClientNotJSON", func(t *testing.T) {
   345  		e := testutils.CreateTestClient(t, ts.URL)
   346  
   347  		e.POST("/auth/register").
   348  			WithHost(domain).
   349  			WithFormField("foo", "bar").
   350  			Expect().Status(400)
   351  	})
   352  
   353  	t.Run("RegisterClientNoRedirectURI", func(t *testing.T) {
   354  		e := testutils.CreateTestClient(t, ts.URL)
   355  
   356  		obj := e.POST("/auth/register").
   357  			WithHost(domain).
   358  			WithHeader("Accept", "application/json").
   359  			WithJSON(map[string]string{
   360  				"client_name": "cozy-test",
   361  				"software_id": "github.com/cozy/cozy-test",
   362  			}).
   363  			Expect().Status(400).
   364  			JSON().Object()
   365  
   366  		obj.ValueEqual("error", "invalid_redirect_uri")
   367  		obj.ValueEqual("error_description", "redirect_uris is mandatory")
   368  	})
   369  
   370  	t.Run("RegisterClient with error", func(t *testing.T) {
   371  		tests := []struct {
   372  			name           string
   373  			body           map[string]interface{}
   374  			err            string
   375  			errDescription string
   376  		}{
   377  			{
   378  				name: "InvalidRedirectURI",
   379  				body: map[string]interface{}{
   380  					"redirect_uris": []string{"http://example.org/foo#bar"},
   381  					"client_name":   "cozy-test",
   382  					"software_id":   "github.com/cozy/cozy-test",
   383  				},
   384  				err:            "invalid_redirect_uri",
   385  				errDescription: "http://example.org/foo#bar is invalid",
   386  			},
   387  			{
   388  				name: "NoClientName",
   389  				body: map[string]interface{}{
   390  					"redirect_uris": []string{"https://example.org/oauth/callback"},
   391  					"software_id":   "github.com/cozy/cozy-test",
   392  				},
   393  				err:            "invalid_client_metadata",
   394  				errDescription: "client_name is mandatory",
   395  			},
   396  			{
   397  				name: "NoSoftwareID",
   398  				body: map[string]interface{}{
   399  					"redirect_uris": []string{"https://example.org/oauth/callback"},
   400  					"client_name":   "cozy-test",
   401  				},
   402  				err:            "invalid_client_metadata",
   403  				errDescription: "software_id is mandatory",
   404  			},
   405  		}
   406  
   407  		for _, test := range tests {
   408  			t.Run(test.name, func(t *testing.T) {
   409  				e := testutils.CreateTestClient(t, ts.URL)
   410  
   411  				obj := e.POST("/auth/register").
   412  					WithHost(domain).
   413  					WithHeader("Accept", "application/json").
   414  					WithJSON(test.body).
   415  					Expect().Status(400).
   416  					JSON().Object()
   417  
   418  				obj.ValueEqual("error", test.err)
   419  				obj.ValueEqual("error_description", test.errDescription)
   420  			})
   421  		}
   422  	})
   423  
   424  	t.Run("RegisterClientSuccessWithJustMandatoryFields", func(t *testing.T) {
   425  		e := testutils.CreateTestClient(t, ts.URL)
   426  
   427  		obj := e.POST("/auth/register").
   428  			WithHost(domain).
   429  			WithHeader("Accept", "application/json").
   430  			WithJSON(map[string]interface{}{
   431  				"redirect_uris": []string{"https://example.org/oauth/callback"},
   432  				"client_name":   "cozy-test",
   433  				"software_id":   "github.com/cozy/cozy-test",
   434  			}).
   435  			Expect().Status(201).
   436  			JSON().Object()
   437  
   438  		obj.ValueEqual("client_secret_expires_at", 0)
   439  		obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"})
   440  		obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"})
   441  		obj.ValueEqual("response_types", []string{"code"})
   442  		obj.ValueEqual("client_name", "cozy-test")
   443  		obj.ValueEqual("software_id", "github.com/cozy/cozy-test")
   444  
   445  		clientID = obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw()
   446  		clientSecret = obj.Value("client_secret").String().NotEmpty().NotEqual("ignored").Raw()
   447  		registrationToken = obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw()
   448  	})
   449  
   450  	t.Run("RegisterClientSuccessWithAllFields", func(t *testing.T) {
   451  		e := testutils.CreateTestClient(t, ts.URL)
   452  
   453  		obj := e.POST("/auth/register").
   454  			WithHost(domain).
   455  			WithHeader("Accept", "application/json").
   456  			WithJSON(map[string]interface{}{
   457  				"_id":                       "ignored",
   458  				"_rev":                      "ignored",
   459  				"client_id":                 "ignored",
   460  				"client_secret":             "ignored",
   461  				"client_secret_expires_at":  42,
   462  				"registration_access_token": "ignored",
   463  				"redirect_uris":             []string{"https://example.org/oauth/callback"},
   464  				"grant_types":               []string{"ignored"},
   465  				"response_types":            []string{"ignored"},
   466  				"client_name":               "new-cozy-test",
   467  				"client_kind":               "test",
   468  				"client_uri":                "https://github.com/cozy/cozy-test",
   469  				"logo_uri":                  "https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png",
   470  				"policy_uri":                "https://github/com/cozy/cozy-test/master/policy.md",
   471  				"software_id":               "github.com/cozy/cozy-test",
   472  				"software_version":          "v0.1.2",
   473  			}).
   474  			Expect().Status(201).
   475  			JSON().Object()
   476  
   477  		obj.NotContainsKey("_id")
   478  		obj.NotContainsKey("_rev")
   479  		obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw()
   480  
   481  		obj.ValueEqual("client_secret_expires_at", 0)
   482  		obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"})
   483  		obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"})
   484  		obj.ValueEqual("response_types", []string{"code"})
   485  		obj.ValueEqual("client_name", "new-cozy-test")
   486  		obj.ValueEqual("client_kind", "test")
   487  		obj.ValueEqual("client_uri", "https://github.com/cozy/cozy-test")
   488  		obj.ValueEqual("logo_uri", "https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png")
   489  		obj.ValueEqual("policy_uri", "https://github/com/cozy/cozy-test/master/policy.md")
   490  		obj.ValueEqual("software_id", "github.com/cozy/cozy-test")
   491  		obj.ValueEqual("software_version", "v0.1.2")
   492  
   493  		altClientID = obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw()
   494  		altRegistrationToken = obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw()
   495  	})
   496  
   497  	t.Run("RegisterSharingClientSuccess", func(t *testing.T) {
   498  		e := testutils.CreateTestClient(t, ts.URL)
   499  
   500  		obj := e.POST("/auth/register").
   501  			WithHost(domain).
   502  			WithHeader("Accept", "application/json").
   503  			WithJSON(map[string]interface{}{
   504  				"redirect_uris": []string{"https://cozy.example.org/sharings/answer"},
   505  				"client_name":   "John",
   506  				"software_id":   "github.com/cozy/cozy-stack",
   507  				"client_kind":   "sharing",
   508  				"client_uri":    "https://cozy.example.org",
   509  			}).
   510  			Expect().Status(201).
   511  			JSON().Object()
   512  
   513  		obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw()
   514  		obj.Value("client_secret").String().NotEmpty().NotEqual("ignored").Raw()
   515  		obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw()
   516  
   517  		obj.ValueEqual("client_secret_expires_at", 0)
   518  		obj.ValueEqual("redirect_uris", []string{"https://cozy.example.org/sharings/answer"})
   519  		obj.ValueEqual("client_name", "John")
   520  		obj.ValueEqual("software_id", "github.com/cozy/cozy-stack")
   521  	})
   522  
   523  	t.Run("DeleteClientNoToken", func(t *testing.T) {
   524  		e := testutils.CreateTestClient(t, ts.URL)
   525  
   526  		e.DELETE("/auth/register/" + altClientID).
   527  			WithHost(domain).
   528  			Expect().Status(401)
   529  	})
   530  
   531  	t.Run("DeleteClientSuccess", func(t *testing.T) {
   532  		e := testutils.CreateTestClient(t, ts.URL)
   533  
   534  		e.DELETE("/auth/register/"+altClientID).
   535  			WithHost(domain).
   536  			WithHeader("Authorization", "Bearer "+altRegistrationToken).
   537  			Expect().Status(204)
   538  
   539  		// And next calls should return a 204 too
   540  		e.DELETE("/auth/register/"+altClientID).
   541  			WithHost(domain).
   542  			WithHeader("Authorization", "Bearer "+altRegistrationToken).
   543  			Expect().Status(204)
   544  	})
   545  
   546  	t.Run("ReadClientNoToken", func(t *testing.T) {
   547  		e := testutils.CreateTestClient(t, ts.URL)
   548  
   549  		e.GET("/auth/register/"+clientID).
   550  			WithHost(domain).
   551  			WithHeader("Accept", "application/json").
   552  			Expect().Status(401).
   553  			Body().NotContains(clientSecret)
   554  	})
   555  
   556  	t.Run("ReadClientInvalidToken", func(t *testing.T) {
   557  		e := testutils.CreateTestClient(t, ts.URL)
   558  
   559  		e.GET("/auth/register/"+clientID).
   560  			WithHost(domain).
   561  			WithHeader("Accept", "application/json").
   562  			WithHeader("Authorization", "Bearer "+altRegistrationToken).
   563  			Expect().Status(401).
   564  			Body().NotContains(clientSecret)
   565  	})
   566  
   567  	t.Run("DeprecatedApp", func(t *testing.T) {
   568  		e := testutils.CreateTestClient(t, ts.URL)
   569  
   570  		deprecatedClientID := e.POST("/auth/register").
   571  			WithHost(domain).
   572  			WithHeader("Accept", "application/json").
   573  			WithJSON(map[string]interface{}{
   574  				"redirect_uris": []string{"https://example.org/oauth/callback"},
   575  				"client_name":   "cozy-test",
   576  				"software_id":   "github.com/some-deprecated-app",
   577  			}).
   578  			Expect().Status(201).
   579  			JSON().Object().
   580  			Value("client_id").String().NotEmpty().NotEqual("ignored").Raw()
   581  
   582  		body := e.GET("/auth/authorize").
   583  			WithHeader("User-Agent", "Mozilla/5.0 (Linux; U; Android 1.5; de-; HTC Magic Build/PLAT-RC33) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1").
   584  			WithQuery("response_type", "code").
   585  			WithQuery("state", "123456").
   586  			WithQuery("scope", "files:read").
   587  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   588  			WithQuery("client_id", deprecatedClientID).
   589  			WithHost(domain).
   590  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   591  			Expect().Status(200).
   592  			Body()
   593  
   594  		body.Contains(`href="market://some-market-url"`)
   595  	})
   596  
   597  	t.Run("ReadClientInvalidClientID", func(t *testing.T) {
   598  		e := testutils.CreateTestClient(t, ts.URL)
   599  
   600  		e.GET("/auth/register/"+altClientID).
   601  			WithHost(domain).
   602  			WithHeader("Accept", "application/json").
   603  			WithHeader("Authorization", "Bearer "+altRegistrationToken).
   604  			Expect().Status(404)
   605  	})
   606  
   607  	t.Run("ReadClientSuccess", func(t *testing.T) {
   608  		e := testutils.CreateTestClient(t, ts.URL)
   609  
   610  		obj := e.GET("/auth/register/"+clientID).
   611  			WithHost(domain).
   612  			WithHeader("Accept", "application/json").
   613  			WithHeader("Authorization", "Bearer "+registrationToken).
   614  			Expect().Status(200).
   615  			JSON().Object()
   616  
   617  		obj.ValueEqual("client_id", clientID)
   618  		obj.ValueEqual("client_secret", clientSecret)
   619  		obj.ValueEqual("client_secret_expires_at", 0)
   620  		obj.NotContainsKey("registration_access_token")
   621  		obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"})
   622  		obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"})
   623  		obj.ValueEqual("response_types", []string{"code"})
   624  		obj.ValueEqual("client_name", "cozy-test")
   625  		obj.ValueEqual("software_id", "github.com/cozy/cozy-test")
   626  	})
   627  
   628  	t.Run("UpdateClientDeletedClientID", func(t *testing.T) {
   629  		e := testutils.CreateTestClient(t, ts.URL)
   630  
   631  		e.PUT("/auth/register/"+altClientID).
   632  			WithHost(domain).
   633  			WithHeader("Accept", "application/json").
   634  			WithHeader("Authorization", "Bearer "+registrationToken).
   635  			WithJSON(map[string]string{
   636  				"client_id": altClientID,
   637  			}).
   638  			Expect().Status(404)
   639  	})
   640  
   641  	t.Run("UpdateClientInvalidClientID", func(t *testing.T) {
   642  		e := testutils.CreateTestClient(t, ts.URL)
   643  
   644  		obj := e.PUT("/auth/register/"+clientID).
   645  			WithHost(domain).
   646  			WithHeader("Accept", "application/json").
   647  			WithHeader("Authorization", "Bearer "+registrationToken).
   648  			WithJSON(map[string]string{
   649  				"client_id": "123456789",
   650  			}).
   651  			Expect().Status(400).
   652  			JSON().Object()
   653  
   654  		obj.ValueEqual("error", "invalid_client_id")
   655  		obj.ValueEqual("error_description", "client_id is mandatory")
   656  	})
   657  
   658  	t.Run("UpdateClientNoRedirectURI", func(t *testing.T) {
   659  		e := testutils.CreateTestClient(t, ts.URL)
   660  
   661  		obj := e.PUT("/auth/register/"+clientID).
   662  			WithHost(domain).
   663  			WithHeader("Accept", "application/json").
   664  			WithHeader("Authorization", "Bearer "+registrationToken).
   665  			WithJSON(map[string]string{
   666  				"client_id":   clientID,
   667  				"client_name": "cozy-test",
   668  				"software_id": "github.com/cozy/cozy-test",
   669  			}).
   670  			Expect().Status(400).
   671  			JSON().Object()
   672  
   673  		obj.ValueEqual("error", "invalid_redirect_uri")
   674  		obj.ValueEqual("error_description", "redirect_uris is mandatory")
   675  	})
   676  
   677  	t.Run("UpdateClientSuccess", func(t *testing.T) {
   678  		e := testutils.CreateTestClient(t, ts.URL)
   679  
   680  		obj := e.PUT("/auth/register/"+clientID).
   681  			WithHost(domain).
   682  			WithHeader("Accept", "application/json").
   683  			WithHeader("Authorization", "Bearer "+registrationToken).
   684  			WithJSON(map[string]interface{}{
   685  				"client_id":        clientID,
   686  				"client_name":      "cozy-test-new-name",
   687  				"redirect_uris":    []string{"https://example.org/oauth/callback"},
   688  				"software_id":      "github.com/cozy/cozy-test",
   689  				"software_version": "v0.1.3",
   690  			}).
   691  			Expect().Status(200).
   692  			JSON().Object()
   693  
   694  		obj.ValueEqual("client_id", clientID)
   695  		obj.ValueEqual("client_secret", clientSecret)
   696  		obj.ValueEqual("client_secret_expires_at", 0)
   697  		obj.NotContainsKey("registration_access_token")
   698  		obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"})
   699  		obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"})
   700  		obj.ValueEqual("response_types", []string{"code"})
   701  		obj.ValueEqual("client_name", "cozy-test-new-name")
   702  		obj.ValueEqual("software_id", "github.com/cozy/cozy-test")
   703  		obj.ValueEqual("software_version", "v0.1.3")
   704  	})
   705  
   706  	t.Run("UpdateClientSecret", func(t *testing.T) {
   707  		e := testutils.CreateTestClient(t, ts.URL)
   708  
   709  		obj := e.PUT("/auth/register/"+clientID).
   710  			WithHost(domain).
   711  			WithHeader("Accept", "application/json").
   712  			WithHeader("Authorization", "Bearer "+registrationToken).
   713  			WithJSON(map[string]interface{}{
   714  				"client_id":        clientID,
   715  				"client_secret":    clientSecret,
   716  				"redirect_uris":    []string{"https://example.org/oauth/callback"},
   717  				"client_name":      "cozy-test",
   718  				"software_id":      "github.com/cozy/cozy-test",
   719  				"software_version": "v0.1.4",
   720  			}).
   721  			Expect().Status(200).
   722  			JSON().Object()
   723  
   724  		clientSecret = obj.Value("client_secret").String().NotEqual(clientSecret).Raw()
   725  
   726  		obj.ValueEqual("client_id", clientID)
   727  		obj.ValueEqual("client_secret_expires_at", 0)
   728  		obj.NotContainsKey("registration_access_token")
   729  		obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"})
   730  		obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"})
   731  		obj.ValueEqual("response_types", []string{"code"})
   732  		obj.ValueEqual("client_name", "cozy-test")
   733  		obj.ValueEqual("software_id", "github.com/cozy/cozy-test")
   734  		obj.ValueEqual("software_version", "v0.1.4")
   735  	})
   736  
   737  	t.Run("AuthorizeFormRedirectsWhenNotLoggedIn", func(t *testing.T) {
   738  		e := testutils.CreateTestClient(t, ts.URL)
   739  
   740  		e.GET("/auth/authorize").
   741  			WithQuery("response_type", "code").
   742  			WithQuery("state", "123456").
   743  			WithQuery("scope", "files:read").
   744  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   745  			WithQuery("client_id", clientID).
   746  			WithHost(domain).
   747  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   748  			Expect().Status(303)
   749  	})
   750  
   751  	t.Run("AuthorizeFormBadResponseType", func(t *testing.T) {
   752  		e := testutils.CreateTestClient(t, ts.URL)
   753  
   754  		e.GET("/auth/authorize").
   755  			WithQuery("response_type", "token"). // invalid
   756  			WithQuery("state", "123456").
   757  			WithQuery("scope", "files:read").
   758  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   759  			WithQuery("client_id", clientID).
   760  			WithHost(domain).
   761  			Expect().Status(400).
   762  			ContentType("text/html", "utf-8").
   763  			Body().Contains("Invalid response type")
   764  	})
   765  
   766  	t.Run("AuthorizeFormNoState", func(t *testing.T) {
   767  		e := testutils.CreateTestClient(t, ts.URL)
   768  
   769  		e.GET("/auth/authorize").
   770  			WithQuery("response_type", "code").
   771  			WithQuery("scope", "files:read").
   772  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   773  			WithQuery("client_id", clientID).
   774  			WithHost(domain).
   775  			Expect().Status(400).
   776  			ContentType("text/html", "utf-8").
   777  			Body().Contains("The state parameter is mandatory")
   778  	})
   779  
   780  	t.Run("AuthorizeFormNoClientId", func(t *testing.T) {
   781  		e := testutils.CreateTestClient(t, ts.URL)
   782  
   783  		e.GET("/auth/authorize").
   784  			WithQuery("response_type", "code").
   785  			WithQuery("state", "123456").
   786  			WithQuery("scope", "files:read").
   787  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   788  			WithHost(domain).
   789  			Expect().Status(400).
   790  			ContentType("text/html", "utf-8").
   791  			Body().Contains("The client_id parameter is mandatory")
   792  	})
   793  
   794  	t.Run("AuthorizeFormNoRedirectURI", func(t *testing.T) {
   795  		e := testutils.CreateTestClient(t, ts.URL)
   796  
   797  		e.GET("/auth/authorize").
   798  			WithQuery("response_type", "code").
   799  			WithQuery("state", "123456").
   800  			WithQuery("scope", "files:read").
   801  			WithQuery("client_id", clientID).
   802  			WithHost(domain).
   803  			Expect().Status(400).
   804  			ContentType("text/html", "utf-8").
   805  			Body().Contains("The redirect_uri parameter is mandatory")
   806  	})
   807  
   808  	t.Run("AuthorizeFormNoScope", func(t *testing.T) {
   809  		e := testutils.CreateTestClient(t, ts.URL)
   810  
   811  		e.GET("/auth/authorize").
   812  			WithQuery("response_type", "code").
   813  			WithQuery("state", "123456").
   814  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   815  			WithQuery("client_id", clientID).
   816  			WithHost(domain).
   817  			Expect().Status(400).
   818  			ContentType("text/html", "utf-8").
   819  			Body().Contains("The scope parameter is mandatory")
   820  	})
   821  
   822  	t.Run("AuthorizeFormInvalidClient", func(t *testing.T) {
   823  		e := testutils.CreateTestClient(t, ts.URL)
   824  
   825  		e.GET("/auth/authorize").
   826  			WithQuery("response_type", "code").
   827  			WithQuery("state", "123456").
   828  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   829  			WithQuery("scope", "files:read").
   830  			WithQuery("client_id", "foo").
   831  			WithHost(domain).
   832  			Expect().Status(400).
   833  			ContentType("text/html", "utf-8").
   834  			Body().Contains("The client must be registered")
   835  	})
   836  
   837  	t.Run("AuthorizeFormInvalidRedirectURI", func(t *testing.T) {
   838  		e := testutils.CreateTestClient(t, ts.URL)
   839  
   840  		e.GET("/auth/authorize").
   841  			WithQuery("response_type", "code").
   842  			WithQuery("state", "123456").
   843  			WithQuery("redirect_uri", "https://evil.com/").
   844  			WithQuery("scope", "files:read").
   845  			WithQuery("client_id", clientID).
   846  			WithHost(domain).
   847  			Expect().Status(400).
   848  			ContentType("text/html", "utf-8").
   849  			Body().Contains("The redirect_uri parameter doesn&#39;t match the registered ones")
   850  	})
   851  
   852  	t.Run("AuthorizeFormSuccess", func(t *testing.T) {
   853  		e := testutils.CreateTestClient(t, ts.URL)
   854  
   855  		resBody := e.GET("/auth/authorize").
   856  			WithQuery("response_type", "code").
   857  			WithQuery("state", "123456").
   858  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   859  			WithQuery("scope", "files:read").
   860  			WithQuery("client_id", clientID).
   861  			WithCookie(session.CookieName(testInstance), sessionID).
   862  			WithHost(domain).
   863  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
   864  			Expect().Status(200).
   865  			ContentType("text/html", "utf-8").
   866  			Body()
   867  
   868  		resBody.Contains("would like permission to access your Cozy")
   869  		matches := resBody.Match(`<input type="hidden" name="csrf_token" value="(\w+)"`)
   870  		matches.Length().Equal(2)
   871  		csrfToken = matches.Index(1).Raw()
   872  	})
   873  
   874  	t.Run("AuthorizeFormClientMobileApp", func(t *testing.T) {
   875  		e := testutils.CreateTestClient(t, ts.URL)
   876  
   877  		var oauthClient oauth.Client
   878  
   879  		u := "https://example.org/oauth/callback"
   880  		oauthClient.RedirectURIs = []string{u}
   881  		oauthClient.ClientName = "cozy-test-2"
   882  		oauthClient.SoftwareID = "registry://drive"
   883  		oauthClient.Create(testInstance)
   884  
   885  		e.GET("/auth/authorize").
   886  			WithQuery("response_type", "code").
   887  			WithQuery("state", "123456").
   888  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   889  			WithQuery("client_id", oauthClient.ClientID).
   890  			WithCookie(session.CookieName(testInstance), sessionID).
   891  			WithHost(domain).
   892  			Expect().Status(200).
   893  			ContentType("text/html", "utf-8").
   894  			Body().
   895  			Contains("io.cozy.files")
   896  	})
   897  
   898  	t.Run("AuthorizeFormFlagshipApp", func(t *testing.T) {
   899  		e := testutils.CreateTestClient(t, ts.URL)
   900  
   901  		var oauthClient oauth.Client
   902  
   903  		u := "https://example.org/oauth/callback"
   904  		oauthClient.RedirectURIs = []string{u}
   905  		oauthClient.ClientName = "cozy-test-2"
   906  		oauthClient.SoftwareID = "registry://drive"
   907  		oauthClient.Create(testInstance)
   908  
   909  		e.GET("/auth/authorize").
   910  			WithQuery("response_type", "code").
   911  			WithQuery("state", "123456").
   912  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
   913  			WithQuery("client_id", clientID).
   914  			WithQuery("scope", "*").
   915  			WithQuery("code_challenge", "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI").
   916  			WithQuery("code_challenge_method", "S256").
   917  			WithCookie(session.CookieName(testInstance), sessionID).
   918  			WithHost(domain).
   919  			Expect().Status(200).
   920  			ContentType("text/html", "utf-8").
   921  			Body().
   922  			NotContains("would like permission to access your Cozy").
   923  			Contains("The origin of this application is not certified.")
   924  	})
   925  
   926  	t.Run("AuthorizeWhenNotLoggedIn", func(t *testing.T) {
   927  		e := testutils.CreateTestClient(t, ts.URL)
   928  
   929  		e.POST("/auth/authorize").
   930  			WithFormField("state", "123456").
   931  			WithFormField("client_id", clientID).
   932  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
   933  			WithFormField("scope", "files:read").
   934  			WithFormField("csrf_token", csrfToken).
   935  			WithFormField("response_type", "code").
   936  			WithHost(domain).
   937  			Expect().Status(403)
   938  	})
   939  
   940  	t.Run("AuthorizeWithInvalidCSRFToken", func(t *testing.T) {
   941  		e := testutils.CreateTestClient(t, ts.URL)
   942  
   943  		e.POST("/auth/authorize").
   944  			WithFormField("state", "123456").
   945  			WithFormField("client_id", clientID).
   946  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
   947  			WithFormField("scope", "files:read").
   948  			WithFormField("csrf_token", "azertyuiop").
   949  			WithFormField("response_type", "code").
   950  			WithHost(domain).
   951  			WithCookie("_csrf", csrfToken).
   952  			Expect().Status(403).
   953  			Text(httpexpect.ContentOpts{MediaType: "text/plain"}).
   954  			Contains("invalid csrf token")
   955  	})
   956  
   957  	t.Run("AuthorizeWithNoState", func(t *testing.T) {
   958  		e := testutils.CreateTestClient(t, ts.URL)
   959  
   960  		e.POST("/auth/authorize").
   961  			WithFormField("client_id", clientID).
   962  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
   963  			WithFormField("scope", "files:read").
   964  			WithFormField("csrf_token", csrfToken).
   965  			WithFormField("response_type", "code").
   966  			WithHost(domain).
   967  			WithCookie("_csrf", csrfToken).
   968  			Expect().Status(400).
   969  			ContentType("text/html", "utf-8").
   970  			Body().Contains("The state parameter is mandatory")
   971  	})
   972  
   973  	t.Run("AuthorizeWithNoClientID", func(t *testing.T) {
   974  		e := testutils.CreateTestClient(t, ts.URL)
   975  
   976  		e.POST("/auth/authorize").
   977  			WithFormField("state", "123456").
   978  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
   979  			WithFormField("scope", "files:read").
   980  			WithFormField("csrf_token", csrfToken).
   981  			WithFormField("response_type", "code").
   982  			WithHost(domain).
   983  			WithCookie("_csrf", csrfToken).
   984  			Expect().Status(400).
   985  			ContentType("text/html", "utf-8").
   986  			Body().Contains("The client_id parameter is mandatory")
   987  	})
   988  
   989  	t.Run("AuthorizeWithInvalidClientID", func(t *testing.T) {
   990  		e := testutils.CreateTestClient(t, ts.URL)
   991  
   992  		e.POST("/auth/authorize").
   993  			WithFormField("state", "123456").
   994  			WithFormField("client_id", "invalid").
   995  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
   996  			WithFormField("scope", "files:read").
   997  			WithFormField("csrf_token", csrfToken).
   998  			WithFormField("response_type", "code").
   999  			WithHost(domain).
  1000  			WithCookie("_csrf", csrfToken).
  1001  			Expect().Status(400).
  1002  			ContentType("text/html", "utf-8").
  1003  			Body().Contains("The client must be registered")
  1004  	})
  1005  
  1006  	t.Run("AuthorizeWithNoRedirectURI", func(t *testing.T) {
  1007  		e := testutils.CreateTestClient(t, ts.URL)
  1008  
  1009  		e.POST("/auth/authorize").
  1010  			WithFormField("state", "123456").
  1011  			WithFormField("client_id", clientID).
  1012  			WithFormField("scope", "files:read").
  1013  			WithFormField("csrf_token", csrfToken).
  1014  			WithFormField("response_type", "code").
  1015  			WithHost(domain).
  1016  			WithCookie("_csrf", csrfToken).
  1017  			Expect().Status(400).
  1018  			ContentType("text/html", "utf-8").
  1019  			Body().Contains("The redirect_uri parameter is mandatory")
  1020  	})
  1021  
  1022  	t.Run("AuthorizeWithInvalidURI", func(t *testing.T) {
  1023  		e := testutils.CreateTestClient(t, ts.URL)
  1024  
  1025  		e.POST("/auth/authorize").
  1026  			WithFormField("state", "123456").
  1027  			WithFormField("client_id", clientID).
  1028  			WithFormField("scope", "files:read").
  1029  			WithFormField("redirect_uri", "/oauth/callback").
  1030  			WithFormField("csrf_token", csrfToken).
  1031  			WithFormField("response_type", "code").
  1032  			WithHost(domain).
  1033  			WithCookie("_csrf", csrfToken).
  1034  			Expect().Status(400).
  1035  			ContentType("text/html", "utf-8").
  1036  			Body().Contains("The redirect_uri parameter doesn&#39;t match the registered ones")
  1037  	})
  1038  
  1039  	t.Run("AuthorizeWithNoScope", func(t *testing.T) {
  1040  		e := testutils.CreateTestClient(t, ts.URL)
  1041  
  1042  		e.POST("/auth/authorize").
  1043  			WithFormField("state", "123456").
  1044  			WithFormField("client_id", clientID).
  1045  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
  1046  			WithFormField("csrf_token", csrfToken).
  1047  			WithFormField("response_type", "code").
  1048  			WithHost(domain).
  1049  			WithCookie("_csrf", csrfToken).
  1050  			Expect().Status(400).
  1051  			ContentType("text/html", "utf-8").
  1052  			Body().Contains("The scope parameter is mandatory")
  1053  	})
  1054  
  1055  	t.Run("AuthorizeSuccess", func(t *testing.T) {
  1056  		e := testutils.CreateTestClient(t, ts.URL)
  1057  
  1058  		redirectURL := e.POST("/auth/authorize").
  1059  			WithFormField("state", "123456").
  1060  			WithFormField("client_id", clientID).
  1061  			WithFormField("scope", "files:read").
  1062  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
  1063  			WithFormField("csrf_token", csrfToken).
  1064  			WithFormField("response_type", "code").
  1065  			WithHost(domain).
  1066  			WithCookie("_csrf", csrfToken).
  1067  			WithCookie(session.CookieName(testInstance), sessionID).
  1068  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1069  			Expect().Status(302).
  1070  			Header("Location")
  1071  
  1072  		var results []oauth.AccessCode
  1073  		req := &couchdb.AllDocsRequest{}
  1074  		err := couchdb.GetAllDocs(testInstance, consts.OAuthAccessCodes, req, &results)
  1075  		require.NoError(t, err)
  1076  		require.Len(t, results, 1)
  1077  
  1078  		code = results[0].Code
  1079  		redirectURL.Equal(fmt.Sprintf("https://example.org/oauth/callback?access_code=%s&code=%s&state=123456#", code, code))
  1080  		assert.Equal(t, results[0].ClientID, clientID)
  1081  		assert.Equal(t, results[0].Scope, "files:read")
  1082  	})
  1083  
  1084  	t.Run("AccessTokenNoGrantType", func(t *testing.T) {
  1085  		e := testutils.CreateTestClient(t, ts.URL)
  1086  
  1087  		e.POST("/auth/access_token").
  1088  			WithFormField("client_id", clientID).
  1089  			WithFormField("client_secret", clientSecret).
  1090  			WithFormField("code", code).
  1091  			WithHost(domain).
  1092  			Expect().Status(400).
  1093  			JSON().Object().
  1094  			ValueEqual("error", "the grant_type parameter is mandatory")
  1095  	})
  1096  
  1097  	t.Run("AccessTokenInvalidGrantType", func(t *testing.T) {
  1098  		e := testutils.CreateTestClient(t, ts.URL)
  1099  
  1100  		e.POST("/auth/access_token").
  1101  			WithFormField("grant_type", "token"). // invalide
  1102  			WithFormField("client_id", clientID).
  1103  			WithFormField("client_secret", clientSecret).
  1104  			WithFormField("code", code).
  1105  			WithHost(domain).
  1106  			Expect().Status(400).
  1107  			JSON().Object().
  1108  			ValueEqual("error", "invalid grant type")
  1109  	})
  1110  
  1111  	t.Run("AccessTokenNoClientID", func(t *testing.T) {
  1112  		e := testutils.CreateTestClient(t, ts.URL)
  1113  
  1114  		e.POST("/auth/access_token").
  1115  			WithFormField("grant_type", "authorization_code").
  1116  			WithFormField("client_secret", clientSecret).
  1117  			WithFormField("code", code).
  1118  			WithHost(domain).
  1119  			Expect().Status(400).
  1120  			JSON().Object().
  1121  			ValueEqual("error", "the client_id parameter is mandatory")
  1122  	})
  1123  
  1124  	t.Run("AccessTokenInvalidClientID", func(t *testing.T) {
  1125  		e := testutils.CreateTestClient(t, ts.URL)
  1126  
  1127  		e.POST("/auth/access_token").
  1128  			WithFormField("grant_type", "authorization_code").
  1129  			WithFormField("client_id", "foo"). // invalid
  1130  			WithFormField("client_secret", clientSecret).
  1131  			WithFormField("code", code).
  1132  			WithHost(domain).
  1133  			Expect().Status(400).
  1134  			JSON().Object().
  1135  			ValueEqual("error", "the client must be registered")
  1136  	})
  1137  
  1138  	t.Run("AccessTokenNoClientSecret", func(t *testing.T) {
  1139  		e := testutils.CreateTestClient(t, ts.URL)
  1140  
  1141  		e.POST("/auth/access_token").
  1142  			WithFormField("grant_type", "authorization_code").
  1143  			WithFormField("client_id", clientID).
  1144  			WithFormField("code", code).
  1145  			WithHost(domain).
  1146  			Expect().Status(400).
  1147  			JSON().Object().
  1148  			ValueEqual("error", "the client_secret parameter is mandatory")
  1149  	})
  1150  
  1151  	t.Run("AccessTokenInvalidClientSecret", func(t *testing.T) {
  1152  		e := testutils.CreateTestClient(t, ts.URL)
  1153  
  1154  		e.POST("/auth/access_token").
  1155  			WithFormField("grant_type", "authorization_code").
  1156  			WithFormField("client_id", clientID).
  1157  			WithFormField("client_secret", "foo"). // invalid
  1158  			WithFormField("code", code).
  1159  			WithHost(domain).
  1160  			Expect().Status(400).
  1161  			JSON().Object().
  1162  			ValueEqual("error", "invalid client_secret")
  1163  	})
  1164  
  1165  	t.Run("AccessTokenNoCode", func(t *testing.T) {
  1166  		e := testutils.CreateTestClient(t, ts.URL)
  1167  
  1168  		e.POST("/auth/access_token").
  1169  			WithFormField("grant_type", "authorization_code").
  1170  			WithFormField("client_id", clientID).
  1171  			WithFormField("client_secret", clientSecret).
  1172  			WithHost(domain).
  1173  			Expect().Status(400).
  1174  			JSON().Object().
  1175  			ValueEqual("error", "the code parameter is mandatory")
  1176  	})
  1177  
  1178  	t.Run("AccessTokenInvalidCode", func(t *testing.T) {
  1179  		e := testutils.CreateTestClient(t, ts.URL)
  1180  
  1181  		e.POST("/auth/access_token").
  1182  			WithFormField("grant_type", "authorization_code").
  1183  			WithFormField("client_id", clientID).
  1184  			WithFormField("client_secret", clientSecret).
  1185  			WithFormField("code", "foo").
  1186  			WithHost(domain).
  1187  			Expect().Status(400).
  1188  			JSON().Object().
  1189  			ValueEqual("error", "invalid code")
  1190  	})
  1191  
  1192  	t.Run("AccessTokenSuccess", func(t *testing.T) {
  1193  		e := testutils.CreateTestClient(t, ts.URL)
  1194  
  1195  		obj := e.POST("/auth/access_token").
  1196  			WithFormField("grant_type", "authorization_code").
  1197  			WithFormField("client_id", clientID).
  1198  			WithFormField("client_secret", clientSecret).
  1199  			WithFormField("code", code).
  1200  			WithHost(domain).
  1201  			Expect().Status(200).
  1202  			JSON().Object()
  1203  
  1204  		obj.ValueEqual("token_type", "bearer")
  1205  		obj.ValueEqual("scope", "files:read")
  1206  
  1207  		assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read")
  1208  		assertValidToken(t, testInstance, obj.Value("refresh_token").String().Raw(), "refresh", clientID, "files:read")
  1209  
  1210  		refreshToken = obj.Value("refresh_token").String().NotEmpty().Raw()
  1211  	})
  1212  
  1213  	t.Run("RefreshTokenNoToken", func(t *testing.T) {
  1214  		e := testutils.CreateTestClient(t, ts.URL)
  1215  
  1216  		e.POST("/auth/access_token").
  1217  			WithFormField("grant_type", "refresh_token").
  1218  			WithFormField("client_id", clientID).
  1219  			WithFormField("client_secret", clientSecret).
  1220  			WithHost(domain).
  1221  			Expect().Status(400).
  1222  			JSON().Object().
  1223  			ValueEqual("error", "invalid refresh token")
  1224  	})
  1225  
  1226  	t.Run("RefreshTokenInvalidToken", func(t *testing.T) {
  1227  		e := testutils.CreateTestClient(t, ts.URL)
  1228  
  1229  		e.POST("/auth/access_token").
  1230  			WithFormField("grant_type", "refresh_token").
  1231  			WithFormField("client_id", clientID).
  1232  			WithFormField("client_secret", clientSecret).
  1233  			WithFormField("refresh_token", "foo").
  1234  			WithHost(domain).
  1235  			Expect().Status(400).
  1236  			JSON().Object().
  1237  			ValueEqual("error", "invalid refresh token")
  1238  	})
  1239  
  1240  	t.Run("RefreshTokenInvalidSigningMethod", func(t *testing.T) {
  1241  		e := testutils.CreateTestClient(t, ts.URL)
  1242  
  1243  		claims := permission.Claims{
  1244  			RegisteredClaims: jwt.RegisteredClaims{
  1245  				Audience: jwt.ClaimStrings{consts.RefreshTokenAudience},
  1246  				Issuer:   domain,
  1247  				IssuedAt: jwt.NewNumericDate(time.Now()),
  1248  				Subject:  clientID,
  1249  			},
  1250  			Scope: "files:write",
  1251  		}
  1252  		token := jwt.NewWithClaims(jwt.GetSigningMethod("none"), claims)
  1253  		fakeToken, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
  1254  		require.NoError(t, err)
  1255  
  1256  		e.POST("/auth/access_token").
  1257  			WithFormField("grant_type", "refresh_token").
  1258  			WithFormField("client_id", clientID).
  1259  			WithFormField("client_secret", clientSecret).
  1260  			WithFormField("refresh_token", fakeToken).
  1261  			WithHost(domain).
  1262  			Expect().Status(400).
  1263  			JSON().Object().
  1264  			ValueEqual("error", "invalid refresh token")
  1265  	})
  1266  
  1267  	t.Run("RefreshTokenSuccess", func(t *testing.T) {
  1268  		e := testutils.CreateTestClient(t, ts.URL)
  1269  
  1270  		obj := e.POST("/auth/access_token").
  1271  			WithFormField("grant_type", "refresh_token").
  1272  			WithFormField("client_id", clientID).
  1273  			WithFormField("client_secret", clientSecret).
  1274  			WithFormField("refresh_token", refreshToken).
  1275  			WithHost(domain).
  1276  			Expect().Status(200).
  1277  			JSON().Object()
  1278  
  1279  		obj.ValueEqual("token_type", "bearer")
  1280  		obj.ValueEqual("scope", "files:read")
  1281  		obj.NotContainsKey("refresh_token")
  1282  
  1283  		assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read")
  1284  	})
  1285  
  1286  	t.Run("OAuthWithPKCE", func(t *testing.T) {
  1287  		e := testutils.CreateTestClient(t, ts.URL)
  1288  
  1289  		/* Values taken from https://datatracker.ietf.org/doc/html/rfc7636#appendix-B */
  1290  		challenge := "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
  1291  		verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  1292  
  1293  		/* 1. GET /auth/authorize */
  1294  		resBody := e.GET("/auth/authorize").
  1295  			WithQuery("response_type", "code").
  1296  			WithQuery("state", "123456").
  1297  			WithQuery("redirect_uri", "https://example.org/oauth/callback").
  1298  			WithQuery("scope", "files:read").
  1299  			WithQuery("client_id", clientID).
  1300  			WithQuery("code_challenge", challenge).
  1301  			WithQuery("code_challenge_method", "S256").
  1302  			WithCookie(session.CookieName(testInstance), sessionID).
  1303  			WithHost(domain).
  1304  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1305  			Expect().Status(200).
  1306  			ContentType("text/html", "utf-8").
  1307  			Body()
  1308  
  1309  		matches := resBody.Match(`<input type="hidden" name="csrf_token" value="(\w+)"`)
  1310  		matches.Length().Equal(2)
  1311  		csrfToken = matches.Index(1).Raw()
  1312  
  1313  		/* 2. POST /auth/authorize */
  1314  		e.POST("/auth/authorize").
  1315  			WithFormField("state", "123456").
  1316  			WithFormField("client_id", clientID).
  1317  			WithFormField("scope", "files:read").
  1318  			WithFormField("redirect_uri", "https://example.org/oauth/callback").
  1319  			WithFormField("csrf_token", csrfToken).
  1320  			WithFormField("response_type", "code").
  1321  			WithFormField("code_challenge", challenge).
  1322  			WithFormField("code_challenge_method", "S256").
  1323  			WithHost(domain).
  1324  			WithCookie("_csrf", csrfToken).
  1325  			WithCookie(session.CookieName(testInstance), sessionID).
  1326  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1327  			Expect().Status(302)
  1328  
  1329  		var results []oauth.AccessCode
  1330  		allReq := &couchdb.AllDocsRequest{}
  1331  		err := couchdb.GetAllDocs(testInstance, consts.OAuthAccessCodes, allReq, &results)
  1332  		assert.NoError(t, err)
  1333  		var code string
  1334  		for _, result := range results {
  1335  			if result.Challenge != "" {
  1336  				code = result.Code
  1337  			}
  1338  		}
  1339  		require.NotEmpty(t, code)
  1340  
  1341  		/* 3. POST /auth/access_token without code_verifier must fail */
  1342  		e.POST("/auth/access_token").
  1343  			WithFormField("grant_type", "authorization_code").
  1344  			WithFormField("client_id", clientID).
  1345  			WithFormField("client_secret", clientSecret).
  1346  			WithFormField("code", code).
  1347  			WithHost(domain).
  1348  			Expect().Status(400).
  1349  			JSON().Object().
  1350  			ValueEqual("error", "invalid code_verifier")
  1351  
  1352  		/* 4. POST /auth/access_token with code_verifier should succeed */
  1353  		obj := e.POST("/auth/access_token").
  1354  			WithFormField("grant_type", "authorization_code").
  1355  			WithFormField("client_id", clientID).
  1356  			WithFormField("client_secret", clientSecret).
  1357  			WithFormField("code", code).
  1358  			WithFormField("code_verifier", verifier).
  1359  			WithHost(domain).
  1360  			Expect().Status(200).
  1361  			JSON().Object()
  1362  
  1363  		obj.ValueEqual("token_type", "bearer")
  1364  		obj.ValueEqual("scope", "files:read")
  1365  
  1366  		assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read")
  1367  		assertValidToken(t, testInstance, obj.Value("refresh_token").String().Raw(), "refresh", clientID, "files:read")
  1368  	})
  1369  
  1370  	t.Run("ConfirmFlagship", func(t *testing.T) {
  1371  		e := testutils.CreateTestClient(t, ts.URL)
  1372  
  1373  		token, code, err := oauth.GenerateConfirmCode(testInstance, clientID)
  1374  		require.NoError(t, err)
  1375  
  1376  		e.POST("/auth/clients/"+clientID+"/flagship").
  1377  			WithFormField("code", code).
  1378  			WithFormField("token", string(token)).
  1379  			WithHost(domain).
  1380  			Expect().Status(204)
  1381  
  1382  		client, err := oauth.FindClient(testInstance, clientID)
  1383  		require.NoError(t, err)
  1384  		assert.True(t, client.Flagship)
  1385  	})
  1386  
  1387  	t.Run("LoginFlagship", func(t *testing.T) {
  1388  		oauthClient := &oauth.Client{
  1389  			RedirectURIs:    []string{"cozy://flagship"},
  1390  			ClientName:      "Cozy Flagship",
  1391  			ClientKind:      "mobile",
  1392  			SoftwareID:      "cozy-flagship",
  1393  			SoftwareVersion: "0.1.0",
  1394  		}
  1395  
  1396  		require.Nil(t, oauthClient.Create(testInstance))
  1397  		client, err := oauth.FindClient(testInstance, oauthClient.ClientID)
  1398  		require.NoError(t, err)
  1399  		client.CertifiedFromStore = true
  1400  		require.NoError(t, client.SetFlagship(testInstance))
  1401  
  1402  		t.Run("WithAnInvalidPassPhrase", func(t *testing.T) {
  1403  			e := testutils.CreateTestClient(t, ts.URL)
  1404  
  1405  			e.POST("/auth/login/flagship").
  1406  				WithHeader("Accept", "application/json").
  1407  				WithJSON(map[string]string{
  1408  					"passphrase":    "InvalidPassphrase",
  1409  					"client_id":     client.CouchID,
  1410  					"client_secret": client.ClientSecret,
  1411  				}).
  1412  				WithHost(domain).
  1413  				Expect().Status(401)
  1414  		})
  1415  
  1416  		t.Run("WithAnInvalidClientSecret", func(t *testing.T) {
  1417  			e := testutils.CreateTestClient(t, ts.URL)
  1418  
  1419  			e.POST("/auth/login/flagship").
  1420  				WithHeader("Accept", "application/json").
  1421  				WithJSON(map[string]string{
  1422  					"passphrase":    "MyPassphrase",
  1423  					"client_id":     client.CouchID,
  1424  					"client_secret": "InvalidClientSecret",
  1425  				}).
  1426  				WithHost(domain).
  1427  				Expect().Status(400)
  1428  		})
  1429  
  1430  		t.Run("Success", func(t *testing.T) {
  1431  			e := testutils.CreateTestClient(t, ts.URL)
  1432  
  1433  			obj := e.POST("/auth/login/flagship").
  1434  				WithHeader("Accept", "application/json").
  1435  				WithJSON(map[string]string{
  1436  					"passphrase":    "MyPassphrase",
  1437  					"client_id":     client.CouchID,
  1438  					"client_secret": client.ClientSecret,
  1439  				}).
  1440  				WithHost(domain).
  1441  				Expect().Status(200).
  1442  				JSON().Object()
  1443  
  1444  			obj.Value("access_token").String().NotEmpty()
  1445  			obj.Value("refresh_token").String().NotEmpty()
  1446  			obj.ValueEqual("scope", "*")
  1447  			obj.ValueEqual("token_type", "bearer")
  1448  		})
  1449  	})
  1450  
  1451  	t.Run("LogoutNoToken", func(t *testing.T) {
  1452  		e := testutils.CreateTestClient(t, ts.URL)
  1453  
  1454  		e.DELETE("/auth/login").
  1455  			WithHost(domain).
  1456  			Expect().Status(401)
  1457  	})
  1458  
  1459  	t.Run("LogoutSuccess", func(t *testing.T) {
  1460  		e := testutils.CreateTestClient(t, ts.URL)
  1461  
  1462  		token := testInstance.BuildAppToken("home", "")
  1463  
  1464  		_, err := permission.CreateWebappSet(testInstance, "home", permission.Set{}, "1.0.0")
  1465  		assert.NoError(t, err)
  1466  
  1467  		e.DELETE("/auth/login").
  1468  			WithHeader("Authorization", "Bearer "+token).
  1469  			WithHost(domain).
  1470  			Expect().Status(204)
  1471  
  1472  		err = permission.DestroyWebapp(testInstance, "home")
  1473  		require.NoError(t, err)
  1474  	})
  1475  
  1476  	t.Run("LogoutOthers", func(t *testing.T) {
  1477  		// First two connexion
  1478  		e1 := testutils.CreateTestClient(t, ts.URL)
  1479  		e2 := testutils.CreateTestClient(t, ts.URL)
  1480  
  1481  		// Third connexion closing e2 using the cookies from e1
  1482  		e3 := testutils.CreateTestClient(t, ts.URL)
  1483  
  1484  		// Authenticate user 1
  1485  		csrfToken := getLoginCSRFToken(e1)
  1486  		res := e1.POST("/auth/login").
  1487  			WithFormField("passphrase", "MyPassphrase").
  1488  			WithFormField("csrf_token", csrfToken).
  1489  			WithCookie("_csrf", csrfToken).
  1490  			WithHost(domain).
  1491  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1492  			Expect().Status(303)
  1493  
  1494  		res.Cookie("_csrf").Value().NotEmpty()
  1495  
  1496  		// Retrieve the session id from cozysessid
  1497  		rawSessID1 := res.Cookie("cozysessid").Value().NotEmpty().Raw()
  1498  		b, err := base64.RawURLEncoding.DecodeString(rawSessID1)
  1499  		require.NoError(t, err)
  1500  		sessionID1 := string(b[8 : 8+32])
  1501  
  1502  		// Authenticate user 2
  1503  		csrfToken = getLoginCSRFToken(e2)
  1504  		res = e2.POST("/auth/login").
  1505  			WithFormField("passphrase", "MyPassphrase").
  1506  			WithFormField("csrf_token", csrfToken).
  1507  			WithCookie("_csrf", csrfToken).
  1508  			WithHost(domain).
  1509  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1510  			Expect().Status(303)
  1511  
  1512  		res.Cookie("_csrf").Value().NotEmpty()
  1513  		res.Cookie("cozysessid").Value().NotEmpty()
  1514  
  1515  		token := testInstance.BuildAppToken("home", sessionID1)
  1516  		_, err = permission.CreateWebappSet(testInstance, "home", permission.Set{}, "1.0.0")
  1517  		assert.NoError(t, err)
  1518  
  1519  		// Delete all the other sessions
  1520  		e3.DELETE("/auth/login/others").
  1521  			WithHost(domain).
  1522  			WithHeader("Authorization", "Bearer "+token).
  1523  			WithCookie("cozysessid", rawSessID1).
  1524  			Expect().Status(204)
  1525  
  1526  		// Delete all the other sessions again give the same output
  1527  		e3.DELETE("/auth/login/others").
  1528  			WithHost(domain).
  1529  			WithHeader("Authorization", "Bearer "+token).
  1530  			WithCookie("cozysessid", rawSessID1).
  1531  			Expect().Status(204)
  1532  
  1533  		err = permission.DestroyWebapp(testInstance, "home")
  1534  		assert.NoError(t, err)
  1535  	})
  1536  
  1537  	t.Run("PassphraseResetLoggedIn", func(t *testing.T) {
  1538  		e := testutils.CreateTestClient(t, ts.URL)
  1539  
  1540  		body := e.GET("/auth/passphrase_reset").
  1541  			WithHost(domain).
  1542  			Expect().Status(200).
  1543  			ContentType("text/html", "utf-8").
  1544  			Body()
  1545  
  1546  		body.Contains("my password")
  1547  		body.Contains(`<input type="hidden" name="csrf_token"`)
  1548  	})
  1549  
  1550  	t.Run("PassphraseReset", func(t *testing.T) {
  1551  		e := testutils.CreateTestClient(t, ts.URL)
  1552  
  1553  		csrfToken := e.GET("/auth/passphrase_reset").
  1554  			WithHost(domain).
  1555  			Expect().Status(200).
  1556  			Cookie("_csrf").Value().Raw()
  1557  
  1558  		e.POST("/auth/passphrase_reset").
  1559  			WithFormField("csrf_token", csrfToken).
  1560  			WithCookie("_csrf", csrfToken).
  1561  			WithHost(domain).
  1562  			Expect().Status(200).
  1563  			ContentType("text/html", "utf-8")
  1564  	})
  1565  
  1566  	t.Run("PassphraseRenewFormNoToken", func(t *testing.T) {
  1567  		e := testutils.CreateTestClient(t, ts.URL)
  1568  
  1569  		e.GET("/auth/passphrase_renew").
  1570  			WithHost(domain).
  1571  			Expect().Status(400).
  1572  			ContentType("text/html", "utf-8").
  1573  			Body().Contains(`The link to reset the password is truncated or has expired`)
  1574  	})
  1575  
  1576  	t.Run("PassphraseRenewFormBadToken", func(t *testing.T) {
  1577  		e := testutils.CreateTestClient(t, ts.URL)
  1578  
  1579  		e.GET("/auth/passphrase_renew").
  1580  			WithQuery("token", "invalid"). // invalid
  1581  			WithHost(domain).
  1582  			Expect().Status(400).
  1583  			ContentType("text/html", "utf-8").
  1584  			Body().Contains(`The link to reset the password is truncated or has expired`)
  1585  	})
  1586  
  1587  	t.Run("PassphraseRenewFormWithToken", func(t *testing.T) {
  1588  		e := testutils.CreateTestClient(t, ts.URL)
  1589  
  1590  		e.GET("/auth/passphrase_renew").
  1591  			WithQuery("token", "badbee"). // good format but invalid
  1592  			WithHost(domain).
  1593  			Expect().Status(400).
  1594  			JSON().Object().
  1595  			ValueEqual("error", "invalid_token")
  1596  	})
  1597  
  1598  	t.Run("PassphraseRenew", func(t *testing.T) {
  1599  		e := testutils.CreateTestClient(t, ts.URL)
  1600  
  1601  		d := "test.cozycloud.cc.web_reset_form"
  1602  		_ = lifecycle.Destroy(d)
  1603  		in1, err := lifecycle.Create(&lifecycle.Options{
  1604  			Domain: d,
  1605  			Locale: "en",
  1606  			Email:  "alice@example.com",
  1607  		})
  1608  		require.NoError(t, err)
  1609  		t.Cleanup(func() { _ = lifecycle.Destroy(d) })
  1610  
  1611  		err = lifecycle.RegisterPassphrase(in1, in1.RegisterToken, lifecycle.PassParameters{
  1612  			Pass:       []byte("MyPass"),
  1613  			Iterations: 5000,
  1614  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
  1615  		})
  1616  		require.NoError(t, err)
  1617  
  1618  		csrfToken := e.GET("/auth/passphrase_reset").
  1619  			WithHost(d).
  1620  			Expect().Status(200).
  1621  			Cookie("_csrf").Value().Raw()
  1622  
  1623  		e.POST("/auth/passphrase_reset").
  1624  			WithFormField("csrf_token", csrfToken).
  1625  			WithCookie("_csrf", csrfToken).
  1626  			WithHost(d).
  1627  			Expect().Status(200)
  1628  
  1629  		in2, err := instance.Get(d)
  1630  		require.NoError(t, err)
  1631  
  1632  		e.POST("/auth/passphrase_renew").
  1633  			WithFormField("passphrase_reset_token", hex.EncodeToString(in2.PassphraseResetToken)).
  1634  			WithFormField("passphrase", "NewPassphrase").
  1635  			WithFormField("csrf_token", csrfToken).
  1636  			WithCookie("_csrf", csrfToken).
  1637  			WithHost(d).
  1638  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1639  			Expect().Status(303).
  1640  			Header("Location").
  1641  			Equal("https://test.cozycloud.cc.web_reset_form/auth/login")
  1642  	})
  1643  
  1644  	t.Run("IsLoggedOutAfterLogout", func(t *testing.T) {
  1645  		e := testutils.CreateTestClient(t, ts.URL)
  1646  
  1647  		e.GET("/test").
  1648  			WithHost(domain).
  1649  			Expect().Status(200).
  1650  			Text(httpexpect.ContentOpts{MediaType: "text/plain"}).
  1651  			Equal("who_are_you")
  1652  	})
  1653  
  1654  	t.Run("PassphraseOnboarding", func(t *testing.T) {
  1655  		// e := testutils.CreateTestClient(t, ts.URL)
  1656  		e := httpexpect.WithConfig(httpexpect.Config{
  1657  			TestName: t.Name(),
  1658  			BaseURL:  ts.URL,
  1659  			Reporter: httpexpect.NewAssertReporter(t),
  1660  			Printers: []httpexpect.Printer{
  1661  				httpexpect.NewDebugPrinter(t, true),
  1662  			},
  1663  		})
  1664  
  1665  		// Here we create a new instance without passphrase
  1666  		d := "test.cozycloud.cc.web_passphrase"
  1667  		_ = lifecycle.Destroy(d)
  1668  		inst, err := lifecycle.Create(&lifecycle.Options{
  1669  			Domain: d,
  1670  			Locale: "en",
  1671  			Email:  "alice@example.com",
  1672  		})
  1673  		require.NoError(t, err)
  1674  		require.False(t, inst.OnboardingFinished)
  1675  
  1676  		// Should redirect to /auth/passphrase
  1677  		e.GET("/").
  1678  			WithQuery("registerToken", hex.EncodeToString(inst.RegisterToken)).
  1679  			WithHost(inst.Domain).
  1680  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1681  			Expect().Status(303).
  1682  			Header("Location").Contains("/auth/passphrase?registerToken=")
  1683  
  1684  		// Adding a passphrase and check if we are redirected to home
  1685  		pass := []byte("passphrase")
  1686  		err = lifecycle.RegisterPassphrase(inst, inst.RegisterToken, lifecycle.PassParameters{
  1687  			Pass:       pass,
  1688  			Iterations: 5000,
  1689  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
  1690  		})
  1691  		assert.NoError(t, err)
  1692  
  1693  		inst.OnboardingFinished = true
  1694  
  1695  		e.GET("/").
  1696  			WithQuery("registerToken", hex.EncodeToString(inst.RegisterToken)).
  1697  			WithHost(domain).
  1698  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1699  			Expect().Status(303).
  1700  			Header("Location").Contains("/auth/login")
  1701  	})
  1702  
  1703  	t.Run("PassphraseOnboardingFinished", func(t *testing.T) {
  1704  		e := testutils.CreateTestClient(t, ts.URL)
  1705  
  1706  		// Using the testInstance which had already been onboarded
  1707  		// Should redirect to home
  1708  		e.GET("/auth/passphrase").
  1709  			WithHost(domain).
  1710  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1711  			Expect().Status(303).
  1712  			Header("Location").Equal("https://home.cozy.example.net/")
  1713  	})
  1714  
  1715  	t.Run("PassphraseOnboardingBadRegisterToken", func(t *testing.T) {
  1716  		e := testutils.CreateTestClient(t, ts.URL)
  1717  
  1718  		// Should render need_onboarding
  1719  		d := "test.cozycloud.cc.web_passphrase_bad_token"
  1720  		_ = lifecycle.Destroy(d)
  1721  		inst, err := lifecycle.Create(&lifecycle.Options{
  1722  			Domain: d,
  1723  			Locale: "en",
  1724  			Email:  "alice@example.com",
  1725  		})
  1726  		assert.NoError(t, err)
  1727  		assert.False(t, inst.OnboardingFinished)
  1728  
  1729  		// Should redirect to /auth/passphrase
  1730  		e.GET("/auth/passphrase").
  1731  			WithQuery("registerToken", "coincoin").
  1732  			WithHost(inst.Domain).
  1733  			WithRedirectPolicy(httpexpect.DontFollowRedirects).
  1734  			Expect().Status(200).
  1735  			ContentType("text/html", "utf-8").
  1736  			Body().Contains("Your Cozy has not been yet activated.")
  1737  	})
  1738  
  1739  	t.Run("LoginOnboardingNotFinished", func(t *testing.T) {
  1740  		e := testutils.CreateTestClient(t, ts.URL)
  1741  
  1742  		// Should render need_onboarding
  1743  		d := "test.cozycloud.cc.web_login_onboarding_not_finished"
  1744  		_ = lifecycle.Destroy(d)
  1745  		inst, err := lifecycle.Create(&lifecycle.Options{
  1746  			Domain: d,
  1747  			Locale: "en",
  1748  			Email:  "alice@example.com",
  1749  		})
  1750  		assert.NoError(t, err)
  1751  		assert.False(t, inst.OnboardingFinished)
  1752  
  1753  		// Should redirect to /auth/passphrase
  1754  		e.GET("/auth/login").
  1755  			WithHost(inst.Domain).
  1756  			Expect().Status(200).
  1757  			ContentType("text/html", "utf-8").
  1758  			Body().Contains("Your Cozy has not been yet activated.")
  1759  	})
  1760  
  1761  	t.Run("ShowConfirmForm", func(t *testing.T) {
  1762  		e := testutils.CreateTestClient(t, ts.URL)
  1763  
  1764  		body := e.GET("/auth/confirm").
  1765  			WithQuery("state", "342dd650-599b-0139-cfb0-543d7eb8149c").
  1766  			WithHost(domain).
  1767  			Expect().Status(200).
  1768  			ContentType("text/html", "utf-8").
  1769  			Body()
  1770  
  1771  		body.Contains(`<input id="state" type="hidden" name="state" value="342dd650-599b-0139-cfb0-543d7eb8149c" />`)
  1772  		body.NotContains("myfragment")
  1773  	})
  1774  
  1775  	t.Run("SendConfirmBadCSRFToken", func(t *testing.T) {
  1776  		e := testutils.CreateTestClient(t, ts.URL)
  1777  
  1778  		e.POST("/auth/confirm").
  1779  			WithHeader("Accept", "application/json").
  1780  			WithFormField("passphrase", "MyPassphrase").
  1781  			WithFormField("csrf_token", "123456").
  1782  			WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c").
  1783  			WithCookie("_csrf", csrfToken).
  1784  			WithHost(domain).
  1785  			Expect().Status(403)
  1786  	})
  1787  
  1788  	t.Run("SendConfirmBadPass", func(t *testing.T) {
  1789  		e := testutils.CreateTestClient(t, ts.URL)
  1790  
  1791  		token := getConfirmCSRFToken(e)
  1792  
  1793  		e.POST("/auth/confirm").
  1794  			WithHeader("Accept", "application/json").
  1795  			WithFormField("passphrase", "InvalidPassphrase"). // invalid
  1796  			WithFormField("csrf_token", token).
  1797  			WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c").
  1798  			WithCookie("_csrf", token).
  1799  			WithHost(domain).
  1800  			Expect().Status(401)
  1801  	})
  1802  
  1803  	t.Run("SendConfirmOK", func(t *testing.T) {
  1804  		var confirmCode string
  1805  
  1806  		t.Run("GetConfirmCode", func(t *testing.T) {
  1807  			e := testutils.CreateTestClient(t, ts.URL)
  1808  
  1809  			token := getConfirmCSRFToken(e)
  1810  
  1811  			obj := e.POST("/auth/confirm").
  1812  				WithHeader("Accept", "application/json").
  1813  				WithFormField("passphrase", "MyPassphrase").
  1814  				WithFormField("csrf_token", token).
  1815  				WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c").
  1816  				WithCookie("_csrf", token).
  1817  				WithHost(domain).
  1818  				Expect().Status(200).
  1819  				JSON().Object()
  1820  
  1821  			redirect := obj.Value("redirect").String().Raw()
  1822  
  1823  			u, err := url.Parse(redirect)
  1824  			assert.NoError(t, err)
  1825  
  1826  			confirmCode = u.Query().Get("code")
  1827  			assert.NotEmpty(t, confirmCode)
  1828  			state := u.Query().Get("state")
  1829  			assert.Equal(t, "342dd650-599b-0139-cfb0-543d7eb8149c", state)
  1830  		})
  1831  
  1832  		t.Run("ConfirmBadCode", func(t *testing.T) {
  1833  			e := testutils.CreateTestClient(t, ts.URL)
  1834  
  1835  			e.GET("/auth/confirm/123456").
  1836  				WithHost(domain).
  1837  				Expect().Status(401)
  1838  		})
  1839  
  1840  		t.Run("ConfirmCodeOK", func(t *testing.T) {
  1841  			e := testutils.CreateTestClient(t, ts.URL)
  1842  
  1843  			e.GET("/auth/confirm/" + confirmCode).
  1844  				WithHost(domain).
  1845  				Expect().Status(204)
  1846  		})
  1847  	})
  1848  
  1849  	t.Run("KonnectorTokens", func(t *testing.T) {
  1850  		konnSlug, err := setup.InstallMiniKonnector()
  1851  		require.NoError(t, err, "Could not install mini konnector.")
  1852  
  1853  		t.Run("BuildKonnectorToken", func(t *testing.T) {
  1854  			e := testutils.CreateTestClient(t, ts.URL)
  1855  
  1856  			// Create an flagship OAuth client
  1857  			oauthClient := oauth.Client{
  1858  				RedirectURIs: []string{"cozy://client"},
  1859  				ClientName:   "oauth-client",
  1860  				SoftwareID:   "github.com/cozy/cozy-stack/testing/client",
  1861  				Flagship:     true,
  1862  			}
  1863  			require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending))
  1864  
  1865  			// Give it the maximal permission
  1866  			token, err := testInstance.MakeJWT(consts.AccessTokenAudience,
  1867  				oauthClient.ClientID, "*", "", time.Now())
  1868  			require.NoError(t, err)
  1869  
  1870  			// Get konnector access_token
  1871  			konnToken := e.POST("/auth/tokens/konnectors/"+konnSlug).
  1872  				WithHeader("Accept", "application/json").
  1873  				WithHeader("Authorization", "Bearer "+token).
  1874  				WithHost(domain).
  1875  				Expect().Status(201).
  1876  				JSON().String().Raw()
  1877  
  1878  			// Validate token
  1879  			claims := permission.Claims{}
  1880  			err = crypto.ParseJWT(konnToken, func(token *jwt.Token) (interface{}, error) {
  1881  				return testInstance.SessionSecret(), nil
  1882  			}, &claims)
  1883  			assert.NoError(t, err)
  1884  			assert.Equal(t, consts.KonnectorAudience, claims.Audience[0])
  1885  			assert.Equal(t, domain, claims.Issuer)
  1886  			assert.Equal(t, konnSlug, claims.Subject)
  1887  			assert.Equal(t, "", claims.Scope)
  1888  		})
  1889  
  1890  		t.Run("BuildKonnectorTokenNotFlagshipApp", func(t *testing.T) {
  1891  			e := testutils.CreateTestClient(t, ts.URL)
  1892  
  1893  			// Create an OAuth client
  1894  			oauthClient := oauth.Client{
  1895  				RedirectURIs: []string{"cozy://client"},
  1896  				ClientName:   "oauth-client",
  1897  				SoftwareID:   "github.com/cozy/cozy-stack/testing/client",
  1898  				Flagship:     false,
  1899  			}
  1900  			require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending))
  1901  
  1902  			// Give it the maximal permission
  1903  			token, err := testInstance.MakeJWT(consts.AccessTokenAudience,
  1904  				oauthClient.ClientID, "*", "", time.Now())
  1905  			require.NoError(t, err)
  1906  
  1907  			e.POST("/auth/tokens/konnectors/"+konnSlug).
  1908  				WithHeader("Accept", "application/json").
  1909  				WithHeader("Authorization", "Bearer "+token).
  1910  				WithHost(domain).
  1911  				Expect().Status(403)
  1912  		})
  1913  
  1914  		t.Run("BuildKonnectorTokenInvalidSlug", func(t *testing.T) {
  1915  			e := testutils.CreateTestClient(t, ts.URL)
  1916  
  1917  			// Create an flagship OAuth client
  1918  			oauthClient := oauth.Client{
  1919  				RedirectURIs: []string{"cozy://client"},
  1920  				ClientName:   "oauth-client",
  1921  				SoftwareID:   "github.com/cozy/cozy-stack/testing/client",
  1922  				Flagship:     true,
  1923  			}
  1924  			require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending))
  1925  
  1926  			// Give it the maximal permission
  1927  			token, err := testInstance.MakeJWT(consts.AccessTokenAudience,
  1928  				oauthClient.ClientID, "*", "", time.Now())
  1929  			require.NoError(t, err)
  1930  
  1931  			e.POST("/auth/tokens/konnectors/missin").
  1932  				WithHeader("Accept", "application/json").
  1933  				WithHeader("Authorization", "Bearer "+token).
  1934  				WithHost(domain).
  1935  				Expect().Status(404)
  1936  		})
  1937  	})
  1938  
  1939  	t.Run("Share by link protected by password", func(t *testing.T) {
  1940  		e := testutils.CreateTestClient(t, ts.URL)
  1941  
  1942  		set := permission.Set{
  1943  			permission.Rule{Type: consts.Files, Title: "files"},
  1944  		}
  1945  		parent := &permission.Permission{
  1946  			Type:        permission.TypeWebapp,
  1947  			Permissions: set,
  1948  		}
  1949  		sourceID := "io.cozy.apps/drive"
  1950  		codes := map[string]string{"email": "123456789123456789"}
  1951  		shortcodes := map[string]string{"email": "123456"}
  1952  		md, err := metadata.NewWithApp("drive", "", permission.DocTypeVersion)
  1953  		require.NoError(t, err)
  1954  		subdoc := permission.Permission{
  1955  			Permissions: set,
  1956  			Password:    "the_password!",
  1957  			Metadata:    md,
  1958  		}
  1959  		perm, err := permission.CreateShareSet(testInstance, parent, sourceID, codes, shortcodes, subdoc, nil)
  1960  		require.NoError(t, err)
  1961  
  1962  		e.POST("/auth/share-by-link/password").
  1963  			WithHeader("Accept", "application/json").
  1964  			WithFormField("perm_id", perm.ID()).
  1965  			WithFormField("password", "bad_password").
  1966  			WithHost(domain).
  1967  			Expect().Status(403)
  1968  
  1969  		res := e.POST("/auth/share-by-link/password").
  1970  			WithHeader("Accept", "application/json").
  1971  			WithFormField("perm_id", perm.ID()).
  1972  			WithFormField("password", "the_password!").
  1973  			WithHost(domain).
  1974  			Expect().Status(200)
  1975  
  1976  		res.Cookies().Length().Equal(1)
  1977  		res.Cookie("pass" + perm.ID()).Value().NotEmpty()
  1978  	})
  1979  
  1980  	t.Run("MagicLink", func(t *testing.T) {
  1981  		e := testutils.CreateTestClient(t, ts.URL)
  1982  
  1983  		d := "test.cozycloud.cc.magic_link"
  1984  		_ = lifecycle.Destroy(d)
  1985  		magicLink := true
  1986  		inst, err := lifecycle.Create(&lifecycle.Options{
  1987  			Domain:    d,
  1988  			Locale:    "en",
  1989  			Email:     "alice@example.com",
  1990  			MagicLink: &magicLink,
  1991  		})
  1992  		require.NoError(t, err)
  1993  		t.Cleanup(func() { _ = lifecycle.Destroy(d) })
  1994  
  1995  		t.Run("Failure", func(t *testing.T) {
  1996  			code := "badcode"
  1997  
  1998  			e.GET("/auth/magic_link").
  1999  				WithHost(d).
  2000  				WithRedirectPolicy(httpexpect.DontFollowRedirects).
  2001  				WithQuery("code", code).
  2002  				Expect().Status(400)
  2003  		})
  2004  
  2005  		t.Run("Success", func(t *testing.T) {
  2006  			code, err := lifecycle.CreateMagicLinkCode(inst)
  2007  			require.NoError(t, err)
  2008  
  2009  			e.GET("/auth/magic_link").
  2010  				WithHost(d).
  2011  				WithRedirectPolicy(httpexpect.DontFollowRedirects).
  2012  				WithQuery("code", code).
  2013  				Expect().Status(303).
  2014  				Header("Location").Equal("https://home." + d + "/")
  2015  		})
  2016  
  2017  		t.Run("Flagship", func(t *testing.T) {
  2018  			oauthClient := &oauth.Client{
  2019  				RedirectURIs:    []string{"cozy://flagship"},
  2020  				ClientName:      "Cozy Flagship",
  2021  				ClientKind:      "mobile",
  2022  				SoftwareID:      "cozy-flagship",
  2023  				SoftwareVersion: "0.1.0",
  2024  			}
  2025  
  2026  			require.Nil(t, oauthClient.Create(inst))
  2027  			client, err := oauth.FindClient(inst, oauthClient.ClientID)
  2028  			require.NoError(t, err)
  2029  			client.CertifiedFromStore = true
  2030  			require.NoError(t, client.SetFlagship(inst))
  2031  
  2032  			code, err := lifecycle.CreateMagicLinkCode(inst)
  2033  			require.NoError(t, err)
  2034  
  2035  			obj := e.POST("/auth/magic_link/flagship").
  2036  				WithHost(d).
  2037  				WithHeader("Accept", "application/json").
  2038  				WithJSON(map[string]string{
  2039  					"magic_code":    code,
  2040  					"client_id":     client.CouchID,
  2041  					"client_secret": client.ClientSecret,
  2042  				}).
  2043  				Expect().Status(200).
  2044  				JSON().Object()
  2045  
  2046  			obj.Value("access_token").String().NotEmpty()
  2047  			obj.Value("refresh_token").String().NotEmpty()
  2048  			obj.ValueEqual("scope", "*")
  2049  			obj.ValueEqual("token_type", "bearer")
  2050  		})
  2051  	})
  2052  }
  2053  
  2054  func getLoginCSRFToken(e *httpexpect.Expect) string {
  2055  	return e.GET("/auth/login").
  2056  		WithHost(domain).
  2057  		Expect().Status(200).
  2058  		Cookie("_csrf").Value().Raw()
  2059  }
  2060  
  2061  func getConfirmCSRFToken(e *httpexpect.Expect) string {
  2062  	return e.GET("/auth/confirm").
  2063  		WithQuery("state", "123").
  2064  		WithHost(domain).
  2065  		Expect().Status(200).
  2066  		Cookie("_csrf").Value().Raw()
  2067  }
  2068  
  2069  func fakeAPI(g *echo.Group) {
  2070  	g.Use(middlewares.NeedInstance, middlewares.LoadSession)
  2071  	g.GET("", func(c echo.Context) error {
  2072  		var content string
  2073  		if middlewares.IsLoggedIn(c) {
  2074  			content = "logged_in"
  2075  		} else {
  2076  			content = "who_are_you"
  2077  		}
  2078  		return c.String(http.StatusOK, content)
  2079  	})
  2080  }
  2081  
  2082  func assertValidToken(t *testing.T, testInstance *instance.Instance, token, audience, subject, scope string) {
  2083  	claims := permission.Claims{}
  2084  	err := crypto.ParseJWT(token, func(token *jwt.Token) (interface{}, error) {
  2085  		return testInstance.OAuthSecret, nil
  2086  	}, &claims)
  2087  	assert.NoError(t, err)
  2088  	assert.Equal(t, audience, claims.Audience[0])
  2089  	assert.Equal(t, domain, claims.Issuer)
  2090  	assert.Equal(t, subject, claims.Subject)
  2091  	assert.Equal(t, scope, claims.Scope)
  2092  }