Skip to content

Commit fb22c21

Browse files
committed
Merge branch 'main' into flaky-test-1
2 parents 44408b3 + ee74df3 commit fb22c21

15 files changed

+149
-77
lines changed

agent/agent.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ func (a *agent) trackConnGoroutine(fn func()) error {
245245
return nil
246246
}
247247

248-
func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (network *tailnet.Conn, err error) {
248+
func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (_ *tailnet.Conn, err error) {
249249
a.closeMutex.Lock()
250250
if a.isClosed() {
251251
a.closeMutex.Unlock()
252252
return nil, xerrors.New("closed")
253253
}
254-
network, err = tailnet.NewConn(&tailnet.Options{
254+
network, err := tailnet.NewConn(&tailnet.Options{
255255
Addresses: []netip.Prefix{netip.PrefixFrom(codersdk.TailnetIP, 128)},
256256
DERPMap: derpMap,
257257
Logger: a.logger.Named("tailnet"),
@@ -266,7 +266,6 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (ne
266266
network.Close()
267267
}
268268
}()
269-
a.network = network
270269
a.closeMutex.Unlock()
271270

272271
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSSHPort))

cli/deployment/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ func newConfig() *codersdk.DeploymentConfig {
216216
Flag: "oidc-client-secret",
217217
Secret: true,
218218
},
219-
EmailDomain: &codersdk.DeploymentConfigField[string]{
219+
EmailDomain: &codersdk.DeploymentConfigField[[]string]{
220220
Name: "OIDC Email Domain",
221-
Usage: "Email domain that clients logging in with OIDC must match.",
221+
Usage: "Email domains that clients logging in with OIDC must match.",
222222
Flag: "oidc-email-domain",
223223
},
224224
IssuerURL: &codersdk.DeploymentConfigField[string]{

cli/deployment/config_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func TestConfig(t *testing.T) {
154154
},
155155
Valid: func(config *codersdk.DeploymentConfig) {
156156
require.Equal(t, config.OIDC.IssuerURL.Value, "https://accounts.google.com")
157-
require.Equal(t, config.OIDC.EmailDomain.Value, "coder.com")
157+
require.Equal(t, config.OIDC.EmailDomain.Value, []string{"coder.com"})
158158
require.Equal(t, config.OIDC.ClientID.Value, "client")
159159
require.Equal(t, config.OIDC.ClientSecret.Value, "secret")
160160
require.False(t, config.OIDC.AllowSignups.Value)

cli/testdata/coder_server_--help.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ Flags:
9595
Consumes $CODER_OIDC_CLIENT_ID
9696
--oidc-client-secret string Client secret to use for Login with OIDC.
9797
Consumes $CODER_OIDC_CLIENT_SECRET
98-
--oidc-email-domain string Email domain that clients logging in with
99-
OIDC must match.
98+
--oidc-email-domain strings Email domains that clients logging in
99+
with OIDC must match.
100100
Consumes $CODER_OIDC_EMAIL_DOMAIN
101101
--oidc-ignore-email-verified Ignore the email_verified claim from the
102102
upstream provider.

coderd/userauth.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ type OIDCConfig struct {
192192
httpmw.OAuth2Config
193193

194194
Verifier *oidc.IDTokenVerifier
195-
// EmailDomain is the domain to enforce when a user authenticates.
196-
EmailDomain string
195+
// EmailDomains are the domains to enforce when a user authenticates.
196+
EmailDomain []string
197197
AllowSignups bool
198198
// IgnoreEmailVerified allows ignoring the email_verified claim
199199
// from an upstream OIDC provider. See #5065 for context.
@@ -289,10 +289,17 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
289289
}
290290
username = httpapi.UsernameFrom(username)
291291
}
292-
if api.OIDCConfig.EmailDomain != "" {
293-
if !strings.HasSuffix(strings.ToLower(email), strings.ToLower(api.OIDCConfig.EmailDomain)) {
292+
if len(api.OIDCConfig.EmailDomain) > 0 {
293+
ok = false
294+
for _, domain := range api.OIDCConfig.EmailDomain {
295+
if strings.HasSuffix(strings.ToLower(email), strings.ToLower(domain)) {
296+
ok = true
297+
break
298+
}
299+
}
300+
if !ok {
294301
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
295-
Message: fmt.Sprintf("Your email %q is not a part of the %q domain!", email, api.OIDCConfig.EmailDomain),
302+
Message: fmt.Sprintf("Your email %q is not in domains %q !", email, api.OIDCConfig.EmailDomain),
296303
})
297304
return
298305
}

coderd/userauth_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ func TestUserOIDC(t *testing.T) {
482482
Name string
483483
Claims jwt.MapClaims
484484
AllowSignups bool
485-
EmailDomain string
485+
EmailDomain []string
486486
Username string
487487
AvatarURL string
488488
StatusCode int
@@ -528,17 +528,21 @@ func TestUserOIDC(t *testing.T) {
528528
"email_verified": true,
529529
},
530530
AllowSignups: true,
531-
EmailDomain: "coder.com",
532-
StatusCode: http.StatusForbidden,
531+
EmailDomain: []string{
532+
"coder.com",
533+
},
534+
StatusCode: http.StatusForbidden,
533535
}, {
534536
Name: "EmailDomainCaseInsensitive",
535537
Claims: jwt.MapClaims{
536538
"email": "kyle@KWC.io",
537539
"email_verified": true,
538540
},
539541
AllowSignups: true,
540-
EmailDomain: "kwc.io",
541-
StatusCode: http.StatusTemporaryRedirect,
542+
EmailDomain: []string{
543+
"kwc.io",
544+
},
545+
StatusCode: http.StatusTemporaryRedirect,
542546
}, {
543547
Name: "EmptyClaims",
544548
Claims: jwt.MapClaims{},

coderd/workspaces.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,11 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
672672
var dbTTL sql.NullInt64
673673

674674
err := api.Database.InTx(func(s database.Store) error {
675+
var validityErr error
675676
// don't override 0 ttl with template default here because it indicates disabled auto-stop
676-
dbTTL, err := validWorkspaceTTLMillis(req.TTLMillis, 0)
677-
if err != nil {
678-
return codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()}
677+
dbTTL, validityErr = validWorkspaceTTLMillis(req.TTLMillis, 0)
678+
if validityErr != nil {
679+
return codersdk.ValidationError{Field: "ttl_ms", Detail: validityErr.Error()}
679680
}
680681
if err := s.UpdateWorkspaceTTL(ctx, database.UpdateWorkspaceTTLParams{
681682
ID: workspace.ID,

codersdk/deploymentconfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ type OIDCConfig struct {
9191
AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"`
9292
ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"`
9393
ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"`
94-
EmailDomain *DeploymentConfigField[string] `json:"email_domain" typescript:",notnull"`
94+
EmailDomain *DeploymentConfigField[[]string] `json:"email_domain" typescript:",notnull"`
9595
IssuerURL *DeploymentConfigField[string] `json:"issuer_url" typescript:",notnull"`
9696
Scopes *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"`
9797
IgnoreEmailVerified *DeploymentConfigField[bool] `json:"ignore_email_verified" typescript:",notnull"`

docs/admin/auth.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Navigate to your Coder host and run the following command to start up the Coder
6363
server:
6464

6565
```console
66-
coder server --oidc-issuer-url="https://accounts.google.com" --oidc-email-domain="your-domain" --oidc-client-id="533...ent.com" --oidc-client-secret="G0CSP...7qSM"
66+
coder server --oidc-issuer-url="https://accounts.google.com" --oidc-email-domain="your-domain-1,your-domain-2" --oidc-client-id="533...ent.com" --oidc-client-secret="G0CSP...7qSM"
6767
```
6868

6969
Alternatively, if you are running Coder as a system service, you can achieve the
@@ -72,7 +72,7 @@ to the `/etc/coder.d/coder.env` file:
7272

7373
```console
7474
CODER_OIDC_ISSUER_URL="https://accounts.google.com"
75-
CODER_OIDC_EMAIL_DOMAIN="your-domain"
75+
CODER_OIDC_EMAIL_DOMAIN="your-domain-1,your-domain-2"
7676
CODER_OIDC_CLIENT_ID="533...ent.com"
7777
CODER_OIDC_CLIENT_SECRET="G0CSP...7qSM"
7878
```

site/src/api/typesGenerated.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ export interface OIDCConfig {
442442
readonly allow_signups: DeploymentConfigField<boolean>
443443
readonly client_id: DeploymentConfigField<string>
444444
readonly client_secret: DeploymentConfigField<string>
445-
readonly email_domain: DeploymentConfigField<string>
445+
readonly email_domain: DeploymentConfigField<string[]>
446446
readonly issuer_url: DeploymentConfigField<string>
447447
readonly scopes: DeploymentConfigField<string[]>
448448
readonly ignore_email_verified: DeploymentConfigField<boolean>

site/src/components/Resources/ResourceAvatar.stories.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,12 @@ UnknownResource.args = {
5050
type: "noexistentvalue",
5151
},
5252
}
53+
54+
export const EmptyIcon = Template.bind({})
55+
EmptyIcon.args = {
56+
resource: {
57+
...MockWorkspaceResource,
58+
type: "helm_release",
59+
icon: "",
60+
},
61+
}

site/src/components/Resources/ResourceAvatar.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { makeStyles } from "@material-ui/core/styles"
33
import React from "react"
44
import { WorkspaceResource } from "../../api/typesGenerated"
55

6+
const FALLBACK_ICON = "/icon/widgets.svg"
7+
68
// NOTE @jsjoeio, @BrunoQuaresma
79
// These resources (i.e. docker_image, kubernetes_deployment) map to Terraform
810
// resource types. These are the most used ones and are based on user usage.
911
// We may want to update from time-to-time.
10-
const DEFAULT_ICON_PATHS: {
12+
const BUILT_IN_ICON_PATHS: {
1113
[resourceType: WorkspaceResource["type"]]: string
1214
} = {
1315
docker_volume: "/icon/folder.svg",
@@ -19,15 +21,15 @@ const DEFAULT_ICON_PATHS: {
1921
google_compute_instance: "/icon/memory.svg",
2022
aws_instance: "/icon/memory.svg",
2123
kubernetes_deployment: "/icon/memory.svg",
22-
null_resource: "/icon/widgets.svg",
24+
null_resource: FALLBACK_ICON,
2325
}
2426

2527
const getIconPathResource = (resourceType: string): string => {
26-
if (resourceType in DEFAULT_ICON_PATHS) {
27-
return DEFAULT_ICON_PATHS[resourceType]
28+
if (resourceType in BUILT_IN_ICON_PATHS) {
29+
return BUILT_IN_ICON_PATHS[resourceType]
2830
}
2931

30-
return DEFAULT_ICON_PATHS[resourceType]
32+
return FALLBACK_ICON
3133
}
3234

3335
export type ResourceAvatarProps = { resource: WorkspaceResource }

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { AlertBanner } from "components/AlertBanner/AlertBanner"
1414
import { makeStyles } from "@material-ui/core/styles"
1515
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
1616
import { FullScreenLoader } from "components/Loader/FullScreenLoader"
17+
import { SelectedTemplate } from "./SelectedTemplate"
1718

1819
export enum CreateWorkspaceErrors {
1920
GET_TEMPLATES_ERROR = "getTemplatesError",
@@ -171,28 +172,7 @@ export const CreateWorkspacePageView: FC<
171172
className={styles.formSectionFields}
172173
>
173174
{props.selectedTemplate && (
174-
<Stack
175-
direction="row"
176-
spacing={2}
177-
className={styles.template}
178-
alignItems="center"
179-
>
180-
<div className={styles.templateIcon}>
181-
<img src={props.selectedTemplate.icon} alt="" />
182-
</div>
183-
<Stack direction="column" spacing={0.5}>
184-
<span className={styles.templateName}>
185-
{props.selectedTemplate.display_name.length > 0
186-
? props.selectedTemplate.display_name
187-
: props.selectedTemplate.name}
188-
</span>
189-
{props.selectedTemplate.description && (
190-
<span className={styles.templateDescription}>
191-
{props.selectedTemplate.description}
192-
</span>
193-
)}
194-
</Stack>
195-
</Stack>
175+
<SelectedTemplate template={props.selectedTemplate} />
196176
)}
197177

198178
<TextField
@@ -327,31 +307,6 @@ const useStyles = makeStyles((theme) => ({
327307
formSectionFields: {
328308
width: "100%",
329309
},
330-
331-
template: {
332-
padding: theme.spacing(2.5, 3),
333-
borderRadius: theme.shape.borderRadius,
334-
backgroundColor: theme.palette.background.paper,
335-
border: `1px solid ${theme.palette.divider}`,
336-
},
337-
338-
templateName: {
339-
fontSize: 16,
340-
},
341-
342-
templateDescription: {
343-
fontSize: 14,
344-
color: theme.palette.text.secondary,
345-
},
346-
347-
templateIcon: {
348-
width: theme.spacing(5),
349-
lineHeight: 1,
350-
351-
"& img": {
352-
width: "100%",
353-
},
354-
},
355310
}))
356311

357312
const useFormFooterStyles = makeStyles((theme) => ({
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import { MockTemplate } from "../../testHelpers/entities"
3+
import { SelectedTemplate, SelectedTemplateProps } from "./SelectedTemplate"
4+
5+
export default {
6+
title: "components/SelectedTemplate",
7+
component: SelectedTemplate,
8+
} as ComponentMeta<typeof SelectedTemplate>
9+
10+
const Template: Story<SelectedTemplateProps> = (args) => (
11+
<SelectedTemplate {...args} />
12+
)
13+
14+
export const WithIcon = Template.bind({})
15+
WithIcon.args = {
16+
template: MockTemplate,
17+
}
18+
19+
export const WithoutIcon = Template.bind({})
20+
WithoutIcon.args = {
21+
template: {
22+
...MockTemplate,
23+
icon: "",
24+
},
25+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import Avatar from "@material-ui/core/Avatar"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import { Template } from "api/typesGenerated"
4+
import { Stack } from "components/Stack/Stack"
5+
import React, { FC } from "react"
6+
import { firstLetter } from "util/firstLetter"
7+
8+
export interface SelectedTemplateProps {
9+
template: Template
10+
}
11+
12+
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
13+
const styles = useStyles()
14+
15+
return (
16+
<Stack
17+
direction="row"
18+
spacing={3}
19+
className={styles.template}
20+
alignItems="center"
21+
>
22+
<div className={styles.templateIcon}>
23+
{template.icon === "" ? (
24+
<Avatar>{firstLetter(template.name)}</Avatar>
25+
) : (
26+
<img src={template.icon} alt="" />
27+
)}
28+
</div>
29+
<Stack direction="column" spacing={0.5}>
30+
<span className={styles.templateName}>
31+
{template.display_name.length > 0
32+
? template.display_name
33+
: template.name}
34+
</span>
35+
{template.description && (
36+
<span className={styles.templateDescription}>
37+
{template.description}
38+
</span>
39+
)}
40+
</Stack>
41+
</Stack>
42+
)
43+
}
44+
45+
const useStyles = makeStyles((theme) => ({
46+
template: {
47+
padding: theme.spacing(2.5, 3),
48+
borderRadius: theme.shape.borderRadius,
49+
backgroundColor: theme.palette.background.paper,
50+
border: `1px solid ${theme.palette.divider}`,
51+
},
52+
53+
templateName: {
54+
fontSize: 16,
55+
},
56+
57+
templateDescription: {
58+
fontSize: 14,
59+
color: theme.palette.text.secondary,
60+
},
61+
62+
templateIcon: {
63+
width: theme.spacing(4),
64+
lineHeight: 1,
65+
66+
"& img": {
67+
width: "100%",
68+
},
69+
},
70+
}))

0 commit comments

Comments
 (0)