mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add flag to see all tokens if owner (#6227)
* added query for tokens by user id * updated query args * adding owner col * fix request params * update-golden-files * added owners col to ls table output * added ttoken translations * prettier * format table according to arg * using slice.Contains * refactored token state * cleanup
This commit is contained in:
+29
-8
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -175,15 +176,35 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Success 200 {array} codersdk.APIKey
|
||||
// @Router /users/{user}/keys/tokens [get]
|
||||
func (api *API) tokens(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user = httpmw.UserParam(r)
|
||||
keys []database.APIKey
|
||||
err error
|
||||
queryStr = r.URL.Query().Get("include_all")
|
||||
includeAll, _ = strconv.ParseBool(queryStr)
|
||||
)
|
||||
|
||||
keys, err := api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching API keys.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
if includeAll {
|
||||
// get tokens for all users
|
||||
keys, err = api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching API keys.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// get user's tokens only
|
||||
keys, err = api.Database.GetAPIKeysByUserID(ctx, database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: user.ID})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching API keys.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
keys, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, keys)
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestTokenCRUD(t *testing.T) {
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestTokenCRUD(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
|
||||
keys, err = client.Tokens(ctx, codersdk.Me)
|
||||
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
@@ -45,7 +45,7 @@ func TestTokenCRUD(t *testing.T) {
|
||||
|
||||
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
|
||||
require.NoError(t, err)
|
||||
keys, err = client.Tokens(ctx, codersdk.Me)
|
||||
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func TestTokenScoped(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
|
||||
keys, err := client.Tokens(ctx, codersdk.Me)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
@@ -83,7 +83,7 @@ func TestTokenDuration(t *testing.T) {
|
||||
Lifetime: time.Hour * 24 * 7,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
|
||||
|
||||
@@ -347,7 +347,9 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
|
||||
})
|
||||
require.NoError(t, err, "create token")
|
||||
|
||||
apiKeys, err := client.Tokens(ctx, admin.UserID.String())
|
||||
apiKeys, err := client.Tokens(ctx, admin.UserID.String(), codersdk.TokensFilter{
|
||||
IncludeAll: true,
|
||||
})
|
||||
require.NoError(t, err, "get tokens")
|
||||
apiKey := apiKeys[0]
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ func (q *querier) GetAPIKeysByLoginType(ctx context.Context, loginType database.
|
||||
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByLoginType)(ctx, loginType)
|
||||
}
|
||||
|
||||
func (q *querier) GetAPIKeysByUserID(ctx context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) {
|
||||
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByUserID)(ctx, database.GetAPIKeysByUserIDParams{LoginType: params.LoginType, UserID: params.UserID})
|
||||
}
|
||||
|
||||
func (q *querier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]database.APIKey, error) {
|
||||
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysLastUsedAfter)(ctx, lastUsed)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,18 @@ func (s *MethodTestSuite) TestAPIKey() {
|
||||
Asserts(a, rbac.ActionRead, b, rbac.ActionRead).
|
||||
Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("GetAPIKeysByUserID", s.Subtest(func(db database.Store, check *expects) {
|
||||
idAB := uuid.New()
|
||||
idC := uuid.New()
|
||||
|
||||
keyA, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: idAB, LoginType: database.LoginTypeToken})
|
||||
keyB, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: idAB, LoginType: database.LoginTypeToken})
|
||||
_, _ = dbgen.APIKey(s.T(), db, database.APIKey{UserID: idC, LoginType: database.LoginTypeToken})
|
||||
|
||||
check.Args(database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: idAB}).
|
||||
Asserts(keyA, rbac.ActionRead, keyB, rbac.ActionRead).
|
||||
Returns(slice.New(keyA, keyB))
|
||||
}))
|
||||
s.Run("GetAPIKeysLastUsedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
a, _ := dbgen.APIKey(s.T(), db, database.APIKey{LastUsed: time.Now().Add(time.Hour)})
|
||||
b, _ := dbgen.APIKey(s.T(), db, database.APIKey{LastUsed: time.Now().Add(time.Hour)})
|
||||
|
||||
@@ -484,6 +484,19 @@ func (q *fakeQuerier) GetAPIKeysByLoginType(_ context.Context, t database.LoginT
|
||||
return apiKeys, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetAPIKeysByUserID(_ context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
apiKeys := make([]database.APIKey, 0)
|
||||
for _, key := range q.apiKeys {
|
||||
if key.UserID == params.UserID && key.LoginType == params.LoginType {
|
||||
apiKeys = append(apiKeys, key)
|
||||
}
|
||||
}
|
||||
return apiKeys, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) DeleteAPIKeyByID(_ context.Context, id string) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
@@ -31,6 +31,7 @@ type sqlcQuerier interface {
|
||||
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
|
||||
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
|
||||
GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error)
|
||||
GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error)
|
||||
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
|
||||
GetActiveUserCount(ctx context.Context) (int64, error)
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
|
||||
@@ -249,6 +249,50 @@ func (q *sqlQuerier) GetAPIKeysByLoginType(ctx context.Context, loginType LoginT
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getAPIKeysByUserID = `-- name: GetAPIKeysByUserID :many
|
||||
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope FROM api_keys WHERE login_type = $1 AND user_id = $2
|
||||
`
|
||||
|
||||
type GetAPIKeysByUserIDParams struct {
|
||||
LoginType LoginType `db:"login_type" json:"login_type"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAPIKeysByUserID, arg.LoginType, arg.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []APIKey
|
||||
for rows.Next() {
|
||||
var i APIKey
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.HashedSecret,
|
||||
&i.UserID,
|
||||
&i.LastUsed,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.LoginType,
|
||||
&i.LifetimeSeconds,
|
||||
&i.IPAddress,
|
||||
&i.Scope,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getAPIKeysLastUsedAfter = `-- name: GetAPIKeysLastUsedAfter :many
|
||||
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope FROM api_keys WHERE last_used > $1
|
||||
`
|
||||
|
||||
@@ -14,6 +14,9 @@ SELECT * FROM api_keys WHERE last_used > $1;
|
||||
-- name: GetAPIKeysByLoginType :many
|
||||
SELECT * FROM api_keys WHERE login_type = $1;
|
||||
|
||||
-- name: GetAPIKeysByUserID :many
|
||||
SELECT * FROM api_keys WHERE login_type = $1 AND user_id = $2;
|
||||
|
||||
-- name: InsertAPIKey :one
|
||||
INSERT INTO
|
||||
api_keys (
|
||||
|
||||
Reference in New Issue
Block a user