add users logic

This commit is contained in:
Efremenko Arhip
2026-07-26 14:38:24 +03:00
parent 16db1e41f7
commit a3db1a1e67
65 changed files with 3626 additions and 19 deletions
+7
View File
@@ -53,6 +53,7 @@ func (uc *CreateJob) Execute(ctx context.Context, in CreateJobInput) (*domain.Jo
if err != nil {
return nil, err
}
job.OwnerID = ownerFromContext(ctx)
err = uc.tx.WithinTx(ctx, func(ctx context.Context) error {
if err := uc.jobs.Insert(ctx, job); err != nil {
@@ -97,6 +98,9 @@ func (uc *CancelJob) Execute(ctx context.Context, jobID uuid.UUID) (int64, error
if err != nil {
return err
}
if err := authorizeJobAccess(ctx, job); err != nil {
return err
}
if job.Status == domain.JobCancelled {
return nil
}
@@ -132,6 +136,9 @@ func (uc *GetJobStatus) Execute(ctx context.Context, jobID uuid.UUID) (domain.Jo
if err != nil {
return domain.JobProgress{}, err
}
if err := authorizeJobAccess(ctx, job); err != nil {
return domain.JobProgress{}, err
}
counts, err := uc.tasks.CountByStatus(ctx, jobID)
if err != nil {
return domain.JobProgress{}, err
+39
View File
@@ -0,0 +1,39 @@
package usecase
import (
"context"
"github.com/google/uuid"
"github.com/emil28092005/SciMesh/coordinator/internal/authctx"
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
)
// ownerFromContext returns the authenticated user id to stamp on a new job, or
// nil when the request was not authenticated as a user — worker or legacy
// traffic, or user-JWT auth disabled. A nil owner is stored as NULL.
func ownerFromContext(ctx context.Context) *uuid.UUID {
if r, ok := authctx.From(ctx); ok {
id := r.UserID
return &id
}
return nil
}
// authorizeJobAccess enforces that a non-admin user may only act on their own
// job. It returns ErrJobNotFound — not a 403 — on a mismatch, so the response
// never reveals that another user's job exists.
//
// Requests with no authenticated user (worker/legacy traffic, or JWT auth
// disabled) are not restricted here: the shared service token already gated
// them, and worker endpoints legitimately operate across all jobs.
func authorizeJobAccess(ctx context.Context, job *domain.Job) error {
r, ok := authctx.From(ctx)
if !ok || r.IsAdmin() {
return nil
}
if job.OwnerID == nil || *job.OwnerID != r.UserID {
return domain.ErrJobNotFound
}
return nil
}
@@ -0,0 +1,66 @@
package usecase
import (
"context"
"errors"
"testing"
"github.com/google/uuid"
"github.com/emil28092005/SciMesh/coordinator/internal/authctx"
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
)
func TestOwnerFromContext(t *testing.T) {
if ownerFromContext(context.Background()) != nil {
t.Error("no requester must yield a nil owner")
}
id := uuid.New()
ctx := authctx.With(context.Background(), authctx.Requester{UserID: id, Role: "user"})
got := ownerFromContext(ctx)
if got == nil || *got != id {
t.Errorf("owner = %v, want %v", got, id)
}
}
func TestAuthorizeJobAccess(t *testing.T) {
owner := uuid.New()
other := uuid.New()
job := &domain.Job{ID: uuid.New(), OwnerID: &owner}
ctxOf := func(id uuid.UUID, role string) context.Context {
return authctx.With(context.Background(), authctx.Requester{UserID: id, Role: role})
}
cases := []struct {
name string
ctx context.Context
wantErr bool
}{
{"no requester (worker/legacy) allowed", context.Background(), false},
{"owner allowed", ctxOf(owner, "user"), false},
{"admin allowed", ctxOf(other, "admin"), false},
{"non-owner denied", ctxOf(other, "user"), true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := authorizeJobAccess(tc.ctx, job)
if tc.wantErr {
if !errors.Is(err, domain.ErrJobNotFound) {
t.Errorf("got %v, want ErrJobNotFound", err)
}
} else if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestAuthorizeJobAccessNilOwner(t *testing.T) {
// A legacy job with no owner must not be readable by an arbitrary user.
job := &domain.Job{ID: uuid.New(), OwnerID: nil}
ctx := authctx.With(context.Background(), authctx.Requester{UserID: uuid.New(), Role: "user"})
if err := authorizeJobAccess(ctx, job); !errors.Is(err, domain.ErrJobNotFound) {
t.Errorf("got %v, want ErrJobNotFound", err)
}
}
+3
View File
@@ -126,6 +126,9 @@ func (uc *GetJobResult) Execute(ctx context.Context, jobID uuid.UUID) (*domain.A
if err != nil {
return nil, nil, err
}
if err := authorizeJobAccess(ctx, job); err != nil {
return nil, nil, err
}
if job.Status != domain.JobCompleted || job.ResultArtifactID == nil {
return nil, nil, domain.ErrArtifactNotFound
}
+1
View File
@@ -43,6 +43,7 @@ func (uc *SubmitDataset) Execute(ctx context.Context, in SubmitDatasetInput) (Su
if err != nil {
return SubmitDatasetResult{}, err
}
job.OwnerID = ownerFromContext(ctx)
// Everything written to blob storage, so a failed transaction can undo it.
var putKeys []string