Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42451c13ec |
Generated
+6
@@ -11917,6 +11917,12 @@ const docTemplate = `{
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"allow_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.APIAllowListTarget"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
||||
Generated
+6
@@ -10613,6 +10613,12 @@
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"allow_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.APIAllowListTarget"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
||||
@@ -51,6 +51,8 @@ func TestTokenCRUD(t *testing.T) {
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
|
||||
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
|
||||
// no update
|
||||
|
||||
@@ -86,6 +88,8 @@ func TestTokenScoped(t *testing.T) {
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
}
|
||||
|
||||
// Ensure backward-compat: when a token is created using the legacy singular
|
||||
@@ -132,6 +136,8 @@ func TestTokenLegacySingularScopeCompat(t *testing.T) {
|
||||
require.Len(t, keys, 1)
|
||||
require.Equal(t, tc.scope, keys[0].Scope)
|
||||
require.ElementsMatch(t, keys[0].Scopes, tc.scopes)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ type CheckConstraint string
|
||||
|
||||
// CheckConstraint enums.
|
||||
const (
|
||||
CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys
|
||||
CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users
|
||||
CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users
|
||||
CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
|
||||
|
||||
@@ -51,6 +51,13 @@ func ListLazy[F any, T any](convert func(F) T) func(list []F) []T {
|
||||
}
|
||||
}
|
||||
|
||||
func APIAllowListTarget(entry rbac.AllowListElement) codersdk.APIAllowListTarget {
|
||||
return codersdk.APIAllowListTarget{
|
||||
Type: codersdk.RBACResource(entry.Type),
|
||||
ID: entry.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type ExternalAuthMeta struct {
|
||||
Authenticated bool
|
||||
ValidateError string
|
||||
|
||||
Generated
+2
-1
@@ -1126,7 +1126,8 @@ CREATE TABLE api_keys (
|
||||
ip_address inet DEFAULT '0.0.0.0'::inet NOT NULL,
|
||||
token_name text DEFAULT ''::text NOT NULL,
|
||||
scopes api_key_scope[] NOT NULL,
|
||||
allow_list text[] NOT NULL
|
||||
allow_list text[] NOT NULL,
|
||||
CONSTRAINT api_keys_allow_list_not_empty CHECK ((array_length(allow_list, 1) > 0))
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN api_keys.hashed_secret IS 'hashed_secret contains a SHA256 hash of the key secret. This is considered a secret and MUST NOT be returned from the API as it is used for API key encryption in app proxying code.';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Drop all CHECK constraints added in the up migration
|
||||
ALTER TABLE api_keys
|
||||
DROP CONSTRAINT api_keys_allow_list_not_empty;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Defensively update any API keys with empty allow_list to have default '*:*'
|
||||
-- This ensures all existing keys have at least one entry before adding the constraint
|
||||
UPDATE api_keys
|
||||
SET allow_list = ARRAY['*:*']
|
||||
WHERE allow_list = ARRAY[]::text[] OR array_length(allow_list, 1) IS NULL;
|
||||
|
||||
-- Add CHECK constraint to ensure allow_list array is never empty
|
||||
ALTER TABLE api_keys
|
||||
ADD CONSTRAINT api_keys_allow_list_not_empty
|
||||
CHECK (array_length(allow_list, 1) > 0);
|
||||
@@ -1608,5 +1608,6 @@ func convertAPIKey(k database.APIKey) codersdk.APIKey {
|
||||
Scopes: scopes,
|
||||
LifetimeSeconds: k.LifetimeSeconds,
|
||||
TokenName: k.TokenName,
|
||||
AllowList: db2sdk.List(k.AllowList, db2sdk.APIAllowListTarget),
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -12,17 +12,18 @@ import (
|
||||
|
||||
// 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"`
|
||||
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.
|
||||
|
||||
Generated
+20
-13
@@ -744,6 +744,12 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"allow_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"type": "*"
|
||||
}
|
||||
],
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
@@ -762,19 +768,20 @@
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------|-------------------------------------------------------|----------|--------------|---------------------------------|
|
||||
| `created_at` | string | true | | |
|
||||
| `expires_at` | string | true | | |
|
||||
| `id` | string | true | | |
|
||||
| `last_used` | string | true | | |
|
||||
| `lifetime_seconds` | integer | true | | |
|
||||
| `login_type` | [codersdk.LoginType](#codersdklogintype) | true | | |
|
||||
| `scope` | [codersdk.APIKeyScope](#codersdkapikeyscope) | false | | Deprecated: use Scopes instead. |
|
||||
| `scopes` | array of [codersdk.APIKeyScope](#codersdkapikeyscope) | false | | |
|
||||
| `token_name` | string | true | | |
|
||||
| `updated_at` | string | true | | |
|
||||
| `user_id` | string | true | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------|---------------------------------------------------------------------|----------|--------------|---------------------------------|
|
||||
| `allow_list` | array of [codersdk.APIAllowListTarget](#codersdkapiallowlisttarget) | false | | |
|
||||
| `created_at` | string | true | | |
|
||||
| `expires_at` | string | true | | |
|
||||
| `id` | string | true | | |
|
||||
| `last_used` | string | true | | |
|
||||
| `lifetime_seconds` | integer | true | | |
|
||||
| `login_type` | [codersdk.LoginType](#codersdklogintype) | true | | |
|
||||
| `scope` | [codersdk.APIKeyScope](#codersdkapikeyscope) | false | | Deprecated: use Scopes instead. |
|
||||
| `scopes` | array of [codersdk.APIKeyScope](#codersdkapikeyscope) | false | | |
|
||||
| `token_name` | string | true | | |
|
||||
| `updated_at` | string | true | | |
|
||||
| `user_id` | string | true | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
|
||||
Generated
+85
-22
@@ -757,6 +757,12 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
||||
```json
|
||||
[
|
||||
{
|
||||
"allow_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"type": "*"
|
||||
}
|
||||
],
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
@@ -784,31 +790,76 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------|--------------------------------------------------------|----------|--------------|---------------------------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» created_at` | string(date-time) | true | | |
|
||||
| `» expires_at` | string(date-time) | true | | |
|
||||
| `» id` | string | true | | |
|
||||
| `» last_used` | string(date-time) | true | | |
|
||||
| `» lifetime_seconds` | integer | true | | |
|
||||
| `» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | true | | |
|
||||
| `» scope` | [codersdk.APIKeyScope](schemas.md#codersdkapikeyscope) | false | | Deprecated: use Scopes instead. |
|
||||
| `» scopes` | array | false | | |
|
||||
| `» token_name` | string | true | | |
|
||||
| `» updated_at` | string(date-time) | true | | |
|
||||
| `» user_id` | string(uuid) | true | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------|----------------------------------------------------------|----------|--------------|---------------------------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» allow_list` | array | false | | |
|
||||
| `»» id` | string | false | | |
|
||||
| `»» type` | [codersdk.RBACResource](schemas.md#codersdkrbacresource) | false | | |
|
||||
| `» created_at` | string(date-time) | true | | |
|
||||
| `» expires_at` | string(date-time) | true | | |
|
||||
| `» id` | string | true | | |
|
||||
| `» last_used` | string(date-time) | true | | |
|
||||
| `» lifetime_seconds` | integer | true | | |
|
||||
| `» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | true | | |
|
||||
| `» scope` | [codersdk.APIKeyScope](schemas.md#codersdkapikeyscope) | false | | Deprecated: use Scopes instead. |
|
||||
| `» scopes` | array | false | | |
|
||||
| `» token_name` | string | true | | |
|
||||
| `» updated_at` | string(date-time) | true | | |
|
||||
| `» user_id` | string(uuid) | true | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
|--------------|-----------------------|
|
||||
| `login_type` | `password` |
|
||||
| `login_type` | `github` |
|
||||
| `login_type` | `oidc` |
|
||||
| `login_type` | `token` |
|
||||
| `scope` | `all` |
|
||||
| `scope` | `application_connect` |
|
||||
| Property | Value |
|
||||
|--------------|------------------------------------|
|
||||
| `type` | `*` |
|
||||
| `type` | `aibridge_interception` |
|
||||
| `type` | `api_key` |
|
||||
| `type` | `assign_org_role` |
|
||||
| `type` | `assign_role` |
|
||||
| `type` | `audit_log` |
|
||||
| `type` | `connection_log` |
|
||||
| `type` | `crypto_key` |
|
||||
| `type` | `debug_info` |
|
||||
| `type` | `deployment_config` |
|
||||
| `type` | `deployment_stats` |
|
||||
| `type` | `file` |
|
||||
| `type` | `group` |
|
||||
| `type` | `group_member` |
|
||||
| `type` | `idpsync_settings` |
|
||||
| `type` | `inbox_notification` |
|
||||
| `type` | `license` |
|
||||
| `type` | `notification_message` |
|
||||
| `type` | `notification_preference` |
|
||||
| `type` | `notification_template` |
|
||||
| `type` | `oauth2_app` |
|
||||
| `type` | `oauth2_app_code_token` |
|
||||
| `type` | `oauth2_app_secret` |
|
||||
| `type` | `organization` |
|
||||
| `type` | `organization_member` |
|
||||
| `type` | `prebuilt_workspace` |
|
||||
| `type` | `provisioner_daemon` |
|
||||
| `type` | `provisioner_jobs` |
|
||||
| `type` | `replicas` |
|
||||
| `type` | `system` |
|
||||
| `type` | `tailnet_coordinator` |
|
||||
| `type` | `task` |
|
||||
| `type` | `template` |
|
||||
| `type` | `usage_event` |
|
||||
| `type` | `user` |
|
||||
| `type` | `user_secret` |
|
||||
| `type` | `webpush_subscription` |
|
||||
| `type` | `workspace` |
|
||||
| `type` | `workspace_agent_devcontainers` |
|
||||
| `type` | `workspace_agent_resource_monitor` |
|
||||
| `type` | `workspace_dormant` |
|
||||
| `type` | `workspace_proxy` |
|
||||
| `login_type` | `password` |
|
||||
| `login_type` | `github` |
|
||||
| `login_type` | `oidc` |
|
||||
| `login_type` | `token` |
|
||||
| `scope` | `all` |
|
||||
| `scope` | `application_connect` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -896,6 +947,12 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/{keyname} \
|
||||
|
||||
```json
|
||||
{
|
||||
"allow_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"type": "*"
|
||||
}
|
||||
],
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
@@ -946,6 +1003,12 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/{keyid} \
|
||||
|
||||
```json
|
||||
{
|
||||
"allow_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"type": "*"
|
||||
}
|
||||
],
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
|
||||
Generated
+1
@@ -141,6 +141,7 @@ export interface APIKey {
|
||||
readonly scopes: readonly APIKeyScope[];
|
||||
readonly token_name: string;
|
||||
readonly lifetime_seconds: number;
|
||||
readonly allow_list: readonly APIAllowListTarget[];
|
||||
}
|
||||
|
||||
// From codersdk/apikey.go
|
||||
|
||||
@@ -85,6 +85,7 @@ export const MockToken: TypesGen.APIKeyWithOwner = {
|
||||
login_type: "token",
|
||||
scope: "all",
|
||||
scopes: ["coder:all"],
|
||||
allow_list: [{ type: "*", id: "*" }],
|
||||
lifetime_seconds: 2592000,
|
||||
token_name: "token-one",
|
||||
username: "admin",
|
||||
@@ -102,6 +103,7 @@ export const MockTokens: TypesGen.APIKeyWithOwner[] = [
|
||||
login_type: "token",
|
||||
scope: "all",
|
||||
scopes: ["coder:all"],
|
||||
allow_list: [{ type: "*", id: "*" }],
|
||||
lifetime_seconds: 2592000,
|
||||
token_name: "token-two",
|
||||
username: "admin",
|
||||
|
||||
Reference in New Issue
Block a user