Skip to content

Commit 8e6f3a7

Browse files
authored
feat: add unique name to API token entity (#664)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent a735386 commit 8e6f3a7

26 files changed

+495
-166
lines changed

app/cli/cmd/organization_apitoken_create.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -27,8 +27,8 @@ import (
2727

2828
func newAPITokenCreateCmd() *cobra.Command {
2929
var (
30-
description string
31-
expiresIn time.Duration
30+
description, name string
31+
expiresIn time.Duration
3232
)
3333

3434
cmd := &cobra.Command{
@@ -40,7 +40,7 @@ func newAPITokenCreateCmd() *cobra.Command {
4040
duration = &expiresIn
4141
}
4242

43-
res, err := action.NewAPITokenCreate(actionOpts).Run(context.Background(), description, duration)
43+
res, err := action.NewAPITokenCreate(actionOpts).Run(context.Background(), name, description, duration)
4444
if err != nil {
4545
return fmt.Errorf("creating API token: %w", err)
4646
}
@@ -51,6 +51,9 @@ func newAPITokenCreateCmd() *cobra.Command {
5151

5252
cmd.Flags().StringVar(&description, "description", "", "API token description")
5353
cmd.Flags().DurationVar(&expiresIn, "expiration", 0, "optional API token expiration, in hours i.e 1h, 24h, 178h (week), ...")
54+
cmd.Flags().StringVar(&name, "name", "", "token name")
55+
err := cmd.MarkFlagRequired("name")
56+
cobra.CheckErr(err)
5457

5558
return cmd
5659
}
@@ -63,9 +66,9 @@ func apiTokenListTableOutput(tokens []*action.APITokenItem) error {
6366

6467
t := newTableWriter()
6568

66-
t.AppendHeader(table.Row{"ID", "Description", "Created At", "Expires At", "Revoked At"})
69+
t.AppendHeader(table.Row{"ID", "Name", "Description", "Created At", "Expires At", "Revoked At"})
6770
for _, p := range tokens {
68-
r := table.Row{p.ID, p.Description, p.CreatedAt.Format(time.RFC822)}
71+
r := table.Row{p.ID, p.Name, p.Description, p.CreatedAt.Format(time.RFC822)}
6972
if p.ExpiresAt != nil {
7073
r = append(r, p.ExpiresAt.Format(time.RFC822))
7174
} else {

app/cli/internal/action/apitoken_create.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -33,10 +33,10 @@ func NewAPITokenCreate(cfg *ActionsOpts) *APITokenCreate {
3333
return &APITokenCreate{cfg}
3434
}
3535

36-
func (action *APITokenCreate) Run(ctx context.Context, description string, expiresIn *time.Duration) (*APITokenItem, error) {
36+
func (action *APITokenCreate) Run(ctx context.Context, name, description string, expiresIn *time.Duration) (*APITokenItem, error) {
3737
client := pb.NewAPITokenServiceClient(action.cfg.CPConnection)
3838

39-
req := &pb.APITokenServiceCreateRequest{Description: &description}
39+
req := &pb.APITokenServiceCreateRequest{Name: name, Description: &description}
4040
if expiresIn != nil {
4141
req.ExpiresIn = durationpb.New(*expiresIn)
4242
}
@@ -59,6 +59,7 @@ func (action *APITokenCreate) Run(ctx context.Context, description string, expir
5959

6060
type APITokenItem struct {
6161
ID string `json:"id"`
62+
Name string `json:"name"`
6263
Description string `json:"description"`
6364
// JWT is returned only during the creation
6465
JWT string `json:"jwt,omitempty"`
@@ -74,6 +75,7 @@ func pbAPITokenItemToAPITokenItem(p *pb.APITokenItem) *APITokenItem {
7475

7576
item := &APITokenItem{
7677
ID: p.Id,
78+
Name: p.Name,
7779
Description: p.Description,
7880
CreatedAt: toTimePtr(p.CreatedAt.AsTime()),
7981
}

app/cli/internal/action/apitoken_list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.

app/controlplane/api/controlplane/v1/api_token.pb.go

Lines changed: 105 additions & 86 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/controlplane/v1/api_token.pb.validate.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/controlplane/v1/api_token.proto

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -17,19 +17,21 @@ syntax = "proto3";
1717

1818
package controlplane.v1;
1919

20-
option go_package = "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1;v1";
21-
22-
import "validate/validate.proto";
23-
import "google/protobuf/timestamp.proto";
2420
import "google/protobuf/duration.proto";
21+
import "google/protobuf/timestamp.proto";
22+
import "validate/validate.proto";
23+
24+
option go_package = "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1;v1";
2525

2626
service APITokenService {
27-
rpc Create (APITokenServiceCreateRequest) returns (APITokenServiceCreateResponse);
28-
rpc List (APITokenServiceListRequest) returns (APITokenServiceListResponse);
29-
rpc Revoke (APITokenServiceRevokeRequest) returns (APITokenServiceRevokeResponse);
27+
rpc Create(APITokenServiceCreateRequest) returns (APITokenServiceCreateResponse);
28+
rpc List(APITokenServiceListRequest) returns (APITokenServiceListResponse);
29+
rpc Revoke(APITokenServiceRevokeRequest) returns (APITokenServiceRevokeResponse);
3030
}
3131

3232
message APITokenServiceCreateRequest {
33+
string name = 3 [(validate.rules).string.min_len = 1];
34+
3335
optional string description = 1;
3436
optional google.protobuf.Duration expires_in = 2;
3537
}
@@ -38,8 +40,8 @@ message APITokenServiceCreateResponse {
3840
APITokenFull result = 1;
3941

4042
message APITokenFull {
41-
APITokenItem item = 1;
42-
string jwt = 2;
43+
APITokenItem item = 1;
44+
string jwt = 2;
4345
}
4446
}
4547

@@ -59,9 +61,10 @@ message APITokenServiceListResponse {
5961

6062
message APITokenItem {
6163
string id = 1;
64+
string name = 7;
6265
string description = 2;
6366
string organization_id = 3;
6467
google.protobuf.Timestamp created_at = 4;
6568
google.protobuf.Timestamp revoked_at = 5;
6669
google.protobuf.Timestamp expires_at = 6;
67-
}
70+
}

app/controlplane/api/controlplane/v1/api_token_grpc.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/frontend/controlplane/v1/api_token.ts

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/internal/biz/apitoken.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package biz
1717

1818
import (
1919
"context"
20+
"errors"
2021
"fmt"
2122
"time"
2223

@@ -32,6 +33,7 @@ import (
3233
// API Token is used for unattended access to the control plane API.
3334
type APIToken struct {
3435
ID uuid.UUID
36+
Name string
3537
Description string
3638
// This is the JWT value returned only during creation
3739
JWT string
@@ -45,7 +47,7 @@ type APIToken struct {
4547
}
4648

4749
type APITokenRepo interface {
48-
Create(ctx context.Context, description *string, expiresAt *time.Time, organizationID uuid.UUID) (*APIToken, error)
50+
Create(ctx context.Context, name string, description *string, expiresAt *time.Time, organizationID uuid.UUID) (*APIToken, error)
4951
// List all the tokens optionally filtering it by organization and including revoked tokens
5052
List(ctx context.Context, orgID *uuid.UUID, includeRevoked bool) ([]*APIToken, error)
5153
Revoke(ctx context.Context, orgID, ID uuid.UUID) error
@@ -93,12 +95,21 @@ func NewAPITokenUseCase(apiTokenRepo APITokenRepo, conf *conf.Auth, authzE *auth
9395
}
9496

9597
// expires in is a string that can be parsed by time.ParseDuration
96-
func (uc *APITokenUseCase) Create(ctx context.Context, description *string, expiresIn *time.Duration, orgID string) (*APIToken, error) {
98+
func (uc *APITokenUseCase) Create(ctx context.Context, name string, description *string, expiresIn *time.Duration, orgID string) (*APIToken, error) {
9799
orgUUID, err := uuid.Parse(orgID)
98100
if err != nil {
99101
return nil, NewErrInvalidUUID(err)
100102
}
101103

104+
if name == "" {
105+
return nil, NewErrValidationStr("name is required")
106+
}
107+
108+
// validate format of the name and the project
109+
if err := ValidateIsDNS1123(name); err != nil {
110+
return nil, NewErrValidation(err)
111+
}
112+
102113
// If expiration is provided we store it
103114
// we also validate that it's at least 24 hours and valid string format
104115
var expiresAt *time.Time
@@ -109,8 +120,11 @@ func (uc *APITokenUseCase) Create(ctx context.Context, description *string, expi
109120

110121
// NOTE: the expiration time is stored just for reference, it's also encoded in the JWT
111122
// We store it since Chainloop will not have access to the JWT to check the expiration once created
112-
token, err := uc.apiTokenRepo.Create(ctx, description, expiresAt, orgUUID)
123+
token, err := uc.apiTokenRepo.Create(ctx, name, description, expiresAt, orgUUID)
113124
if err != nil {
125+
if errors.Is(err, ErrAlreadyExists) {
126+
return nil, NewErrValidationStr("name already taken")
127+
}
114128
return nil, fmt.Errorf("storing token: %w", err)
115129
}
116130

0 commit comments

Comments
 (0)