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]
|
Usage: coder users create [flags]
|
||||||
|
|
||||||
[1mOptions[0m
|
[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
|
-e, --email string
|
||||||
Specifies an email address for the new user.
|
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
|
-p, --password string
|
||||||
Specifies a password for the new user.
|
Specifies a password for the new user.
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
@ -18,6 +19,7 @@ func (r *RootCmd) userCreate() *clibase.Cmd {
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
disableLogin bool
|
disableLogin bool
|
||||||
|
loginType string
|
||||||
)
|
)
|
||||||
client := new(codersdk.Client)
|
client := new(codersdk.Client)
|
||||||
cmd := &clibase.Cmd{
|
cmd := &clibase.Cmd{
|
||||||
|
@ -54,7 +56,18 @@ func (r *RootCmd) userCreate() *clibase.Cmd {
|
||||||
return err
|
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)
|
password, err = cryptorand.StringCharset(cryptorand.Human, 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -66,14 +79,22 @@ func (r *RootCmd) userCreate() *clibase.Cmd {
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
OrganizationID: organization.ID,
|
OrganizationID: organization.ID,
|
||||||
DisableLogin: disableLogin,
|
UserLoginType: userLoginType,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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."
|
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!
|
_, _ = 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),
|
Value: clibase.StringOf(&password),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Flag: "disable-login",
|
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. " +
|
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.",
|
"Be careful when using this flag as it can lock the user out of their account.",
|
||||||
Value: clibase.BoolOf(&disableLogin),
|
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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -7536,13 +7536,21 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"disable_login": {
|
"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"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "email"
|
"format": "email"
|
||||||
},
|
},
|
||||||
|
"login_type": {
|
||||||
|
"description": "UserLoginType defaults to LoginTypePassword.",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/codersdk.LoginType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"organization_id": {
|
"organization_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
@ -8449,6 +8457,7 @@ const docTemplate = `{
|
||||||
"codersdk.LoginType": {
|
"codersdk.LoginType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
"",
|
||||||
"password",
|
"password",
|
||||||
"github",
|
"github",
|
||||||
"oidc",
|
"oidc",
|
||||||
|
@ -8456,6 +8465,7 @@ const docTemplate = `{
|
||||||
"none"
|
"none"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"LoginTypeUnknown",
|
||||||
"LoginTypePassword",
|
"LoginTypePassword",
|
||||||
"LoginTypeGithub",
|
"LoginTypeGithub",
|
||||||
"LoginTypeOIDC",
|
"LoginTypeOIDC",
|
||||||
|
|
|
@ -6715,13 +6715,21 @@
|
||||||
"required": ["email", "username"],
|
"required": ["email", "username"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"disable_login": {
|
"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"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "email"
|
"format": "email"
|
||||||
},
|
},
|
||||||
|
"login_type": {
|
||||||
|
"description": "UserLoginType defaults to LoginTypePassword.",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/codersdk.LoginType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"organization_id": {
|
"organization_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
@ -7576,8 +7584,9 @@
|
||||||
},
|
},
|
||||||
"codersdk.LoginType": {
|
"codersdk.LoginType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["password", "github", "oidc", "token", "none"],
|
"enum": ["", "password", "github", "oidc", "token", "none"],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"LoginTypeUnknown",
|
||||||
"LoginTypePassword",
|
"LoginTypePassword",
|
||||||
"LoginTypeGithub",
|
"LoginTypeGithub",
|
||||||
"LoginTypeOIDC",
|
"LoginTypeOIDC",
|
||||||
|
|
|
@ -588,14 +588,7 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var sessionToken string
|
var sessionToken string
|
||||||
if !req.DisableLogin {
|
if req.DisableLogin || req.UserLoginType == codersdk.LoginTypeNone {
|
||||||
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
|
|
||||||
Email: req.Email,
|
|
||||||
Password: req.Password,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
sessionToken = login.SessionToken
|
|
||||||
} else {
|
|
||||||
// Cannot log in with a disabled login user. So make it an api key from
|
// Cannot log in with a disabled login user. So make it an api key from
|
||||||
// the client making this user.
|
// the client making this user.
|
||||||
token, err := client.CreateToken(context.Background(), user.ID.String(), codersdk.CreateTokenRequest{
|
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)
|
require.NoError(t, err)
|
||||||
sessionToken = token.Key
|
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 {
|
if user.Status == codersdk.UserStatusDormant {
|
||||||
|
|
|
@ -145,7 +145,7 @@ func TestUserLogin(t *testing.T) {
|
||||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||||
})
|
})
|
||||||
// Password auth should fail if the user is made without password login.
|
// 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()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, nil)
|
client := coderdtest.New(t, nil)
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
@ -160,6 +160,22 @@ func TestUserLogin(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
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) {
|
func TestUserAuthMethods(t *testing.T) {
|
||||||
|
|
|
@ -287,11 +287,27 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||||
return
|
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
|
// If password auth is disabled, don't allow new users to be
|
||||||
// created with a password!
|
// created with a password!
|
||||||
if api.DeploymentValues.DisablePasswordAuth {
|
if api.DeploymentValues.DisablePasswordAuth && req.UserLoginType == codersdk.LoginTypePassword {
|
||||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
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
|
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
|
var loginType database.LoginType
|
||||||
if req.DisableLogin {
|
switch req.UserLoginType {
|
||||||
|
case codersdk.LoginTypeNone:
|
||||||
loginType = database.LoginTypeNone
|
loginType = database.LoginTypeNone
|
||||||
} else {
|
case codersdk.LoginTypePassword:
|
||||||
err = userpassword.Validate(req.Password)
|
err = userpassword.Validate(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
@ -376,6 +386,14 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loginType = database.LoginTypePassword
|
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{
|
user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestUpdateUserProfile(t *testing.T) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ type APIKey struct {
|
||||||
type LoginType string
|
type LoginType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
LoginTypeUnknown LoginType = ""
|
||||||
LoginTypePassword LoginType = "password"
|
LoginTypePassword LoginType = "password"
|
||||||
LoginTypeGithub LoginType = "github"
|
LoginTypeGithub LoginType = "github"
|
||||||
LoginTypeOIDC LoginType = "oidc"
|
LoginTypeOIDC LoginType = "oidc"
|
||||||
|
|
|
@ -78,9 +78,12 @@ type CreateFirstUserResponse struct {
|
||||||
type CreateUserRequest struct {
|
type CreateUserRequest struct {
|
||||||
Email string `json:"email" validate:"required,email" format:"email"`
|
Email string `json:"email" validate:"required,email" format:"email"`
|
||||||
Username string `json:"username" validate:"required,username"`
|
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
|
// 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.
|
// from being able to use a password or any other authentication method to login.
|
||||||
|
// Deprecated: Set UserLoginType=LoginTypeDisabled instead.
|
||||||
DisableLogin bool `json:"disable_login"`
|
DisableLogin bool `json:"disable_login"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id" validate:"" format:"uuid"`
|
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",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -129,7 +129,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"password": "string",
|
"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",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"state_string": "string",
|
"state_string": "string",
|
||||||
"to_type": "password",
|
"to_type": "",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -245,7 +245,7 @@ curl -X DELETE http://coder-server:8080/api/v2/groups/{group} \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -307,7 +307,7 @@ curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -444,7 +444,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -502,6 +502,7 @@ Status Code **200**
|
||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
| ------------ | ----------- |
|
| ------------ | ----------- |
|
||||||
|
| `login_type` | `` |
|
||||||
| `login_type` | `password` |
|
| `login_type` | `password` |
|
||||||
| `login_type` | `github` |
|
| `login_type` | `github` |
|
||||||
| `login_type` | `oidc` |
|
| `login_type` | `oidc` |
|
||||||
|
@ -562,7 +563,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/groups
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -625,7 +626,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -988,7 +989,7 @@ curl -X PATCH http://coder-server:8080/api/v2/scim/v2/Users/{id} \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1040,7 +1041,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"role": "admin",
|
"role": "admin",
|
||||||
"roles": [
|
"roles": [
|
||||||
|
@ -1086,6 +1087,7 @@ Status Code **200**
|
||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
| ------------ | ----------- |
|
| ------------ | ----------- |
|
||||||
|
| `login_type` | `` |
|
||||||
| `login_type` | `password` |
|
| `login_type` | `password` |
|
||||||
| `login_type` | `github` |
|
| `login_type` | `github` |
|
||||||
| `login_type` | `oidc` |
|
| `login_type` | `oidc` |
|
||||||
|
@ -1197,7 +1199,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl/available \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1222,7 +1224,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl/available \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1278,6 +1280,7 @@ Status Code **200**
|
||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
| ------------ | ----------- |
|
| ------------ | ----------- |
|
||||||
|
| `login_type` | `` |
|
||||||
| `login_type` | `password` |
|
| `login_type` | `password` |
|
||||||
| `login_type` | `github` |
|
| `login_type` | `github` |
|
||||||
| `login_type` | `oidc` |
|
| `login_type` | `oidc` |
|
||||||
|
|
|
@ -792,7 +792,7 @@ _None_
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -817,7 +817,7 @@ _None_
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1086,7 +1086,7 @@ _None_
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1163,7 +1163,7 @@ _None_
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1388,7 +1388,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"password": "string",
|
"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,
|
"disable_login": true,
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
|
"login_type": "",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
|
@ -1673,13 +1674,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| 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. |
|
| `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 | | |
|
| `email` | string | true | | |
|
||||||
| `organization_id` | string | false | | |
|
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | Login type defaults to LoginTypePassword. |
|
||||||
| `password` | string | false | | |
|
| `organization_id` | string | false | | |
|
||||||
| `username` | string | true | | |
|
| `password` | string | false | | |
|
||||||
|
| `username` | string | true | | |
|
||||||
|
|
||||||
## codersdk.CreateWorkspaceBuildRequest
|
## codersdk.CreateWorkspaceBuildRequest
|
||||||
|
|
||||||
|
@ -2752,7 +2754,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -2970,7 +2972,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -3188,7 +3190,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
## codersdk.LoginType
|
## codersdk.LoginType
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"password"
|
""
|
||||||
```
|
```
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
@ -3197,6 +3199,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
|
|
||||||
| Value |
|
| Value |
|
||||||
| ---------- |
|
| ---------- |
|
||||||
|
| `` |
|
||||||
| `password` |
|
| `password` |
|
||||||
| `github` |
|
| `github` |
|
||||||
| `oidc` |
|
| `oidc` |
|
||||||
|
@ -3305,7 +3308,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
{
|
{
|
||||||
"expires_at": "2019-08-24T14:15:22Z",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"state_string": "string",
|
"state_string": "string",
|
||||||
"to_type": "password",
|
"to_type": "",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"role": "admin",
|
"role": "admin",
|
||||||
"roles": [
|
"roles": [
|
||||||
|
@ -5071,7 +5074,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -5196,7 +5199,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"login_type": "password"
|
"login_type": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ curl -X GET http://coder-server:8080/api/v2/users \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -79,6 +79,7 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||||
{
|
{
|
||||||
"disable_login": true,
|
"disable_login": true,
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
|
"login_type": "",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
|
@ -102,7 +103,7 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -360,7 +361,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -411,7 +412,7 @@ curl -X DELETE http://coder-server:8080/api/v2/users/{user} \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -821,7 +822,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/login-type \
|
||||||
|
|
||||||
```json
|
```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",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1056,7 +1057,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1117,7 +1118,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1168,7 +1169,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
@ -1219,7 +1220,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "password",
|
"login_type": "",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,14 +10,6 @@ coder users create [flags]
|
||||||
|
|
||||||
## Options
|
## 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
|
### -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.
|
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
|
### -p, --password
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|
|
|
@ -131,7 +131,7 @@ fatal() {
|
||||||
trap 'fatal "Script encountered an error"' ERR
|
trap 'fatal "Script encountered an error"' ERR
|
||||||
|
|
||||||
cdroot
|
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'
|
echo '== Waiting for Coder to become ready'
|
||||||
# Start the timeout in the background so interrupting this script
|
# Start the timeout in the background so interrupting this script
|
||||||
|
|
|
@ -247,6 +247,7 @@ export interface CreateUserRequest {
|
||||||
readonly email: string
|
readonly email: string
|
||||||
readonly username: string
|
readonly username: string
|
||||||
readonly password: string
|
readonly password: string
|
||||||
|
readonly login_type: LoginType
|
||||||
readonly disable_login: boolean
|
readonly disable_login: boolean
|
||||||
readonly organization_id: string
|
readonly organization_id: string
|
||||||
}
|
}
|
||||||
|
@ -1674,8 +1675,9 @@ export type LogSource = "provisioner" | "provisioner_daemon"
|
||||||
export const LogSources: LogSource[] = ["provisioner", "provisioner_daemon"]
|
export const LogSources: LogSource[] = ["provisioner", "provisioner_daemon"]
|
||||||
|
|
||||||
// From codersdk/apikey.go
|
// From codersdk/apikey.go
|
||||||
export type LoginType = "github" | "none" | "oidc" | "password" | "token"
|
export type LoginType = "" | "github" | "none" | "oidc" | "password" | "token"
|
||||||
export const LoginTypes: LoginType[] = [
|
export const LoginTypes: LoginType[] = [
|
||||||
|
"",
|
||||||
"github",
|
"github",
|
||||||
"none",
|
"none",
|
||||||
"oidc",
|
"oidc",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { FullPageForm } from "../FullPageForm/FullPageForm"
|
||||||
import { Stack } from "../Stack/Stack"
|
import { Stack } from "../Stack/Stack"
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert"
|
import { ErrorAlert } from "components/Alert/ErrorAlert"
|
||||||
import { hasApiFieldErrors, isApiError } from "api/errors"
|
import { hasApiFieldErrors, isApiError } from "api/errors"
|
||||||
|
import MenuItem from "@mui/material/MenuItem"
|
||||||
|
|
||||||
export const Language = {
|
export const Language = {
|
||||||
emailLabel: "Email",
|
emailLabel: "Email",
|
||||||
|
@ -31,6 +32,7 @@ export interface CreateUserFormProps {
|
||||||
error?: unknown
|
error?: unknown
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
myOrgId: string
|
myOrgId: string
|
||||||
|
authMethods?: TypesGen.AuthMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
|
@ -38,13 +40,31 @@ const validationSchema = Yup.object({
|
||||||
.trim()
|
.trim()
|
||||||
.email(Language.emailInvalid)
|
.email(Language.emailInvalid)
|
||||||
.required(Language.emailRequired),
|
.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),
|
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<
|
export const CreateUserForm: FC<
|
||||||
React.PropsWithChildren<CreateUserFormProps>
|
React.PropsWithChildren<CreateUserFormProps>
|
||||||
> = ({ onSubmit, onCancel, error, isLoading, myOrgId }) => {
|
> = ({ onSubmit, onCancel, error, isLoading, myOrgId, authMethods }) => {
|
||||||
const form: FormikContextType<TypesGen.CreateUserRequest> =
|
const form: FormikContextType<TypesGen.CreateUserRequest> =
|
||||||
useFormik<TypesGen.CreateUserRequest>({
|
useFormik<TypesGen.CreateUserRequest>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -53,6 +73,7 @@ export const CreateUserForm: FC<
|
||||||
username: "",
|
username: "",
|
||||||
organization_id: myOrgId,
|
organization_id: myOrgId,
|
||||||
disable_login: false,
|
disable_login: false,
|
||||||
|
login_type: "password",
|
||||||
},
|
},
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
@ -62,6 +83,42 @@ export const CreateUserForm: FC<
|
||||||
error,
|
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 (
|
return (
|
||||||
<FullPageForm title="Create user">
|
<FullPageForm title="Create user">
|
||||||
{isApiError(error) && !hasApiFieldErrors(error) && (
|
{isApiError(error) && !hasApiFieldErrors(error) && (
|
||||||
|
@ -85,13 +142,39 @@ export const CreateUserForm: FC<
|
||||||
label={Language.emailLabel}
|
label={Language.emailLabel}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
{...getFieldHelpers("password")}
|
{...getFieldHelpers(
|
||||||
|
"password",
|
||||||
|
form.values.login_type === "password"
|
||||||
|
? ""
|
||||||
|
: "No password required for this login type",
|
||||||
|
)}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
fullWidth
|
fullWidth
|
||||||
id="password"
|
id="password"
|
||||||
|
data-testid="password-input"
|
||||||
|
disabled={form.values.login_type !== "password"}
|
||||||
label={Language.passwordLabel}
|
label={Language.passwordLabel}
|
||||||
type="password"
|
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>
|
</Stack>
|
||||||
<FormFooter onCancel={onCancel} isLoading={isLoading} />
|
<FormFooter onCancel={onCancel} isLoading={isLoading} />
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -21,7 +21,7 @@ const renderCreateUserPage = async () => {
|
||||||
const fillForm = async ({
|
const fillForm = async ({
|
||||||
username = "someuser",
|
username = "someuser",
|
||||||
email = "someone@coder.com",
|
email = "someone@coder.com",
|
||||||
password = "password",
|
password = "SomeSecurePassword!",
|
||||||
}: {
|
}: {
|
||||||
username?: string
|
username?: string
|
||||||
email?: string
|
email?: string
|
||||||
|
@ -29,10 +29,15 @@ const fillForm = async ({
|
||||||
}) => {
|
}) => {
|
||||||
const usernameField = screen.getByLabelText(FormLanguage.usernameLabel)
|
const usernameField = screen.getByLabelText(FormLanguage.usernameLabel)
|
||||||
const emailField = screen.getByLabelText(FormLanguage.emailLabel)
|
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(usernameField, username)
|
||||||
await userEvent.type(emailField, email)
|
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(
|
const submitButton = await screen.findByText(
|
||||||
FooterLanguage.defaultSubmitLabel,
|
FooterLanguage.defaultSubmitLabel,
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,8 @@ import * as TypesGen from "../../../api/typesGenerated"
|
||||||
import { CreateUserForm } from "../../../components/CreateUserForm/CreateUserForm"
|
import { CreateUserForm } from "../../../components/CreateUserForm/CreateUserForm"
|
||||||
import { Margins } from "../../../components/Margins/Margins"
|
import { Margins } from "../../../components/Margins/Margins"
|
||||||
import { pageTitle } from "../../../utils/page"
|
import { pageTitle } from "../../../utils/page"
|
||||||
|
import { getAuthMethods } from "api/api"
|
||||||
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
|
||||||
export const Language = {
|
export const Language = {
|
||||||
unknownError: "Oops, an unknown error occurred.",
|
unknownError: "Oops, an unknown error occurred.",
|
||||||
|
@ -25,6 +27,13 @@ export const CreateUserPage: FC = () => {
|
||||||
})
|
})
|
||||||
const { error } = createUserState.context
|
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 (
|
return (
|
||||||
<Margins>
|
<Margins>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -33,6 +42,7 @@ export const CreateUserPage: FC = () => {
|
||||||
|
|
||||||
<CreateUserForm
|
<CreateUserForm
|
||||||
error={error}
|
error={error}
|
||||||
|
authMethods={authMethods}
|
||||||
onSubmit={(user: TypesGen.CreateUserRequest) =>
|
onSubmit={(user: TypesGen.CreateUserRequest) =>
|
||||||
createUserSend({ type: "CREATE", user })
|
createUserSend({ type: "CREATE", user })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue