8000 feat(site): add ability to create tokens from account tokens page by Kira-Pilot · Pull Request #6608 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat(site): add ability to create tokens from account tokens page #6608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"thead",
"tios",
"tmpdir",
"tokenconfig",
"tparallel",
"trialer",
"trimprefix",
Expand Down
42 changes: 42 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions coderd/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"database/sql"
"errors"
"fmt"
"math"
"net"
"net/http"
"strconv"
Expand Down Expand Up @@ -339,6 +340,38 @@ func (api *API) deleteAPIKey(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
}

// @Summary Get token config
// @ID get-token-config
// @Security CoderSessionToken
// @Produce json
// @Tags General
// @Param user path string true "User ID, name, or me"
// @Success 200 {object} codersdk.TokenConfig
// @Router /users/{user}/keys/tokens/tokenconfig [get]
func (api *API) tokenConfig(rw http.ResponseWriter, r *http.Request) {
values, err := api.DeploymentValues.WithoutSecrets()
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

var maxTokenLifetime time.Duration
// if --max-token-lifetime is unset (default value is math.MaxInt64)
// send back a falsy value
if values.MaxTokenLifetime.Value() == time.Duration(math.MaxInt64) {
maxTokenLifetime = 0
} else {
maxTokenLifetime = values.MaxTokenLifetime.Value()
}

httpapi.Write(
r.Context(), rw, http.StatusOK,
codersdk.TokenConfig{
MaxTokenLifetime: maxTokenLifetime,
},
)
}

// Generates a new ID and secret for an API key.
func GenerateAPIKeyIDSecret() (id string, secret string, err error) {
// Length of an API Key ID.
Expand Down
1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ func New(options *Options) *API {
r.Route("/tokens", func(r chi.Router) {
r.Post("/", api.postToken)
r.Get("/", api.tokens)
r.Get("/tokenconfig", api.tokenConfig)
r.Route("/{keyname}", func(r chi.Router) {
r.Get("/", api.apiKeyByName)
})
Expand Down
1 change: 1 addition & 0 deletions coderd/coderdtest/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
AssertObject: rbac.ResourceAPIKey,
AssertAction: rbac.ActionRead,
},
"GET:/api/v2/users/{user}/keys/tokens/tokenconfig": {NoAuthorize: true},
"GET:/api/v2/workspacebuilds/{workspacebuild}": {
AssertAction: rbac.ActionRead,
AssertObject: workspaceRBACObj,
Expand Down
18 changes: 18 additions & 0 deletions codersdk/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ type APIKeyWithOwner struct {
Username string `json:"username"`
}

type TokenConfig struct {
MaxTokenLifetime time.Duration `json:"max_token_lifetime"`
}

// asRequestOption returns a function that can be used in (*Client).Request.
// It modifies the request query parameters.
func (f TokensFilter) asRequestOption() RequestOption {
Expand Down Expand Up @@ -161,3 +165,17 @@ func (c *Client) DeleteAPIKey(ctx context.Context, userID string, id string) err
}
return nil
}

// GetTokenConfig returns deployment options related to token management
func (c *Client) GetTokenConfig(ctx context.Context, userID string) (TokenConfig, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/tokens/tokenconfig", userID), nil)
if err != nil {
return TokenConfig{}, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusOK {
return TokenConfig{}, ReadBodyAsError(res)
}
tokenConfig := TokenConfig{}
return tokenConfig, json.NewDecoder(res.Body).Decode(&tokenConfig)
}
37 changes: 37 additions & 0 deletions docs/api/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,40 @@ curl -X GET http://coder-server:8080/api/v2/updatecheck \
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateCheckResponse](schemas.md#codersdkupdatecheckresponse) |

## Get token config

### Code samples

```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/tokenconfig \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```

`GET /users/{user}/keys/tokens/tokenconfig`

### Parameters

| Name | In | Type | Required | Description |
| ------ | ---- | ------ | -------- | -------------------- |
| `user` | path | string | true | User ID, name, or me |

### Example responses

> 200 Response

```json
{
"max_token_lifetime": 0
}
```

### Responses

| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.TokenConfig](schemas.md#codersdktokenconfig) |

To perform this operation, you must be authenticated. [Learn more](authentication.md).
14 changes: 14 additions & 0 deletions docs/api/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -3839,6 +3839,20 @@ Parameter represents a set value for the scope.
| `type` | `number` |
| `type` | `bool` |

## codersdk.TokenConfig

```json
{
"max_token_lifetime": 0
}
```

### Properties

| Name | Type | Required | Restrictions | Description |
| -------------------- | ------- | -------- | ------------ | ----------- |
| `max_token_lifetime` | integer | false | | |

## codersdk.TraceConfig

```json
Expand Down
9 changes: 8 additions & 1 deletion site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ const TemplateVariablesPage = lazy(
() => import("./pages/TemplateVariablesPage/TemplateVariablesPage"),
)

const CreateTokenPage = lazy(
() => import("./pages/CreateTokenPage/CreateTokenPage"),
)

export const AppRouter: FC = () => {
return (
<Suspense fallback={<FullScreenLoader />}>
Expand Down Expand Up @@ -215,7 +219,10 @@ export const AppRouter: FC = () => {
<Route path="account" element={<AccountPage />} />
<Route path="security" element={<SecurityPage />} />
<Route path="ssh-keys" element={<SSHKeysPage />} />
<Route path="tokens" element={<TokensPage />} />
<Route path="tokens">
<Route index element={<TokensPage />} />
<Route path="new" element={<CreateTokenPage />} />
</Route>
</Route>

<Route path="/@:username">
Expand Down
14 changes: 13 additions & 1 deletion site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,22 @@ export const getTokens = async (
return response.data
}

export const deleteAPIKey = async (keyId: string): Promise<void> => {
export const deleteToken = async (keyId: string): Promise<void> => {
await axios.delete("/api/v2/users/me/keys/" + keyId)
}

export const createToken = async (
params: TypesGen.CreateTokenRequest,
): Promise<TypesGen.GenerateAPIKeyResponse> => {
const response = await axios.post(`/api/v2/users/me/keys/tokens`, params)
return response.data
}

export const getTokenConfig = async (): Promise<TypesGen.TokenConfig> => {
const response = await axios.get("/api/v2/users/me/keys/tokens/tokenconfig")
return response.data
}

export const getUsers = async (
options: TypesGen.UsersRequest,
): Promise<TypesGen.GetUsersResponse> => {
Expand Down
6 changes: 6 additions & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,12 @@ export interface TemplateVersionsByTemplateRequest extends Pagination {
readonly template_id: string
}

// From codersdk/apikey.go
export interface TokenConfig {
// This is likely an enum in an external package ("time.Duration")
readonly max_token_lifetime: number
}

// From codersdk/apikey.go
export interface TokensFilter {
readonly include_all: boolean
Expand Down
8 changes: 7 additions & 1 deletion site/src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const FormSection: FC<
description: string | JSX.Element
classes?: {
root?: string
sectionInfo?: string
infoTitle?: string
}
}
Expand All @@ -73,7 +74,12 @@ export const FormSection: FC<

return (
<div className={combineClasses([styles.formSection, classes.root])}>
<div className={styles.formSectionInfo}>
<div
className={combineClasses([
classes.sectionInfo,
styles.formSectionInfo,
])}
>
<h2
className={combineClasses([
styles.formSectionInfoTitle,
Expand Down
10 changes: 6 additions & 4 deletions site/src/components/FullPageForm/FullPageHorizontalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { makeStyles } from "@material-ui/core/styles"
export interface FullPageHorizontalFormProps {
title: string
detail?: ReactNode
onCancel: () => void
onCancel?: () => void
}

export const FullPageHorizontalForm: FC<
Expand All @@ -23,9 +23,11 @@ export const FullPageHorizontalForm: FC<
<Margins size="medium">
<PageHeader
actions={
<Button variant="outlined" size="small" onClick={onCancel}>
Cancel
</Button>
onCancel && (
<Button variant="outlined" size="small" onClick={onCancel}>
Cancel
</Button>
)
}
>
<PageHeaderTitle>{title}</PageHeaderTitle>
Expand Down
Loading
0