mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
chore!: allow CreateUser to accept multiple organizations (#14383)
* chore: allow CreateUser to accept multiple organizations In a multi-org deployment, it makes more sense to allow for multiple org memberships to be assigned at create. The legacy param will still be honored. * Handle sdk deprecation better by maintaining cli functions
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/config"
|
||||
@@ -183,11 +184,11 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
firstUser := coderdtest.CreateFirstUser(t, rootClient)
|
||||
secondUser, err := rootClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "testuser2@coder.com",
|
||||
Username: "testuser2",
|
||||
Password: coderdtest.FirstUserParams.Password,
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
secondUser, err := rootClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "testuser2@coder.com",
|
||||
Username: "testuser2",
|
||||
Password: coderdtest.FirstUserParams.Password,
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
version := coderdtest.CreateTemplateVersion(t, rootClient, firstUser.OrganizationID, nil)
|
||||
|
||||
@@ -35,7 +35,7 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC
|
||||
|
||||
ownerClient, db = coderdtest.NewWithDatabase(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
|
||||
memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Username = "testuser2" // ensure deterministic ordering
|
||||
})
|
||||
_ = dbfake.WorkspaceBuild(t, db, database.Workspace{
|
||||
|
||||
@@ -83,12 +83,12 @@ func (r *RootCmd) newCreateAdminUserCommand() *serpent.Command {
|
||||
|
||||
validateInputs := func(username, email, password string) error {
|
||||
// Use the validator tags so we match the API's validation.
|
||||
req := codersdk.CreateUserRequest{
|
||||
Username: "username",
|
||||
Name: "Admin User",
|
||||
Email: "email@coder.com",
|
||||
Password: "ValidPa$$word123!",
|
||||
OrganizationID: uuid.New(),
|
||||
req := codersdk.CreateUserRequestWithOrgs{
|
||||
Username: "username",
|
||||
Name: "Admin User",
|
||||
Email: "email@coder.com",
|
||||
Password: "ValidPa$$word123!",
|
||||
OrganizationIDs: []uuid.UUID{uuid.New()},
|
||||
}
|
||||
if username != "" {
|
||||
req.Username = username
|
||||
|
||||
+20
-23
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
@@ -26,13 +27,12 @@ func TestUserDelete(t *testing.T) {
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
DisableLogin: false,
|
||||
_, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -57,13 +57,12 @@ func TestUserDelete(t *testing.T) {
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
DisableLogin: false,
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -88,13 +87,12 @@ func TestUserDelete(t *testing.T) {
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
DisableLogin: false,
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -121,13 +119,12 @@ func TestUserDelete(t *testing.T) {
|
||||
// pw, err := cryptorand.String(16)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// toDelete, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
// toDelete, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
// Email: "colin5@coder.com",
|
||||
// Username: "coolin",
|
||||
// Password: pw,
|
||||
// UserLoginType: codersdk.LoginTypePassword,
|
||||
// OrganizationID: aUser.OrganizationID,
|
||||
// DisableLogin: false,
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
|
||||
|
||||
+8
-7
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/pretty"
|
||||
@@ -94,13 +95,13 @@ func (r *RootCmd) userCreate() *serpent.Command {
|
||||
}
|
||||
}
|
||||
|
||||
_, err = client.CreateUser(inv.Context(), codersdk.CreateUserRequest{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Name: name,
|
||||
Password: password,
|
||||
OrganizationID: organization.ID,
|
||||
UserLoginType: userLoginType,
|
||||
_, err = client.CreateUserWithOrgs(inv.Context(), codersdk.CreateUserRequestWithOrgs{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Name: name,
|
||||
Password: password,
|
||||
OrganizationIDs: []uuid.UUID{organization.ID},
|
||||
UserLoginType: userLoginType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Generated
+9
-9
@@ -4875,7 +4875,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateUserRequest"
|
||||
"$ref": "#/definitions/codersdk.CreateUserRequestWithOrgs"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -9449,17 +9449,13 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateUserRequest": {
|
||||
"codersdk.CreateUserRequestWithOrgs": {
|
||||
"type": "object",
|
||||
"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.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
@@ -9475,9 +9471,13 @@ const docTemplate = `{
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"organization_ids": {
|
||||
"description": "OrganizationIDs is a list of organization IDs that the user should be a member of.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
|
||||
Generated
+9
-9
@@ -4305,7 +4305,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateUserRequest"
|
||||
"$ref": "#/definitions/codersdk.CreateUserRequestWithOrgs"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -8413,14 +8413,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateUserRequest": {
|
||||
"codersdk.CreateUserRequestWithOrgs": {
|
||||
"type": "object",
|
||||
"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.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
@@ -8436,9 +8432,13 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"organization_ids": {
|
||||
"description": "OrganizationIDs is a list of organization IDs that the user should be a member of.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
|
||||
@@ -648,11 +648,11 @@ func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirst
|
||||
// CreateAnotherUser creates and authenticates a new user.
|
||||
// Roles can include org scoped roles with 'roleName:<organization_id>'
|
||||
func CreateAnotherUser(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles ...rbac.RoleIdentifier) (*codersdk.Client, codersdk.User) {
|
||||
return createAnotherUserRetry(t, client, organizationID, 5, roles)
|
||||
return createAnotherUserRetry(t, client, []uuid.UUID{organizationID}, 5, roles)
|
||||
}
|
||||
|
||||
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
return createAnotherUserRetry(t, client, organizationID, 5, roles, mutators...)
|
||||
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequestWithOrgs)) (*codersdk.Client, codersdk.User) {
|
||||
return createAnotherUserRetry(t, client, []uuid.UUID{organizationID}, 5, roles, mutators...)
|
||||
}
|
||||
|
||||
// AuthzUserSubject does not include the user's groups.
|
||||
@@ -678,31 +678,31 @@ func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject {
|
||||
}
|
||||
}
|
||||
|
||||
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
req := codersdk.CreateUserRequest{
|
||||
Email: namesgenerator.GetRandomName(10) + "@coder.com",
|
||||
Username: RandomUsername(t),
|
||||
Name: RandomName(t),
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: organizationID,
|
||||
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationIDs []uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequestWithOrgs)) (*codersdk.Client, codersdk.User) {
|
||||
req := codersdk.CreateUserRequestWithOrgs{
|
||||
Email: namesgenerator.GetRandomName(10) + "@coder.com",
|
||||
Username: RandomUsername(t),
|
||||
Name: RandomName(t),
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: organizationIDs,
|
||||
}
|
||||
for _, m := range mutators {
|
||||
m(&req)
|
||||
}
|
||||
|
||||
user, err := client.CreateUser(context.Background(), req)
|
||||
user, err := client.CreateUserWithOrgs(context.Background(), req)
|
||||
var apiError *codersdk.Error
|
||||
// If the user already exists by username or email conflict, try again up to "retries" times.
|
||||
if err != nil && retries >= 0 && xerrors.As(err, &apiError) {
|
||||
if apiError.StatusCode() == http.StatusConflict {
|
||||
retries--
|
||||
return createAnotherUserRetry(t, client, organizationID, retries, roles)
|
||||
return createAnotherUserRetry(t, client, organizationIDs, retries, roles)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
var sessionToken string
|
||||
if req.DisableLogin || req.UserLoginType == codersdk.LoginTypeNone {
|
||||
if 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{
|
||||
@@ -765,8 +765,9 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
|
||||
require.NoError(t, err, "update site roles")
|
||||
|
||||
// isMember keeps track of which orgs the user was added to as a member
|
||||
isMember := map[uuid.UUID]bool{
|
||||
organizationID: true,
|
||||
isMember := make(map[uuid.UUID]bool)
|
||||
for _, orgID := range organizationIDs {
|
||||
isMember[orgID] = true
|
||||
}
|
||||
|
||||
// Update org roles
|
||||
|
||||
@@ -523,7 +523,7 @@ func TestTemplateInsights_Golden(t *testing.T) {
|
||||
|
||||
// Prepare all test users.
|
||||
for _, user := range users {
|
||||
user.client, user.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
|
||||
user.client, user.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Username = user.name
|
||||
})
|
||||
user.client.SetLogger(logger.Named("user").With(slog.Field{Name: "name", Value: user.name}))
|
||||
|
||||
+5
-5
@@ -1436,11 +1436,11 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
}
|
||||
|
||||
//nolint:gocritic
|
||||
user, _, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
|
||||
CreateUserRequest: codersdk.CreateUserRequest{
|
||||
Email: params.Email,
|
||||
Username: params.Username,
|
||||
OrganizationID: defaultOrganization.ID,
|
||||
user, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
|
||||
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
|
||||
Email: params.Email,
|
||||
Username: params.Username,
|
||||
OrganizationIDs: []uuid.UUID{defaultOrganization.ID},
|
||||
},
|
||||
LoginType: params.LoginType,
|
||||
})
|
||||
|
||||
+6
-22
@@ -106,28 +106,12 @@ func TestUserLogin(t *testing.T) {
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
// Password auth should fail if the user is made without password login.
|
||||
t.Run("DisableLoginDeprecatedField", 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.DisableLogin = true
|
||||
})
|
||||
|
||||
_, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
|
||||
Email: anotherUser.Email,
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
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) {
|
||||
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Password = ""
|
||||
r.UserLoginType = codersdk.LoginTypeNone
|
||||
})
|
||||
@@ -1470,11 +1454,11 @@ func TestUserLogout(t *testing.T) {
|
||||
//nolint:gosec
|
||||
password = "SomeSecurePassword123!"
|
||||
)
|
||||
newUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Password: password,
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
newUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Password: password,
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+66
-63
@@ -186,13 +186,13 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
//nolint:gocritic // needed to create first user
|
||||
user, organizationID, err := api.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, CreateUserRequest{
|
||||
CreateUserRequest: codersdk.CreateUserRequest{
|
||||
Email: createUser.Email,
|
||||
Username: createUser.Username,
|
||||
Name: createUser.Name,
|
||||
Password: createUser.Password,
|
||||
OrganizationID: defaultOrg.ID,
|
||||
user, err := api.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, CreateUserRequest{
|
||||
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
|
||||
Email: createUser.Email,
|
||||
Username: createUser.Username,
|
||||
Name: createUser.Name,
|
||||
Password: createUser.Password,
|
||||
OrganizationIDs: []uuid.UUID{defaultOrg.ID},
|
||||
},
|
||||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
@@ -240,7 +240,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
|
||||
UserID: user.ID,
|
||||
OrganizationID: organizationID,
|
||||
OrganizationID: defaultOrg.ID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Users
|
||||
// @Param request body codersdk.CreateUserRequest true "Create user request"
|
||||
// @Param request body codersdk.CreateUserRequestWithOrgs true "Create user request"
|
||||
// @Success 201 {object} codersdk.User
|
||||
// @Router /users [post]
|
||||
func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -356,15 +356,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
defer commitAudit()
|
||||
|
||||
var req codersdk.CreateUserRequest
|
||||
var req codersdk.CreateUserRequestWithOrgs
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
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
|
||||
@@ -386,6 +382,20 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.OrganizationIDs) == 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "No organization specified to place the user as a member of. It is required to specify at least one organization id to place the user in.",
|
||||
Detail: "required at least 1 value for the array 'organization_ids'",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{
|
||||
Field: "organization_ids",
|
||||
Detail: "Missing values, this cannot be empty",
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: @emyrk Authorize the organization create if the createUser will do that.
|
||||
|
||||
_, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
|
||||
@@ -406,44 +416,33 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.OrganizationID != uuid.Nil {
|
||||
// If an organization was provided, make sure it exists.
|
||||
_, err := api.Database.GetOrganizationByID(ctx, req.OrganizationID)
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
// If an organization was provided, make sure it exists.
|
||||
for i, orgID := range req.OrganizationIDs {
|
||||
var orgErr error
|
||||
if orgID != uuid.Nil {
|
||||
_, orgErr = api.Database.GetOrganizationByID(ctx, orgID)
|
||||
} else {
|
||||
var defaultOrg database.Organization
|
||||
defaultOrg, orgErr = api.Database.GetDefaultOrganization(ctx)
|
||||
if orgErr == nil {
|
||||
// converts uuid.Nil --> default org.ID
|
||||
req.OrganizationIDs[i] = defaultOrg.ID
|
||||
}
|
||||
}
|
||||
if orgErr != nil {
|
||||
if httpapi.Is404Error(orgErr) {
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
|
||||
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", orgID),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching organization.",
|
||||
Detail: err.Error(),
|
||||
Detail: orgErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// If no organization is provided, add the user to the default
|
||||
defaultOrg, err := api.Database.GetDefaultOrganization(ctx)
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound,
|
||||
codersdk.Response{
|
||||
Message: "Resource not found or you do not have access to this resource",
|
||||
Detail: "Organization not found",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching orgs.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = defaultOrg.ID
|
||||
}
|
||||
|
||||
var loginType database.LoginType
|
||||
@@ -480,9 +479,9 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||
CreateUserRequest: req,
|
||||
LoginType: loginType,
|
||||
user, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||
CreateUserRequestWithOrgs: req,
|
||||
LoginType: loginType,
|
||||
})
|
||||
if dbauthz.IsNotAuthorizedError(err) {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
@@ -505,7 +504,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
Users: []telemetry.User{telemetry.ConvertUser(user)},
|
||||
})
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.User(user, []uuid.UUID{req.OrganizationID}))
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.User(user, req.OrganizationIDs))
|
||||
}
|
||||
|
||||
// @Summary Delete user
|
||||
@@ -1280,23 +1279,23 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
codersdk.CreateUserRequest
|
||||
codersdk.CreateUserRequestWithOrgs
|
||||
LoginType database.LoginType
|
||||
SkipNotifications bool
|
||||
}
|
||||
|
||||
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, uuid.UUID, error) {
|
||||
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, error) {
|
||||
// Ensure the username is valid. It's the caller's responsibility to ensure
|
||||
// the username is valid and unique.
|
||||
if usernameValid := httpapi.NameValid(req.Username); usernameValid != nil {
|
||||
return database.User{}, uuid.Nil, xerrors.Errorf("invalid username %q: %w", req.Username, usernameValid)
|
||||
return database.User{}, xerrors.Errorf("invalid username %q: %w", req.Username, usernameValid)
|
||||
}
|
||||
|
||||
var user database.User
|
||||
err := store.InTx(func(tx database.Store) error {
|
||||
orgRoles := make([]string, 0)
|
||||
// Organization is required to know where to allocate the user.
|
||||
if req.OrganizationID == uuid.Nil {
|
||||
if len(req.OrganizationIDs) == 0 {
|
||||
return xerrors.Errorf("organization ID must be provided")
|
||||
}
|
||||
|
||||
@@ -1341,26 +1340,30 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert user gitsshkey: %w", err)
|
||||
}
|
||||
_, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
|
||||
OrganizationID: req.OrganizationID,
|
||||
UserID: user.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
// By default give them membership to the organization.
|
||||
Roles: orgRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create organization member: %w", err)
|
||||
|
||||
for _, orgID := range req.OrganizationIDs {
|
||||
_, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
|
||||
OrganizationID: orgID,
|
||||
UserID: user.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
// By default give them membership to the organization.
|
||||
Roles: orgRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create organization member for %q: %w", orgID.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
if err != nil || req.SkipNotifications {
|
||||
return user, req.OrganizationID, err
|
||||
return user, err
|
||||
}
|
||||
|
||||
userAdmins, err := findUserAdmins(ctx, store)
|
||||
if err != nil {
|
||||
return user, req.OrganizationID, xerrors.Errorf("find user admins: %w", err)
|
||||
return user, xerrors.Errorf("find user admins: %w", err)
|
||||
}
|
||||
|
||||
for _, u := range userAdmins {
|
||||
@@ -1373,7 +1376,7 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
|
||||
api.Logger.Warn(ctx, "unable to notify about created user", slog.F("created_user", user.Username), slog.Error(err))
|
||||
}
|
||||
}
|
||||
return user, req.OrganizationID, err
|
||||
return user, err
|
||||
}
|
||||
|
||||
// findUserAdmins fetches all users with user admin permission including owners.
|
||||
|
||||
+128
-128
@@ -220,11 +220,11 @@ func TestPostLogin(t *testing.T) {
|
||||
|
||||
// With a user account.
|
||||
const password = "SomeSecurePassword!"
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "test+user-@coder.com",
|
||||
Username: "user",
|
||||
Password: password,
|
||||
OrganizationID: first.OrganizationID,
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "test+user-@coder.com",
|
||||
Username: "user",
|
||||
Password: password,
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -317,11 +317,11 @@ func TestDeleteUser(t *testing.T) {
|
||||
err := client.DeleteUser(context.Background(), another.ID)
|
||||
require.NoError(t, err)
|
||||
// Attempt to create a user with the same email and username, and delete them again.
|
||||
another, err = client.CreateUser(context.Background(), codersdk.CreateUserRequest{
|
||||
Email: another.Email,
|
||||
Username: another.Username,
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
another, err = client.CreateUserWithOrgs(context.Background(), codersdk.CreateUserRequestWithOrgs{
|
||||
Email: another.Email,
|
||||
Username: another.Username,
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = client.DeleteUser(context.Background(), another.ID)
|
||||
@@ -415,11 +415,11 @@ func TestNotifyUserStatusChanged(t *testing.T) {
|
||||
|
||||
_, userAdmin := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID, rbac.RoleUserAdmin())
|
||||
|
||||
member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
member, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -452,11 +452,11 @@ func TestNotifyUserStatusChanged(t *testing.T) {
|
||||
|
||||
_, userAdmin := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID, rbac.RoleUserAdmin())
|
||||
|
||||
member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
member, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -494,11 +494,11 @@ func TestNotifyDeletedUser(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
user, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
user, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -530,11 +530,11 @@ func TestNotifyDeletedUser(t *testing.T) {
|
||||
|
||||
_, userAdmin := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID, rbac.RoleUserAdmin())
|
||||
|
||||
member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
member, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -625,7 +625,7 @@ func TestPostUsers(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.CreateUser(ctx, codersdk.CreateUserRequest{})
|
||||
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -639,11 +639,11 @@ func TestPostUsers(t *testing.T) {
|
||||
|
||||
me, err := client.User(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: me.Email,
|
||||
Username: me.Username,
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: uuid.New(),
|
||||
_, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: me.Email,
|
||||
Username: me.Username,
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{uuid.New()},
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
@@ -658,11 +658,11 @@ func TestPostUsers(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: uuid.New(),
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{uuid.New()},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
@@ -682,11 +682,11 @@ func TestPostUsers(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -735,12 +735,12 @@ func TestPostUsers(t *testing.T) {
|
||||
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,
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypeNone,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -767,12 +767,12 @@ func TestPostUsers(t *testing.T) {
|
||||
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,
|
||||
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
Email: email,
|
||||
Username: "someone-else",
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypeOIDC,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -804,11 +804,11 @@ func TestNotifyCreatedUser(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
// when
|
||||
user, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
user, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -833,11 +833,11 @@ func TestNotifyCreatedUser(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
userAdmin, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "user-admin@user.org",
|
||||
Username: "mr-user-admin",
|
||||
Password: "SomeSecurePassword!",
|
||||
userAdmin, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "user-admin@user.org",
|
||||
Username: "mr-user-admin",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -849,11 +849,11 @@ func TestNotifyCreatedUser(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// when
|
||||
member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
member, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -908,11 +908,11 @@ func TestUpdateUserProfile(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
existentUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "bruno@coder.com",
|
||||
Username: "bruno",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
existentUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "bruno@coder.com",
|
||||
Username: "bruno",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = client.UpdateUserProfile(ctx, codersdk.Me, codersdk.UpdateUserProfileRequest{
|
||||
@@ -990,11 +990,11 @@ func TestUpdateUserProfile(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "john@coder.com",
|
||||
Username: "john",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "john@coder.com",
|
||||
Username: "john",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = client.UpdateUserProfile(ctx, codersdk.Me, codersdk.UpdateUserProfileRequest{
|
||||
@@ -1032,11 +1032,11 @@ func TestUpdateUserPassword(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
member, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationID: owner.OrganizationID,
|
||||
member, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err, "create member")
|
||||
err = client.UpdateUserPassword(ctx, member.ID.String(), codersdk.UpdateUserPasswordRequest{
|
||||
@@ -1293,11 +1293,11 @@ func TestActivateDormantUser(t *testing.T) {
|
||||
me := coderdtest.CreateFirstUser(t, client)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
anotherUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationID: me.OrganizationID,
|
||||
anotherUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationIDs: []uuid.UUID{me.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1600,11 +1600,11 @@ func TestGetUsers(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
// No params is all users
|
||||
res, err := client.Users(ctx, codersdk.UsersRequest{})
|
||||
@@ -1626,11 +1626,11 @@ func TestGetUsers(t *testing.T) {
|
||||
active = append(active, firstUser)
|
||||
|
||||
// Alice will be suspended
|
||||
alice, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: first.OrganizationID,
|
||||
alice, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1638,11 +1638,11 @@ func TestGetUsers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Tom will be active
|
||||
tom, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "tom@email.com",
|
||||
Username: "tom",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: first.OrganizationID,
|
||||
tom, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "tom@email.com",
|
||||
Username: "tom",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1669,11 +1669,11 @@ func TestGetUsersPagination(t *testing.T) {
|
||||
_, err := client.User(ctx, first.UserID.String())
|
||||
require.NoError(t, err, "")
|
||||
|
||||
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: first.OrganizationID,
|
||||
_, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{first.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1750,11 +1750,11 @@ func TestWorkspacesByUser(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
newUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "test@coder.com",
|
||||
Username: "someone",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
newUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "test@coder.com",
|
||||
Username: "someone",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
auth, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
|
||||
@@ -1790,11 +1790,11 @@ func TestDormantUser(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
// Create a new user
|
||||
newUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "test@coder.com",
|
||||
Username: "someone",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
newUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "test@coder.com",
|
||||
Username: "someone",
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1841,11 +1841,11 @@ func TestSuspendedPagination(t *testing.T) {
|
||||
for i := 0; i < total; i++ {
|
||||
email := fmt.Sprintf("%d@coder.com", i)
|
||||
username := fmt.Sprintf("user%d", i)
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationID: orgID,
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: email,
|
||||
Username: username,
|
||||
Password: "MySecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{orgID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
users = append(users, user)
|
||||
|
||||
@@ -1206,11 +1206,11 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||
// Create a template-admin user in the same org. We don't use an owner
|
||||
// since they have access to everything.
|
||||
ownerClient = appDetails.SDKClient
|
||||
user, err := ownerClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "user@coder.com",
|
||||
Username: "user",
|
||||
Password: password,
|
||||
OrganizationID: appDetails.FirstUser.OrganizationID,
|
||||
user, err := ownerClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "user@coder.com",
|
||||
Username: "user",
|
||||
Password: password,
|
||||
OrganizationIDs: []uuid.UUID{appDetails.FirstUser.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1258,11 +1258,11 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||
Name: "a-different-org",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
userInOtherOrg, err := ownerClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "no-template-access@coder.com",
|
||||
Username: "no-template-access",
|
||||
Password: password,
|
||||
OrganizationID: otherOrg.ID,
|
||||
userInOtherOrg, err := ownerClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "no-template-access@coder.com",
|
||||
Username: "no-template-access",
|
||||
Password: password,
|
||||
OrganizationIDs: []uuid.UUID{otherOrg.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -451,7 +451,7 @@ func TestWorkspacesSortOrder(t *testing.T) {
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
secondUserClient, secondUser := coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, []rbac.RoleIdentifier{rbac.RoleOwner()}, func(r *codersdk.CreateUserRequest) {
|
||||
secondUserClient, secondUser := coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, []rbac.RoleIdentifier{rbac.RoleOwner()}, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Username = "zzz"
|
||||
})
|
||||
|
||||
|
||||
+63
-1
@@ -113,6 +113,11 @@ type CreateFirstUserResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
||||
}
|
||||
|
||||
// CreateUserRequest
|
||||
// Deprecated: Use CreateUserRequestWithOrgs instead. This will be removed.
|
||||
// TODO: When removing, we should rename CreateUserRequestWithOrgs -> CreateUserRequest
|
||||
// Then alias CreateUserRequestWithOrgs to CreateUserRequest.
|
||||
// @typescript-ignore CreateUserRequest
|
||||
type CreateUserRequest struct {
|
||||
Email string `json:"email" validate:"required,email" format:"email"`
|
||||
Username string `json:"username" validate:"required,username"`
|
||||
@@ -127,6 +132,45 @@ type CreateUserRequest struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id" validate:"" format:"uuid"`
|
||||
}
|
||||
|
||||
type CreateUserRequestWithOrgs struct {
|
||||
Email string `json:"email" validate:"required,email" format:"email"`
|
||||
Username string `json:"username" validate:"required,username"`
|
||||
Name string `json:"name" validate:"user_real_name"`
|
||||
Password string `json:"password"`
|
||||
// UserLoginType defaults to LoginTypePassword.
|
||||
UserLoginType LoginType `json:"login_type"`
|
||||
// OrganizationIDs is a list of organization IDs that the user should be a member of.
|
||||
OrganizationIDs []uuid.UUID `json:"organization_ids" validate:"" format:"uuid"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the unmarshal for the legacy param "organization_id".
|
||||
// To accommodate multiple organizations, the field has been switched to a slice.
|
||||
// The previous field will just be appended to the slice.
|
||||
// Note in the previous behavior, omitting the field would result in the
|
||||
// default org being applied, but that is no longer the case.
|
||||
// TODO: Remove this method in it's entirety after some period of time.
|
||||
// This will be released in v1.16.0, and is associated with the multiple orgs
|
||||
// feature.
|
||||
func (r *CreateUserRequestWithOrgs) UnmarshalJSON(data []byte) error {
|
||||
// By using a type alias, we prevent an infinite recursion when unmarshalling.
|
||||
// This allows us to use the default unmarshal behavior of the original type.
|
||||
type AliasedReq CreateUserRequestWithOrgs
|
||||
type DeprecatedCreateUserRequest struct {
|
||||
AliasedReq
|
||||
OrganizationID *uuid.UUID `json:"organization_id" format:"uuid"`
|
||||
}
|
||||
var dep DeprecatedCreateUserRequest
|
||||
err := json.Unmarshal(data, &dep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = CreateUserRequestWithOrgs(dep.AliasedReq)
|
||||
if dep.OrganizationID != nil {
|
||||
r.OrganizationIDs = append(r.OrganizationIDs, *dep.OrganizationID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateUserProfileRequest struct {
|
||||
Username string `json:"username" validate:"required,username"`
|
||||
Name string `json:"name" validate:"user_real_name"`
|
||||
@@ -288,8 +332,26 @@ func (c *Client) CreateFirstUser(ctx context.Context, req CreateFirstUserRequest
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// CreateUser creates a new user.
|
||||
// CreateUser
|
||||
// Deprecated: Use CreateUserWithOrgs instead. This will be removed.
|
||||
// TODO: When removing, we should rename CreateUserWithOrgs -> CreateUser
|
||||
// with an alias of CreateUserWithOrgs.
|
||||
func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (User, error) {
|
||||
if req.DisableLogin {
|
||||
req.UserLoginType = LoginTypeNone
|
||||
}
|
||||
return c.CreateUserWithOrgs(ctx, CreateUserRequestWithOrgs{
|
||||
Email: req.Email,
|
||||
Username: req.Username,
|
||||
Name: req.Name,
|
||||
Password: req.Password,
|
||||
UserLoginType: req.UserLoginType,
|
||||
OrganizationIDs: []uuid.UUID{req.OrganizationID},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateUserWithOrgs creates a new user.
|
||||
func (c *Client) CreateUserWithOrgs(ctx context.Context, req CreateUserRequestWithOrgs) (User, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/users", req)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package codersdk_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestDeprecatedCreateUserRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("DefaultOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := `
|
||||
{
|
||||
"email":"alice@coder.com",
|
||||
"password":"hunter2",
|
||||
"username":"alice",
|
||||
"name":"alice",
|
||||
"organization_id":"00000000-0000-0000-0000-000000000000",
|
||||
"disable_login":false,
|
||||
"login_type":"none"
|
||||
}
|
||||
`
|
||||
var req codersdk.CreateUserRequestWithOrgs
|
||||
err := json.Unmarshal([]byte(input), &req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, req.Email, "alice@coder.com")
|
||||
require.Equal(t, req.Password, "hunter2")
|
||||
require.Equal(t, req.Username, "alice")
|
||||
require.Equal(t, req.Name, "alice")
|
||||
require.Equal(t, req.OrganizationIDs, []uuid.UUID{uuid.Nil})
|
||||
require.Equal(t, req.UserLoginType, codersdk.LoginTypeNone)
|
||||
})
|
||||
|
||||
t.Run("MultipleOrganizations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := `
|
||||
{
|
||||
"email":"alice@coder.com",
|
||||
"password":"hunter2",
|
||||
"username":"alice",
|
||||
"name":"alice",
|
||||
"organization_id":"00000000-0000-0000-0000-000000000000",
|
||||
"organization_ids":["a618cb03-99fb-4380-adb6-aa801629a4cf","8309b0dc-44ea-435d-a9ff-72cb302835e4"],
|
||||
"disable_login":false,
|
||||
"login_type":"none"
|
||||
}
|
||||
`
|
||||
var req codersdk.CreateUserRequestWithOrgs
|
||||
err := json.Unmarshal([]byte(input), &req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, req.Email, "alice@coder.com")
|
||||
require.Equal(t, req.Password, "hunter2")
|
||||
require.Equal(t, req.Username, "alice")
|
||||
require.Equal(t, req.Name, "alice")
|
||||
require.ElementsMatch(t, req.OrganizationIDs,
|
||||
[]uuid.UUID{
|
||||
uuid.Nil,
|
||||
uuid.MustParse("a618cb03-99fb-4380-adb6-aa801629a4cf"),
|
||||
uuid.MustParse("8309b0dc-44ea-435d-a9ff-72cb302835e4"),
|
||||
})
|
||||
|
||||
require.Equal(t, req.UserLoginType, codersdk.LoginTypeNone)
|
||||
})
|
||||
|
||||
t.Run("OmittedOrganizations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := `
|
||||
{
|
||||
"email":"alice@coder.com",
|
||||
"password":"hunter2",
|
||||
"username":"alice",
|
||||
"name":"alice",
|
||||
"disable_login":false,
|
||||
"login_type":"none"
|
||||
}
|
||||
`
|
||||
var req codersdk.CreateUserRequestWithOrgs
|
||||
err := json.Unmarshal([]byte(input), &req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, req.OrganizationIDs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateUserRequestJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
marshalTest := func(t *testing.T, req codersdk.CreateUserRequestWithOrgs) {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
var req2 codersdk.CreateUserRequestWithOrgs
|
||||
err = json.Unmarshal(data, &req2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, req, req2)
|
||||
}
|
||||
|
||||
t.Run("MultipleOrganizations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := codersdk.CreateUserRequestWithOrgs{
|
||||
Email: coderdtest.RandomName(t),
|
||||
Username: coderdtest.RandomName(t),
|
||||
Name: coderdtest.RandomName(t),
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{uuid.New(), uuid.New()},
|
||||
}
|
||||
marshalTest(t, req)
|
||||
})
|
||||
|
||||
t.Run("SingleOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := codersdk.CreateUserRequestWithOrgs{
|
||||
Email: coderdtest.RandomName(t),
|
||||
Username: coderdtest.RandomName(t),
|
||||
Name: coderdtest.RandomName(t),
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{uuid.New()},
|
||||
}
|
||||
marshalTest(t, req)
|
||||
})
|
||||
|
||||
t.Run("NoOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := codersdk.CreateUserRequestWithOrgs{
|
||||
Email: coderdtest.RandomName(t),
|
||||
Username: coderdtest.RandomName(t),
|
||||
Name: coderdtest.RandomName(t),
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationIDs: []uuid.UUID{},
|
||||
}
|
||||
marshalTest(t, req)
|
||||
})
|
||||
}
|
||||
Generated
+10
-12
@@ -1284,15 +1284,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `scope` | `all` |
|
||||
| `scope` | `application_connect` |
|
||||
|
||||
## codersdk.CreateUserRequest
|
||||
## codersdk.CreateUserRequestWithOrgs
|
||||
|
||||
```json
|
||||
{
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
}
|
||||
@@ -1300,15 +1299,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. Deprecated: Set UserLoginType=LoginTypeDisabled instead. |
|
||||
| `email` | string | true | | |
|
||||
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | Login type defaults to LoginTypePassword. |
|
||||
| `name` | string | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
| `password` | string | false | | |
|
||||
| `username` | string | true | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ---------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------- |
|
||||
| `email` | string | true | | |
|
||||
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | Login type defaults to LoginTypePassword. |
|
||||
| `name` | string | false | | |
|
||||
| `organization_ids` | array of string | false | | Organization ids is a list of organization IDs that the user should be a member of. |
|
||||
| `password` | string | false | | |
|
||||
| `username` | string | true | | |
|
||||
|
||||
## codersdk.CreateWorkspaceBuildRequest
|
||||
|
||||
|
||||
Generated
+4
-5
@@ -81,11 +81,10 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||
|
||||
```json
|
||||
{
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
}
|
||||
@@ -93,9 +92,9 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ------------------------------------------------------------------ | -------- | ------------------- |
|
||||
| `body` | body | [codersdk.CreateUserRequest](schemas.md#codersdkcreateuserrequest) | true | Create user request |
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ---------------------------------------------------------------------------------- | -------- | ------------------- |
|
||||
| `body` | body | [codersdk.CreateUserRequestWithOrgs](schemas.md#codersdkcreateuserrequestwithorgs) | true | Create user request |
|
||||
|
||||
### Example responses
|
||||
|
||||
|
||||
@@ -757,11 +757,11 @@ func TestGroup(t *testing.T) {
|
||||
require.Contains(t, group.Members, user2.ReducedUser)
|
||||
|
||||
// cannot explicitly set a dormant user status so must create a new user
|
||||
anotherUser, err := userAdminClient.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationID: user.OrganizationID,
|
||||
anotherUser, err := userAdminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "coder@coder.com",
|
||||
Username: "coder",
|
||||
Password: "SomeStrongPassword!",
|
||||
OrganizationIDs: []uuid.UUID{user.OrganizationID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -228,11 +228,11 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
//nolint:gocritic // needed for SCIM
|
||||
dbUser, _, err = api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
|
||||
CreateUserRequest: codersdk.CreateUserRequest{
|
||||
Username: sUser.UserName,
|
||||
Email: email,
|
||||
OrganizationID: defaultOrganization.ID,
|
||||
dbUser, err = api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
|
||||
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
|
||||
Username: sUser.UserName,
|
||||
Email: email,
|
||||
OrganizationIDs: []uuid.UUID{defaultOrganization.ID},
|
||||
},
|
||||
LoginType: database.LoginTypeOIDC,
|
||||
// Do not send notifications to user admins as SCIM endpoint might be called sequentially to all users.
|
||||
|
||||
@@ -717,7 +717,7 @@ func TestEnterpriseUserLogin(t *testing.T) {
|
||||
Name: customRole.Name,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
},
|
||||
}, func(r *codersdk.CreateUserRequest) {
|
||||
}, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Password = "SomeSecurePassword!"
|
||||
r.UserLoginType = codersdk.LoginTypePassword
|
||||
})
|
||||
@@ -752,7 +752,7 @@ func TestEnterpriseUserLogin(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
|
||||
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
|
||||
r.Password = "SomeSecurePassword!"
|
||||
r.UserLoginType = codersdk.LoginTypePassword
|
||||
})
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
@@ -509,11 +508,11 @@ func TestEnterprisePostUser(t *testing.T) {
|
||||
request.Name = "another"
|
||||
})
|
||||
|
||||
_, err := notInOrg.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "some@domain.com",
|
||||
Username: "anotheruser",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: org.ID,
|
||||
_, err := notInOrg.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "some@domain.com",
|
||||
Username: "anotheruser",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{org.ID},
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
@@ -543,11 +542,11 @@ func TestEnterprisePostUser(t *testing.T) {
|
||||
|
||||
org := coderdenttest.CreateOrganization(t, other, coderdenttest.CreateOrganizationOptions{})
|
||||
|
||||
_, err := notInOrg.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "some@domain.com",
|
||||
Username: "anotheruser",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationID: org.ID,
|
||||
_, err := notInOrg.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "some@domain.com",
|
||||
Username: "anotheruser",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{org.ID},
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
@@ -559,7 +558,7 @@ func TestEnterprisePostUser(t *testing.T) {
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
|
||||
|
||||
client, firstUser := coderdenttest.New(t, &coderdenttest.Options{
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
@@ -578,14 +577,53 @@ func TestEnterprisePostUser(t *testing.T) {
|
||||
|
||||
// nolint:gocritic // intentional using the owner.
|
||||
// Manually making a user with the request instead of the coderdtest util
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.ErrorContains(t, err, "No organization specified")
|
||||
})
|
||||
|
||||
t.Run("MultipleOrganizations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
|
||||
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureMultipleOrganizations: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Add an extra org to assign member into
|
||||
second := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
|
||||
third := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
// nolint:gocritic // intentional using the owner.
|
||||
// Manually making a user with the request instead of the coderdtest util
|
||||
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "SomeSecurePassword!",
|
||||
OrganizationIDs: []uuid.UUID{
|
||||
second.ID,
|
||||
third.ID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, user.OrganizationIDs, 1)
|
||||
assert.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
|
||||
memberedOrgs, err := client.OrganizationsByUser(ctx, user.ID.String())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memberedOrgs, 2)
|
||||
require.ElementsMatch(t, []uuid.UUID{second.ID, third.ID}, []uuid.UUID{memberedOrgs[0].ID, memberedOrgs[1].ID})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,11 +72,11 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error {
|
||||
|
||||
_, _ = fmt.Fprintln(logs, "Creating user:")
|
||||
|
||||
user, err = r.client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: r.cfg.User.OrganizationID,
|
||||
Username: r.cfg.User.Username,
|
||||
Email: r.cfg.User.Email,
|
||||
Password: password,
|
||||
user, err = r.client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
|
||||
OrganizationIDs: []uuid.UUID{r.cfg.User.OrganizationID},
|
||||
Username: r.cfg.User.Username,
|
||||
Email: r.cfg.User.Email,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create user: %w", err)
|
||||
|
||||
+1
-2
@@ -36,8 +36,7 @@ export const createUser = async (orgId: string) => {
|
||||
name: name,
|
||||
password: "s3cure&password!",
|
||||
login_type: "password",
|
||||
disable_login: false,
|
||||
organization_id: orgId,
|
||||
organization_ids: [orgId],
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
+1
-1
@@ -1130,7 +1130,7 @@ class ApiMethods {
|
||||
};
|
||||
|
||||
createUser = async (
|
||||
user: TypesGen.CreateUserRequest,
|
||||
user: TypesGen.CreateUserRequestWithOrgs,
|
||||
): Promise<TypesGen.User> => {
|
||||
const response = await this.axios.post<TypesGen.User>(
|
||||
"/api/v2/users",
|
||||
|
||||
Generated
+2
-3
@@ -303,14 +303,13 @@ export interface CreateTokenRequest {
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface CreateUserRequest {
|
||||
export interface CreateUserRequestWithOrgs {
|
||||
readonly email: string;
|
||||
readonly username: string;
|
||||
readonly name: string;
|
||||
readonly password: string;
|
||||
readonly login_type: LoginType;
|
||||
readonly disable_login: boolean;
|
||||
readonly organization_id: string;
|
||||
readonly organization_ids: Readonly<Array<string>>;
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
|
||||
@@ -61,7 +61,7 @@ export const authMethodLanguage = {
|
||||
};
|
||||
|
||||
export interface CreateUserFormProps {
|
||||
onSubmit: (user: TypesGen.CreateUserRequest) => void;
|
||||
onSubmit: (user: TypesGen.CreateUserRequestWithOrgs) => void;
|
||||
onCancel: () => void;
|
||||
error?: unknown;
|
||||
isLoading: boolean;
|
||||
@@ -86,21 +86,20 @@ const validationSchema = Yup.object({
|
||||
export const CreateUserForm: FC<
|
||||
React.PropsWithChildren<CreateUserFormProps>
|
||||
> = ({ onSubmit, onCancel, error, isLoading, authMethods }) => {
|
||||
const form: FormikContextType<TypesGen.CreateUserRequest> =
|
||||
useFormik<TypesGen.CreateUserRequest>({
|
||||
const form: FormikContextType<TypesGen.CreateUserRequestWithOrgs> =
|
||||
useFormik<TypesGen.CreateUserRequestWithOrgs>({
|
||||
initialValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
username: "",
|
||||
name: "",
|
||||
organization_id: "00000000-0000-0000-0000-000000000000",
|
||||
disable_login: false,
|
||||
organization_ids: ["00000000-0000-0000-0000-000000000000"],
|
||||
login_type: "",
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
});
|
||||
const getFieldHelpers = getFormHelpers<TypesGen.CreateUserRequest>(
|
||||
const getFieldHelpers = getFormHelpers<TypesGen.CreateUserRequestWithOrgs>(
|
||||
form,
|
||||
error,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user