Rename user into identity
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -38,12 +38,12 @@ func NewAccessManagementService(svc *Service) *AccessManagementService {
|
||||
}
|
||||
|
||||
// Authorize implements Model 2 authorization:
|
||||
// - principalID is the actor (User now; later service accounts)
|
||||
// - credentialID is an optional credential (UserAPIKey now)
|
||||
// - principalID is the actor (Identity now; later service accounts)
|
||||
// - credentialID is an optional credential (PersonalAPIKey now)
|
||||
// - intersection semantics: actor must be allowed AND credential (if present) must be allowed.
|
||||
//
|
||||
// Entity scope:
|
||||
// - Global/self-owned entities (User/Session/UserAPIKey) are authorized via ownership checks only (no global admin).
|
||||
// - Global/self-owned entities (Identity/Session/PersonalAPIKey) are authorized via ownership checks only (no global admin).
|
||||
// - Organization-scoped entities are authorized via membership lookups that derive organization_id from entityID.
|
||||
func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid.GID, credentialID *gid.GID, entityID gid.GID, action Action) error {
|
||||
requiredRoles := GetPermissionsForAction(entityID.EntityType(), action)
|
||||
@@ -53,7 +53,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
}
|
||||
|
||||
switch principalID.EntityType() {
|
||||
case coredata.UserEntityType:
|
||||
case coredata.IdentityEntityType:
|
||||
// ok
|
||||
default:
|
||||
return NewUnsupportedPrincipalTypeError(principalID.EntityType())
|
||||
@@ -62,7 +62,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
return s.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
// Global/self-owned path
|
||||
switch entityID.EntityType() {
|
||||
case coredata.UserEntityType:
|
||||
case coredata.IdentityEntityType:
|
||||
if entityID != principalID {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
@@ -73,17 +73,17 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
if err := sess.LoadByID(ctx, conn, entityID); err != nil {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
if sess.UserID != principalID {
|
||||
if sess.IdentityID != principalID {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
return nil
|
||||
|
||||
case coredata.UserAPIKeyEntityType:
|
||||
key := &coredata.UserAPIKey{}
|
||||
case coredata.PersonalAPIKeyEntityType:
|
||||
key := &coredata.PersonalAPIKey{}
|
||||
if err := key.LoadByID(ctx, conn, entityID); err != nil {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
if key.UserID != principalID {
|
||||
if key.IdentityID != principalID {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
return nil
|
||||
@@ -92,7 +92,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
// Organization-scoped path (derive org via joins)
|
||||
scope := coredata.NewScope(entityID.TenantID())
|
||||
|
||||
actorRoleName, err := s.loadUserRoleForEntity(ctx, conn, scope, principalID, entityID)
|
||||
actorRoleName, err := s.loadIdentityRoleForEntity(ctx, conn, scope, principalID, entityID)
|
||||
if err != nil || !requiredRoleNamesContain(actorRoleName, requiredRoles) {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
@@ -100,13 +100,13 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
// Optional credential restriction (intersection)
|
||||
if credentialID != nil {
|
||||
switch credentialID.EntityType() {
|
||||
case coredata.UserAPIKeyEntityType:
|
||||
case coredata.PersonalAPIKeyEntityType:
|
||||
// Defensive check: credential must belong to actor
|
||||
apiKey := &coredata.UserAPIKey{}
|
||||
apiKey := &coredata.PersonalAPIKey{}
|
||||
if err := apiKey.LoadByID(ctx, conn, *credentialID); err != nil {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
if apiKey.UserID != principalID {
|
||||
if apiKey.IdentityID != principalID {
|
||||
return NewInsufficientPermissionsError(principalID, entityID, action)
|
||||
}
|
||||
|
||||
@@ -123,15 +123,15 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AccessManagementService) loadUserRoleForEntity(
|
||||
func (s *AccessManagementService) loadIdentityRoleForEntity(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope coredata.Scoper,
|
||||
userID gid.GID,
|
||||
identityID gid.GID,
|
||||
entityID gid.GID,
|
||||
) (Role, error) {
|
||||
var m coredata.Membership
|
||||
if err := m.LoadRoleByUserAndEntityID(ctx, conn, scope, userID, entityID); err != nil {
|
||||
if err := m.LoadRoleByIdentityAndEntityID(ctx, conn, scope, identityID, entityID); err != nil {
|
||||
// Do not leak existence details
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return "", err
|
||||
@@ -148,7 +148,7 @@ func (s *AccessManagementService) loadAPIKeyRoleForEntity(
|
||||
apiKeyID gid.GID,
|
||||
entityID gid.GID,
|
||||
) (Role, error) {
|
||||
var akm coredata.UserAPIKeyMembership
|
||||
var akm coredata.PersonalAPIKeyMembership
|
||||
if err := akm.LoadRoleByAPIKeyAndEntityID(ctx, conn, scope, apiKeyID, entityID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -173,35 +173,3 @@ func requiredRoleNamesContain(roleName Role, required []Role) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// func (s AccountService) AllAccessibleTenants(ctx context.Context, identityID gid.GID) ([]gid.TenantID, error) {
|
||||
// var tenants []gid.TenantID
|
||||
|
||||
// err := s.pg.WithConn(
|
||||
// ctx,
|
||||
// func(conn pg.Conn) error {
|
||||
// memberships := coredata.Memberships{}
|
||||
// orderBy := page.OrderBy[coredata.MembershipOrderField]{
|
||||
// Field: coredata.MembershipOrderFieldCreatedAt,
|
||||
// Direction: page.OrderDirectionDesc,
|
||||
// }
|
||||
// cursor := page.NewCursor(1000, nil, page.Head, orderBy)
|
||||
|
||||
// err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("cannot load memberships: %w", err)
|
||||
// }
|
||||
|
||||
// for _, membership := range memberships {
|
||||
// tenants = append(tenants, membership.ID.TenantID())
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// )
|
||||
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return tenants, nil
|
||||
// }
|
||||
|
||||
@@ -35,7 +35,7 @@ type (
|
||||
*Service
|
||||
}
|
||||
|
||||
UserAPIKeyTokenData struct {
|
||||
PersonalAPIKeyTokenData struct {
|
||||
Version int `json:"v"`
|
||||
KeyID gid.GID `json:"kid"`
|
||||
PrincipalID gid.GID `json:"pid"`
|
||||
@@ -43,8 +43,8 @@ type (
|
||||
}
|
||||
|
||||
EmailConfirmationData struct {
|
||||
UserID gid.GID `json:"uid"`
|
||||
Email mail.Addr `json:"email"`
|
||||
IdentityID gid.GID `json:"uid"`
|
||||
Email mail.Addr `json:"email"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
|
||||
s.tokenSecret,
|
||||
TokenTypeEmailConfirmation,
|
||||
24*time.Hour,
|
||||
EmailConfirmationData{UserID: identityID, Email: req.NewEmail},
|
||||
EmailConfirmationData{IdentityID: identityID, Email: req.NewEmail},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate confirmation token: %w", err)
|
||||
@@ -97,17 +97,17 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
err := user.LoadByID(ctx, tx, identityID)
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, tx, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.Password), user.HashedPassword)
|
||||
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.Password), identity.HashedPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot compare password: %w", err)
|
||||
}
|
||||
@@ -116,18 +116,18 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
|
||||
return NewInvalidPasswordError("invalid password")
|
||||
}
|
||||
|
||||
user.EmailAddress = req.NewEmail
|
||||
user.EmailAddressVerified = false
|
||||
user.UpdatedAt = time.Now()
|
||||
identity.EmailAddress = req.NewEmail
|
||||
identity.EmailAddressVerified = false
|
||||
identity.UpdatedAt = time.Now()
|
||||
|
||||
err = user.Update(ctx, tx)
|
||||
err = identity.Update(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
return fmt.Errorf("cannot update identity: %w", err)
|
||||
}
|
||||
|
||||
subject, textBody, htmlBody, err := emails.RenderConfirmEmail(
|
||||
s.baseURL,
|
||||
user.FullName,
|
||||
identity.FullName,
|
||||
confirmationUrl,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -135,8 +135,8 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
|
||||
}
|
||||
|
||||
confirmationEmail := coredata.NewEmail(
|
||||
user.FullName,
|
||||
user.EmailAddress,
|
||||
identity.FullName,
|
||||
identity.EmailAddress,
|
||||
subject,
|
||||
textBody,
|
||||
htmlBody,
|
||||
@@ -161,30 +161,30 @@ func (s AccountService) VerifyEmail(ctx context.Context, token string) error {
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
err := user.LoadByID(ctx, tx, payload.Data.UserID)
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, tx, payload.Data.IdentityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(payload.Data.UserID)
|
||||
return NewIdentityNotFoundError(payload.Data.IdentityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
if user.EmailAddress != payload.Data.Email {
|
||||
if identity.EmailAddress != payload.Data.Email {
|
||||
return NewEmailVerificationMismatchError()
|
||||
}
|
||||
|
||||
if user.EmailAddressVerified {
|
||||
if identity.EmailAddressVerified {
|
||||
return NewEmailAlreadyVerifiedError()
|
||||
}
|
||||
|
||||
user.EmailAddressVerified = true
|
||||
user.UpdatedAt = time.Now()
|
||||
identity.EmailAddressVerified = true
|
||||
identity.UpdatedAt = time.Now()
|
||||
|
||||
err = user.Update(ctx, tx)
|
||||
err = identity.Update(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
return fmt.Errorf("cannot update identity: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -205,16 +205,16 @@ func (s *AccountService) AcceptInvitation(
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := coredata.User{}
|
||||
identity := coredata.Identity{}
|
||||
invitation := coredata.Invitation{}
|
||||
|
||||
err := user.LoadByID(ctx, tx, identityID)
|
||||
err := identity.LoadByID(ctx, tx, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
err = invitation.LoadByID(ctx, tx, coredata.NewNoScope(), invitationID)
|
||||
@@ -226,7 +226,7 @@ func (s *AccountService) AcceptInvitation(
|
||||
return fmt.Errorf("cannot load invitation: %w", err)
|
||||
}
|
||||
|
||||
if invitation.Email != user.EmailAddress {
|
||||
if invitation.Email != identity.EmailAddress {
|
||||
return NewInvitationNotFoundError(invitationID)
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ func (s *AccountService) AcceptInvitation(
|
||||
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||
UserID: identityID,
|
||||
IdentityID: identityID,
|
||||
OrganizationID: invitation.OrganizationID,
|
||||
Role: invitation.Role,
|
||||
CreatedAt: now,
|
||||
@@ -286,11 +286,11 @@ func (s *AccountService) ListPendingInvitations(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
identity := coredata.User{}
|
||||
identity := coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
@@ -323,7 +323,7 @@ func (s *AccountService) CountPendingInvitations(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
identity := coredata.User{}
|
||||
identity := coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
@@ -357,7 +357,7 @@ func (s *AccountService) ListMemberships(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
err := memberships.LoadByIdentityID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load memberships: %w", err)
|
||||
}
|
||||
@@ -383,7 +383,7 @@ func (s *AccountService) CountMemberships(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
memberships := coredata.Memberships{}
|
||||
count, err = memberships.CountByUserID(ctx, conn, identityID)
|
||||
count, err = memberships.CountByIdentityID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count memberships: %w", err)
|
||||
}
|
||||
@@ -407,17 +407,17 @@ func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID,
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
err := user.LoadByID(ctx, tx, identityID)
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, tx, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
isLegacyPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.CurrentPassword), user.HashedPassword)
|
||||
isLegacyPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.CurrentPassword), identity.HashedPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot compare legacy password: %w", err)
|
||||
}
|
||||
@@ -431,15 +431,15 @@ func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID,
|
||||
return fmt.Errorf("cannot hash new password: %w", err)
|
||||
}
|
||||
|
||||
user.HashedPassword = newPasswordHash
|
||||
user.UpdatedAt = time.Now()
|
||||
identity.HashedPassword = newPasswordHash
|
||||
identity.UpdatedAt = time.Now()
|
||||
|
||||
err = user.Update(ctx, tx)
|
||||
err = identity.Update(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
return fmt.Errorf("cannot update identity: %w", err)
|
||||
}
|
||||
|
||||
// TODO: email to notify user that their password has been changed
|
||||
// TODO: email to notify identity that their password has been changed
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -453,7 +453,7 @@ func (s AccountService) CountSessions(ctx context.Context, identityID gid.GID) (
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
sessions := coredata.Sessions{}
|
||||
count, err = sessions.CountByUserID(ctx, conn, identityID)
|
||||
count, err = sessions.CountByIdentityID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count sessions: %w", err)
|
||||
}
|
||||
@@ -475,7 +475,7 @@ func (s AccountService) ListSessions(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := sessions.LoadByUserID(ctx, conn, identityID, cursor)
|
||||
err := sessions.LoadByIdentityID(ctx, conn, identityID, cursor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load sessions: %w", err)
|
||||
}
|
||||
@@ -491,19 +491,19 @@ func (s AccountService) ListSessions(
|
||||
return page.NewPage(sessions, cursor), nil
|
||||
}
|
||||
|
||||
func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*coredata.User, error) {
|
||||
user := &coredata.User{}
|
||||
func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*coredata.Identity, error) {
|
||||
identity := &coredata.Identity{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := user.LoadByID(ctx, conn, identityID)
|
||||
err := identity.LoadByID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -513,20 +513,20 @@ func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*c
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (s AccountService) ListPersonalAPIKeys(
|
||||
ctx context.Context,
|
||||
identityID gid.GID,
|
||||
cursor *page.Cursor[coredata.UserAPIKeyOrderField],
|
||||
) (*page.Page[*coredata.UserAPIKey, coredata.UserAPIKeyOrderField], error) {
|
||||
var personalAccessTokens coredata.UserAPIKeys
|
||||
cursor *page.Cursor[coredata.PersonalAPIKeyOrderField],
|
||||
) (*page.Page[*coredata.PersonalAPIKey, coredata.PersonalAPIKeyOrderField], error) {
|
||||
var personalAccessTokens coredata.PersonalAPIKeys
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := personalAccessTokens.LoadByUserID(ctx, conn, identityID)
|
||||
err := personalAccessTokens.LoadByIdentityID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load personal access tokens: %w", err)
|
||||
}
|
||||
@@ -548,8 +548,8 @@ func (s AccountService) CountPersonalAPIKeys(ctx context.Context, identityID gid
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
personalAccessTokens := coredata.UserAPIKeys{}
|
||||
count, err = personalAccessTokens.CountByUserID(ctx, conn, identityID)
|
||||
personalAccessTokens := coredata.PersonalAPIKeys{}
|
||||
count, err = personalAccessTokens.CountByIdentityID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count personal access tokens: %w", err)
|
||||
}
|
||||
@@ -561,10 +561,10 @@ func (s AccountService) CountPersonalAPIKeys(ctx context.Context, identityID gid
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s AccountService) GetIdentityForMembership(ctx context.Context, membershipID gid.GID) (*coredata.User, error) {
|
||||
func (s AccountService) GetIdentityForMembership(ctx context.Context, membershipID gid.GID) (*coredata.Identity, error) {
|
||||
var (
|
||||
scope = coredata.NewScopeFromObjectID(membershipID)
|
||||
identity = &coredata.User{}
|
||||
identity = &coredata.Identity{}
|
||||
)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
@@ -580,10 +580,10 @@ func (s AccountService) GetIdentityForMembership(ctx context.Context, membership
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
err = identity.LoadByID(ctx, conn, membership.UserID)
|
||||
err = identity.LoadByID(ctx, conn, membership.IdentityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(membership.UserID)
|
||||
return NewIdentityNotFoundError(membership.IdentityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
@@ -605,10 +605,10 @@ func (s *AccountService) CreatePersonalAPIKey(
|
||||
identityID gid.GID,
|
||||
name string,
|
||||
expiresAt time.Time,
|
||||
) (*coredata.UserAPIKey, string, error) {
|
||||
) (*coredata.PersonalAPIKey, string, error) {
|
||||
var (
|
||||
userAPIKey *coredata.UserAPIKey
|
||||
token string
|
||||
personalAPIKey *coredata.PersonalAPIKey
|
||||
token string
|
||||
)
|
||||
|
||||
err := s.pg.WithTx(
|
||||
@@ -616,33 +616,33 @@ func (s *AccountService) CreatePersonalAPIKey(
|
||||
func(tx pg.Conn) (err error) {
|
||||
now := time.Now()
|
||||
|
||||
userAPIKey = &coredata.UserAPIKey{
|
||||
ID: gid.New(gid.NilTenant, coredata.UserAPIKeyEntityType),
|
||||
UserID: identityID,
|
||||
Name: name,
|
||||
ExpiresAt: expiresAt,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
personalAPIKey = &coredata.PersonalAPIKey{
|
||||
ID: gid.New(gid.NilTenant, coredata.PersonalAPIKeyEntityType),
|
||||
IdentityID: identityID,
|
||||
Name: name,
|
||||
ExpiresAt: expiresAt,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := userAPIKey.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert user api key: %w", err)
|
||||
if err := personalAPIKey.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert personal api key: %w", err)
|
||||
}
|
||||
|
||||
token, err = statelesstoken.NewDeterministicToken(
|
||||
s.tokenSecret,
|
||||
TokenTypeAPIKey,
|
||||
userAPIKey.ExpiresAt,
|
||||
userAPIKey.CreatedAt,
|
||||
UserAPIKeyTokenData{
|
||||
personalAPIKey.ExpiresAt,
|
||||
personalAPIKey.CreatedAt,
|
||||
PersonalAPIKeyTokenData{
|
||||
Version: 2,
|
||||
KeyID: userAPIKey.ID,
|
||||
KeyID: personalAPIKey.ID,
|
||||
PrincipalID: identityID,
|
||||
IssuedAt: userAPIKey.CreatedAt,
|
||||
IssuedAt: personalAPIKey.CreatedAt,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate user api key token: %w", err)
|
||||
return fmt.Errorf("cannot generate personal api key token: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -653,34 +653,34 @@ func (s *AccountService) CreatePersonalAPIKey(
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return userAPIKey, token, nil
|
||||
return personalAPIKey, token, nil
|
||||
}
|
||||
|
||||
func (s *AccountService) DeletePersonalAPIKey(
|
||||
ctx context.Context,
|
||||
identityID gid.GID,
|
||||
userAPIKeyID gid.GID,
|
||||
personalAPIKeyID gid.GID,
|
||||
) error {
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
userAPIKey := &coredata.UserAPIKey{}
|
||||
err := userAPIKey.LoadByID(ctx, tx, userAPIKeyID)
|
||||
personalAPIKey := &coredata.PersonalAPIKey{}
|
||||
err := personalAPIKey.LoadByID(ctx, tx, personalAPIKeyID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserAPIKeyNotFoundError(userAPIKeyID)
|
||||
return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user api key: %w", err)
|
||||
return fmt.Errorf("cannot load personal api key: %w", err)
|
||||
}
|
||||
|
||||
if userAPIKey.UserID != identityID {
|
||||
return NewUserAPIKeyNotFoundError(userAPIKeyID)
|
||||
if personalAPIKey.IdentityID != identityID {
|
||||
return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
|
||||
}
|
||||
|
||||
err = userAPIKey.Delete(ctx, tx)
|
||||
err = personalAPIKey.Delete(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete user api key: %w", err)
|
||||
return fmt.Errorf("cannot delete personal api key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -699,7 +699,7 @@ func (s AccountService) ListOrganizations(ctx context.Context, identityID gid.GI
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := organizations.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
err := organizations.LoadByIdentityID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load organizations: %w", err)
|
||||
}
|
||||
@@ -714,35 +714,3 @@ func (s AccountService) ListOrganizations(ctx context.Context, identityID gid.GI
|
||||
|
||||
return organizations, nil
|
||||
}
|
||||
|
||||
// func (s AccountService) AllAccessibleTenants(ctx context.Context, identityID gid.GID) ([]gid.TenantID, error) {
|
||||
// var tenants []gid.TenantID
|
||||
|
||||
// err := s.pg.WithConn(
|
||||
// ctx,
|
||||
// func(conn pg.Conn) error {
|
||||
// memberships := coredata.Memberships{}
|
||||
// orderBy := page.OrderBy[coredata.MembershipOrderField]{
|
||||
// Field: coredata.MembershipOrderFieldCreatedAt,
|
||||
// Direction: page.OrderDirectionDesc,
|
||||
// }
|
||||
// cursor := page.NewCursor(1000, nil, page.Head, orderBy)
|
||||
|
||||
// err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("cannot load memberships: %w", err)
|
||||
// }
|
||||
|
||||
// for _, membership := range memberships {
|
||||
// tenants = append(tenants, membership.ID.TenantID())
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// )
|
||||
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return tenants, nil
|
||||
// }
|
||||
|
||||
@@ -35,9 +35,9 @@ func NewAPIKeyService(svc *Service) *APIKeyService {
|
||||
return &APIKeyService{Service: svc}
|
||||
}
|
||||
|
||||
func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata.UserAPIKey, error) {
|
||||
func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata.PersonalAPIKey, error) {
|
||||
var (
|
||||
apiKey = &coredata.UserAPIKey{}
|
||||
apiKey = &coredata.PersonalAPIKey{}
|
||||
now = time.Now()
|
||||
)
|
||||
|
||||
@@ -46,12 +46,12 @@ func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata
|
||||
func(tx pg.Conn) error {
|
||||
if err := apiKey.LoadByID(ctx, tx, keyID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserAPIKeyNotFoundError(keyID)
|
||||
return NewPersonalAPIKeyNotFoundError(keyID)
|
||||
}
|
||||
}
|
||||
|
||||
if apiKey.ExpireReason != nil {
|
||||
return NewUserAPIKeyExpiredError(keyID)
|
||||
return NewPersonalAPIKeyExpiredError(keyID)
|
||||
}
|
||||
|
||||
if now.After(apiKey.ExpiresAt) {
|
||||
@@ -60,10 +60,10 @@ func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata
|
||||
apiKey.UpdatedAt = now
|
||||
|
||||
if err := apiKey.Update(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot update user api key: %w", err)
|
||||
return fmt.Errorf("cannot update personal api key: %w", err)
|
||||
}
|
||||
|
||||
return NewUserAPIKeyExpiredError(keyID)
|
||||
return NewPersonalAPIKeyExpiredError(keyID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -110,7 +110,7 @@ func (req CreateIdentityWithPasswordRequest) Validate() error {
|
||||
func (s *AuthService) CreateIdentityFromInvitation(
|
||||
ctx context.Context,
|
||||
req *CreateIdentityFromInvitationRequest,
|
||||
) (*coredata.User, *coredata.Session, error) {
|
||||
) (*coredata.Identity, *coredata.Session, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (s *AuthService) CreateIdentityFromInvitation(
|
||||
var (
|
||||
scope = coredata.NewScopeFromObjectID(payload.Data.InvitationID)
|
||||
invitation = &coredata.Invitation{}
|
||||
user = &coredata.User{}
|
||||
identity = &coredata.Identity{}
|
||||
session = &coredata.Session{}
|
||||
now = time.Now()
|
||||
)
|
||||
@@ -153,8 +153,8 @@ func (s *AuthService) CreateIdentityFromInvitation(
|
||||
return NewInvitationExpiredError(payload.Data.InvitationID)
|
||||
}
|
||||
|
||||
user = &coredata.User{
|
||||
ID: gid.New(gid.NilTenant, coredata.UserEntityType),
|
||||
identity = &coredata.Identity{
|
||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
||||
EmailAddress: invitation.Email,
|
||||
HashedPassword: hashedPassword,
|
||||
EmailAddressVerified: true,
|
||||
@@ -163,16 +163,16 @@ func (s *AuthService) CreateIdentityFromInvitation(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = user.Insert(ctx, tx)
|
||||
err = identity.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceAlreadyExists {
|
||||
return NewUserAlreadyExistsError(invitation.Email)
|
||||
return NewIdentityAlreadyExistsError(invitation.Email)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot insert user: %w", err)
|
||||
return fmt.Errorf("cannot insert identity: %w", err)
|
||||
}
|
||||
|
||||
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration)
|
||||
session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, s.sessionDuration)
|
||||
err = session.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert session: %w", err)
|
||||
@@ -186,7 +186,7 @@ func (s *AuthService) CreateIdentityFromInvitation(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, session, nil
|
||||
return identity, session, nil
|
||||
}
|
||||
|
||||
func (s AuthService) ResetPassword(
|
||||
@@ -210,26 +210,26 @@ func (s AuthService) ResetPassword(
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
err := user.LoadByEmail(ctx, tx, payload.Data.Email)
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByEmail(ctx, tx, payload.Data.Email)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return nil // Don't leak information about non-existent users
|
||||
return nil // Don't leak information about non-existent identities
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
user.HashedPassword = hashedPassword
|
||||
user.UpdatedAt = time.Now()
|
||||
identity.HashedPassword = hashedPassword
|
||||
identity.UpdatedAt = time.Now()
|
||||
|
||||
err = user.Update(ctx, tx)
|
||||
err = identity.Update(ctx, tx)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return nil // Don't leak information about non-existent users
|
||||
return nil // Don't leak information about non-existent identities
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
return fmt.Errorf("cannot update identity: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -264,18 +264,18 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
if err := user.LoadByEmail(ctx, tx, email); err != nil {
|
||||
identity := &coredata.Identity{}
|
||||
if err := identity.LoadByEmail(ctx, tx, email); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return nil // Don't leak information about non-existent users
|
||||
return nil // Don't leak information about non-existent identities
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
subject, textBody, htmlBody, err := emails.RenderPasswordReset(
|
||||
s.baseURL,
|
||||
user.FullName,
|
||||
identity.FullName,
|
||||
resetPasswordUrl,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -283,8 +283,8 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
|
||||
}
|
||||
|
||||
passwordResetEmail := coredata.NewEmail(
|
||||
user.FullName,
|
||||
user.EmailAddress,
|
||||
identity.FullName,
|
||||
identity.EmailAddress,
|
||||
subject,
|
||||
textBody,
|
||||
htmlBody,
|
||||
@@ -303,7 +303,7 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
|
||||
func (s AuthService) CreateIdentityWithPassword(
|
||||
ctx context.Context,
|
||||
req *CreateIdentityWithPasswordRequest,
|
||||
) (*coredata.User, *coredata.Session, error) {
|
||||
) (*coredata.Identity, *coredata.Session, error) {
|
||||
if s.disableSignup { // TODO Rename this one to disableSignup
|
||||
return nil, nil, NewErrSignupDisabled()
|
||||
}
|
||||
@@ -320,8 +320,8 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
var (
|
||||
now = time.Now()
|
||||
|
||||
user = &coredata.User{
|
||||
ID: gid.New(gid.NilTenant, coredata.UserEntityType),
|
||||
identity = &coredata.Identity{
|
||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
||||
EmailAddress: req.Email,
|
||||
HashedPassword: hashedPassword,
|
||||
EmailAddressVerified: false,
|
||||
@@ -330,14 +330,14 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, 24*time.Hour*7)
|
||||
session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, 24*time.Hour*7)
|
||||
)
|
||||
|
||||
confirmationToken, err := statelesstoken.NewToken(
|
||||
s.tokenSecret,
|
||||
TokenTypeEmailConfirmation,
|
||||
24*time.Hour,
|
||||
EmailConfirmationData{UserID: user.ID, Email: user.EmailAddress},
|
||||
EmailConfirmationData{IdentityID: identity.ID, Email: identity.EmailAddress},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot generate confirmation token: %w", err)
|
||||
@@ -358,7 +358,7 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
|
||||
subject, textBody, htmlBody, err := emails.RenderConfirmEmail(
|
||||
s.baseURL,
|
||||
user.FullName,
|
||||
identity.FullName,
|
||||
confirmationUrl,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -366,8 +366,8 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
}
|
||||
|
||||
confirmationEmail := coredata.NewEmail(
|
||||
user.FullName,
|
||||
user.EmailAddress,
|
||||
identity.FullName,
|
||||
identity.EmailAddress,
|
||||
subject,
|
||||
textBody,
|
||||
htmlBody,
|
||||
@@ -376,13 +376,13 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
err = s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
err := user.Insert(ctx, tx)
|
||||
err := identity.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceAlreadyExists {
|
||||
return NewUserAlreadyExistsError(user.EmailAddress)
|
||||
return NewIdentityAlreadyExistsError(identity.EmailAddress)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot insert user: %w", err)
|
||||
return fmt.Errorf("cannot insert identity: %w", err)
|
||||
}
|
||||
|
||||
if err := confirmationEmail.Insert(ctx, tx); err != nil {
|
||||
@@ -397,16 +397,16 @@ func (s AuthService) CreateIdentityWithPassword(
|
||||
},
|
||||
)
|
||||
|
||||
return user, session, err
|
||||
return identity, session, err
|
||||
}
|
||||
|
||||
func (s AuthService) OpenSessionWithSAML(ctx context.Context, userID gid.GID, organizationID gid.GID) (*coredata.Session, error) {
|
||||
func (s AuthService) OpenSessionWithSAML(ctx context.Context, identityID gid.GID, organizationID gid.GID) (*coredata.Session, error) {
|
||||
session := &coredata.Session{}
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
session = coredata.NewRootSession(userID, coredata.AuthMethodSAML, s.sessionDuration)
|
||||
session = coredata.NewRootSession(identityID, coredata.AuthMethodSAML, s.sessionDuration)
|
||||
err = session.Insert(ctx, conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert session: %w", err)
|
||||
@@ -423,7 +423,7 @@ func (s AuthService) OpenSessionWithSAML(ctx context.Context, userID gid.GID, or
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Addr, password string) (*coredata.User, *coredata.Session, error) {
|
||||
func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Addr, password string) (*coredata.Identity, *coredata.Session, error) {
|
||||
v := validator.New()
|
||||
v.Check(password, "password", PasswordValidator())
|
||||
|
||||
@@ -433,29 +433,29 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
|
||||
}
|
||||
|
||||
var (
|
||||
user = &coredata.User{}
|
||||
session = &coredata.Session{}
|
||||
identity = &coredata.Identity{}
|
||||
session = &coredata.Session{}
|
||||
)
|
||||
|
||||
err = s.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := user.LoadByEmail(ctx, conn, email)
|
||||
err := identity.LoadByEmail(ctx, conn, email)
|
||||
if err != nil {
|
||||
// Do not leak information about non-existent users
|
||||
// Do not leak information about non-existent identities
|
||||
if err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load user by email: %w", err)
|
||||
return fmt.Errorf("cannot load identity by email: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a password comparison even when the user does not exist to mitigate timing attacks
|
||||
// Perform a password comparison even when the identity does not exist to mitigate timing attacks
|
||||
// and prevent revealing account existence.
|
||||
if user.ID == gid.Nil {
|
||||
if identity.ID == gid.Nil {
|
||||
s.hp.ComparePasswordAndHash([]byte(password+"qwertyuiop1234567890"), []byte("qwertyuiop1234567890"))
|
||||
return NewInvalidCredentialsError("invalid email or password")
|
||||
}
|
||||
|
||||
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(password), user.HashedPassword)
|
||||
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(password), identity.HashedPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot verify password: %w", err)
|
||||
}
|
||||
@@ -464,7 +464,7 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
|
||||
return NewInvalidCredentialsError("invalid email or password")
|
||||
}
|
||||
|
||||
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration)
|
||||
session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, s.sessionDuration)
|
||||
err = session.Insert(ctx, conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert session: %w", err)
|
||||
@@ -474,5 +474,5 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
|
||||
},
|
||||
)
|
||||
|
||||
return user, session, err
|
||||
return identity, session, err
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ type AuthorizeParams struct {
|
||||
// It combines self-management policies with role-based policies.
|
||||
func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error {
|
||||
// Validate principal type
|
||||
if params.Principal.EntityType() != coredata.UserEntityType {
|
||||
if params.Principal.EntityType() != coredata.IdentityEntityType {
|
||||
return NewUnsupportedPrincipalTypeError(params.Principal.EntityType())
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func (a *Authorizer) loadRolePolicies(ctx context.Context, principalID gid.GID,
|
||||
scope := coredata.NewScope(resourceID.TenantID())
|
||||
|
||||
var m coredata.Membership
|
||||
if err := m.LoadRoleByUserAndEntityID(ctx, conn, scope, principalID, resourceID); err != nil {
|
||||
if err := m.LoadRoleByIdentityAndEntityID(ctx, conn, scope, principalID, resourceID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil // No membership = no role-based policies
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ func (e ErrInvitationExpired) Error() string {
|
||||
return fmt.Sprintf("invitation %q expired", e.InvitationID)
|
||||
}
|
||||
|
||||
type ErrUserAlreadyExists struct{ EmailAddress mail.Addr }
|
||||
type ErrIdentityAlreadyExists struct{ EmailAddress mail.Addr }
|
||||
|
||||
func NewUserAlreadyExistsError(emailAddress mail.Addr) error {
|
||||
return &ErrUserAlreadyExists{EmailAddress: emailAddress}
|
||||
func NewIdentityAlreadyExistsError(emailAddress mail.Addr) error {
|
||||
return &ErrIdentityAlreadyExists{EmailAddress: emailAddress}
|
||||
}
|
||||
|
||||
func (e ErrUserAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("user %q already exists", e.EmailAddress.String())
|
||||
func (e ErrIdentityAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("identity %q already exists", e.EmailAddress.String())
|
||||
}
|
||||
|
||||
type ErrEmailAlreadyVerified struct{ message string }
|
||||
@@ -81,14 +81,14 @@ func (e ErrEmailAlreadyVerified) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type ErrUserNotFound struct{ UserID gid.GID }
|
||||
type ErrIdentityNotFound struct{ IdentityID gid.GID }
|
||||
|
||||
func NewUserNotFoundError(userID gid.GID) error {
|
||||
return &ErrUserNotFound{userID}
|
||||
func NewIdentityNotFoundError(identityID gid.GID) error {
|
||||
return &ErrIdentityNotFound{identityID}
|
||||
}
|
||||
|
||||
func (e ErrUserNotFound) Error() string {
|
||||
return fmt.Sprintf("user %q not found", e.UserID)
|
||||
func (e ErrIdentityNotFound) Error() string {
|
||||
return fmt.Sprintf("identity %q not found", e.IdentityID)
|
||||
}
|
||||
|
||||
type ErrInvalidPassword struct{ message string }
|
||||
@@ -168,16 +168,16 @@ func (e ErrSessionExpired) Error() string {
|
||||
}
|
||||
|
||||
type ErrMembershipAlreadyExists struct {
|
||||
UserID gid.GID
|
||||
IdentityID gid.GID
|
||||
OrganizationID gid.GID
|
||||
}
|
||||
|
||||
func NewMembershipAlreadyExistsError(userID gid.GID, organizationID gid.GID) error {
|
||||
return &ErrMembershipAlreadyExists{UserID: userID, OrganizationID: organizationID}
|
||||
func NewMembershipAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
||||
return &ErrMembershipAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
||||
}
|
||||
|
||||
func (e ErrMembershipAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("membership already exists for user %q in organization %q", e.UserID, e.OrganizationID)
|
||||
return fmt.Sprintf("membership already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
||||
}
|
||||
|
||||
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
|
||||
@@ -190,24 +190,24 @@ func (e ErrSAMLConfigurationNotFound) Error() string {
|
||||
return fmt.Sprintf("SAML configuration %q not found", e.ConfigID)
|
||||
}
|
||||
|
||||
type ErrUserAPIKeyNotFound struct{ UserAPIKeyID gid.GID }
|
||||
type ErrPersonalAPIKeyNotFound struct{ PersonalAPIKeyID gid.GID }
|
||||
|
||||
func NewUserAPIKeyNotFoundError(userAPIKeyID gid.GID) error {
|
||||
return &ErrUserAPIKeyNotFound{UserAPIKeyID: userAPIKeyID}
|
||||
func NewPersonalAPIKeyNotFoundError(personalAPIKeyID gid.GID) error {
|
||||
return &ErrPersonalAPIKeyNotFound{PersonalAPIKeyID: personalAPIKeyID}
|
||||
}
|
||||
|
||||
func (e ErrUserAPIKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("user API key %q not found", e.UserAPIKeyID)
|
||||
func (e ErrPersonalAPIKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("personal API key %q not found", e.PersonalAPIKeyID)
|
||||
}
|
||||
|
||||
type ErrUserAPIKeyExpired struct{ UserAPIKeyID gid.GID }
|
||||
type ErrPersonalAPIKeyExpired struct{ PersonalAPIKeyID gid.GID }
|
||||
|
||||
func NewUserAPIKeyExpiredError(userAPIKeyID gid.GID) error {
|
||||
return &ErrUserAPIKeyExpired{UserAPIKeyID: userAPIKeyID}
|
||||
func NewPersonalAPIKeyExpiredError(personalAPIKeyID gid.GID) error {
|
||||
return &ErrPersonalAPIKeyExpired{PersonalAPIKeyID: personalAPIKeyID}
|
||||
}
|
||||
|
||||
func (e ErrUserAPIKeyExpired) Error() string {
|
||||
return fmt.Sprintf("user API key %q expired", e.UserAPIKeyID)
|
||||
func (e ErrPersonalAPIKeyExpired) Error() string {
|
||||
return fmt.Sprintf("personal API key %q expired", e.PersonalAPIKeyID)
|
||||
}
|
||||
|
||||
type ErrSAMLConfigurationDomainNotVerified struct{ ConfigID gid.GID }
|
||||
|
||||
@@ -307,22 +307,22 @@ func (s *OrganizationService) InviteMember(
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
user := &coredata.User{}
|
||||
err = user.LoadByEmail(ctx, tx, emailAddress)
|
||||
identity := &coredata.Identity{}
|
||||
err = identity.LoadByEmail(ctx, tx, emailAddress)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
userExists := user.ID != gid.Nil
|
||||
if userExists {
|
||||
identityExists := identity.ID != gid.Nil
|
||||
if identityExists {
|
||||
membership := &coredata.Membership{}
|
||||
err = membership.LoadByUserAndOrg(ctx, tx, scope, user.ID, organizationID)
|
||||
err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, organizationID)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.ID != gid.Nil {
|
||||
return NewMembershipAlreadyExistsError(user.ID, organizationID)
|
||||
return NewMembershipAlreadyExistsError(identity.ID, organizationID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ func (s *OrganizationService) CreateOrganization(
|
||||
|
||||
membership := &coredata.Membership{
|
||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||
UserID: identityID,
|
||||
IdentityID: identityID,
|
||||
OrganizationID: organizationID,
|
||||
Role: coredata.MembershipRoleOwner,
|
||||
CreatedAt: now,
|
||||
|
||||
@@ -413,7 +413,7 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionGetTrustCenterFile: EditRoles,
|
||||
ActionDeleteTrustCenterFile: EditRoles,
|
||||
},
|
||||
coredata.UserEntityType: {
|
||||
coredata.IdentityEntityType: {
|
||||
ActionGet: NonEmployeeRoles,
|
||||
},
|
||||
coredata.MembershipEntityType: {
|
||||
|
||||
@@ -171,10 +171,10 @@ func (s *Service) HandleAssertion(
|
||||
ctx context.Context,
|
||||
samlResponse string,
|
||||
configID gid.GID,
|
||||
) (*coredata.User, *coredata.Membership, error) {
|
||||
) (*coredata.Identity, *coredata.Membership, error) {
|
||||
var (
|
||||
now = time.Now()
|
||||
user = &coredata.User{}
|
||||
identity = &coredata.Identity{}
|
||||
membership = &coredata.Membership{}
|
||||
)
|
||||
|
||||
@@ -251,12 +251,12 @@ func (s *Service) HandleAssertion(
|
||||
return NewEmailDomainMismatchError(email, config.EmailDomain)
|
||||
}
|
||||
|
||||
err = user.LoadByEmail(ctx, tx, email)
|
||||
err = identity.LoadByEmail(ctx, tx, email)
|
||||
if err == coredata.ErrResourceNotFound && !config.AutoSignupEnabled {
|
||||
return NewSAMLAutoSignupDisabledError(config.ID)
|
||||
} else if err == coredata.ErrResourceNotFound && config.AutoSignupEnabled {
|
||||
*user = coredata.User{
|
||||
ID: gid.New(gid.NilTenant, coredata.UserEntityType),
|
||||
*identity = coredata.Identity{
|
||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
||||
EmailAddress: email,
|
||||
HashedPassword: nil,
|
||||
EmailAddressVerified: true,
|
||||
@@ -265,26 +265,26 @@ func (s *Service) HandleAssertion(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err := user.Insert(ctx, tx)
|
||||
err := identity.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert user: %w", err)
|
||||
return fmt.Errorf("cannot insert identity: %w", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
} else {
|
||||
user.SAMLSubject = &assertion.Subject.NameID.Value
|
||||
user.FullName = fullname
|
||||
user.EmailAddress = email
|
||||
user.EmailAddressVerified = true
|
||||
user.UpdatedAt = now
|
||||
identity.SAMLSubject = &assertion.Subject.NameID.Value
|
||||
identity.FullName = fullname
|
||||
identity.EmailAddress = email
|
||||
identity.EmailAddressVerified = true
|
||||
identity.UpdatedAt = now
|
||||
|
||||
err = user.Update(ctx, tx)
|
||||
err = identity.Update(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
return fmt.Errorf("cannot update identity: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = membership.LoadByUserAndOrg(ctx, tx, coredata.NewNoScope(), user.ID, config.OrganizationID)
|
||||
err = membership.LoadByIdentityAndOrg(ctx, tx, coredata.NewNoScope(), identity.ID, config.OrganizationID)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
@@ -293,7 +293,7 @@ func (s *Service) HandleAssertion(
|
||||
if !isMember {
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType),
|
||||
UserID: user.ID,
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Role: coredata.MembershipRoleViewer,
|
||||
CreatedAt: now,
|
||||
@@ -324,7 +324,7 @@ func (s *Service) HandleAssertion(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, membership, nil
|
||||
return identity, membership, nil
|
||||
}
|
||||
|
||||
func (s *Service) validateAssertion(assertion *saml.Assertion, config *coredata.SAMLConfiguration, now time.Time) error {
|
||||
|
||||
@@ -129,14 +129,14 @@ func (s SessionService) RevokeSession(ctx context.Context, identityID gid.GID, s
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
user := &coredata.User{}
|
||||
err := user.LoadByID(ctx, tx, identityID)
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByID(ctx, tx, identityID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewUserNotFoundError(identityID)
|
||||
return NewIdentityNotFoundError(identityID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
session := &coredata.Session{}
|
||||
@@ -150,7 +150,7 @@ func (s SessionService) RevokeSession(ctx context.Context, identityID gid.GID, s
|
||||
}
|
||||
|
||||
// TODO: move to dedicated query instead of LoadByID
|
||||
if session.UserID != identityID {
|
||||
if session.IdentityID != identityID {
|
||||
return NewSessionNotFoundError(sessionID)
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (s SessionService) RevokeAllSessions(ctx context.Context, currentSessionID
|
||||
}
|
||||
|
||||
sessions := coredata.Sessions{}
|
||||
count, err = sessions.ExpireAllForUserExceptOneSession(ctx, tx, session.UserID, session.ID)
|
||||
count, err = sessions.ExpireAllForIdentityExceptOneSession(ctx, tx, session.IdentityID, session.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot expire all sessions: %w", err)
|
||||
}
|
||||
@@ -323,7 +323,7 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
var (
|
||||
now = time.Now()
|
||||
rootSession = &coredata.Session{}
|
||||
user = &coredata.User{}
|
||||
identity = &coredata.Identity{}
|
||||
membership = &coredata.Membership{}
|
||||
childSession = &coredata.Session{}
|
||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||
@@ -348,12 +348,12 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
return NewSessionExpiredError(sessionID)
|
||||
}
|
||||
|
||||
err = user.LoadByID(ctx, tx, rootSession.UserID)
|
||||
err = identity.LoadByID(ctx, tx, rootSession.IdentityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load user: %w", err)
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
err = membership.LoadByUserInOrganization(ctx, tx, rootSession.UserID, organizationID)
|
||||
err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewMembershipNotFoundError(organizationID)
|
||||
@@ -367,7 +367,7 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
tx,
|
||||
scope,
|
||||
organizationID,
|
||||
user.EmailAddress.Domain(),
|
||||
identity.EmailAddress.Domain(),
|
||||
)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load SAML configuration: %w", err)
|
||||
@@ -389,7 +389,7 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
tenantID := scope.GetTenantID()
|
||||
childSession = &coredata.Session{
|
||||
ID: gid.New(tenantID, coredata.SessionEntityType),
|
||||
UserID: rootSession.UserID,
|
||||
IdentityID: rootSession.IdentityID,
|
||||
TenantID: &tenantID,
|
||||
MembershipID: &membership.ID,
|
||||
ParentSessionID: &rootSession.ID,
|
||||
|
||||
Reference in New Issue
Block a user