feat: roles SPA Users, Profile, API keys, Activity
This commit is contained in:
1 parent
3db490a615
commit
34dffe222f
98 files changed
+4957
-434
No files matched your search
@@ -92,12 +92,17 @@ func (a *AcceptInviteUseCase) accept(command AcceptInviteCommand, pending invite
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
inviterID, err := a.inviterOf(pending)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
createdUser, err := a.inviteRepository.AcceptWithUser(pending.ID, now, user.User{
|
||||
Username: command.Username,
|
||||
Password: hashedPassword,
|
||||
Email: pending.Email,
|
||||
IsVerified: true,
|
||||
RoleID: assigned.ID,
|
||||
Username: command.Username,
|
||||
Password: hashedPassword,
|
||||
Email: pending.Email,
|
||||
IsVerified: true,
|
||||
RoleID: assigned.ID,
|
||||
RoleAssignedByID: inviterID,
|
||||
})
|
||||
if errors.Is(err, invite.ErrInviteNotFound) {
|
||||
return user.User{}, invite.ErrInviteInvalid
|
||||
@@ -109,6 +114,14 @@ func (a *AcceptInviteUseCase) accept(command AcceptInviteCommand, pending invite
|
||||
return createdUser, nil
|
||||
}
|
||||
|
||||
func (a *AcceptInviteUseCase) inviterOf(pending invite.Invite) (uint32, error) {
|
||||
assigner, err := resolveRoleAssigner(a.userRepository, pending.InvitedBy)
|
||||
if err != nil || assigner == nil {
|
||||
return 0, err
|
||||
}
|
||||
return assigner.ID, nil
|
||||
}
|
||||
|
||||
func (a *AcceptInviteUseCase) emailHasAccount(email string) bool {
|
||||
for _, existingUser := range a.userRepository.GetAll() {
|
||||
if strings.EqualFold(existingUser.Email, email) {
|
||||
|
||||
@@ -48,10 +48,9 @@ func (u *ChangeUserRoleUseCase) ChangeRole(ctx context.Context, command ChangeUs
|
||||
return DirectoryEntry{}, err
|
||||
}
|
||||
if held && current.ID == next.ID {
|
||||
ref := NewRoleRef(next)
|
||||
return userEntry(target, &ref), nil
|
||||
return u.entryOf(target, next)
|
||||
}
|
||||
return u.write(ctx, target, current, next)
|
||||
return u.write(ctx, command, target, current, held, next)
|
||||
}
|
||||
|
||||
func (u *ChangeUserRoleUseCase) heldRole(target user.User) (role.Role, bool, error) {
|
||||
@@ -81,10 +80,10 @@ func (u *ChangeUserRoleUseCase) checkCoverage(command ChangeUserRoleCommand, tar
|
||||
return ensureCoveredBy(command.Caller, typeKeys, next)
|
||||
}
|
||||
|
||||
func (u *ChangeUserRoleUseCase) write(ctx context.Context, target user.User, current role.Role, next role.Role) (DirectoryEntry, error) {
|
||||
func (u *ChangeUserRoleUseCase) write(ctx context.Context, command ChangeUserRoleCommand, target user.User, current role.Role, held bool, next role.Role) (DirectoryEntry, error) {
|
||||
event := audit.NewEvent(audit.ActionUserChangeRole, audit.CategoryUsers)
|
||||
event.Target = audit.NewTarget(audit.TargetUser, target.ID, target.Username)
|
||||
updated, err := u.users.UpdateRole(target.ID, target.RoleID, next.ID)
|
||||
updated, err := u.users.UpdateRole(target.ID, target.RoleID, next.ID, command.Caller.UserID)
|
||||
if errors.Is(err, role.ErrRoleNotFound) {
|
||||
return DirectoryEntry{}, role.ErrUnknownRole
|
||||
}
|
||||
@@ -92,12 +91,22 @@ func (u *ChangeUserRoleUseCase) write(ctx context.Context, target user.User, cur
|
||||
u.recordWriteFailure(ctx, event, err)
|
||||
return DirectoryEntry{}, err
|
||||
}
|
||||
event.Params[audit.ParamFrom] = current.ID
|
||||
if held {
|
||||
event.Params[audit.ParamFrom] = current.ID
|
||||
event.Changes = audit.AppendTextChange(event.Changes, audit.FieldRole, current.Name, next.Name)
|
||||
}
|
||||
event.Params[audit.ParamTo] = next.ID
|
||||
event.Changes = audit.AppendTextChange(event.Changes, audit.FieldRole, current.Name, next.Name)
|
||||
u.recorder.Record(ctx, event)
|
||||
ref := NewRoleRef(next)
|
||||
return userEntry(updated, &ref), nil
|
||||
return u.entryOf(updated, next)
|
||||
}
|
||||
|
||||
func (u *ChangeUserRoleUseCase) entryOf(account user.User, assigned role.Role) (DirectoryEntry, error) {
|
||||
assigner, err := resolveRoleAssigner(u.users, account.RoleAssignedByID)
|
||||
if err != nil {
|
||||
return DirectoryEntry{}, err
|
||||
}
|
||||
ref := NewRoleRef(assigned)
|
||||
return userEntry(account, &ref, assigner), nil
|
||||
}
|
||||
|
||||
func (u *ChangeUserRoleUseCase) recordWriteFailure(ctx context.Context, event audit.Event, err error) {
|
||||
|
||||
@@ -6,16 +6,18 @@ import (
|
||||
)
|
||||
|
||||
type Access struct {
|
||||
Role role.Role
|
||||
Permissions []role.Grant
|
||||
Role role.Role
|
||||
Permissions []role.Grant
|
||||
RoleAssignedBy *RoleAssigner
|
||||
}
|
||||
|
||||
type GetMyAccessUseCase struct {
|
||||
roles role.Repository
|
||||
users user.Repository
|
||||
}
|
||||
|
||||
func NewGetMyAccessUseCase(roles role.Repository) *GetMyAccessUseCase {
|
||||
return &GetMyAccessUseCase{roles: roles}
|
||||
func NewGetMyAccessUseCase(roles role.Repository, users user.Repository) *GetMyAccessUseCase {
|
||||
return &GetMyAccessUseCase{roles: roles, users: users}
|
||||
}
|
||||
|
||||
func resolveRole(roles role.Repository, account user.User) (role.Role, error) {
|
||||
@@ -30,5 +32,10 @@ func (u *GetMyAccessUseCase) Get(account user.User) (Access, error) {
|
||||
if err != nil {
|
||||
return Access{}, err
|
||||
}
|
||||
return Access{Role: resolved, Permissions: resolved.EffectiveGrants()}, nil
|
||||
access := Access{Role: resolved, Permissions: resolved.EffectiveGrants()}
|
||||
access.RoleAssignedBy, err = resolveRoleAssigner(u.users, account.RoleAssignedByID)
|
||||
if err != nil {
|
||||
return Access{}, err
|
||||
}
|
||||
return access, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"RenewCMS/internal/domain/invite"
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
@@ -19,34 +20,67 @@ type ListUsersAndInvitesUseCase struct {
|
||||
roles role.Repository
|
||||
}
|
||||
|
||||
type RoleAssigner struct {
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func newRoleAssigner(account user.User) *RoleAssigner {
|
||||
return &RoleAssigner{ID: account.ID, Name: account.NameOrUsername()}
|
||||
}
|
||||
|
||||
func resolveRoleAssigner(users user.Repository, id uint32) (*RoleAssigner, error) {
|
||||
if id == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
found, err := users.Get(id)
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newRoleAssigner(found), nil
|
||||
}
|
||||
|
||||
type DirectoryEntry struct {
|
||||
ID uint32
|
||||
Kind string
|
||||
Username string
|
||||
DisplayName string
|
||||
Email string
|
||||
Role *RoleRef
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
ID uint32
|
||||
Kind string
|
||||
Username string
|
||||
DisplayName string
|
||||
Email string
|
||||
Role *RoleRef
|
||||
RoleAssignedBy *RoleAssigner
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func NewListUsersAndInvitesUseCase(userRepository user.Repository, inviteRepository invite.Repository, roles role.Repository) *ListUsersAndInvitesUseCase {
|
||||
return &ListUsersAndInvitesUseCase{userRepository, inviteRepository, roles}
|
||||
}
|
||||
|
||||
func userEntry(account user.User, found *RoleRef) DirectoryEntry {
|
||||
func userEntry(account user.User, found *RoleRef, assigner *RoleAssigner) DirectoryEntry {
|
||||
return DirectoryEntry{
|
||||
ID: account.ID,
|
||||
Kind: EntryKindUser,
|
||||
Username: account.Username,
|
||||
DisplayName: account.DisplayName,
|
||||
Email: account.Email,
|
||||
Role: found,
|
||||
Status: string(invite.StatusAccepted),
|
||||
CreatedAt: account.CreatedAt,
|
||||
ID: account.ID,
|
||||
Kind: EntryKindUser,
|
||||
Username: account.Username,
|
||||
DisplayName: account.DisplayName,
|
||||
Email: account.Email,
|
||||
Role: found,
|
||||
RoleAssignedBy: assigner,
|
||||
Status: string(invite.StatusAccepted),
|
||||
CreatedAt: account.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func assignerAmong(loaded map[uint32]user.User, id uint32) *RoleAssigner {
|
||||
found, present := loaded[id]
|
||||
if id == 0 || !present {
|
||||
return nil
|
||||
}
|
||||
return newRoleAssigner(found)
|
||||
}
|
||||
|
||||
func (g *ListUsersAndInvitesUseCase) ListUsersAndInvites() ([]DirectoryEntry, error) {
|
||||
refs, err := roleRefsByID(g.roles)
|
||||
if err != nil {
|
||||
@@ -55,8 +89,13 @@ func (g *ListUsersAndInvitesUseCase) ListUsersAndInvites() ([]DirectoryEntry, er
|
||||
now := time.Now()
|
||||
entries := []DirectoryEntry{}
|
||||
|
||||
for _, existingUser := range g.userRepository.GetAll() {
|
||||
entries = append(entries, userEntry(existingUser, lookupRoleRef(refs, existingUser.RoleID)))
|
||||
users := g.userRepository.GetAll()
|
||||
loaded := make(map[uint32]user.User, len(users))
|
||||
for _, existingUser := range users {
|
||||
loaded[existingUser.ID] = existingUser
|
||||
}
|
||||
for _, existingUser := range users {
|
||||
entries = append(entries, userEntry(existingUser, lookupRoleRef(refs, existingUser.RoleID), assignerAmong(loaded, existingUser.RoleAssignedByID)))
|
||||
}
|
||||
|
||||
for _, existingInvite := range g.inviteRepository.GetAll() {
|
||||
|
||||
Reference in new issue
Block a user