Skip to content

Commit a4258c7

Browse files
committed
setup with github oauth2
1 parent dab3105 commit a4258c7

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

coderd/userauth.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/coder/coder/v2/coderd/cryptokeys"
2828
"github.com/coder/coder/v2/coderd/idpsync"
2929
"github.com/coder/coder/v2/coderd/jwtutils"
30+
"github.com/coder/coder/v2/coderd/telemetry"
3031
"github.com/coder/coder/v2/coderd/util/ptr"
3132

3233
"github.com/coder/coder/v2/coderd/apikey"
@@ -1632,6 +1633,18 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
16321633
return xerrors.Errorf("unable to fetch default organization: %w", err)
16331634
}
16341635

1636+
// nolint:gocritic // Getting user count is a system function.
1637+
userCount, err := api.Database.GetUserCount(dbauthz.AsSystemRestricted(ctx))
1638+
if err != nil {
1639+
return xerrors.Errorf("unable to fetch user count: %w", err)
1640+
}
1641+
1642+
rbacRoles := []string{}
1643+
// If this is the first user, add the owner role.
1644+
if userCount == 0 {
1645+
rbacRoles = append(rbacRoles, rbac.RoleOwner().String())
1646+
}
1647+
16351648
//nolint:gocritic
16361649
user, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
16371650
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
@@ -1646,10 +1659,20 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
16461659
},
16471660
LoginType: params.LoginType,
16481661
accountCreatorName: "oauth",
1662+
RBACRoles: rbacRoles,
16491663
})
16501664
if err != nil {
16511665
return xerrors.Errorf("create user: %w", err)
16521666
}
1667+
1668+
if userCount == 0 {
1669+
telemetryUser := telemetry.ConvertUser(user)
1670+
// The email is not anonymized for the first user.
1671+
telemetryUser.Email = &user.Email
1672+
api.Telemetry.Report(&telemetry.Snapshot{
1673+
Users: []telemetry.User{telemetryUser},
1674+
})
1675+
}
16531676
}
16541677

16551678
// Activate dormant user on sign-in

coderd/users.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
118118
// @Success 201 {object} codersdk.CreateFirstUserResponse
119119
// @Router /users/first [post]
120120
func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
121+
// The first user can also be created via oidc, so if making changes to the flow,
122+
// ensure that the oidc flow is also updated.
121123
ctx := r.Context()
122124
var createUser codersdk.CreateFirstUserRequest
123125
if !httpapi.Read(ctx, rw, r, &createUser) {
@@ -218,9 +220,12 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
218220
api.Logger.Debug(ctx, "entitlements will not be refreshed")
219221
}
220222

223+
rbacRoles := []string{rbac.RoleOwner().String()}
224+
221225
telemetryUser := telemetry.ConvertUser(user)
222226
// Send the initial users email address!
223227
telemetryUser.Email = &user.Email
228+
telemetryUser.RBACRoles = rbacRoles
224229
api.Telemetry.Report(&telemetry.Snapshot{
225230
Users: []telemetry.User{telemetryUser},
226231
})
@@ -231,7 +236,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
231236
// Add the admin role to this first user.
232237
//nolint:gocritic // needed to create first user
233238
_, err = api.Database.UpdateUserRoles(dbauthz.AsSystemRestricted(ctx), database.UpdateUserRolesParams{
234-
GrantedRoles: []string{rbac.RoleOwner().String()},
239+
GrantedRoles: rbacRoles,
235240
ID: user.ID,
236241
})
237242
if err != nil {
@@ -1345,6 +1350,7 @@ type CreateUserRequest struct {
13451350
LoginType database.LoginType
13461351
SkipNotifications bool
13471352
accountCreatorName string
1353+
RBACRoles []string
13481354
}
13491355

13501356
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, error) {
@@ -1354,6 +1360,13 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
13541360
return database.User{}, xerrors.Errorf("invalid username %q: %w", req.Username, usernameValid)
13551361
}
13561362

1363+
// If the caller didn't specify rbac roles, default to
1364+
// a member of the site.
1365+
rbacRoles := []string{}
1366+
if req.RBACRoles != nil {
1367+
rbacRoles = req.RBACRoles
1368+
}
1369+
13571370
var user database.User
13581371
err := store.InTx(func(tx database.Store) error {
13591372
orgRoles := make([]string, 0)
@@ -1370,10 +1383,9 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
13701383
CreatedAt: dbtime.Now(),
13711384
UpdatedAt: dbtime.Now(),
13721385
HashedPassword: []byte{},
1373-
// All new users are defaulted to members of the site.
1374-
RBACRoles: []string{},
1375-
LoginType: req.LoginType,
1376-
Status: status,
1386+
RBACRoles: rbacRoles,
1387+
LoginType: req.LoginType,
1388+
Status: status,
13771389
}
13781390
// If a user signs up with OAuth, they can have no password!
13791391
if req.Password != "" {

site/src/pages/SetupPage/SetupPageView.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import GitHubIcon from "@mui/icons-material/GitHub";
12
import LoadingButton from "@mui/lab/LoadingButton";
23
import AlertTitle from "@mui/material/AlertTitle";
34
import Autocomplete from "@mui/material/Autocomplete";
5+
import Button from "@mui/material/Button";
46
import Checkbox from "@mui/material/Checkbox";
57
import Link from "@mui/material/Link";
68
import MenuItem from "@mui/material/MenuItem";
@@ -16,7 +18,6 @@ import { SignInLayout } from "components/SignInLayout/SignInLayout";
1618
import { Stack } from "components/Stack/Stack";
1719
import { type FormikContextType, useFormik } from "formik";
1820
import type { FC } from "react";
19-
import { useEffect } from "react";
2021
import { docs } from "utils/docs";
2122
import {
2223
getFormHelpers,
@@ -34,6 +35,7 @@ export const Language = {
3435
emailRequired: "Please enter an email address.",
3536
passwordRequired: "Please enter a password.",
3637
create: "Create account",
38+
githubCreate: "Create account with GitHub",
3739
welcomeMessage: <>Welcome to Coder</>,
3840
firstNameLabel: "First name",
3941
lastNameLabel: "Last name",
@@ -81,6 +83,11 @@ const numberOfDevelopersOptions = [
8183
"2500+",
8284
];
8385

86+
const iconStyles = {
87+
width: 16,
88+
height: 16,
89+
};
90+
8491
export interface SetupPageViewProps {
8592
onSubmit: (firstUser: TypesGen.CreateFirstUserRequest) => void;
8693
error?: unknown;
@@ -140,6 +147,19 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
140147
Let&lsquo;s create your first admin user account
141148
</div>
142149
</header>
150+
<div className="mb-8">
151+
<Button
152+
component="a"
153+
href="/api/v2/users/oauth2/github/callback"
154+
variant="contained"
155+
startIcon={<GitHubIcon css={iconStyles} />}
156+
fullWidth
157+
type="submit"
158+
size="xlarge"
159+
>
160+
{Language.githubCreate}
161+
</Button>
162+
</div>
143163
<VerticalForm onSubmit={form.handleSubmit}>
144164
<FormFields>
145165
<TextField

0 commit comments

Comments
 (0)