Skip to content

Commit 6c4c3d6

Browse files
authored
feat: add login type 'none' to prevent password login (#8009)
* feat: add login type 'none' to prevent login Users with this login type must use tokens to authenticate. Tokens must come from some other source, not a /login with password authentication
1 parent cbd49ab commit 6c4c3d6

File tree

18 files changed

+160
-41
lines changed

18 files changed

+160
-41
lines changed

cli/testdata/coder_users_create_--help.golden

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Usage: coder users create [flags]
22

33
Options
4+
--disable-login bool
5+
Disabling login for a user prevents the user from authenticating via
6+
password or IdP login. Authentication requires an API key/token
7+
generated by an admin. Be careful when using this flag as it can lock
8+
the user out of their account.
9+
410
-e, --email string
511
Specifies an email address for the new user.
612

cli/usercreate.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import (
1414

1515
func (r *RootCmd) userCreate() *clibase.Cmd {
1616
var (
17-
email string
18-
username string
19-
password string
17+
email string
18+
username string
19+
password string
20+
disableLogin bool
2021
)
2122
client := new(codersdk.Client)
2223
cmd := &clibase.Cmd{
@@ -53,7 +54,7 @@ func (r *RootCmd) userCreate() *clibase.Cmd {
5354
return err
5455
}
5556
}
56-
if password == "" {
57+
if password == "" && !disableLogin {
5758
password, err = cryptorand.StringCharset(cryptorand.Human, 20)
5859
if err != nil {
5960
return err
@@ -65,10 +66,16 @@ func (r *RootCmd) userCreate() *clibase.Cmd {
6566
Username: username,
6667
Password: password,
6768
OrganizationID: organization.ID,
69+
DisableLogin: disableLogin,
6870
})
6971
if err != nil {
7072
return err
7173
}
74+
authenticationMethod := `Your password is: ` + cliui.DefaultStyles.Field.Render(password)
75+
if disableLogin {
76+
authenticationMethod = "Login has been disabled for this user. Contact your administrator to authenticate."
77+
}
78+
7279
_, _ = fmt.Fprintln(inv.Stderr, `A new user has been created!
7380
Share the instructions below to get them started.
7481
`+cliui.DefaultStyles.Placeholder.Render("—————————————————————————————————————————————————")+`
@@ -78,7 +85,7 @@ https://github.com/coder/coder/releases
7885
Run `+cliui.DefaultStyles.Code.Render("coder login "+client.URL.String())+` to authenticate.
7986
8087
Your email is: `+cliui.DefaultStyles.Field.Render(email)+`
81-
Your password is: `+cliui.DefaultStyles.Field.Render(password)+`
88+
`+authenticationMethod+`
8289
8390
Create a workspace `+cliui.DefaultStyles.Code.Render("coder create")+`!`)
8491
return nil
@@ -103,6 +110,12 @@ Create a workspace `+cliui.DefaultStyles.Code.Render("coder create")+`!`)
103110
Description: "Specifies a password for the new user.",
104111
Value: clibase.StringOf(&password),
105112
},
113+
{
114+
Flag: "disable-login",
115+
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. " +
116+
"Be careful when using this flag as it can lock the user out of their account.",
117+
Value: clibase.BoolOf(&disableLogin),
118+
},
106119
}
107120
return cmd
108121
}

coderd/apidoc/docs.go

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderdtest/coderdtest.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -503,36 +503,57 @@ func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirst
503503

504504
// CreateAnotherUser creates and authenticates a new user.
505505
func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, roles ...string) (*codersdk.Client, codersdk.User) {
506-
return createAnotherUserRetry(t, client, organizationID, 5, roles...)
506+
return createAnotherUserRetry(t, client, organizationID, 5, roles)
507507
}
508508

509-
func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, retries int, roles ...string) (*codersdk.Client, codersdk.User) {
509+
func CreateAnotherUserMutators(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, roles []string, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
510+
return createAnotherUserRetry(t, client, organizationID, 5, roles, mutators...)
511+
}
512+
513+
func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []string, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
510514
req := codersdk.CreateUserRequest{
511515
Email: namesgenerator.GetRandomName(10) + "@coder.com",
512516
Username: randomUsername(t),
513517
Password: "SomeSecurePassword!",
514518
OrganizationID: organizationID,
515519
}
520+
for _, m := range mutators {
521+
m(&req)
522+
}
516523

517524
user, err := client.CreateUser(context.Background(), req)
518525
var apiError *codersdk.Error
519526
// If the user already exists by username or email conflict, try again up to "retries" times.
520527
if err != nil && retries >= 0 && xerrors.As(err, &apiError) {
521528
if apiError.StatusCode() == http.StatusConflict {
522529
retries--
523-
return createAnotherUserRetry(t, client, organizationID, retries, roles...)
530+
return createAnotherUserRetry(t, client, organizationID, retries, roles)
524531
}
525532
}
526533
require.NoError(t, err)
527534

528-
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
529-
Email: req.Email,
530-
Password: req.Password,
531-
})
532-
require.NoError(t, err)
535+
var sessionToken string
536+
if !req.DisableLogin {
537+
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
538+
Email: req.Email,
539+
Password: req.Password,
540+
})
541+
require.NoError(t, err)
542+
sessionToken = login.SessionToken
543+
} else {
544+
// Cannot log in with a disabled login user. So make it an api key from
545+
// the client making this user.
546+
token, err := client.CreateToken(context.Background(), user.ID.String(), codersdk.CreateTokenRequest{
547+
Lifetime: time.Hour * 24,
548+
Scope: codersdk.APIKeyScopeAll,
549+
TokenName: "no-password-user-token",
550+
})
551+
require.NoError(t, err)
552+
sessionToken = token.Key
553+
}
533554

534555
other := codersdk.New(client.URL)
535-
other.SetSessionToken(login.SessionToken)
556+
other.SetSessionToken(sessionToken)
536557
t.Cleanup(func() {
537558
other.HTTPClient.CloseIdleConnections()
538559
})

coderd/database/dump.sql

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT
2+
-- EXISTS".
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TYPE login_type ADD VALUE IF NOT EXISTS 'none';
2+
3+
COMMENT ON TYPE login_type IS 'Specifies the method of authentication. "none" is a special case in which no authentication method is allowed.';

coderd/database/models.go

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/userauth_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ func TestUserLogin(t *testing.T) {
5656
require.ErrorAs(t, err, &apiErr)
5757
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
5858
})
59+
// Password auth should fail if the user is made without password login.
60+
t.Run("LoginTypeNone", func(t *testing.T) {
61+
t.Parallel()
62+
client := coderdtest.New(t, nil)
63+
user := coderdtest.CreateFirstUser(t, client)
64+
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
65+
r.Password = ""
66+
r.DisableLogin = true
67+
})
68+
69+
_, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
70+
Email: anotherUser.Email,
71+
Password: "SomeSecurePassword!",
72+
})
73+
require.Error(t, err)
74+
})
5975
}
6076

6177
func TestUserAuthMethods(t *testing.T) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy