Files
coder/codersdk/apikey.go
Sushant P 4820f13eb4 docs: add deprecation warning for login-type none (#24594)
The `--login-type none` option for `coder users create` is deprecated.
This adds deprecation warnings to all docs that reference it and updates
the CI/CD tutorial to recommend the replacement flows.

Refs DEVEX-224

<details>
<summary>Changes</summary>

- `cli/usercreate.go`: Append deprecation notice to `--login-type` flag
description.
- `docs/tutorials/testing-templates.md`: Replace `--login-type none`
example with separate Premium (`--service-account`) and OSS
(`--login-type password`) examples.
- `docs/reference/cli/users_create.md`: Regenerated from CLI source.
- `cli/testdata/coder_users_create_--help.golden`: Updated golden
snapshot.

</details>

> [!NOTE]
> Generated by Coder Agents.
2026-04-27 22:51:01 +00:00

206 lines
7.2 KiB
Go

package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
)
// APIKey: do not ever return the HashedSecret
type APIKey struct {
ID string `json:"id" validate:"required"`
UserID uuid.UUID `json:"user_id" validate:"required" format:"uuid"`
LastUsed time.Time `json:"last_used" validate:"required" format:"date-time"`
ExpiresAt time.Time `json:"expires_at" validate:"required" format:"date-time"`
CreatedAt time.Time `json:"created_at" validate:"required" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" validate:"required" format:"date-time"`
LoginType LoginType `json:"login_type" validate:"required" enums:"password,github,oidc,token"`
Scope APIKeyScope `json:"scope" enums:"all,application_connect"` // Deprecated: use Scopes instead.
Scopes []APIKeyScope `json:"scopes"`
TokenName string `json:"token_name" validate:"required"`
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
AllowList []APIAllowListTarget `json:"allow_list"`
}
// LoginType is the type of login used to create the API key.
type LoginType string
const (
LoginTypeUnknown LoginType = ""
LoginTypePassword LoginType = "password"
LoginTypeGithub LoginType = "github"
LoginTypeOIDC LoginType = "oidc"
LoginTypeToken LoginType = "token"
// LoginTypeNone is used if no login method is available for this
// user. If this is set, the user has no method of logging in.
// API keys can still be created by an owner and used by the user.
// These keys would use the `LoginTypeToken` type.
//
// Deprecated: Use service accounts (Premium) for headless/machine
// access, or password/github/oidc login types for regular users.
LoginTypeNone LoginType = "none"
)
type APIKeyScope string
type CreateTokenRequest struct {
Lifetime time.Duration `json:"lifetime"`
Scope APIKeyScope `json:"scope,omitempty"` // Deprecated: use Scopes instead.
Scopes []APIKeyScope `json:"scopes,omitempty"`
TokenName string `json:"token_name"`
AllowList []APIAllowListTarget `json:"allow_list,omitempty"`
}
// GenerateAPIKeyResponse contains an API key for a user.
type GenerateAPIKeyResponse struct {
Key string `json:"key"`
}
// CreateToken generates an API key for the user ID provided with
// custom expiration. These tokens can be used for long-lived access,
// like for use with CI.
func (c *Client) CreateToken(ctx context.Context, userID string, req CreateTokenRequest) (GenerateAPIKeyResponse, error) {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys/tokens", userID), req)
if err != nil {
return GenerateAPIKeyResponse{}, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusCreated {
return GenerateAPIKeyResponse{}, ReadBodyAsError(res)
}
var apiKey GenerateAPIKeyResponse
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
}
// CreateAPIKey generates an API key for the user ID provided.
// CreateToken should be used over CreateAPIKey. CreateToken allows better
// tracking of the token's usage and allows for custom expiration.
// Only use CreateAPIKey if you want to emulate the session created for
// a browser like login.
func (c *Client) CreateAPIKey(ctx context.Context, user string) (GenerateAPIKeyResponse, error) {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", user), nil)
if err != nil {
return GenerateAPIKeyResponse{}, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusCreated {
return GenerateAPIKeyResponse{}, ReadBodyAsError(res)
}
var apiKey GenerateAPIKeyResponse
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
}
type TokensFilter struct {
IncludeAll bool `json:"include_all"`
IncludeExpired bool `json:"include_expired"`
}
type APIKeyWithOwner struct {
APIKey
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 {
return func(r *http.Request) {
q := r.URL.Query()
q.Set("include_all", fmt.Sprintf("%t", f.IncludeAll))
q.Set("include_expired", fmt.Sprintf("%t", f.IncludeExpired))
r.URL.RawQuery = q.Encode()
}
}
// Tokens list machine API keys.
func (c *Client) Tokens(ctx context.Context, userID string, filter TokensFilter) ([]APIKeyWithOwner, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/tokens", userID), nil, filter.asRequestOption())
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusOK {
return nil, ReadBodyAsError(res)
}
apiKey := []APIKeyWithOwner{}
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
}
// APIKeyByID returns the api key by id.
func (c *Client) APIKeyByID(ctx context.Context, userID string, id string) (*APIKey, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/%s", userID, id), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusCreated {
return nil, ReadBodyAsError(res)
}
apiKey := &APIKey{}
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
}
// APIKeyByName returns the api key by name.
func (c *Client) APIKeyByName(ctx context.Context, userID string, name string) (*APIKey, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/tokens/%s", userID, name), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode > http.StatusCreated {
return nil, ReadBodyAsError(res)
}
apiKey := &APIKey{}
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
}
// DeleteAPIKey deletes API key by id.
func (c *Client) DeleteAPIKey(ctx context.Context, userID string, id string) error {
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/users/%s/keys/%s", userID, id), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode > http.StatusNoContent {
return ReadBodyAsError(res)
}
return nil
}
// ExpireAPIKey expires an API key by id, setting its expiry to now.
// This preserves the API key record for audit purposes rather than deleting it.
func (c *Client) ExpireAPIKey(ctx context.Context, userID string, id string) error {
res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/keys/%s/expire", userID, id), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode > http.StatusNoContent {
return ReadBodyAsError(res)
}
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)
}