From bdec01fa39dd89efb224bf2a255daef666dfb5c0 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 8 Aug 2023 15:43:32 -0500 Subject: [PATCH 01/15] feat: allow creating manual oidc/github based users --- cli/usercreate.go | 42 ++++++++++++++++++++++++++++----- coderd/apidoc/docs.go | 10 +++++++- coderd/apidoc/swagger.json | 10 +++++++- coderd/coderdtest/coderdtest.go | 16 ++++++------- coderd/userauth_test.go | 18 +++++++++++++- coderd/users.go | 40 ++++++++++++++++++++++--------- codersdk/users.go | 5 +++- docs/api/schemas.md | 16 +++++++------ docs/api/users.md | 1 + scripts/develop.sh | 2 +- site/src/api/typesGenerated.ts | 1 + 11 files changed, 124 insertions(+), 37 deletions(-) diff --git a/cli/usercreate.go b/cli/usercreate.go index b38bbb2d6401f..74f4d0ba472ba 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "strings" "github.com/go-playground/validator/v10" "golang.org/x/xerrors" @@ -18,6 +19,7 @@ func (r *RootCmd) userCreate() *clibase.Cmd { username string password string disableLogin bool + loginType string ) client := new(codersdk.Client) cmd := &clibase.Cmd{ @@ -54,7 +56,18 @@ func (r *RootCmd) userCreate() *clibase.Cmd { return err } } - if password == "" && !disableLogin { + userLoginType := codersdk.LoginTypePassword + if disableLogin { + userLoginType = codersdk.LoginTypeNone + } else if loginType != "" { + if disableLogin { + return xerrors.New("You cannot specify both --disable-login and --login-type") + } + userLoginType = codersdk.LoginType(loginType) + } + + if password == "" && userLoginType == codersdk.LoginTypePassword { + // Generate a random password password, err = cryptorand.StringCharset(cryptorand.Human, 20) if err != nil { return err @@ -66,14 +79,22 @@ func (r *RootCmd) userCreate() *clibase.Cmd { Username: username, Password: password, OrganizationID: organization.ID, - DisableLogin: disableLogin, + UserLoginType: userLoginType, }) if err != nil { return err } - authenticationMethod := `Your password is: ` + cliui.DefaultStyles.Field.Render(password) - if disableLogin { + + authenticationMethod := "" + switch codersdk.LoginType(strings.ToLower(string(userLoginType))) { + case codersdk.LoginTypePassword: + authenticationMethod = `Your password is: ` + cliui.DefaultStyles.Field.Render(password) + case codersdk.LoginTypeNone: authenticationMethod = "Login has been disabled for this user. Contact your administrator to authenticate." + case codersdk.LoginTypeGithub: + authenticationMethod = `Login is authenticated through GitHub.` + case codersdk.LoginTypeOIDC: + authenticationMethod = `Login is authenticated through the configured OIDC provider.` } _, _ = fmt.Fprintln(inv.Stderr, `A new user has been created! @@ -111,11 +132,20 @@ Create a workspace `+cliui.DefaultStyles.Code.Render("coder create")+`!`) Value: clibase.StringOf(&password), }, { - Flag: "disable-login", - Description: "Disabling login for a user prevents the user from authenticating via password or IdP login. Authentication requires an API key/token generated by an admin. " + + Flag: "disable-login", + Hidden: true, + Description: "Deprecated: Use '--login-type=none'. \nDisabling login for a user prevents the user from authenticating via password or IdP login. Authentication requires an API key/token generated by an admin. " + "Be careful when using this flag as it can lock the user out of their account.", Value: clibase.BoolOf(&disableLogin), }, + { + Flag: "login-type", + Description: fmt.Sprintf("Optionally specify the login type for the user. Valid values are: %s.", + strings.Join([]string{ + string(codersdk.LoginTypePassword), string(codersdk.LoginTypeNone), string(codersdk.LoginTypeGithub), string(codersdk.LoginTypeOIDC)}, ", ", + )), + Value: clibase.StringOf(&loginType), + }, } return cmd } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index f930bf74f48fc..5ed8d068d0e94 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7529,13 +7529,21 @@ const docTemplate = `{ ], "properties": { "disable_login": { - "description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.", + "description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.", "type": "boolean" }, "email": { "type": "string", "format": "email" }, + "login_type": { + "description": "UserLoginType defaults to LoginTypePassword.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.LoginType" + } + ] + }, "organization_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0d691b237b655..967778f1eb4ed 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6708,13 +6708,21 @@ "required": ["email", "username"], "properties": { "disable_login": { - "description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.", + "description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.", "type": "boolean" }, "email": { "type": "string", "format": "email" }, + "login_type": { + "description": "UserLoginType defaults to LoginTypePassword.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.LoginType" + } + ] + }, "organization_id": { "type": "string", "format": "uuid" diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 5e2e55d5c032f..04470509682e2 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -588,14 +588,7 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI require.NoError(t, err) var sessionToken string - if !req.DisableLogin { - login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{ - Email: req.Email, - Password: req.Password, - }) - require.NoError(t, err) - sessionToken = login.SessionToken - } else { + if req.DisableLogin || req.UserLoginType == codersdk.LoginTypeNone { // Cannot log in with a disabled login user. So make it an api key from // the client making this user. token, err := client.CreateToken(context.Background(), user.ID.String(), codersdk.CreateTokenRequest{ @@ -605,6 +598,13 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI }) require.NoError(t, err) sessionToken = token.Key + } else { + login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{ + Email: req.Email, + Password: req.Password, + }) + require.NoError(t, err) + sessionToken = login.SessionToken } if user.Status == codersdk.UserStatusDormant { diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index efa7673890863..8910bce286818 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -145,7 +145,7 @@ func TestUserLogin(t *testing.T) { require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode()) }) // Password auth should fail if the user is made without password login. - t.Run("LoginTypeNone", func(t *testing.T) { + t.Run("DisableLoginDeprecatedField", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) @@ -160,6 +160,22 @@ func TestUserLogin(t *testing.T) { }) require.Error(t, err) }) + + t.Run("LoginTypeNone", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequest) { + r.Password = "" + r.UserLoginType = codersdk.LoginTypeNone + }) + + _, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{ + Email: anotherUser.Email, + Password: "SomeSecurePassword!", + }) + require.Error(t, err) + }) } func TestUserAuthMethods(t *testing.T) { diff --git a/coderd/users.go b/coderd/users.go index 017e20d408586..32d6fe935d079 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -287,11 +287,27 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { return } + if req.UserLoginType == "" && req.DisableLogin { + // Handle the deprecated field + req.UserLoginType = codersdk.LoginTypeNone + } + if req.UserLoginType == "" { + // Default to password auth + req.UserLoginType = codersdk.LoginTypePassword + } + + if req.UserLoginType != codersdk.LoginTypePassword && req.Password != "" { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: fmt.Sprintf("Password cannot be set for non-password (%q) authentication.", req.UserLoginType), + }) + return + } + // If password auth is disabled, don't allow new users to be // created with a password! - if api.DeploymentValues.DisablePasswordAuth { + if api.DeploymentValues.DisablePasswordAuth && req.UserLoginType == codersdk.LoginTypePassword { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ - Message: "You cannot manually provision new users with password authentication disabled!", + Message: "Password based authentication is disabled! Unable to provision new users with password authentication.", }) return } @@ -353,17 +369,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { } } - if req.DisableLogin && req.Password != "" { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Cannot set password when disabling login.", - }) - return - } - var loginType database.LoginType - if req.DisableLogin { + switch req.UserLoginType { + case codersdk.LoginTypeNone: loginType = database.LoginTypeNone - } else { + case codersdk.LoginTypePassword: err = userpassword.Validate(req.Password) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -376,6 +386,14 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { return } loginType = database.LoginTypePassword + case codersdk.LoginTypeOIDC: + loginType = database.LoginTypeOIDC + case codersdk.LoginTypeGithub: + loginType = database.LoginTypeGithub + default: + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: fmt.Sprintf("Unsupported login type %q for manually creating new users.", req.UserLoginType), + }) } user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{ diff --git a/codersdk/users.go b/codersdk/users.go index daeefee5f12bf..c11846ebdac2b 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -78,9 +78,12 @@ type CreateFirstUserResponse struct { type CreateUserRequest struct { Email string `json:"email" validate:"required,email" format:"email"` Username string `json:"username" validate:"required,username"` - Password string `json:"password" validate:"required_if=DisableLogin false"` + Password string `json:"password"` + // UserLoginType defaults to LoginTypePassword. + UserLoginType LoginType `json:"login_type"` // DisableLogin sets the user's login type to 'none'. This prevents the user // from being able to use a password or any other authentication method to login. + // Deprecated: Set UserLoginType=LoginTypeDisabled instead. DisableLogin bool `json:"disable_login"` OrganizationID uuid.UUID `json:"organization_id" validate:"" format:"uuid"` } diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 7aed5d4f60022..649a6bf8853d5 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1655,6 +1655,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in { "disable_login": true, "email": "user@example.com", + "login_type": "password", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "password": "string", "username": "string" @@ -1663,13 +1664,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `disable_login` | boolean | false | | Disable login sets the user's login type to 'none'. This prevents the user from being able to use a password or any other authentication method to login. | -| `email` | string | true | | | -| `organization_id` | string | false | | | -| `password` | string | false | | | -| `username` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| ----------------- | ---------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `disable_login` | boolean | false | | Disable login sets the user's login type to 'none'. This prevents the user from being able to use a password or any other authentication method to login. Deprecated: Set UserLoginType=LoginTypeDisabled instead. | +| `email` | string | true | | | +| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | Login type defaults to LoginTypePassword. | +| `organization_id` | string | false | | | +| `password` | string | false | | | +| `username` | string | true | | | ## codersdk.CreateWorkspaceBuildRequest diff --git a/docs/api/users.md b/docs/api/users.md index 3c583e15787db..2f3509c0f9081 100644 --- a/docs/api/users.md +++ b/docs/api/users.md @@ -79,6 +79,7 @@ curl -X POST http://coder-server:8080/api/v2/users \ { "disable_login": true, "email": "user@example.com", + "login_type": "password", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "password": "string", "username": "string" diff --git a/scripts/develop.sh b/scripts/develop.sh index 671c46a0bd5cc..cc1ab23f0554e 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -131,7 +131,7 @@ fatal() { trap 'fatal "Script encountered an error"' ERR cdroot - start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "http://127.0.0.1:3000" --dangerous-allow-cors-requests=true --experiments "*,moons" "$@" + start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "http://127.0.0.1:3000" --dangerous-allow-cors-requests=true "$@" echo '== Waiting for Coder to become ready' # Start the timeout in the background so interrupting this script diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 3e5acc5671299..8a89f1024b820 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -247,6 +247,7 @@ export interface CreateUserRequest { readonly email: string readonly username: string readonly password: string + readonly login_type: LoginType readonly disable_login: boolean readonly organization_id: string } From 11b7a67cbc239226432dd73e0a578df8c3e8080e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 10:32:20 -0500 Subject: [PATCH 02/15] Add unit test for oidc and no login type create --- cli/usercreate.go | 6 ++-- coderd/users_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/cli/usercreate.go b/cli/usercreate.go index 74f4d0ba472ba..e71a30f5d85e5 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -57,12 +57,12 @@ func (r *RootCmd) userCreate() *clibase.Cmd { } } userLoginType := codersdk.LoginTypePassword + if disableLogin && loginType != "" { + return xerrors.New("You cannot specify both --disable-login and --login-type") + } if disableLogin { userLoginType = codersdk.LoginTypeNone } else if loginType != "" { - if disableLogin { - return xerrors.New("You cannot specify both --disable-login and --login-type") - } userLoginType = codersdk.LoginType(loginType) } diff --git a/coderd/users_test.go b/coderd/users_test.go index eff3174ad83a2..fdf30dcbd1334 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/golang-jwt/jwt" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -565,6 +566,71 @@ func TestPostUsers(t *testing.T) { } } }) + + t.Run("CreateNoneLoginType", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, client) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{ + OrganizationID: first.OrganizationID, + Email: "another@user.org", + Username: "someone-else", + Password: "", + UserLoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) + + found, err := client.User(ctx, user.ID.String()) + require.NoError(t, err) + require.Equal(t, found.LoginType, codersdk.LoginTypeNone) + }) + + t.Run("CreateOIDCLoginType", func(t *testing.T) { + t.Parallel() + email := "another@user.org" + conf := coderdtest.NewOIDCConfig(t, "") + config := conf.OIDCConfig(t, jwt.MapClaims{ + "email": email, + }) + config.AllowSignups = false + config.IgnoreUserInfo = true + + client := coderdtest.New(t, &coderdtest.Options{ + OIDCConfig: config, + }) + first := coderdtest.CreateFirstUser(t, client) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + _, err := client.CreateUser(ctx, codersdk.CreateUserRequest{ + OrganizationID: first.OrganizationID, + Email: email, + Username: "someone-else", + Password: "", + UserLoginType: codersdk.LoginTypeOIDC, + }) + require.NoError(t, err) + + // Try to log in with OIDC. + userClient := codersdk.New(client.URL) + resp := oidcCallback(t, userClient, conf.EncodeClaims(t, jwt.MapClaims{ + "email": email, + })) + require.Equal(t, resp.StatusCode, http.StatusTemporaryRedirect) + // Set the client to use this OIDC context + authCookie := authCookieValue(resp.Cookies()) + userClient.SetSessionToken(authCookie) + _ = resp.Body.Close() + + found, err := userClient.User(ctx, "me") + require.NoError(t, err) + require.Equal(t, found.LoginType, codersdk.LoginTypeOIDC) + }) } func TestUpdateUserProfile(t *testing.T) { From c2629989905b6da9adc4751f7ff20a1a0fb1f454 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 11:23:04 -0500 Subject: [PATCH 03/15] make gen --- coderd/apidoc/docs.go | 2 ++ coderd/apidoc/swagger.json | 3 ++- codersdk/apikey.go | 1 + docs/api/audit.md | 2 +- docs/api/authorization.md | 4 +-- docs/api/enterprise.md | 23 +++++++++------- docs/api/schemas.md | 27 ++++++++++--------- docs/api/users.md | 22 +++++++-------- docs/cli/users_create.md | 14 +++++----- site/src/api/typesGenerated.ts | 3 ++- .../CreateUserForm/CreateUserForm.tsx | 1 + 11 files changed, 56 insertions(+), 46 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5ed8d068d0e94..e3324b11d674f 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8450,6 +8450,7 @@ const docTemplate = `{ "codersdk.LoginType": { "type": "string", "enum": [ + "", "password", "github", "oidc", @@ -8457,6 +8458,7 @@ const docTemplate = `{ "none" ], "x-enum-varnames": [ + "LoginTypeUnknown", "LoginTypePassword", "LoginTypeGithub", "LoginTypeOIDC", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 967778f1eb4ed..a9b7a550e9daa 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7577,8 +7577,9 @@ }, "codersdk.LoginType": { "type": "string", - "enum": ["password", "github", "oidc", "token", "none"], + "enum": ["", "password", "github", "oidc", "token", "none"], "x-enum-varnames": [ + "LoginTypeUnknown", "LoginTypePassword", "LoginTypeGithub", "LoginTypeOIDC", diff --git a/codersdk/apikey.go b/codersdk/apikey.go index 514b519f5ffda..32c97cf538417 100644 --- a/codersdk/apikey.go +++ b/codersdk/apikey.go @@ -28,6 +28,7 @@ type APIKey struct { type LoginType string const ( + LoginTypeUnknown LoginType = "" LoginTypePassword LoginType = "password" LoginTypeGithub LoginType = "github" LoginTypeOIDC LoginType = "oidc" diff --git a/docs/api/audit.md b/docs/api/audit.md index d5aeb78665d31..5efe1f3410809 100644 --- a/docs/api/audit.md +++ b/docs/api/audit.md @@ -63,7 +63,7 @@ curl -X GET http://coder-server:8080/api/v2/audit?q=string \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { diff --git a/docs/api/authorization.md b/docs/api/authorization.md index d57a5e7542c35..17fc2e81d2299 100644 --- a/docs/api/authorization.md +++ b/docs/api/authorization.md @@ -129,7 +129,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \ ```json { "password": "string", - "to_type": "password" + "to_type": "" } ``` @@ -148,7 +148,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \ { "expires_at": "2019-08-24T14:15:22Z", "state_string": "string", - "to_type": "password", + "to_type": "", "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" } ``` diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index fc887cd12b6e3..15ba8c12b4ea3 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -183,7 +183,7 @@ curl -X GET http://coder-server:8080/api/v2/groups/{group} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -245,7 +245,7 @@ curl -X DELETE http://coder-server:8080/api/v2/groups/{group} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -307,7 +307,7 @@ curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -444,7 +444,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -502,6 +502,7 @@ Status Code **200** | Property | Value | | ------------ | ----------- | +| `login_type` | `` | | `login_type` | `password` | | `login_type` | `github` | | `login_type` | `oidc` | @@ -562,7 +563,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/groups "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -625,7 +626,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -988,7 +989,7 @@ curl -X PATCH http://coder-server:8080/api/v2/scim/v2/Users/{id} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1040,7 +1041,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "role": "admin", "roles": [ @@ -1086,6 +1087,7 @@ Status Code **200** | Property | Value | | ------------ | ----------- | +| `login_type` | `` | | `login_type` | `password` | | `login_type` | `github` | | `login_type` | `oidc` | @@ -1197,7 +1199,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl/available \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1222,7 +1224,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl/available \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1278,6 +1280,7 @@ Status Code **200** | Property | Value | | ------------ | ----------- | +| `login_type` | `` | | `login_type` | `password` | | `login_type` | `github` | | `login_type` | `oidc` | diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 649a6bf8853d5..8a539b41f02dd 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -784,7 +784,7 @@ _None_ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -809,7 +809,7 @@ _None_ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1076,7 +1076,7 @@ _None_ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1153,7 +1153,7 @@ _None_ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1378,7 +1378,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "password": "string", - "to_type": "password" + "to_type": "" } ``` @@ -1655,7 +1655,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in { "disable_login": true, "email": "user@example.com", - "login_type": "password", + "login_type": "", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "password": "string", "username": "string" @@ -2744,7 +2744,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -2962,7 +2962,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -3180,7 +3180,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ## codersdk.LoginType ```json -"password" +"" ``` ### Properties @@ -3189,6 +3189,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Value | | ---------- | +| `` | | `password` | | `github` | | `oidc` | @@ -3297,7 +3298,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in { "expires_at": "2019-08-24T14:15:22Z", "state_string": "string", - "to_type": "password", + "to_type": "", "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" } ``` @@ -4555,7 +4556,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "role": "admin", "roles": [ @@ -5063,7 +5064,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -5188,7 +5189,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| ```json { - "login_type": "password" + "login_type": "" } ``` diff --git a/docs/api/users.md b/docs/api/users.md index 2f3509c0f9081..fdeed691da48f 100644 --- a/docs/api/users.md +++ b/docs/api/users.md @@ -36,7 +36,7 @@ curl -X GET http://coder-server:8080/api/v2/users \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -79,7 +79,7 @@ curl -X POST http://coder-server:8080/api/v2/users \ { "disable_login": true, "email": "user@example.com", - "login_type": "password", + "login_type": "", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "password": "string", "username": "string" @@ -103,7 +103,7 @@ curl -X POST http://coder-server:8080/api/v2/users \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -361,7 +361,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -412,7 +412,7 @@ curl -X DELETE http://coder-server:8080/api/v2/users/{user} \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -822,7 +822,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/login-type \ ```json { - "login_type": "password" + "login_type": "" } ``` @@ -1006,7 +1006,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1057,7 +1057,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1118,7 +1118,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1169,7 +1169,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { @@ -1220,7 +1220,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \ "email": "user@example.com", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "password", + "login_type": "", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], "roles": [ { diff --git a/docs/cli/users_create.md b/docs/cli/users_create.md index 2eb78318ffa0a..8e3efa61a2081 100644 --- a/docs/cli/users_create.md +++ b/docs/cli/users_create.md @@ -10,21 +10,21 @@ coder users create [flags] ## Options -### --disable-login +### -e, --email -| | | -| ---- | ----------------- | -| Type | bool | +| | | +| ---- | ------------------- | +| Type | string | -Disabling login for a user prevents the user from authenticating via password or IdP login. Authentication requires an API key/token generated by an admin. Be careful when using this flag as it can lock the user out of their account. +Specifies an email address for the new user. -### -e, --email +### --login-type | | | | ---- | ------------------- | | Type | string | -Specifies an email address for the new user. +Optionally specify the login type for the user. Valid values are: password, none, github, oidc. ### -p, --password diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 8a89f1024b820..5e1b9e608dd87 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1671,8 +1671,9 @@ export type LogSource = "provisioner" | "provisioner_daemon" export const LogSources: LogSource[] = ["provisioner", "provisioner_daemon"] // From codersdk/apikey.go -export type LoginType = "github" | "none" | "oidc" | "password" | "token" +export type LoginType = "" | "github" | "none" | "oidc" | "password" | "token" export const LoginTypes: LoginType[] = [ + "", "github", "none", "oidc", diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index c2f03155e7c62..3f85eee9e1151 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -53,6 +53,7 @@ export const CreateUserForm: FC< username: "", organization_id: myOrgId, disable_login: false, + login_type: "", }, validationSchema, onSubmit, From 3f08b1645deb4fad84ad1b9f7bfe1c5aaea26f4e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 13:14:03 -0500 Subject: [PATCH 04/15] Begin FE selector --- .../CreateUserForm/CreateUserForm.tsx | 33 +++++++++++++++++-- .../CreateUserPage/CreateUserPage.tsx | 10 ++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index 3f85eee9e1151..328bcf744a5b6 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -13,6 +13,8 @@ import { FullPageForm } from "../FullPageForm/FullPageForm" import { Stack } from "../Stack/Stack" import { ErrorAlert } from "components/Alert/ErrorAlert" import { hasApiFieldErrors, isApiError } from "api/errors" +import Select from "@mui/material/Select" +import MenuItem from "@mui/material/MenuItem" export const Language = { emailLabel: "Email", @@ -31,6 +33,7 @@ export interface CreateUserFormProps { error?: unknown isLoading: boolean myOrgId: string + authMethods?: TypesGen.AuthMethods } const validationSchema = Yup.object({ @@ -42,9 +45,13 @@ const validationSchema = Yup.object({ username: nameValidator(Language.usernameLabel), }) +const authMethodSelect = (title: string, value: string) => { + return {title} +} + export const CreateUserForm: FC< React.PropsWithChildren -> = ({ onSubmit, onCancel, error, isLoading, myOrgId }) => { +> = ({ onSubmit, onCancel, error, isLoading, myOrgId, authMethods }) => { const form: FormikContextType = useFormik({ initialValues: { @@ -53,7 +60,7 @@ export const CreateUserForm: FC< username: "", organization_id: myOrgId, disable_login: false, - login_type: "", + login_type: "password", }, validationSchema, onSubmit, @@ -63,6 +70,18 @@ export const CreateUserForm: FC< error, ) + const methods = [] + if (authMethods?.password.enabled) { + methods.push(authMethodSelect("Password", "password")) + } + if (authMethods?.oidc.enabled) { + methods.push(authMethodSelect("OIDC", "oidc")) + } + if (authMethods?.github.enabled) { + methods.push(authMethodSelect("Github", "github")) + } + methods.push(authMethodSelect("None", "none")) + return ( {isApiError(error) && !hasApiFieldErrors(error) && ( @@ -93,6 +112,16 @@ export const CreateUserForm: FC< label={Language.passwordLabel} type="password" /> + diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.tsx index cd905c39d88ae..cd92b6bc8141e 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.tsx @@ -8,6 +8,8 @@ import * as TypesGen from "../../../api/typesGenerated" import { CreateUserForm } from "../../../components/CreateUserForm/CreateUserForm" import { Margins } from "../../../components/Margins/Margins" import { pageTitle } from "../../../utils/page" +import { getAuthMethods } from "api/api" +import { useQuery } from "@tanstack/react-query" export const Language = { unknownError: "Oops, an unknown error occurred.", @@ -25,6 +27,13 @@ export const CreateUserPage: FC = () => { }) const { error } = createUserState.context + // TODO: We should probably place this somewhere else to reduce the number of calls. + // This would be called each time this page is loaded. + const { data: authMethods } = useQuery({ + queryKey: ["authMethods"], + queryFn: getAuthMethods, + }) + return ( @@ -33,6 +42,7 @@ export const CreateUserPage: FC = () => { createUserSend({ type: "CREATE", user }) } From 5dca49386c998d79ed9329af50287f3cd6f3f76e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 13:29:06 -0500 Subject: [PATCH 05/15] fix selector onchange --- .../CreateUserForm/CreateUserForm.tsx | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index 328bcf744a5b6..700ed231235ee 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -45,8 +45,17 @@ const validationSchema = Yup.object({ username: nameValidator(Language.usernameLabel), }) -const authMethodSelect = (title: string, value: string) => { - return {title} +const authMethodSelect = ( + title: string, + value: string, + description: string, +) => { + return ( + + {title} + {/* TODO: Add description */} + + ) } export const CreateUserForm: FC< @@ -72,15 +81,39 @@ export const CreateUserForm: FC< const methods = [] if (authMethods?.password.enabled) { - methods.push(authMethodSelect("Password", "password")) + methods.push( + authMethodSelect( + "Password", + "password", + "User can provide their email and password to login.", + ), + ) } if (authMethods?.oidc.enabled) { - methods.push(authMethodSelect("OIDC", "oidc")) + methods.push( + authMethodSelect( + "OIDC", + "oidc", + "Uses an OIDC provider to authenticate the user.", + ), + ) } if (authMethods?.github.enabled) { - methods.push(authMethodSelect("Github", "github")) + methods.push( + authMethodSelect( + "Github", + "github", + "Uses github oauth to authenticate the user.", + ), + ) } - methods.push(authMethodSelect("None", "none")) + methods.push( + authMethodSelect( + "None", + "none", + "User authentication is disabled. This user an only be used if an api token is created for them.", + ), + ) return ( @@ -118,7 +151,9 @@ export const CreateUserForm: FC< id="login_type" value={form.values.login_type} label="Login Type" - onChange={form.handleChange} + onChange={async (e) => { + await form.setFieldValue("login_type", e.target.value) + }} > {methods} From c04285ca14ca024cd724ca91c3ac6bf22a892ec1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 14:49:51 -0500 Subject: [PATCH 06/15] FE --- .../CreateUserForm/CreateUserForm.tsx | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index 700ed231235ee..29e2bf09dba2e 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -13,7 +13,6 @@ import { FullPageForm } from "../FullPageForm/FullPageForm" import { Stack } from "../Stack/Stack" import { ErrorAlert } from "components/Alert/ErrorAlert" import { hasApiFieldErrors, isApiError } from "api/errors" -import Select from "@mui/material/Select" import MenuItem from "@mui/material/MenuItem" export const Language = { @@ -41,13 +40,18 @@ const validationSchema = Yup.object({ .trim() .email(Language.emailInvalid) .required(Language.emailRequired), - password: Yup.string().required(Language.passwordRequired), + password: Yup.string().when("login_type", { + is: "password", + then: (schema) => schema.required(Language.passwordRequired), + otherwise: (schema) => schema, + }), username: nameValidator(Language.usernameLabel), }) const authMethodSelect = ( title: string, value: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- future will use this description: string, ) => { return ( @@ -138,25 +142,37 @@ export const CreateUserForm: FC< label={Language.emailLabel} /> - + From 4b7128418b2a892751e0fa8e957f84af11691996 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 15:35:47 -0500 Subject: [PATCH 07/15] formatting --- cli/usercreate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/usercreate.go b/cli/usercreate.go index e71a30f5d85e5..2b5438a3dfdec 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -142,7 +142,8 @@ Create a workspace `+cliui.DefaultStyles.Code.Render("coder create")+`!`) Flag: "login-type", Description: fmt.Sprintf("Optionally specify the login type for the user. Valid values are: %s.", strings.Join([]string{ - string(codersdk.LoginTypePassword), string(codersdk.LoginTypeNone), string(codersdk.LoginTypeGithub), string(codersdk.LoginTypeOIDC)}, ", ", + string(codersdk.LoginTypePassword), string(codersdk.LoginTypeNone), string(codersdk.LoginTypeGithub), string(codersdk.LoginTypeOIDC), + }, ", ", )), Value: clibase.StringOf(&loginType), }, From 873110a3d7a4f8bece0b76fc9cc8c2ff360ec7f6 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 15:41:13 -0500 Subject: [PATCH 08/15] update golden files --- cli/testdata/coder_users_create_--help.golden | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cli/testdata/coder_users_create_--help.golden b/cli/testdata/coder_users_create_--help.golden index bb94cac633bc0..1f72a4db8b5c1 100644 --- a/cli/testdata/coder_users_create_--help.golden +++ b/cli/testdata/coder_users_create_--help.golden @@ -1,15 +1,13 @@ Usage: coder users create [flags] Options - --disable-login bool - Disabling login for a user prevents the user from authenticating via - password or IdP login. Authentication requires an API key/token - generated by an admin. Be careful when using this flag as it can lock - the user out of their account. - -e, --email string Specifies an email address for the new user. + --login-type string + Optionally specify the login type for the user. Valid values are: + password, none, github, oidc. + -p, --password string Specifies a password for the new user. From c9128ce2781082220fee0799de94b60cff36b17a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 15:45:51 -0500 Subject: [PATCH 09/15] Add more phrasing --- cli/usercreate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/usercreate.go b/cli/usercreate.go index 2b5438a3dfdec..80118d7fced0e 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -140,7 +140,8 @@ Create a workspace `+cliui.DefaultStyles.Code.Render("coder create")+`!`) }, { Flag: "login-type", - Description: fmt.Sprintf("Optionally specify the login type for the user. Valid values are: %s.", + Description: fmt.Sprintf("Optionally specify the login type for the user. Valid values are: %s. "+ + "Using 'none' prevents the user from authenticating and requires an API key/token to be generated by an admin.", strings.Join([]string{ string(codersdk.LoginTypePassword), string(codersdk.LoginTypeNone), string(codersdk.LoginTypeGithub), string(codersdk.LoginTypeOIDC), }, ", ", From 1e1c1354f40814938cb1ff8ab646e9eb0833ce5b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 15:47:47 -0500 Subject: [PATCH 10/15] Add more phrasing --- cli/testdata/coder_users_create_--help.golden | 4 +++- docs/cli/users_create.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/testdata/coder_users_create_--help.golden b/cli/testdata/coder_users_create_--help.golden index 1f72a4db8b5c1..275e89803d4c6 100644 --- a/cli/testdata/coder_users_create_--help.golden +++ b/cli/testdata/coder_users_create_--help.golden @@ -6,7 +6,9 @@ Usage: coder users create [flags] --login-type string Optionally specify the login type for the user. Valid values are: - password, none, github, oidc. + password, none, github, oidc. Using 'none' prevents the user from + authenticating and requires an API key/token to be generated by an + admin. -p, --password string Specifies a password for the new user. diff --git a/docs/cli/users_create.md b/docs/cli/users_create.md index 8e3efa61a2081..b89ff2aeb6d45 100644 --- a/docs/cli/users_create.md +++ b/docs/cli/users_create.md @@ -24,7 +24,7 @@ Specifies an email address for the new user. | ---- | ------------------- | | Type | string | -Optionally specify the login type for the user. Valid values are: password, none, github, oidc. +Optionally specify the login type for the user. Valid values are: password, none, github, oidc. Using 'none' prevents the user from authenticating and requires an API key/token to be generated by an admin. ### -p, --password From 6dc874c5986b023153fa172c406d1b1ec9f922b7 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 15:49:47 -0500 Subject: [PATCH 11/15] fix js selector --- site/src/components/CreateUserForm/CreateUserForm.tsx | 1 + site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index 29e2bf09dba2e..e9c0c5b0bdac8 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -151,6 +151,7 @@ export const CreateUserForm: FC< autoComplete="current-password" fullWidth id="password" + data-testid="password" disabled={form.values.login_type !== "password"} label={Language.passwordLabel} type="password" diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx index d342ace9bdf75..f73d180086c2c 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx @@ -29,7 +29,7 @@ const fillForm = async ({ }) => { const usernameField = screen.getByLabelText(FormLanguage.usernameLabel) const emailField = screen.getByLabelText(FormLanguage.emailLabel) - const passwordField = screen.getByLabelText(FormLanguage.passwordLabel) + const passwordField = screen.getByTestId("password") await userEvent.type(usernameField, username) await userEvent.type(emailField, email) await userEvent.type(passwordField, password) From c62c4ed9cabb74dd044216a0170073aaaf560d52 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 16:30:04 -0500 Subject: [PATCH 12/15] jsx test debugging --- site/src/components/CreateUserForm/CreateUserForm.tsx | 5 +++-- .../pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index e9c0c5b0bdac8..caa86352e91f6 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -55,7 +55,7 @@ const authMethodSelect = ( description: string, ) => { return ( - + {title} {/* TODO: Add description */} @@ -151,7 +151,7 @@ export const CreateUserForm: FC< autoComplete="current-password" fullWidth id="password" - data-testid="password" + data-testid="password-input" disabled={form.values.login_type !== "password"} label={Language.passwordLabel} type="password" @@ -163,6 +163,7 @@ export const CreateUserForm: FC< )} select id="login_type" + data-testid="login-type-input" value={form.values.login_type} label="Login Type" onChange={async (e) => { diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx index f73d180086c2c..4f7f8bbc14080 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx @@ -21,7 +21,7 @@ const renderCreateUserPage = async () => { const fillForm = async ({ username = "someuser", email = "someone@coder.com", - password = "password", + password = "SomeSecurePassword!", }: { username?: string email?: string @@ -29,9 +29,11 @@ const fillForm = async ({ }) => { const usernameField = screen.getByLabelText(FormLanguage.usernameLabel) const emailField = screen.getByLabelText(FormLanguage.emailLabel) - const passwordField = screen.getByTestId("password") + const passwordField = screen.getByTestId("password-input") + const loginTypeField = screen.getByTestId("login-type-input") await userEvent.type(usernameField, username) await userEvent.type(emailField, email) + await userEvent.type(loginTypeField, "password") await userEvent.type(passwordField, password) const submitButton = await screen.findByText( FooterLanguage.defaultSubmitLabel, From c237c70cb51471b66d0b0bdfc1ca94137c28c8ef Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 16:53:33 -0500 Subject: [PATCH 13/15] Fix js test --- .../UsersPage/CreateUserPage/CreateUserPage.test.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx index 4f7f8bbc14080..8c987d3a2129a 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen } from "@testing-library/react" +import { fireEvent, screen, within } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { rest } from "msw" import { Language as FormLanguage } from "../../../components/CreateUserForm/CreateUserForm" @@ -29,12 +29,15 @@ const fillForm = async ({ }) => { const usernameField = screen.getByLabelText(FormLanguage.usernameLabel) const emailField = screen.getByLabelText(FormLanguage.emailLabel) - const passwordField = screen.getByTestId("password-input") + const passwordField = screen + .getByTestId("password-input") + .querySelector("input") + const loginTypeField = screen.getByTestId("login-type-input") await userEvent.type(usernameField, username) await userEvent.type(emailField, email) await userEvent.type(loginTypeField, "password") - await userEvent.type(passwordField, password) + await userEvent.type(passwordField as HTMLElement, password) const submitButton = await screen.findByText( FooterLanguage.defaultSubmitLabel, ) From f7a01a431f059cc256aafee05a4f5955c9e583c4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 17:22:42 -0500 Subject: [PATCH 14/15] linting --- site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx index 8c987d3a2129a..ceeb30528d4f3 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen, within } from "@testing-library/react" +import { fireEvent, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { rest } from "msw" import { Language as FormLanguage } from "../../../components/CreateUserForm/CreateUserForm" From fd6752a50fb02969e2a0f5d95f6287fb90f1feed Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Aug 2023 22:44:13 -0500 Subject: [PATCH 15/15] Change to open id connect --- site/src/components/CreateUserForm/CreateUserForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/CreateUserForm/CreateUserForm.tsx b/site/src/components/CreateUserForm/CreateUserForm.tsx index caa86352e91f6..6270f0ca88799 100644 --- a/site/src/components/CreateUserForm/CreateUserForm.tsx +++ b/site/src/components/CreateUserForm/CreateUserForm.tsx @@ -96,9 +96,9 @@ export const CreateUserForm: FC< if (authMethods?.oidc.enabled) { methods.push( authMethodSelect( - "OIDC", + "OpenID Connect", "oidc", - "Uses an OIDC provider to authenticate the user.", + "Uses an OpenID connect provider to authenticate the user.", ), ) }