Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7546691

Browse files
wilfred-asomaniiStorj Robot
authored andcommitted
satellite/admin: add create rest API key endpoint
Issue: #7621 Change-Id: Iadf526c38926bbc2bdae2aa446db5310a1bef336
1 parent fa02d4a commit 7546691

File tree

11 files changed

+215
-3
lines changed

11 files changed

+215
-3
lines changed

satellite/admin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
306306
peer.FreezeAccounts.Service,
307307
peer.Analytics.Service,
308308
peer.Payments.Accounts,
309+
peer.REST.Keys,
309310
placement,
310311
config.Metainfo.ProjectLimits.MaxBuckets,
311312
config.Metainfo.RateLimiter.Rate,

satellite/admin/back-office/api-docs.gen.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* [Freeze User](#usermanagement-freeze-user)
2121
* [Unfreeze User](#usermanagement-unfreeze-user)
2222
* [Disable MFA](#usermanagement-disable-mfa)
23+
* [Create Rest Key](#usermanagement-create-rest-key)
2324
* ProjectManagement
2425
* [Get project](#projectmanagement-get-project)
2526
* [Update project limits](#projectmanagement-update-project-limits)
@@ -38,6 +39,7 @@ Gets the settings of the service and relevant Storj services settings
3839
features: {
3940
account: {
4041
create: boolean
42+
createRestKey: boolean
4143
delete: boolean
4244
history: boolean
4345
list: boolean
@@ -467,6 +469,33 @@ Disables MFA for a user
467469
|---|---|---|
468470
| `userID` | `string` | UUID formatted as `00000000-0000-0000-0000-000000000000` |
469471

472+
<h3 id='usermanagement-create-rest-key'>Create Rest Key (<a href='#list-of-endpoints'>go to full list</a>)</h3>
473+
474+
Creates a rest API key a user
475+
476+
`POST /back-office/api/v1/users/rest-keys/{userID}`
477+
478+
**Path Params:**
479+
480+
| name | type | elaboration |
481+
|---|---|---|
482+
| `userID` | `string` | UUID formatted as `00000000-0000-0000-0000-000000000000` |
483+
484+
**Request body:**
485+
486+
```typescript
487+
{
488+
expiration: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
489+
}
490+
491+
```
492+
493+
**Response body:**
494+
495+
```typescript
496+
string
497+
```
498+
470499
<h3 id='projectmanagement-get-project'>Get project (<a href='#list-of-endpoints'>go to full list</a>)</h3>
471500

472501
Gets project by ID

satellite/admin/back-office/authorization.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
PermAccountSuspendPermanently
3232
PermAccountReActivatePermanently
3333
PermAccountDeleteNoData
34+
PermAccountCreateRestKey
3435
PermAccountDeleteWithData
3536
PermProjectView
3637
PermProjectSetLimits
@@ -49,7 +50,7 @@ const (
4950
const (
5051
RoleAdmin = Authorization(
5152
PermAccountView | PermAccountChangeEmail | PermAccountDisableMFA | PermAccountChangeLimits |
52-
PermAccountChangeName | PermAccountChangeKind | PermAccountChangeStatus |
53+
PermAccountChangeName | PermAccountChangeKind | PermAccountChangeStatus | PermAccountCreateRestKey |
5354
PermAccountSetDataPlacement | PermAccountRemoveDataPlacement | PermAccountSetUserAgent |
5455
PermAccountSuspendTemporary | PermAccountReActivateTemporary | PermAccountSuspendPermanently |
5556
PermAccountReActivatePermanently | PermAccountDeleteNoData | PermAccountDeleteWithData |

satellite/admin/back-office/gen/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,21 @@ func main() {
206206
},
207207
})
208208

209+
group.Post("/rest-keys/{userID}", &apigen.Endpoint{
210+
Name: "Create Rest Key",
211+
Description: "Creates a rest API key a user",
212+
GoName: "CreateRestKey",
213+
TypeScriptName: "createRestKey",
214+
PathParams: []apigen.Param{
215+
apigen.NewParam("userID", uuid.UUID{}),
216+
},
217+
Request: backoffice.CreateRestKeyRequest{},
218+
Response: "",
219+
Settings: map[any]any{
220+
authPermsKey: []backoffice.Permission{backoffice.PermAccountCreateRestKey},
221+
},
222+
})
223+
209224
group = api.Group("ProjectManagement", "projects")
210225
group.Middleware = append(group.Middleware, authMiddleware{})
211226

satellite/admin/back-office/handlers.gen.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type UserManagementService interface {
4343
FreezeUser(ctx context.Context, userID uuid.UUID, request FreezeUserRequest) api.HTTPError
4444
UnfreezeUser(ctx context.Context, userID uuid.UUID) api.HTTPError
4545
DisableMFA(ctx context.Context, userID uuid.UUID) api.HTTPError
46+
CreateRestKey(ctx context.Context, userID uuid.UUID, request CreateRestKeyRequest) (*string, api.HTTPError)
4647
}
4748

4849
type ProjectManagementService interface {
@@ -128,6 +129,7 @@ func NewUserManagement(log *zap.Logger, mon *monkit.Scope, service UserManagemen
128129
usersRouter.HandleFunc("/{userID}/freeze-events", handler.handleFreezeUser).Methods("POST")
129130
usersRouter.HandleFunc("/{userID}/freeze-events", handler.handleUnfreezeUser).Methods("DELETE")
130131
usersRouter.HandleFunc("/mfa/{userID}", handler.handleDisableMFA).Methods("DELETE")
132+
usersRouter.HandleFunc("/rest-keys/{userID}", handler.handleCreateRestKey).Methods("POST")
131133

132134
return handler
133135
}
@@ -574,6 +576,52 @@ func (h *UserManagementHandler) handleDisableMFA(w http.ResponseWriter, r *http.
574576
}
575577
}
576578

579+
func (h *UserManagementHandler) handleCreateRestKey(w http.ResponseWriter, r *http.Request) {
580+
ctx := r.Context()
581+
var err error
582+
defer h.mon.Task()(&ctx)(&err)
583+
584+
w.Header().Set("Content-Type", "application/json")
585+
586+
userIDParam, ok := mux.Vars(r)["userID"]
587+
if !ok {
588+
api.ServeError(h.log, w, http.StatusBadRequest, errs.New("missing userID route param"))
589+
return
590+
}
591+
592+
userID, err := uuid.FromString(userIDParam)
593+
if err != nil {
594+
api.ServeError(h.log, w, http.StatusBadRequest, err)
595+
return
596+
}
597+
598+
payload := CreateRestKeyRequest{}
599+
if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
600+
api.ServeError(h.log, w, http.StatusBadRequest, err)
601+
return
602+
}
603+
604+
if err = h.auth.VerifyHost(r); err != nil {
605+
api.ServeError(h.log, w, http.StatusForbidden, err)
606+
return
607+
}
608+
609+
if h.auth.IsRejected(w, r, 32768) {
610+
return
611+
}
612+
613+
retVal, httpErr := h.service.CreateRestKey(ctx, userID, payload)
614+
if httpErr.Err != nil {
615+
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
616+
return
617+
}
618+
619+
err = json.NewEncoder(w).Encode(retVal)
620+
if err != nil {
621+
h.log.Debug("failed to write json CreateRestKey response", zap.Error(ErrUsersAPI.Wrap(err)))
622+
}
623+
}
624+
577625
func (h *ProjectManagementHandler) handleGetProject(w http.ResponseWriter, r *http.Request) {
578626
ctx := r.Context()
579627
var err error
@@ -598,7 +646,7 @@ func (h *ProjectManagementHandler) handleGetProject(w http.ResponseWriter, r *ht
598646
return
599647
}
600648

601-
if h.auth.IsRejected(w, r, 65536) {
649+
if h.auth.IsRejected(w, r, 131072) {
602650
return
603651
}
604652

@@ -644,7 +692,7 @@ func (h *ProjectManagementHandler) handleUpdateProjectLimits(w http.ResponseWrit
644692
return
645693
}
646694

647-
if h.auth.IsRejected(w, r, 131072) {
695+
if h.auth.IsRejected(w, r, 262144) {
648696
return
649697
}
650698

satellite/admin/back-office/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"storj.io/storj/satellite/accounting"
1212
"storj.io/storj/satellite/analytics"
1313
"storj.io/storj/satellite/console"
14+
"storj.io/storj/satellite/console/restapikeys"
1415
"storj.io/storj/satellite/nodeselection"
1516
"storj.io/storj/satellite/payments"
1617
)
@@ -29,6 +30,7 @@ type Service struct {
2930

3031
accountingDB accounting.ProjectAccounting
3132
consoleDB console.DB
33+
restKeys restapikeys.Service
3234

3335
accountFreeze *console.AccountFreezeService
3436
accounting *accounting.Service
@@ -53,6 +55,7 @@ func NewService(
5355
accountFreeze *console.AccountFreezeService,
5456
analytics *analytics.Service,
5557
payments payments.Accounts,
58+
restKeys restapikeys.Service,
5659
placement nodeselection.PlacementDefinitions,
5760
defaultMaxBuckets int,
5861
defaultRateLimit float64,
@@ -62,6 +65,7 @@ func NewService(
6265
return &Service{
6366
log: log,
6467
consoleDB: consoleDB,
68+
restKeys: restKeys,
6569
analytics: analytics,
6670
accountingDB: accountingDB,
6771
accounting: accounting,

satellite/admin/back-office/settings.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type FeatureFlags struct {
4141
// AccountFlags are the feature flags related to user's accounts.
4242
type AccountFlags struct {
4343
Create bool `json:"create"`
44+
CreateRestKey bool `json:"createRestKey"`
4445
Delete bool `json:"delete"`
4546
History bool `json:"history"`
4647
List bool `json:"list"`
@@ -127,6 +128,9 @@ func (s *Service) GetSettings(_ context.Context, authInfo *AuthInfo) (*Settings,
127128
if s.authorizer.HasPermissions(g, PermAccountDisableMFA) {
128129
settings.Admin.Features.Account.DisableMFA = true
129130
}
131+
if s.authorizer.HasPermissions(g, PermAccountCreateRestKey) {
132+
settings.Admin.Features.Account.CreateRestKey = true
133+
}
130134

131135
// project permission features
132136
if s.authorizer.HasPermissions(g, PermProjectView) {

satellite/admin/back-office/settings_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func TestGetSettings(t *testing.T) {
3535
Admin: backoffice.SettingsAdmin{
3636
Features: backoffice.FeatureFlags{
3737
Account: backoffice.AccountFlags{
38+
CreateRestKey: true,
3839
Delete: true,
3940
DisableMFA: true,
4041
View: true,

satellite/admin/back-office/ui/src/api/client.gen.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Time, UUID } from '@/types/common';
66

77
export class AccountFlags {
88
create: boolean;
9+
createRestKey: boolean;
910
delete: boolean;
1011
history: boolean;
1112
list: boolean;
@@ -44,6 +45,10 @@ export class BucketFlags {
4445
view: boolean;
4546
}
4647

48+
export class CreateRestKeyRequest {
49+
expiration: Time;
50+
}
51+
4752
export class FeatureFlags {
4853
account: AccountFlags;
4954
project: ProjectFlags;
@@ -336,6 +341,16 @@ export class UserManagementHttpApiV1 {
336341
const err = await response.json();
337342
throw new APIError(err.error, response.status);
338343
}
344+
345+
public async createRestKey(request: CreateRestKeyRequest, userID: UUID): Promise<string> {
346+
const fullPath = `${this.ROOT_PATH}/rest-keys/${userID}`;
347+
const response = await this.http.post(fullPath, JSON.stringify(request));
348+
if (response.ok) {
349+
return response.json().then((body) => body as string);
350+
}
351+
const err = await response.json();
352+
throw new APIError(err.error, response.status);
353+
}
339354
}
340355

341356
export class ProjectManagementHttpApiV1 {

satellite/admin/back-office/users.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ type UpdateUserRequest struct {
7272
SegmentLimit *int64 `json:"segmentLimit"`
7373
}
7474

75+
// CreateRestKeyRequest represents a request to create rest key.
76+
type CreateRestKeyRequest struct {
77+
Expiration time.Time `json:"expiration"`
78+
}
79+
7580
// UserProject is project owned by a user with basic information, usage, and limits.
7681
type UserProject struct {
7782
ID uuid.UUID `json:"id"` // This is the public ID
@@ -734,3 +739,47 @@ func (s *Service) DisableMFA(ctx context.Context, userID uuid.UUID) api.HTTPErro
734739

735740
return api.HTTPError{}
736741
}
742+
743+
// CreateRestKey creates a new REST API key for a user.
744+
func (s *Service) CreateRestKey(ctx context.Context, userID uuid.UUID, request CreateRestKeyRequest) (*string, api.HTTPError) {
745+
var err error
746+
defer mon.Task()(&ctx)(&err)
747+
748+
user, err := s.consoleDB.Users().Get(ctx, userID)
749+
if err != nil {
750+
status := http.StatusInternalServerError
751+
if errors.Is(err, sql.ErrNoRows) {
752+
status = http.StatusNotFound
753+
err = errors.New("user not found")
754+
}
755+
return nil, api.HTTPError{
756+
Status: status,
757+
Err: Error.Wrap(err),
758+
}
759+
}
760+
761+
if request.Expiration.IsZero() {
762+
return nil, api.HTTPError{
763+
Status: http.StatusBadRequest,
764+
Err: Error.New("expiration is required"),
765+
}
766+
}
767+
768+
expiration := time.Until(request.Expiration)
769+
if expiration < 0 {
770+
return nil, api.HTTPError{
771+
Status: http.StatusBadRequest,
772+
Err: Error.New("expiration must be in the future"),
773+
}
774+
}
775+
776+
apiKey, _, err := s.restKeys.CreateNoAuth(ctx, user.ID, &expiration)
777+
if err != nil {
778+
return nil, api.HTTPError{
779+
Status: http.StatusInternalServerError,
780+
Err: Error.Wrap(err),
781+
}
782+
}
783+
784+
return &apiKey, api.HTTPError{}
785+
}

0 commit comments

Comments
 (0)