mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: implement sign up with GitHub for the first user (#16629)
Second PR to address https://github.com/coder/coder/issues/16230. See the issue for more context and discussion. It adds a "Continue with GitHub" button to the `/setup` page, so the deployment's admin can sign up with it. It also removes the "Username" and "Full Name" fields to make signing up with email faster. In the email flow, the username is now auto-generated based on the email, and full name is left empty. <img width="1512" alt="Screenshot 2025-02-21 at 17 51 22" src="https://github.com/user-attachments/assets/e7c6986b-c05e-458b-bb01-c3aea3b74c0e" /> There's a separate, follow up issue to visually align the `/setup` page with the new design system: https://github.com/coder/coder/issues/16653
This commit is contained in:
@ -27,6 +27,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/cryptokeys"
|
||||
"github.com/coder/coder/v2/coderd/idpsync"
|
||||
"github.com/coder/coder/v2/coderd/jwtutils"
|
||||
"github.com/coder/coder/v2/coderd/telemetry"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/apikey"
|
||||
@ -1054,6 +1055,10 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
|
||||
defer params.CommitAuditLogs()
|
||||
if err != nil {
|
||||
if httpErr := idpsync.IsHTTPError(err); httpErr != nil {
|
||||
// In the device flow, the error page is rendered client-side.
|
||||
if api.GithubOAuth2Config.DeviceFlowEnabled && httpErr.RenderStaticPage {
|
||||
httpErr.RenderStaticPage = false
|
||||
}
|
||||
httpErr.Write(rw, r)
|
||||
return
|
||||
}
|
||||
@ -1634,7 +1639,17 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
isConvertLoginType = true
|
||||
}
|
||||
|
||||
if user.ID == uuid.Nil && !params.AllowSignups {
|
||||
// nolint:gocritic // Getting user count is a system function.
|
||||
userCount, err := tx.GetUserCount(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to fetch user count: %w", err)
|
||||
}
|
||||
|
||||
// Allow the first user to sign up with OIDC, regardless of
|
||||
// whether signups are enabled or not.
|
||||
allowSignup := userCount == 0 || params.AllowSignups
|
||||
|
||||
if user.ID == uuid.Nil && !allowSignup {
|
||||
signupsDisabledText := "Please contact your Coder administrator to request access."
|
||||
if api.OIDCConfig != nil && api.OIDCConfig.SignupsDisabledText != "" {
|
||||
signupsDisabledText = render.HTMLFromMarkdown(api.OIDCConfig.SignupsDisabledText)
|
||||
@ -1695,6 +1710,12 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
return xerrors.Errorf("unable to fetch default organization: %w", err)
|
||||
}
|
||||
|
||||
rbacRoles := []string{}
|
||||
// If this is the first user, add the owner role.
|
||||
if userCount == 0 {
|
||||
rbacRoles = append(rbacRoles, rbac.RoleOwner().String())
|
||||
}
|
||||
|
||||
//nolint:gocritic
|
||||
user, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
|
||||
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
|
||||
@ -1709,10 +1730,20 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
},
|
||||
LoginType: params.LoginType,
|
||||
accountCreatorName: "oauth",
|
||||
RBACRoles: rbacRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create user: %w", err)
|
||||
}
|
||||
|
||||
if userCount == 0 {
|
||||
telemetryUser := telemetry.ConvertUser(user)
|
||||
// The email is not anonymized for the first user.
|
||||
telemetryUser.Email = &user.Email
|
||||
api.Telemetry.Report(&telemetry.Snapshot{
|
||||
Users: []telemetry.User{telemetryUser},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Activate dormant user on sign-in
|
||||
|
Reference in New Issue
Block a user