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 <stevenmasley@gmail.com>
This commit is contained in:
Asher 2024-01-22 13:25:25 -09:00 committed by GitHub
parent 8e0a153725
commit 3014777d2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 187 additions and 32 deletions

23
coderd/apidoc/docs.go generated
View File

@ -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"
},

View File

@ -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"
},

View File

@ -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
}

View File

@ -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

38
docs/api/enterprise.md generated
View File

@ -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"

36
docs/api/schemas.md generated
View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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<EditOAuth2AppProps> = ({
onCancel={() => setShowDelete(false)}
/>
<h2 css={{ marginBottom: 0 }}>Client ID</h2>
<CopyableValue value={app.id}>
{app.id}{" "}
<CopyIcon
css={{
width: 16,
height: 16,
}}
/>
</CopyableValue>
<dl css={styles.dataList}>
<dt>Client ID</dt>
<dd>
<CopyableValue value={app.id}>
{app.id} <CopyIcon css={{ width: 16, height: 16 }} />
</CopyableValue>
</dd>
<dt>Authorization URL</dt>
<dd>
<CopyableValue value={app.endpoints.authorization}>
{app.endpoints.authorization}{" "}
<CopyIcon css={{ width: 16, height: 16 }} />
</CopyableValue>
</dd>
<dt>Token URL</dt>
<dd>
<CopyableValue value={app.endpoints.token}>
{app.endpoints.token}{" "}
<CopyIcon css={{ width: 16, height: 16 }} />
</CopyableValue>
</dd>
</dl>
<Divider css={{ borderColor: theme.palette.divider }} />
@ -303,3 +315,16 @@ const OAuth2SecretRow: FC<OAuth2SecretRowProps> = ({
</TableRow>
);
};
const styles = {
dataList: {
display: "grid",
gridTemplateColumns: "max-content auto",
"& > dt": {
fontWeight: "bold",
},
"& > dd": {
marginLeft: 10,
},
},
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -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: "",
},
},
];