From 3014777d2a3d7be25dbfdca8fc7fe3867a5fc83f Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 22 Jan 2024 13:25:25 -0900 Subject: [PATCH] feat: add endpoints to oauth2 provider applications (#11718) These will show up when configuring the application along with the client ID and everything else. Should make it easier to configure the application, otherwise you will have to go look up the URLs in the docs (which are not yet written). Co-authored-by: Steven Masley --- coderd/apidoc/docs.go | 23 +++++++++ coderd/apidoc/swagger.json | 23 +++++++++ coderd/database/db2sdk/db2sdk.go | 17 +++++-- codersdk/oauth2.go | 12 +++++ docs/api/enterprise.md | 38 ++++++++++++--- docs/api/schemas.md | 36 +++++++++++--- enterprise/coderd/oauth2.go | 10 ++-- site/src/api/typesGenerated.ts | 8 ++++ .../EditOAuth2AppPageView.tsx | 47 ++++++++++++++----- site/src/testHelpers/entities.ts | 5 ++ 10 files changed, 187 insertions(+), 32 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 06ed3e19df..e17e2d8081 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9688,6 +9688,21 @@ const docTemplate = `{ } } }, + "codersdk.OAuth2AppEndpoints": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + }, + "device_authorization": { + "description": "DeviceAuth is optional.", + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "codersdk.OAuth2Config": { "type": "object", "properties": { @@ -9734,6 +9749,14 @@ const docTemplate = `{ "callback_url": { "type": "string" }, + "endpoints": { + "description": "Endpoints are included in the app response for easier discovery. The OAuth2\nspec does not have a defined place to find these (for comparison, OIDC has\na '/.well-known/openid-configuration' endpoint).", + "allOf": [ + { + "$ref": "#/definitions/codersdk.OAuth2AppEndpoints" + } + ] + }, "icon": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8982d4a4a7..34b4bd36df 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8683,6 +8683,21 @@ } } }, + "codersdk.OAuth2AppEndpoints": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + }, + "device_authorization": { + "description": "DeviceAuth is optional.", + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "codersdk.OAuth2Config": { "type": "object", "properties": { @@ -8729,6 +8744,14 @@ "callback_url": { "type": "string" }, + "endpoints": { + "description": "Endpoints are included in the app response for easier discovery. The OAuth2\nspec does not have a defined place to find these (for comparison, OIDC has\na '/.well-known/openid-configuration' endpoint).", + "allOf": [ + { + "$ref": "#/definitions/codersdk.OAuth2AppEndpoints" + } + ] + }, "icon": { "type": "string" }, diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index c88b8d5c8a..3d9953dd87 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -4,6 +4,7 @@ package db2sdk import ( "encoding/json" "fmt" + "net/url" "strconv" "strings" "time" @@ -226,19 +227,29 @@ func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.Tem return options, nil } -func OAuth2ProviderApp(dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp { +func OAuth2ProviderApp(accessURL *url.URL, dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp { return codersdk.OAuth2ProviderApp{ ID: dbApp.ID, Name: dbApp.Name, CallbackURL: dbApp.CallbackURL, Icon: dbApp.Icon, + Endpoints: codersdk.OAuth2AppEndpoints{ + Authorization: accessURL.ResolveReference(&url.URL{ + Path: "/login/oauth2/authorize", + }).String(), + Token: accessURL.ResolveReference(&url.URL{ + Path: "/login/oauth2/tokens", + }).String(), + // We do not currently support DeviceAuth. + DeviceAuth: "", + }, } } -func OAuth2ProviderApps(dbApps []database.OAuth2ProviderApp) []codersdk.OAuth2ProviderApp { +func OAuth2ProviderApps(accessURL *url.URL, dbApps []database.OAuth2ProviderApp) []codersdk.OAuth2ProviderApp { apps := []codersdk.OAuth2ProviderApp{} for _, dbApp := range dbApps { - apps = append(apps, OAuth2ProviderApp(dbApp)) + apps = append(apps, OAuth2ProviderApp(accessURL, dbApp)) } return apps } diff --git a/codersdk/oauth2.go b/codersdk/oauth2.go index acebc9c718..318743959d 100644 --- a/codersdk/oauth2.go +++ b/codersdk/oauth2.go @@ -14,6 +14,18 @@ type OAuth2ProviderApp struct { Name string `json:"name"` CallbackURL string `json:"callback_url"` Icon string `json:"icon"` + + // Endpoints are included in the app response for easier discovery. The OAuth2 + // spec does not have a defined place to find these (for comparison, OIDC has + // a '/.well-known/openid-configuration' endpoint). + Endpoints OAuth2AppEndpoints `json:"endpoints"` +} + +type OAuth2AppEndpoints struct { + Authorization string `json:"authorization"` + Token string `json:"token"` + // DeviceAuth is optional. + DeviceAuth string `json:"device_authorization"` } // OAuth2ProviderApps returns the applications configured to authenticate using diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 956bb75653..1ae77d4b7e 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -454,6 +454,11 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps \ [ { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -471,13 +476,17 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps \ Status Code **200** -| Name | Type | Required | Restrictions | Description | -| ---------------- | ------------ | -------- | ------------ | ----------- | -| `[array item]` | array | false | | | -| `» callback_url` | string | false | | | -| `» icon` | string | false | | | -| `» id` | string(uuid) | false | | | -| `» name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[array item]` | array | false | | | +| `» callback_url` | string | false | | | +| `» endpoints` | [codersdk.OAuth2AppEndpoints](schemas.md#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | +| `»» authorization` | string | false | | | +| `»» device_authorization` | string | false | | Device authorization is optional. | +| `»» token` | string | false | | | +| `» icon` | string | false | | | +| `» id` | string(uuid) | false | | | +| `» name` | string | false | | | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -518,6 +527,11 @@ curl -X POST http://coder-server:8080/api/v2/oauth2-provider/apps \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -558,6 +572,11 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps/{app} \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -610,6 +629,11 @@ curl -X PUT http://coder-server:8080/api/v2/oauth2-provider/apps/{app} \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" diff --git a/docs/api/schemas.md b/docs/api/schemas.md index a51b3bcdfd..3ec2e2ede8 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3519,6 +3519,24 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `id` | string | true | | | | `username` | string | true | | | +## codersdk.OAuth2AppEndpoints + +```json +{ + "authorization": "string", + "device_authorization": "string", + "token": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------------- | ------ | -------- | ------------ | --------------------------------- | +| `authorization` | string | false | | | +| `device_authorization` | string | false | | Device authorization is optional. | +| `token` | string | false | | | + ## codersdk.OAuth2Config ```json @@ -3572,6 +3590,11 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -3580,12 +3603,13 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `callback_url` | string | false | | | -| `icon` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ---------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callback_url` | string | false | | | +| `endpoints` | [codersdk.OAuth2AppEndpoints](#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | +| `icon` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | ## codersdk.OAuth2ProviderAppSecret diff --git a/enterprise/coderd/oauth2.go b/enterprise/coderd/oauth2.go index 3d4c822131..3ebb39aaee 100644 --- a/enterprise/coderd/oauth2.go +++ b/enterprise/coderd/oauth2.go @@ -54,7 +54,7 @@ func (api *API) oAuth2ProviderApps(rw http.ResponseWriter, r *http.Request) { httpapi.InternalServerError(rw, err) return } - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApps(dbApps)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApps(api.AccessURL, dbApps)) } // @Summary Get OAuth2 application. @@ -65,10 +65,10 @@ func (api *API) oAuth2ProviderApps(rw http.ResponseWriter, r *http.Request) { // @Param app path string true "App ID" // @Success 200 {object} codersdk.OAuth2ProviderApp // @Router /oauth2-provider/apps/{app} [get] -func (*API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { +func (api *API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() app := httpmw.OAuth2ProviderApp(r) - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Create OAuth2 application. @@ -101,7 +101,7 @@ func (api *API) postOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { }) return } - httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Update OAuth2 application. @@ -135,7 +135,7 @@ func (api *API) putOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { }) return } - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Delete OAuth2 application. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7127b5f72c..48d05be9d9 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -656,6 +656,13 @@ export interface MinimalUser { readonly avatar_url: string; } +// From codersdk/oauth2.go +export interface OAuth2AppEndpoints { + readonly authorization: string; + readonly token: string; + readonly device_authorization: string; +} + // From codersdk/deployment.go export interface OAuth2Config { readonly github: OAuth2GithubConfig; @@ -678,6 +685,7 @@ export interface OAuth2ProviderApp { readonly name: string; readonly callback_url: string; readonly icon: string; + readonly endpoints: OAuth2AppEndpoints; } // From codersdk/oauth2.go diff --git a/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx b/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx index dc0629e10b..2a28ad668d 100644 --- a/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx +++ b/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx @@ -1,4 +1,4 @@ -import { useTheme } from "@emotion/react"; +import { type Interpolation, type Theme, useTheme } from "@emotion/react"; import CopyIcon from "@mui/icons-material/FileCopyOutlined"; import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; import Divider from "@mui/material/Divider"; @@ -139,16 +139,28 @@ export const EditOAuth2AppPageView: FC = ({ onCancel={() => setShowDelete(false)} /> -

Client ID

- - {app.id}{" "} - - +
+
Client ID
+
+ + {app.id} + +
+
Authorization URL
+
+ + {app.endpoints.authorization}{" "} + + +
+
Token URL
+
+ + {app.endpoints.token}{" "} + + +
+
@@ -303,3 +315,16 @@ const OAuth2SecretRow: FC = ({ ); }; + +const styles = { + dataList: { + display: "grid", + gridTemplateColumns: "max-content auto", + "& > dt": { + fontWeight: "bold", + }, + "& > dd": { + marginLeft: 10, + }, + }, +} satisfies Record>; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 9075f02f4c..59d60fda1a 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -3371,6 +3371,11 @@ export const MockOAuth2ProviderApps: TypesGen.OAuth2ProviderApp[] = [ name: "foo", callback_url: "http://localhost:3001", icon: "/icon/github.svg", + endpoints: { + authorization: "http://localhost:3001/login/oauth2/authorize", + token: "http://localhost:3001/login/oauth2/token", + device_authorization: "", + }, }, ];