Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
VERSION := $(shell git describe --tags ${TAG})
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto admin-app compose-up-dev
.DEFAULT_GOAL := build
PROTON_COMMIT := "e5878c3542f056400593735a448925a2e6ea6116"
PROTON_COMMIT := "241685584afc362ce445309926b6dbe845899444"

admin-app:
@echo " > generating admin build"
Expand Down
59 changes: 59 additions & 0 deletions core/userpat/mocks/policy_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions core/userpat/mocks/repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions core/userpat/models/pat.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/raystack/frontier/pkg/metadata"
"github.com/raystack/frontier/pkg/utils"
)

type PAT struct {
Expand All @@ -13,8 +14,15 @@ type PAT struct {
Title string `rql:"name=title,type=string"`
SecretHash string `json:"-"`
Metadata metadata.Metadata
RoleIDs []string `json:"role_ids,omitempty"`
ProjectIDs []string `json:"project_ids,omitempty"`
LastUsedAt *time.Time `rql:"name=last_used_at,type=datetime"` // last_used_at can be null
ExpiresAt time.Time `rql:"name=expires_at,type=datetime"`
CreatedAt time.Time `rql:"name=created_at,type=datetime"`
UpdatedAt time.Time `rql:"name=updated_at,type=datetime"`
}

type PATList struct {
PATs []PAT
Page utils.Page
}
51 changes: 51 additions & 0 deletions core/userpat/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import (
patmodels "github.com/raystack/frontier/core/userpat/models"
"github.com/raystack/frontier/internal/bootstrap/schema"
pkgAuditRecord "github.com/raystack/frontier/pkg/auditrecord"
pkgUtils "github.com/raystack/frontier/pkg/utils"
"github.com/raystack/salt/log"
"github.com/raystack/salt/rql"
"golang.org/x/crypto/sha3"
)

Expand All @@ -34,6 +36,7 @@ type RoleService interface {

type PolicyService interface {
Create(ctx context.Context, pol policy.Policy) (policy.Policy, error)
List(ctx context.Context, flt policy.Filter) ([]policy.Policy, error)
}

type AuditRecordRepository interface {
Expand Down Expand Up @@ -304,6 +307,54 @@ func (s *Service) createProjectScopedPolicies(ctx context.Context, patID, orgID
return nil
}

// List retrieves all PATs for a user in an org and enriches each with scope fields.
func (s *Service) List(ctx context.Context, userID, orgID string, query *rql.Query) (patmodels.PATList, error) {
if !s.config.Enabled {
return patmodels.PATList{}, paterrors.ErrDisabled
}
result, err := s.repo.List(ctx, userID, orgID, query)
if err != nil {
return patmodels.PATList{}, err
}
for i := range result.PATs {
if err := s.enrichWithScope(ctx, &result.PATs[i]); err != nil {
return patmodels.PATList{}, fmt.Errorf("enriching PAT scope: %w", err)
}
}
return result, nil
}

// enrichWithScope derives role_ids and project_ids
func (s *Service) enrichWithScope(ctx context.Context, pat *patmodels.PAT) error {
policies, err := s.policyService.List(ctx, policy.Filter{
PrincipalID: pat.ID,
PrincipalType: schema.PATPrincipal,
})
if err != nil {
return fmt.Errorf("listing policies for PAT %s: %w", pat.ID, err)
}

var roleIDs []string
allProjects := false
var projectIDs []string
for _, pol := range policies {
roleIDs = append(roleIDs, pol.RoleID)
if pol.ResourceType == schema.ProjectNamespace {
projectIDs = append(projectIDs, pol.ResourceID)
}
if pol.GrantRelation == schema.PATGrantRelationName {
allProjects = true
}
}

pat.RoleIDs = pkgUtils.Deduplicate(roleIDs)
if !allProjects {
pat.ProjectIDs = projectIDs
}
// allProjects → pat.ProjectIDs stays nil (empty = all projects, matching create semantics)
return nil
}

// generatePAT creates a random PAT string with the configured prefix and returns
// the plaintext value along with its SHA3-256 hash for storage.
// The hash is computed over the raw secret bytes (not the formatted PAT string)
Expand Down
2 changes: 2 additions & 0 deletions core/userpat/userpat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"time"

"github.com/raystack/frontier/core/userpat/models"
"github.com/raystack/salt/rql"
)

type Repository interface {
Create(ctx context.Context, pat models.PAT) (models.PAT, error)
CountActive(ctx context.Context, userID, orgID string) (int64, error)
GetByID(ctx context.Context, id string) (models.PAT, error)
List(ctx context.Context, userID, orgID string, query *rql.Query) (models.PATList, error)
GetBySecretHash(ctx context.Context, secretHash string) (models.PAT, error)
UpdateLastUsedAt(ctx context.Context, id string, at time.Time) error
}
1 change: 1 addition & 0 deletions internal/api/v1beta1connect/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,5 @@ type AuditRecordService interface {
type UserPATService interface {
ValidateExpiry(expiresAt time.Time) error
Create(ctx context.Context, req userpat.CreateRequest) (models.PAT, string, error)
List(ctx context.Context, userID, orgID string, query *rql.Query) (models.PATList, error)
}
61 changes: 61 additions & 0 deletions internal/api/v1beta1connect/mocks/user_pat_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading