Skip to content

Commit 5fb6932

Browse files
committed
setup with github oauth2
1 parent dab3105 commit 5fb6932

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { SignInLayout } from "components/SignInLayout/SignInLayout";
1616
import { Stack } from "components/Stack/Stack";
1717
import { type FormikContextType, useFormik } from "formik";
1818
import type { FC } from "react";
19+
import Button from "@mui/material/Button";
20+
import GitHubIcon from "@mui/icons-material/GitHub";
1921
import { useEffect } from "react";
2022
import { docs } from "utils/docs";
2123
import {
@@ -34,6 +36,7 @@ export const Language = {
3436
emailRequired: "Please enter an email address.",
3537
passwordRequired: "Please enter a password.",
3638
create: "Create account",
39+
githubCreate: "Create account with GitHub",
3740
welcomeMessage: <>Welcome to Coder</>,
3841
firstNameLabel: "First name",
3942
lastNameLabel: "Last name",
@@ -81,6 +84,11 @@ const numberOfDevelopersOptions = [
8184
"2500+",
8285
];
8386

87+
const iconStyles = {
88+
width: 16,
89+
height: 16,
90+
};
91+
8492
export interface SetupPageViewProps {
8593
onSubmit: (firstUser: TypesGen.CreateFirstUserRequest) => void;
8694
error?: unknown;
@@ -140,6 +148,19 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
140148
Let&lsquo;s create your first admin user account
141149
</div>
142150
</header>
151+
<div className="mb-8">
152+
<Button
153+
component="a"
154+
href="/api/v2/users/oauth2/github/callback"
155+
variant="contained"
156+
startIcon={<GitHubIcon css={iconStyles} />}
157+
fullWidth
158+
type="submit"
159+
size="xlarge"
160+
>
161+
{Language.githubCreate}
162+
</Button>
163+
</div>
143164
<VerticalForm onSubmit={form.handleSubmit}>
144165
<FormFields>
145166
<TextField

0 commit comments

Comments
 (0)