mirror of https://github.com/coder/coder.git
feat: allow creating manual oidc/github based users (#9000)
* feat: allow creating manual oidc/github based users * Add unit test for oidc and no login type create
This commit is contained in:
parent
6fd5344d0a
commit
40f3fc3a1c
|
@ -1,15 +1,15 @@
|
|||
Usage: coder users create [flags]
|
||||
|
||||
[1mOptions[0m
|
||||
--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. 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.
|
||||
|
||||
|
|
|
@ -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 && loginType != "" {
|
||||
return xerrors.New("You cannot specify both --disable-login and --login-type")
|
||||
}
|
||||
if disableLogin {
|
||||
userLoginType = codersdk.LoginTypeNone
|
||||
} else if loginType != "" {
|
||||
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,22 @@ 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. "+
|
||||
"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),
|
||||
}, ", ",
|
||||
)),
|
||||
Value: clibase.StringOf(&loginType),
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -7536,13 +7536,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"
|
||||
|
@ -8449,6 +8457,7 @@ const docTemplate = `{
|
|||
"codersdk.LoginType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"",
|
||||
"password",
|
||||
"github",
|
||||
"oidc",
|
||||
|
@ -8456,6 +8465,7 @@ const docTemplate = `{
|
|||
"none"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LoginTypeUnknown",
|
||||
"LoginTypePassword",
|
||||
"LoginTypeGithub",
|
||||
"LoginTypeOIDC",
|
||||
|
|
|
@ -6715,13 +6715,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"
|
||||
|
@ -7576,8 +7584,9 @@
|
|||
},
|
||||
"codersdk.LoginType": {
|
||||
"type": "string",
|
||||
"enum": ["password", "github", "oidc", "token", "none"],
|
||||
"enum": ["", "password", "github", "oidc", "token", "none"],
|
||||
"x-enum-varnames": [
|
||||
"LoginTypeUnknown",
|
||||
"LoginTypePassword",
|
||||
"LoginTypeGithub",
|
||||
"LoginTypeOIDC",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -566,6 +567,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) {
|
||||
|
|
|
@ -28,6 +28,7 @@ type APIKey struct {
|
|||
type LoginType string
|
||||
|
||||
const (
|
||||
LoginTypeUnknown LoginType = ""
|
||||
LoginTypePassword LoginType = "password"
|
||||
LoginTypeGithub LoginType = "github"
|
||||
LoginTypeOIDC LoginType = "oidc"
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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` |
|
||||
|
|
|
@ -792,7 +792,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": [
|
||||
{
|
||||
|
@ -817,7 +817,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": [
|
||||
{
|
||||
|
@ -1086,7 +1086,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": [
|
||||
{
|
||||
|
@ -1163,7 +1163,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": [
|
||||
{
|
||||
|
@ -1388,7 +1388,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
```json
|
||||
{
|
||||
"password": "string",
|
||||
"to_type": "password"
|
||||
"to_type": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1665,6 +1665,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
{
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
|
@ -1673,13 +1674,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
|
||||
|
||||
|
@ -2752,7 +2754,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": [
|
||||
{
|
||||
|
@ -2970,7 +2972,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": [
|
||||
{
|
||||
|
@ -3188,7 +3190,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
## codersdk.LoginType
|
||||
|
||||
```json
|
||||
"password"
|
||||
""
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
@ -3197,6 +3199,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
|
||||
| Value |
|
||||
| ---------- |
|
||||
| `` |
|
||||
| `password` |
|
||||
| `github` |
|
||||
| `oidc` |
|
||||
|
@ -3305,7 +3308,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"
|
||||
}
|
||||
```
|
||||
|
@ -4563,7 +4566,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": [
|
||||
|
@ -5071,7 +5074,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": [
|
||||
{
|
||||
|
@ -5196,7 +5199,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
|
||||
```json
|
||||
{
|
||||
"login_type": "password"
|
||||
"login_type": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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,6 +79,7 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
|||
{
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
|
@ -102,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": [
|
||||
{
|
||||
|
@ -360,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": [
|
||||
{
|
||||
|
@ -411,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": [
|
||||
{
|
||||
|
@ -821,7 +822,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/login-type \
|
|||
|
||||
```json
|
||||
{
|
||||
"login_type": "password"
|
||||
"login_type": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1005,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": [
|
||||
{
|
||||
|
@ -1056,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": [
|
||||
{
|
||||
|
@ -1117,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": [
|
||||
{
|
||||
|
@ -1168,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": [
|
||||
{
|
||||
|
@ -1219,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": [
|
||||
{
|
||||
|
|
|
@ -10,14 +10,6 @@ coder users create [flags]
|
|||
|
||||
## Options
|
||||
|
||||
### --disable-login
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
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
|
||||
|
||||
| | |
|
||||
|
@ -26,6 +18,14 @@ Disabling login for a user prevents the user from authenticating via password or
|
|||
|
||||
Specifies an email address for the new user.
|
||||
|
||||
### --login-type
|
||||
|
||||
| | |
|
||||
| ---- | ------------------- |
|
||||
| Type | <code>string</code> |
|
||||
|
||||
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
|
||||
|
||||
| | |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -1674,8 +1675,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",
|
||||
|
|
|
@ -13,6 +13,7 @@ import { FullPageForm } from "../FullPageForm/FullPageForm"
|
|||
import { Stack } from "../Stack/Stack"
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert"
|
||||
import { hasApiFieldErrors, isApiError } from "api/errors"
|
||||
import MenuItem from "@mui/material/MenuItem"
|
||||
|
||||
export const Language = {
|
||||
emailLabel: "Email",
|
||||
|
@ -31,6 +32,7 @@ export interface CreateUserFormProps {
|
|||
error?: unknown
|
||||
isLoading: boolean
|
||||
myOrgId: string
|
||||
authMethods?: TypesGen.AuthMethods
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
|
@ -38,13 +40,31 @@ 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 (
|
||||
<MenuItem key="value" id={"item-" + value} value={value}>
|
||||
{title}
|
||||
{/* TODO: Add description */}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const CreateUserForm: FC<
|
||||
React.PropsWithChildren<CreateUserFormProps>
|
||||
> = ({ onSubmit, onCancel, error, isLoading, myOrgId }) => {
|
||||
> = ({ onSubmit, onCancel, error, isLoading, myOrgId, authMethods }) => {
|
||||
const form: FormikContextType<TypesGen.CreateUserRequest> =
|
||||
useFormik<TypesGen.CreateUserRequest>({
|
||||
initialValues: {
|
||||
|
@ -53,6 +73,7 @@ export const CreateUserForm: FC<
|
|||
username: "",
|
||||
organization_id: myOrgId,
|
||||
disable_login: false,
|
||||
login_type: "password",
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
|
@ -62,6 +83,42 @@ export const CreateUserForm: FC<
|
|||
error,
|
||||
)
|
||||
|
||||
const methods = []
|
||||
if (authMethods?.password.enabled) {
|
||||
methods.push(
|
||||
authMethodSelect(
|
||||
"Password",
|
||||
"password",
|
||||
"User can provide their email and password to login.",
|
||||
),
|
||||
)
|
||||
}
|
||||
if (authMethods?.oidc.enabled) {
|
||||
methods.push(
|
||||
authMethodSelect(
|
||||
"OpenID Connect",
|
||||
"oidc",
|
||||
"Uses an OpenID connect provider to authenticate the user.",
|
||||
),
|
||||
)
|
||||
}
|
||||
if (authMethods?.github.enabled) {
|
||||
methods.push(
|
||||
authMethodSelect(
|
||||
"Github",
|
||||
"github",
|
||||
"Uses github oauth to authenticate the user.",
|
||||
),
|
||||
)
|
||||
}
|
||||
methods.push(
|
||||
authMethodSelect(
|
||||
"None",
|
||||
"none",
|
||||
"User authentication is disabled. This user an only be used if an api token is created for them.",
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
<FullPageForm title="Create user">
|
||||
{isApiError(error) && !hasApiFieldErrors(error) && (
|
||||
|
@ -85,13 +142,39 @@ export const CreateUserForm: FC<
|
|||
label={Language.emailLabel}
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("password")}
|
||||
{...getFieldHelpers(
|
||||
"password",
|
||||
form.values.login_type === "password"
|
||||
? ""
|
||||
: "No password required for this login type",
|
||||
)}
|
||||
autoComplete="current-password"
|
||||
fullWidth
|
||||
id="password"
|
||||
data-testid="password-input"
|
||||
disabled={form.values.login_type !== "password"}
|
||||
label={Language.passwordLabel}
|
||||
type="password"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers(
|
||||
"login_type",
|
||||
"Authentication method for this user",
|
||||
)}
|
||||
select
|
||||
id="login_type"
|
||||
data-testid="login-type-input"
|
||||
value={form.values.login_type}
|
||||
label="Login Type"
|
||||
onChange={async (e) => {
|
||||
if (e.target.value !== "password") {
|
||||
await form.setFieldValue("password", "")
|
||||
}
|
||||
await form.setFieldValue("login_type", e.target.value)
|
||||
}}
|
||||
>
|
||||
{methods}
|
||||
</TextField>
|
||||
</Stack>
|
||||
<FormFooter onCancel={onCancel} isLoading={isLoading} />
|
||||
</form>
|
||||
|
|
|
@ -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,10 +29,15 @@ 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-input")
|
||||
.querySelector("input")
|
||||
|
||||
const loginTypeField = screen.getByTestId("login-type-input")
|
||||
await userEvent.type(usernameField, username)
|
||||
await userEvent.type(emailField, email)
|
||||
await userEvent.type(passwordField, password)
|
||||
await userEvent.type(loginTypeField, "password")
|
||||
await userEvent.type(passwordField as HTMLElement, password)
|
||||
const submitButton = await screen.findByText(
|
||||
FooterLanguage.defaultSubmitLabel,
|
||||
)
|
||||
|
|
|
@ -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 (
|
||||
<Margins>
|
||||
<Helmet>
|
||||
|
@ -33,6 +42,7 @@ export const CreateUserPage: FC = () => {
|
|||
|
||||
<CreateUserForm
|
||||
error={error}
|
||||
authMethods={authMethods}
|
||||
onSubmit={(user: TypesGen.CreateUserRequest) =>
|
||||
createUserSend({ type: "CREATE", user })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue