Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9e3a1cc
feat(coderd/database): add oauth2_dcr_enabled site config setting
BobbyHo Jul 16, 2026
beb4746
feat: gate OAuth2 dynamic client registration behind an admin toggle
BobbyHo Jul 17, 2026
60b98c5
fix(coderd): default dynamic client registration to disabled
BobbyHo Jul 17, 2026
86af338
fix(coderd): use per-subtest context in parallel oauth2 test
BobbyHo Jul 17, 2026
4135b4d
fix(coderd): match oauth2 provider settings PUT route ID to its summary
BobbyHo Jul 17, 2026
a128d67
test(coderd/oauth2provider): unit test the DCR enabled/disabled gate
BobbyHo Jul 18, 2026
ef51374
docs(coderd): clarify DCR-never-configured comment wording
BobbyHo Jul 18, 2026
24c2d4a
docs(coderd/oauth2provider): explain why DCR flag isn't cached
BobbyHo Jul 18, 2026
71bebf0
test(coderd/oauth2provider): unit test the discovery metadata DCR gate
BobbyHo Jul 18, 2026
d2f687f
test(coderd): consolidate DCR settings permission tests into a table
BobbyHo Jul 18, 2026
a136a30
feat(cli): add coder oauth2-provider dcr enable/disable
BobbyHo Jul 20, 2026
2555c86
Merge remote-tracking branch 'origin/main' into coder-eng-3056-dcr-flag
BobbyHo Jul 20, 2026
2428fe7
fix(coderd/database): renumber migration to avoid collision with main
BobbyHo Jul 20, 2026
f36b93e
fix: record previous value in OAuth2 provider settings audit diff
BobbyHo Jul 20, 2026
4ec488e
Merge remote-tracking branch 'origin/main' into coder-eng-3056-dcr-flag
BobbyHo Jul 20, 2026
79a94ae
fix(coderd/database): renumber migration to avoid collision with main
BobbyHo Jul 20, 2026
752fe15
Merge remote-tracking branch 'origin/main' into coder-eng-3056-dcr-flag
BobbyHo Jul 21, 2026
43bfeec
docs(docs/admin/integrations): document the DCR enable/disable toggle
BobbyHo Jul 21, 2026
8e57b0c
refactor(coderd): push GetOAuth2DCREnabled's default into the query
BobbyHo Jul 22, 2026
324a8f3
fix(coderd): make the DCR settings read-old/write-new atomic
BobbyHo Jul 22, 2026
2e19569
refactor(cli): deduplicate oauth2-provider dcr enable/disable
BobbyHo Jul 22, 2026
15be31e
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 22, 2026
ec1de5f
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 27, 2026
9cca19f
fix(coderd/oauth2): use pointer field so PUT can omit unrelated settings
BobbyHo Jul 27, 2026
e93cd5f
fix(coderd/database/migrations): fix stale migration number in test
BobbyHo Jul 27, 2026
569a0eb
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 27, 2026
e60f2e3
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 27, 2026
a2c8127
fix(coderd/database/migrations): renumber to avoid collision with main
BobbyHo Jul 27, 2026
0dd2749
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 28, 2026
ba7a9e3
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 28, 2026
b58ac98
fix(coderd/database/migrations): renumber to 000557 to avoid collisio…
BobbyHo Jul 28, 2026
3c31bb7
Merge branch 'main' into coder-eng-3056-dcr-flag
BobbyHo Jul 28, 2026
daa597d
fix(coderd/database/migrations): renumber to 000558 to avoid collisio…
BobbyHo Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions cli/oauth2provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cli

import (
"fmt"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)

func (r *RootCmd) oauth2Provider() *serpent.Command {
cmd := &serpent.Command{
Use: "oauth2-provider",
Short: "Manage Coder OAuth2 provider settings",
Long: "Administrators can use these commands to change OAuth2 provider settings.\n" + FormatExamples(
Example{
Description: "Enable dynamic client registration (RFC 7591), allowing OAuth2/MCP clients to self-register without an admin creating an app first",
Command: "coder oauth2-provider dcr enable",
},
Example{
Description: "Disable dynamic client registration. Clients that already registered are unaffected; only new self-registration attempts are rejected",
Command: "coder oauth2-provider dcr disable",
},
),
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.oauth2ProviderDCR(),
},
}
return cmd
}

func (r *RootCmd) oauth2ProviderDCR() *serpent.Command {
cmd := &serpent.Command{
Use: "dcr",
Short: "Manage OAuth2 dynamic client registration (RFC 7591)",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.oauth2ProviderDCRToggle(dcrToggleEnable),
r.oauth2ProviderDCRToggle(dcrToggleDisable),
},
}
return cmd
}

// dcrToggleAction distinguishes the "enable" and "disable" subcommands of
// `coder oauth2-provider dcr`, which are otherwise identical.
type dcrToggleAction int

const (
dcrToggleDisable dcrToggleAction = iota
dcrToggleEnable
)

func (r *RootCmd) oauth2ProviderDCRToggle(action dcrToggleAction) *serpent.Command {
enabled := action == dcrToggleEnable
use, short, verb := "disable", "Disable OAuth2 dynamic client registration", "disable"
if enabled {
use, short, verb = "enable", "Enable OAuth2 dynamic client registration", "enable"
}

cmd := &serpent.Command{
Use: use,
Short: short,
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
),
Handler: func(inv *serpent.Invocation) error {
client, err := r.InitClient(inv)
if err != nil {
return err
}

_, err = client.PutOAuth2ProviderSettings(inv.Context(), codersdk.OAuth2ProviderSettings{
DynamicClientRegistrationEnabled: ptr.Ref(enabled),
})
if err != nil {
return xerrors.Errorf("unable to %s dynamic client registration: %w", verb, err)
}

state := "disabled"
if enabled {
state = "enabled"
}
_, _ = fmt.Fprintf(inv.Stderr, "Dynamic client registration is now %s.\n", state)
return nil
},
}
return cmd
}
82 changes: 82 additions & 0 deletions cli/oauth2provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cli_test

import (
"bytes"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)

func TestOAuth2ProviderDCR(t *testing.T) {
t.Parallel()

tests := []struct {
name string
command string
expectValue bool
expectMsg string
}{
{
name: "Enable",
command: "enable",
expectValue: true,
expectMsg: "Dynamic client registration is now enabled.",
},
{
name: "Disable",
command: "disable",
expectValue: false,
expectMsg: "Dynamic client registration is now disabled.",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)

inv, root := clitest.New(t, "oauth2-provider", "dcr", tt.command)
clitest.SetupConfig(t, client, root)

var buf bytes.Buffer
inv.Stderr = &buf
err := inv.Run()
require.NoError(t, err)
assert.Contains(t, buf.String(), tt.expectMsg)

ctx := testutil.Context(t, testutil.WaitShort)
settings, err := client.OAuth2ProviderSettings(ctx)
require.NoError(t, err)
require.NotNil(t, settings.DynamicClientRegistrationEnabled, "GET must always return a concrete value")
require.Equal(t, tt.expectValue, *settings.DynamicClientRegistrationEnabled)
})
}
}

func TestOAuth2ProviderDCR_RegularUser(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
anotherClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)

inv, root := clitest.New(t, "oauth2-provider", "dcr", "enable")
clitest.SetupConfig(t, anotherClient, root)

var buf bytes.Buffer
inv.Stderr = &buf
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
assert.Equal(t, http.StatusForbidden, sdkError.StatusCode())
}
1 change: 1 addition & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.logout(),
r.netcheck(),
r.notifications(),
r.oauth2Provider(),
r.organizations(),
r.portForward(),
r.publickey(),
Expand Down
103 changes: 52 additions & 51 deletions cli/testdata/coder_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,58 @@ USAGE:
$ coder templates init

SUBCOMMANDS:
autoupdate Toggle auto-update policy for a workspace
completion Install or update shell completion scripts for the
detected or chosen shell.
config-ssh Add an SSH Host entry for your workspaces "ssh
workspace.coder"
create Create a workspace
delete Delete a workspace
dotfiles Personalize your workspace by applying a canonical
dotfiles repository
external-auth Manage external authentication
favorite Add a workspace to your favorites
list List workspaces
login Authenticate with Coder deployment
logout Unauthenticate your local session
logs View logs for a workspace
netcheck Print network debug information for DERP and STUN
notifications Manage Coder notifications
open Open a workspace
organizations Organization related commands
ping Ping a workspace
port-forward Forward ports from a workspace to the local machine. For
reverse port forwarding, use "coder ssh -R".
provisioner View and manage provisioner daemons and jobs
publickey Output your Coder public key used for Git operations
rename Rename a workspace
reset-password Directly connect to the database to reset a user's
password
restart Restart a workspace
schedule Schedule automated start and stop times for workspaces
secret Manage secrets
server Start a Coder server
show Display details of a workspace's resources and agents
speedtest Run upload and download tests from your machine to a
workspace
ssh Start a shell into a workspace or run a command
start Start a workspace
stat Show resource usage for the current workspace.
state Manually manage Terraform state to fix broken workspaces
stop Stop a workspace
support Commands for troubleshooting issues with a Coder
deployment.
task Manage tasks
templates Manage templates
tokens Manage personal access tokens
unfavorite Remove a workspace from your favorites
update Will update and start a given workspace if it is out of
date. If the workspace is already running, it will be
stopped first.
users Manage users
version Show coder version
whoami Fetch authenticated user info for Coder deployment
autoupdate Toggle auto-update policy for a workspace
completion Install or update shell completion scripts for the
detected or chosen shell.
config-ssh Add an SSH Host entry for your workspaces "ssh
workspace.coder"
create Create a workspace
delete Delete a workspace
dotfiles Personalize your workspace by applying a canonical
dotfiles repository
external-auth Manage external authentication
favorite Add a workspace to your favorites
list List workspaces
login Authenticate with Coder deployment
logout Unauthenticate your local session
logs View logs for a workspace
netcheck Print network debug information for DERP and STUN
notifications Manage Coder notifications
oauth2-provider Manage Coder OAuth2 provider settings
open Open a workspace
organizations Organization related commands
ping Ping a workspace
port-forward Forward ports from a workspace to the local machine. For
reverse port forwarding, use "coder ssh -R".
provisioner View and manage provisioner daemons and jobs
publickey Output your Coder public key used for Git operations
rename Rename a workspace
reset-password Directly connect to the database to reset a user's
password
restart Restart a workspace
schedule Schedule automated start and stop times for workspaces
secret Manage secrets
server Start a Coder server
show Display details of a workspace's resources and agents
speedtest Run upload and download tests from your machine to a
workspace
ssh Start a shell into a workspace or run a command
start Start a workspace
stat Show resource usage for the current workspace.
state Manually manage Terraform state to fix broken workspaces
stop Stop a workspace
support Commands for troubleshooting issues with a Coder
deployment.
task Manage tasks
templates Manage templates
tokens Manage personal access tokens
unfavorite Remove a workspace from your favorites
update Will update and start a given workspace if it is out of
date. If the workspace is already running, it will be
stopped first.
users Manage users
version Show coder version
whoami Fetch authenticated user info for Coder deployment

GLOBAL OPTIONS:
Global options are applied to all commands. They can be set using environment
Expand Down
24 changes: 24 additions & 0 deletions cli/testdata/coder_oauth2-provider_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
coder v0.0.0-devel

USAGE:
coder oauth2-provider

Manage Coder OAuth2 provider settings

Administrators can use these commands to change OAuth2 provider settings.
- Enable dynamic client registration (RFC 7591), allowing OAuth2/MCP clients
to
self-register without an admin creating an app first:

$ coder oauth2-provider dcr enable

- Disable dynamic client registration. Clients that already registered are
unaffected; only new self-registration attempts are rejected:

$ coder oauth2-provider dcr disable

SUBCOMMANDS:
dcr Manage OAuth2 dynamic client registration (RFC 7591)

———
Run `coder --help` for a list of global options.
13 changes: 13 additions & 0 deletions cli/testdata/coder_oauth2-provider_dcr_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
coder v0.0.0-devel

USAGE:
coder oauth2-provider dcr

Manage OAuth2 dynamic client registration (RFC 7591)

SUBCOMMANDS:
disable Disable OAuth2 dynamic client registration
enable Enable OAuth2 dynamic client registration

———
Run `coder --help` for a list of global options.
9 changes: 9 additions & 0 deletions cli/testdata/coder_oauth2-provider_dcr_disable_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coder v0.0.0-devel

USAGE:
coder oauth2-provider dcr disable

Disable OAuth2 dynamic client registration

———
Run `coder --help` for a list of global options.
9 changes: 9 additions & 0 deletions cli/testdata/coder_oauth2-provider_dcr_enable_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coder v0.0.0-devel

USAGE:
coder oauth2-provider dcr enable

Enable OAuth2 dynamic client registration

———
Run `coder --help` for a list of global options.
Loading
Loading