mirror of https://github.com/coder/coder.git
feat: make OAuth2 provider not enterprise-only (#12732)
This commit is contained in:
parent
60f335113c
commit
40e5ad5499
|
@ -689,6 +689,34 @@ func New(options *Options) *API {
|
|||
})
|
||||
}
|
||||
|
||||
// OAuth2 linking routes do not make sense under the /api/v2 path. These are
|
||||
// for an external application to use Coder as an OAuth2 provider, not for
|
||||
// logging into Coder with an external OAuth2 provider.
|
||||
r.Route("/oauth2", func(r chi.Router) {
|
||||
r.Use(
|
||||
api.oAuth2ProviderMiddleware,
|
||||
// Fetch the app as system because in the /tokens route there will be no
|
||||
// authenticated user.
|
||||
httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderApp(options.Database)),
|
||||
)
|
||||
r.Route("/authorize", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddlewareRedirect)
|
||||
r.Get("/", api.getOAuth2ProviderAppAuthorize())
|
||||
})
|
||||
r.Route("/tokens", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
// DELETE on /tokens is not part of the OAuth2 spec. It is our own
|
||||
// route used to revoke permissions from an application. It is here for
|
||||
// parity with POST on /tokens.
|
||||
r.Delete("/", api.deleteOAuth2ProviderAppTokens())
|
||||
})
|
||||
// The POST /tokens endpoint will be called from an unauthorized client so
|
||||
// we cannot require an API key.
|
||||
r.Post("/", api.postOAuth2ProviderAppToken())
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/api/v2", func(r chi.Router) {
|
||||
api.APIHandler = r
|
||||
|
||||
|
@ -1098,6 +1126,34 @@ func New(options *Options) *API {
|
|||
}
|
||||
r.Method("GET", "/expvar", expvar.Handler()) // contains DERP metrics as well as cmdline and memstats
|
||||
})
|
||||
// Manage OAuth2 applications that can use Coder as an OAuth2 provider.
|
||||
r.Route("/oauth2-provider", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
api.oAuth2ProviderMiddleware,
|
||||
)
|
||||
r.Route("/apps", func(r chi.Router) {
|
||||
r.Get("/", api.oAuth2ProviderApps)
|
||||
r.Post("/", api.postOAuth2ProviderApp)
|
||||
|
||||
r.Route("/{app}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2ProviderApp(options.Database))
|
||||
r.Get("/", api.oAuth2ProviderApp)
|
||||
r.Put("/", api.putOAuth2ProviderApp)
|
||||
r.Delete("/", api.deleteOAuth2ProviderApp)
|
||||
|
||||
r.Route("/secrets", func(r chi.Router) {
|
||||
r.Get("/", api.oAuth2ProviderAppSecrets)
|
||||
r.Post("/", api.postOAuth2ProviderAppSecret)
|
||||
|
||||
r.Route("/{secretID}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2ProviderAppSecret(options.Database))
|
||||
r.Delete("/", api.deleteOAuth2ProviderAppSecret)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if options.SwaggerEndpoint {
|
||||
|
|
|
@ -13,11 +13,11 @@ import (
|
|||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/identityprovider"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/identityprovider"
|
||||
)
|
||||
|
||||
func (api *API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
|
||||
func (*API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !buildinfo.IsDev() {
|
||||
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
||||
|
@ -26,17 +26,6 @@ func (api *API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
api.entitlementsMu.RLock()
|
||||
entitled := api.entitlements.Features[codersdk.FeatureOAuth2Provider].Entitlement != codersdk.EntitlementNotEntitled
|
||||
api.entitlementsMu.RUnlock()
|
||||
|
||||
if !entitled {
|
||||
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: "OAuth2 provider is an Enterprise feature. Contact sales!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
@ -111,7 +100,7 @@ func (api *API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|||
func (api *API) postOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
|
@ -157,7 +146,7 @@ func (api *API) putOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|||
var (
|
||||
ctx = r.Context()
|
||||
app = httpmw.OAuth2ProviderApp(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
|
@ -200,7 +189,7 @@ func (api *API) deleteOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request)
|
|||
var (
|
||||
ctx = r.Context()
|
||||
app = httpmw.OAuth2ProviderApp(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
|
@ -263,7 +252,7 @@ func (api *API) postOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Requ
|
|||
var (
|
||||
ctx = r.Context()
|
||||
app = httpmw.OAuth2ProviderApp(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderAppSecret](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
|
@ -317,7 +306,7 @@ func (api *API) deleteOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Re
|
|||
var (
|
||||
ctx = r.Context()
|
||||
secret = httpmw.OAuth2ProviderAppSecret(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderAppSecret](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
|
@ -19,12 +19,10 @@ import (
|
|||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/identityprovider"
|
||||
"github.com/coder/coder/v2/coderd/userpassword"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/identityprovider"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
|
@ -34,11 +32,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
|
|||
t.Run("Validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
topCtx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
|
@ -178,11 +173,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
|
|||
t.Run("DeleteNonExisting", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
another, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
@ -194,11 +186,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
|
|||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
another, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
@ -269,11 +258,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
|
|||
|
||||
t.Run("ByUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
another, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
_ = generateApps(ctx, t, client, "by-user")
|
||||
|
@ -288,11 +274,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
|
|||
func TestOAuth2ProviderAppSecrets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
topCtx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
|
@ -383,17 +366,11 @@ func TestOAuth2ProviderTokenExchange(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
},
|
||||
ownerClient := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
topCtx := testutil.Context(t, testutil.WaitLong)
|
||||
apps := generateApps(topCtx, t, ownerClient, "token-exchange")
|
||||
|
||||
|
@ -764,17 +741,11 @@ func TestOAuth2ProviderTokenRefresh(t *testing.T) {
|
|||
topCtx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
},
|
||||
ownerClient := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
apps := generateApps(topCtx, t, ownerClient, "token-refresh")
|
||||
|
||||
//nolint:gocritic // OAauth2 app management requires owner permission.
|
||||
|
@ -935,11 +906,8 @@ type exchangeSetup struct {
|
|||
func TestOAuth2ProviderRevoke(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureOAuth2Provider: 1,
|
||||
},
|
||||
}})
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -1138,3 +1106,10 @@ func authorizationFlow(ctx context.Context, client *codersdk.Client, cfg *oauth2
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
func must[T any](value T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
}
|
|
@ -53,7 +53,6 @@ const (
|
|||
FeatureExternalTokenEncryption FeatureName = "external_token_encryption"
|
||||
FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions"
|
||||
FeatureAccessControl FeatureName = "access_control"
|
||||
FeatureOAuth2Provider FeatureName = "oauth2_provider"
|
||||
FeatureControlSharedPorts FeatureName = "control_shared_ports"
|
||||
)
|
||||
|
||||
|
@ -74,7 +73,6 @@ var FeatureNames = []FeatureName{
|
|||
FeatureExternalTokenEncryption,
|
||||
FeatureWorkspaceBatchActions,
|
||||
FeatureAccessControl,
|
||||
FeatureOAuth2Provider,
|
||||
FeatureControlSharedPorts,
|
||||
}
|
||||
|
||||
|
@ -85,8 +83,6 @@ func (n FeatureName) Humanize() string {
|
|||
return "Template RBAC"
|
||||
case FeatureSCIM:
|
||||
return "SCIM"
|
||||
case FeatureOAuth2Provider:
|
||||
return "OAuth Provider"
|
||||
default:
|
||||
return strings.Title(strings.ReplaceAll(string(n), "_", " "))
|
||||
}
|
||||
|
|
|
@ -153,16 +153,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||
SessionTokenFunc: nil, // Default behavior
|
||||
PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc,
|
||||
})
|
||||
// Same as above but it redirects to the login page.
|
||||
apiKeyMiddlewareRedirect := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: options.Database,
|
||||
OAuth2Configs: oauthConfigs,
|
||||
RedirectToLogin: true,
|
||||
DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(),
|
||||
Optional: false,
|
||||
SessionTokenFunc: nil, // Default behavior
|
||||
PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc,
|
||||
})
|
||||
apiKeyMiddlewareOptional := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: options.Database,
|
||||
OAuth2Configs: oauthConfigs,
|
||||
|
@ -178,33 +168,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||
return nil, xerrors.Errorf("failed to get deployment ID: %w", err)
|
||||
}
|
||||
|
||||
api.AGPL.RootHandler.Group(func(r chi.Router) {
|
||||
// OAuth2 linking routes do not make sense under the /api/v2 path.
|
||||
r.Route("/oauth2", func(r chi.Router) {
|
||||
r.Use(
|
||||
api.oAuth2ProviderMiddleware,
|
||||
// Fetch the app as system because in the /tokens route there will be no
|
||||
// authenticated user.
|
||||
httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderApp(options.Database)),
|
||||
)
|
||||
r.Route("/authorize", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddlewareRedirect)
|
||||
r.Get("/", api.getOAuth2ProviderAppAuthorize())
|
||||
})
|
||||
r.Route("/tokens", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
// DELETE on /tokens is not part of the OAuth2 spec. It is our own
|
||||
// route used to revoke permissions from an application. It is here for
|
||||
// parity with POST on /tokens.
|
||||
r.Delete("/", api.deleteOAuth2ProviderAppTokens())
|
||||
})
|
||||
// The POST /tokens endpoint will be called from an unauthorized client so we
|
||||
// cannot require an API key.
|
||||
r.Post("/", api.postOAuth2ProviderAppToken())
|
||||
})
|
||||
})
|
||||
})
|
||||
api.AGPL.RefreshEntitlements = func(ctx context.Context) error {
|
||||
return api.refreshEntitlements(ctx)
|
||||
}
|
||||
|
@ -365,33 +328,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||
r.Get("/", api.userQuietHoursSchedule)
|
||||
r.Put("/", api.putUserQuietHoursSchedule)
|
||||
})
|
||||
r.Route("/oauth2-provider", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
api.oAuth2ProviderMiddleware,
|
||||
)
|
||||
r.Route("/apps", func(r chi.Router) {
|
||||
r.Get("/", api.oAuth2ProviderApps)
|
||||
r.Post("/", api.postOAuth2ProviderApp)
|
||||
|
||||
r.Route("/{app}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2ProviderApp(options.Database))
|
||||
r.Get("/", api.oAuth2ProviderApp)
|
||||
r.Put("/", api.putOAuth2ProviderApp)
|
||||
r.Delete("/", api.deleteOAuth2ProviderApp)
|
||||
|
||||
r.Route("/secrets", func(r chi.Router) {
|
||||
r.Get("/", api.oAuth2ProviderAppSecrets)
|
||||
r.Post("/", api.postOAuth2ProviderAppSecret)
|
||||
|
||||
r.Route("/{secretID}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOAuth2ProviderAppSecret(options.Database))
|
||||
r.Delete("/", api.deleteOAuth2ProviderAppSecret)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
r.Route("/integrations", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
|
@ -596,7 +532,6 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
|||
codersdk.FeatureBrowserOnly: api.BrowserOnly,
|
||||
codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0,
|
||||
codersdk.FeatureMultipleExternalAuth: len(api.ExternalAuthConfigs) > 1,
|
||||
codersdk.FeatureOAuth2Provider: true,
|
||||
codersdk.FeatureTemplateRBAC: api.RBAC,
|
||||
codersdk.FeatureExternalTokenEncryption: len(api.ExternalTokenEncryption) > 0,
|
||||
codersdk.FeatureExternalProvisionerDaemons: true,
|
||||
|
|
|
@ -2051,7 +2051,6 @@ export type FeatureName =
|
|||
| "external_token_encryption"
|
||||
| "high_availability"
|
||||
| "multiple_external_auth"
|
||||
| "oauth2_provider"
|
||||
| "scim"
|
||||
| "template_rbac"
|
||||
| "user_limit"
|
||||
|
@ -2069,7 +2068,6 @@ export const FeatureNames: FeatureName[] = [
|
|||
"external_token_encryption",
|
||||
"high_availability",
|
||||
"multiple_external_auth",
|
||||
"oauth2_provider",
|
||||
"scim",
|
||||
"template_rbac",
|
||||
"user_limit",
|
||||
|
|
|
@ -2,12 +2,10 @@ import type { FC } from "react";
|
|||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
import { getApps } from "api/queries/oauth2";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { pageTitle } from "utils/page";
|
||||
import OAuth2AppsSettingsPageView from "./OAuth2AppsSettingsPageView";
|
||||
|
||||
const OAuth2AppsSettingsPage: FC = () => {
|
||||
const { entitlements } = useDashboard();
|
||||
const appsQuery = useQuery(getApps());
|
||||
|
||||
return (
|
||||
|
@ -19,9 +17,6 @@ const OAuth2AppsSettingsPage: FC = () => {
|
|||
apps={appsQuery.data}
|
||||
isLoading={appsQuery.isLoading}
|
||||
error={appsQuery.error}
|
||||
isEntitled={
|
||||
entitlements.features.oauth2_provider.entitlement !== "not_entitled"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -23,21 +23,13 @@ export const Error: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
export const Unentitled: Story = {
|
||||
export const Apps: Story = {
|
||||
args: {
|
||||
isLoading: false,
|
||||
apps: MockOAuth2ProviderApps,
|
||||
},
|
||||
};
|
||||
|
||||
export const Entitled: Story = {
|
||||
args: {
|
||||
isLoading: false,
|
||||
apps: MockOAuth2ProviderApps,
|
||||
isEntitled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
isLoading: false,
|
||||
|
|
|
@ -14,12 +14,6 @@ import type * as TypesGen from "api/typesGenerated";
|
|||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import {
|
||||
Badges,
|
||||
DisabledBadge,
|
||||
EnterpriseBadge,
|
||||
EntitledBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
|
@ -27,14 +21,12 @@ import { Header } from "../Header";
|
|||
|
||||
type OAuth2AppsSettingsProps = {
|
||||
apps?: TypesGen.OAuth2ProviderApp[];
|
||||
isEntitled: boolean;
|
||||
isLoading: boolean;
|
||||
error: unknown;
|
||||
};
|
||||
|
||||
const OAuth2AppsSettingsPageView: FC<OAuth2AppsSettingsProps> = ({
|
||||
apps,
|
||||
isEntitled,
|
||||
isLoading,
|
||||
error,
|
||||
}) => {
|
||||
|
@ -50,10 +42,6 @@ const OAuth2AppsSettingsPageView: FC<OAuth2AppsSettingsProps> = ({
|
|||
title="OAuth2 Applications"
|
||||
description="Configure applications to use Coder as an OAuth2 provider."
|
||||
/>
|
||||
<Badges>
|
||||
{isEntitled ? <EntitledBadge /> : <DisabledBadge />}
|
||||
<EnterpriseBadge />
|
||||
</Badges>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
|
|
Loading…
Reference in New Issue