2022-10-06 19:02:27 +00:00
|
|
|
package codersdk
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
)
|
|
|
|
|
2023-01-11 15:05:42 +00:00
|
|
|
// APIKey: do not ever return the HashedSecret
|
2022-10-06 19:02:27 +00:00
|
|
|
type APIKey struct {
|
2023-02-23 15:00:27 +00:00
|
|
|
ID string `json:"id" validate:"required"`
|
2023-01-11 13:08:04 +00:00
|
|
|
UserID uuid.UUID `json:"user_id" validate:"required" format:"uuid"`
|
2023-02-23 15:00:27 +00:00
|
|
|
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"`
|
2023-01-11 13:08:04 +00:00
|
|
|
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" validate:"required" enums:"all,application_connect"`
|
2023-03-02 17:39:38 +00:00
|
|
|
TokenName string `json:"token_name" validate:"required"`
|
2022-10-14 16:46:38 +00:00
|
|
|
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
// LoginType is the type of login used to create the API key.
|
2022-10-06 19:02:27 +00:00
|
|
|
type LoginType string
|
|
|
|
|
|
|
|
const (
|
2023-08-11 01:04:35 +00:00
|
|
|
LoginTypeUnknown LoginType = ""
|
2022-10-06 19:02:27 +00:00
|
|
|
LoginTypePassword LoginType = "password"
|
|
|
|
LoginTypeGithub LoginType = "github"
|
|
|
|
LoginTypeOIDC LoginType = "oidc"
|
|
|
|
LoginTypeToken LoginType = "token"
|
2023-06-14 17:48:43 +00:00
|
|
|
// 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.
|
|
|
|
LoginTypeNone LoginType = "none"
|
2022-10-06 19:02:27 +00:00
|
|
|
)
|
|
|
|
|
2022-10-14 16:46:38 +00:00
|
|
|
type APIKeyScope string
|
|
|
|
|
|
|
|
const (
|
2023-01-29 21:47:24 +00:00
|
|
|
// APIKeyScopeAll is a scope that allows the user to do everything.
|
|
|
|
APIKeyScopeAll APIKeyScope = "all"
|
|
|
|
// APIKeyScopeApplicationConnect is a scope that allows the user
|
|
|
|
// to connect to applications in a workspace.
|
2022-10-14 16:46:38 +00:00
|
|
|
APIKeyScopeApplicationConnect APIKeyScope = "application_connect"
|
|
|
|
)
|
|
|
|
|
|
|
|
type CreateTokenRequest struct {
|
2023-03-02 17:39:38 +00:00
|
|
|
Lifetime time.Duration `json:"lifetime"`
|
|
|
|
Scope APIKeyScope `json:"scope" enums:"all,application_connect"`
|
|
|
|
TokenName string `json:"token_name"`
|
2022-10-14 16:46:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateAPIKeyResponse contains an API key for a user.
|
|
|
|
type GenerateAPIKeyResponse struct {
|
|
|
|
Key string `json:"key"`
|
|
|
|
}
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
// 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.
|
2022-10-14 16:46:38 +00:00
|
|
|
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)
|
2022-10-06 19:02:27 +00:00
|
|
|
if err != nil {
|
2022-10-14 16:46:38 +00:00
|
|
|
return GenerateAPIKeyResponse{}, err
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode > http.StatusCreated {
|
2023-01-29 21:47:24 +00:00
|
|
|
return GenerateAPIKeyResponse{}, ReadBodyAsError(res)
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
2022-10-14 16:46:38 +00:00
|
|
|
|
|
|
|
var apiKey GenerateAPIKeyResponse
|
|
|
|
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
|
|
|
|
2022-10-06 21:56:43 +00:00
|
|
|
// CreateAPIKey generates an API key for the user ID provided.
|
2023-07-05 12:59:38 +00:00
|
|
|
// 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.
|
2022-10-14 16:46:38 +00:00
|
|
|
func (c *Client) CreateAPIKey(ctx context.Context, user string) (GenerateAPIKeyResponse, error) {
|
2022-10-06 21:56:43 +00:00
|
|
|
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", user), nil)
|
|
|
|
if err != nil {
|
2022-10-14 16:46:38 +00:00
|
|
|
return GenerateAPIKeyResponse{}, err
|
2022-10-06 21:56:43 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode > http.StatusCreated {
|
2023-01-29 21:47:24 +00:00
|
|
|
return GenerateAPIKeyResponse{}, ReadBodyAsError(res)
|
2022-10-06 21:56:43 +00:00
|
|
|
}
|
2022-10-14 16:46:38 +00:00
|
|
|
|
|
|
|
var apiKey GenerateAPIKeyResponse
|
|
|
|
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
|
2022-10-06 21:56:43 +00:00
|
|
|
}
|
|
|
|
|
2023-02-23 15:00:27 +00:00
|
|
|
type TokensFilter struct {
|
|
|
|
IncludeAll bool `json:"include_all"`
|
|
|
|
}
|
|
|
|
|
2023-03-01 16:35:55 +00:00
|
|
|
type APIKeyWithOwner struct {
|
|
|
|
APIKey
|
|
|
|
Username string `json:"username"`
|
|
|
|
}
|
|
|
|
|
2023-03-16 15:25:08 +00:00
|
|
|
type TokenConfig struct {
|
|
|
|
MaxTokenLifetime time.Duration `json:"max_token_lifetime"`
|
|
|
|
}
|
|
|
|
|
2023-02-23 15:00:27 +00:00
|
|
|
// 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))
|
|
|
|
r.URL.RawQuery = q.Encode()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
// Tokens list machine API keys.
|
2023-03-01 16:35:55 +00:00
|
|
|
func (c *Client) Tokens(ctx context.Context, userID string, filter TokensFilter) ([]APIKeyWithOwner, error) {
|
2023-02-23 15:00:27 +00:00
|
|
|
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/tokens", userID), nil, filter.asRequestOption())
|
2022-10-06 19:02:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode > http.StatusOK {
|
2023-01-29 21:47:24 +00:00
|
|
|
return nil, ReadBodyAsError(res)
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
2023-03-01 16:35:55 +00:00
|
|
|
apiKey := []APIKeyWithOwner{}
|
2022-10-06 19:02:27 +00:00
|
|
|
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
|
|
|
|
}
|
|
|
|
|
2023-03-02 17:39:38 +00:00
|
|
|
// APIKeyByID returns the api key by id.
|
|
|
|
func (c *Client) APIKeyByID(ctx context.Context, userID string, id string) (*APIKey, error) {
|
2022-10-06 19:02:27 +00:00
|
|
|
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 {
|
2023-01-29 21:47:24 +00:00
|
|
|
return nil, ReadBodyAsError(res)
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
|
|
|
apiKey := &APIKey{}
|
|
|
|
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
|
|
|
|
}
|
|
|
|
|
2023-03-02 17:39:38 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2022-10-06 19:02:27 +00:00
|
|
|
// 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 {
|
2023-01-29 21:47:24 +00:00
|
|
|
return ReadBodyAsError(res)
|
2022-10-06 19:02:27 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-16 15:25:08 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|