diff --git a/cmd/zitadel/authz.yaml b/cmd/zitadel/authz.yaml
index 3f69f637f7..4027b5a384 100644
--- a/cmd/zitadel/authz.yaml
+++ b/cmd/zitadel/authz.yaml
@@ -330,6 +330,11 @@ InternalAuthZ:
- "user.global.read"
- "user.grant.read"
- "user.membership.read"
+ - Role: 'SELF_MANAGEMENT_GLOBAL'
+ Permissions:
+ - "org.create"
+ - "policy.read"
+ - "user.self.delete"
- Role: 'PROJECT_OWNER_GLOBAL'
Permissions:
- "org.global.read"
diff --git a/console/.stylelintrc b/console/.stylelintrc
index 25790c73cc..b5da5bb74c 100644
--- a/console/.stylelintrc
+++ b/console/.stylelintrc
@@ -1,7 +1,5 @@
{
- "plugins": [
- "stylelint-scss"
- ],
+ "plugins": ["stylelint-scss"],
"extends": "stylelint-config-standard",
"rules": {
"value-keyword-case": null,
@@ -11,30 +9,20 @@
"at-rule-empty-line-before": [
"always",
{
- "except": [
- "blockless-after-same-name-blockless",
- "first-nested"
- ],
- "ignore": [
- "after-comment"
- ],
- "ignoreAtRules": [
- "else"
- ]
+ "except": ["blockless-after-same-name-blockless", "first-nested"],
+ "ignore": ["after-comment"],
+ "ignoreAtRules": ["else"]
}
],
"block-closing-brace-newline-after": [
"always",
{
- "ignoreAtRules": [
- "if",
- "else"
- ]
+ "ignoreAtRules": ["if", "else"]
}
],
"max-line-length": 125,
"no-empty-source": null,
- "number-leading-zero": "never",
+ "number-leading-zero": null,
"scss/at-rule-no-unknown": null,
"scss/at-else-if-parentheses-space-before": "always",
"scss/at-function-parentheses-space-before": "never",
diff --git a/console/src/app/modules/card/card.component.html b/console/src/app/modules/card/card.component.html
index c5ea815930..7ff316a0de 100644
--- a/console/src/app/modules/card/card.component.html
+++ b/console/src/app/modules/card/card.component.html
@@ -1,18 +1,18 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/console/src/app/modules/card/card.component.ts b/console/src/app/modules/card/card.component.ts
index 204aceefba..b86b8dc477 100644
--- a/console/src/app/modules/card/card.component.ts
+++ b/console/src/app/modules/card/card.component.ts
@@ -11,14 +11,13 @@ import { Component, Input } from '@angular/core';
style({ height: '0', opacity: 0 }),
animate('150ms ease-in-out', style({ height: '*', opacity: 1 })),
]),
- transition(':leave', [
- animate('150ms ease-in-out', style({ height: '0', opacity: 0 })),
- ]),
+ transition(':leave', [animate('150ms ease-in-out', style({ height: '0', opacity: 0 }))]),
]),
],
})
export class CardComponent {
@Input() public expanded: boolean = true;
+ @Input() public warn: boolean = false;
@Input() public title: string = '';
@Input() public description: string = '';
@Input() public animate: boolean = false;
diff --git a/console/src/app/modules/card/card.scss b/console/src/app/modules/card/card.scss
index 8e9b83dbc3..51c61ed5a3 100644
--- a/console/src/app/modules/card/card.scss
+++ b/console/src/app/modules/card/card.scss
@@ -7,19 +7,23 @@
$background: map-get($theme, background);
$card-background-color: mat.get-color-from-palette($background, card);
$is-dark-theme: map-get($theme, is-dark);
- $border-color: if($is-dark-theme, rgba(#8795a1, .2), rgba(#8795a1, .2));
+ $border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #ffffff, #000000);
/* stylelint-enable */
.card {
background-color: $card-background-color;
- transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
+ transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
border: 1px solid $border-color;
box-sizing: border-box;
- border-radius: .5rem;
+ border-radius: 0.5rem;
outline: none;
height: 100%;
+ &.warn {
+ border-color: var(--warn);
+ }
+
.selection-icon {
opacity: 0;
position: absolute;
diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html
index 542490bcc6..8dcacc9cfc 100644
--- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html
+++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html
@@ -55,6 +55,17 @@
+
+
+
+ {{'USER.PAGES.DELETEACCOUNT_DESC'| translate}}
+
+
+
+
+
+
diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss
index 00d1595575..0563e8e73a 100644
--- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss
+++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss
@@ -60,3 +60,8 @@
.side-padding {
padding-top: 1rem;
}
+
+.delete-account-wrapper {
+ display: flex;
+ justify-content: flex-end;
+}
diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts
index e6af4abbda..316e6d50ad 100644
--- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts
+++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts
@@ -4,7 +4,9 @@ import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
+import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
+import { AuthenticationService } from 'src/app/services/authentication.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -36,6 +38,7 @@ export class AuthUserDetailComponent implements OnDestroy {
private toast: ToastService,
public userService: GrpcAuthService,
private dialog: MatDialog,
+ private auth: AuthenticationService,
) {
this.loading = true;
this.refreshUser();
@@ -252,4 +255,30 @@ export class AuthUserDetailComponent implements OnDestroy {
break;
}
}
+
+ public deleteAccount(): void {
+ const dialogRef = this.dialog.open(WarnDialogComponent, {
+ data: {
+ confirmKey: 'USER.DIALOG.DELETE_BTN',
+ cancelKey: 'ACTIONS.CANCEL',
+ titleKey: 'USER.DIALOG.DELETE_TITLE',
+ descriptionKey: 'USER.DIALOG.DELETE_AUTH_DESCRIPTION',
+ },
+ width: '400px',
+ });
+
+ dialogRef.afterClosed().subscribe((resp) => {
+ if (resp) {
+ this.userService
+ .RemoveMyUser()
+ .then(() => {
+ this.toast.showInfo('USER.PAGES.DELETEACCOUNT_SUCCESS', true);
+ this.auth.signout();
+ })
+ .catch((error) => {
+ this.toast.showError(error);
+ });
+ }
+ });
+ }
}
diff --git a/console/src/app/services/grpc-auth.service.ts b/console/src/app/services/grpc-auth.service.ts
index a5694c5102..45bc1df0df 100644
--- a/console/src/app/services/grpc-auth.service.ts
+++ b/console/src/app/services/grpc-auth.service.ts
@@ -56,6 +56,8 @@ import {
RemoveMyPasswordlessResponse,
RemoveMyPhoneRequest,
RemoveMyPhoneResponse,
+ RemoveMyUserRequest,
+ RemoveMyUserResponse,
ResendMyEmailVerificationRequest,
ResendMyEmailVerificationResponse,
ResendMyPhoneVerificationRequest,
@@ -400,6 +402,11 @@ export class GrpcAuthService {
return this.grpcService.auth.listMyMemberships(req, null).then((resp) => resp.toObject());
}
+ public RemoveMyUser(): Promise {
+ const req = new RemoveMyUserRequest();
+ return this.grpcService.auth.removeMyUser(req, null).then((resp) => resp.toObject());
+ }
+
public getMyEmail(): Promise {
const req = new GetMyEmailRequest();
return this.grpcService.auth.getMyEmail(req, null).then((resp) => resp.toObject());
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index f619e2c70c..786a8f0d8a 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -182,7 +182,11 @@
"STATE": "Status",
"DELETE": "Benutzer löschen",
"UNLOCK": "Benutzer entsperren",
- "LOCKEDDESCRIPTION": "Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden."
+ "LOCKEDDESCRIPTION": "Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden.",
+ "DELETEACCOUNT": "Account löschen",
+ "DELETEACCOUNT_DESC": "Wenn du diese Aktion ausführst, wirst du abgemeldet und danach keinen Zugriff mehr auf dein Konto haben. Diese Aktion kann nicht rückgängig gemacht werden.",
+ "DELETEACCOUNT_BTN": "Account löschen",
+ "DELETEACCOUNT_SUCCESS": "Account erfolgreich gelöscht!"
},
"DETAILS": {
"DATECREATED": "Erstellt",
@@ -190,7 +194,9 @@
},
"DIALOG": {
"DELETE_TITLE": "User löschen",
- "DELETE_DESCRIPTION": "Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?"
+ "DELETE_DESCRIPTION": "Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?",
+ "DELETE_AUTH_DESCRIPTION": "Sie sind im Begriff Ihren Account endgültig zu löschen. Wollen Sie dies wirklich tun?",
+ "DELETE_BTN": "Entgültig löschen"
},
"SENDEMAILDIALOG": {
"TITLE": "Email Benachrichtigung senden",
@@ -807,9 +813,9 @@
},
"PRIVATELABELING_POLICY": {
"TITLE": "Private Labeling",
- "BTN":"Datei auswählen",
+ "BTN": "Datei auswählen",
"DESCRIPTION": "Definiere das Erscheinungsbild des Logins.",
- "ACTIVATEPREVIEW":"Preview aktivieren"
+ "ACTIVATEPREVIEW": "Preview aktivieren"
},
"LOGIN_POLICY": {
"TITLE": "Login Richtlinien",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index 3d0ac6736c..16c54dfd51 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -182,7 +182,11 @@
"STATE": "Status",
"DELETE": "Delete User",
"UNLOCK": "Unlock User",
- "LOCKEDDESCRIPTION": "This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again."
+ "LOCKEDDESCRIPTION": "This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again.",
+ "DELETEACCOUNT": "Delete Account",
+ "DELETEACCOUNT_DESC": "If you perform this action, you will be logged out and will no longer have access to your account. This action is not reversible, so please continue with caution.",
+ "DELETEACCOUNT_BTN": "Delete Account",
+ "DELETEACCOUNT_SUCCESS": "Account deleted successfully!"
},
"DETAILS": {
"DATECREATED": "Created",
@@ -190,7 +194,9 @@
},
"DIALOG": {
"DELETE_TITLE": "Delete User",
- "DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?"
+ "DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?",
+ "DELETE_AUTH_DESCRIPTION": "You are about to permanently delete your personal account. Are you sure?",
+ "DELETE_BTN": "Delete permanently"
},
"SENDEMAILDIALOG": {
"TITLE": "Send Email Notification",
diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json
index 0bc7d9ff5f..f4de1cc499 100644
--- a/console/src/assets/i18n/it.json
+++ b/console/src/assets/i18n/it.json
@@ -182,7 +182,11 @@
"STATE": "Stato",
"DELETE": "Elimina utente",
"UNLOCK": "Sblocca utente",
- "LOCKEDDESCRIPTION": "Questo utente \u00e8 stato bloccato a causa del superamento dei tentativi massimi di accesso e deve essere sbloccato per essere utilizzato di nuovo."
+ "LOCKEDDESCRIPTION": "Questo utente \u00e8 stato bloccato a causa del superamento dei tentativi massimi di accesso e deve essere sbloccato per essere utilizzato di nuovo.",
+ "DELETEACCOUNT": "Elimina account personale",
+ "DELETEACCOUNT_DESC": "Se esegui questa azione, sarai disconnesso e non avrai più accesso al tuo account. Questa azione non può essere invertita.",
+ "DELETEACCOUNT_BTN": "Elimina",
+ "DELETEACCOUNT_SUCCESS": "Account eliminato con successo!"
},
"DETAILS": {
"DATECREATED": "Creato",
@@ -190,7 +194,9 @@
},
"DIALOG": {
"DELETE_TITLE": "Elimina utente",
- "DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?"
+ "DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?",
+ "DELETE_AUTH_DESCRIPTION": "Stai per eleminare il tuo account personale in modo permanente. Vuoi continuare?",
+ "DELETE_BTN": "Elimina"
},
"SENDEMAILDIALOG": {
"TITLE": "Invia una notifica via e-mail",
diff --git a/docs/docs/apis/proto/auth.md b/docs/docs/apis/proto/auth.md
index 80ba2d5784..2127690cec 100644
--- a/docs/docs/apis/proto/auth.md
+++ b/docs/docs/apis/proto/auth.md
@@ -43,6 +43,18 @@ Returns my full blown user
GET: /users/me
+### RemoveMyUser
+
+> **rpc** RemoveMyUser([RemoveMyUserRequest](#removemyuserrequest))
+[RemoveMyUserResponse](#removemyuserresponse)
+
+Changes the user state to deleted
+
+
+
+ DELETE: /users/me
+
+
### ListMyUserChanges
> **rpc** ListMyUserChanges([ListMyUserChangesRequest](#listmyuserchangesrequest))
@@ -1250,6 +1262,24 @@ This is an empty request
+### RemoveMyUserRequest
+This is an empty request
+the request parameters are read from the token-header
+
+
+
+
+### RemoveMyUserResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### ResendMyEmailVerificationRequest
This is an empty request
diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go
index 33c78dde52..0c19e9b136 100644
--- a/internal/api/grpc/auth/user.go
+++ b/internal/api/grpc/auth/user.go
@@ -11,7 +11,9 @@ import (
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/org"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
+ "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1/models"
+ user_model "github.com/caos/zitadel/internal/user/model"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
)
@@ -24,6 +26,25 @@ func (s *Server) GetMyUser(ctx context.Context, _ *auth_pb.GetMyUserRequest) (*a
return &auth_pb.GetMyUserResponse{User: user_grpc.UserToPb(user)}, nil
}
+func (s *Server) RemoveMyUser(ctx context.Context, _ *auth_pb.RemoveMyUserRequest) (*auth_pb.RemoveMyUserResponse, error) {
+ ctxData := authz.GetCtxData(ctx)
+ grants, err := s.repo.SearchMyUserGrants(ctx, &grant_model.UserGrantSearchRequest{Queries: []*grant_model.UserGrantSearchQuery{}})
+ if err != nil {
+ return nil, err
+ }
+ membersShips, err := s.repo.SearchMyUserMemberships(ctx, &user_model.UserMembershipSearchRequest{Queries: []*user_model.UserMembershipSearchQuery{}})
+ if err != nil {
+ return nil, err
+ }
+ details, err := s.command.RemoveUser(ctx, ctxData.UserID, ctxData.ResourceOwner, UserMembershipViewsToDomain(membersShips.Result), userGrantsToIDs(grants.Result)...)
+ if err != nil {
+ return nil, err
+ }
+ return &auth_pb.RemoveMyUserResponse{
+ Details: obj_grpc.DomainToChangeDetailsPb(details),
+ }, nil
+}
+
func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserChangesRequest) (*auth_pb.ListMyUserChangesResponse, error) {
sequence, limit, asc := change.ChangeQueryToModel(req.Query)
features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).ResourceOwner)
@@ -137,3 +158,46 @@ func ListMyProjectOrgsRequestToModel(req *auth_pb.ListMyProjectOrgsRequest) (*gr
Queries: queries,
}, nil
}
+
+func UserMembershipViewsToDomain(memberships []*user_model.UserMembershipView) []*domain.UserMembership {
+ result := make([]*domain.UserMembership, len(memberships))
+ for i, membership := range memberships {
+ result[i] = &domain.UserMembership{
+ UserID: membership.UserID,
+ MemberType: MemberTypeToDomain(membership.MemberType),
+ AggregateID: membership.AggregateID,
+ ObjectID: membership.ObjectID,
+ Roles: membership.Roles,
+ DisplayName: membership.DisplayName,
+ CreationDate: membership.CreationDate,
+ ChangeDate: membership.ChangeDate,
+ ResourceOwner: membership.ResourceOwner,
+ ResourceOwnerName: membership.ResourceOwnerName,
+ Sequence: membership.Sequence,
+ }
+ }
+ return result
+}
+
+func MemberTypeToDomain(mType user_model.MemberType) domain.MemberType {
+ switch mType {
+ case user_model.MemberTypeIam:
+ return domain.MemberTypeIam
+ case user_model.MemberTypeOrganisation:
+ return domain.MemberTypeOrganisation
+ case user_model.MemberTypeProject:
+ return domain.MemberTypeProject
+ case user_model.MemberTypeProjectGrant:
+ return domain.MemberTypeProjectGrant
+ default:
+ return domain.MemberTypeUnspecified
+ }
+}
+
+func userGrantsToIDs(userGrants []*grant_model.UserGrantView) []string {
+ converted := make([]string, len(userGrants))
+ for i, grant := range userGrants {
+ converted[i] = grant.ID
+ }
+ return converted
+}
diff --git a/internal/command/setup_step21.go b/internal/command/setup_step21.go
new file mode 100644
index 0000000000..5b9aa9a914
--- /dev/null
+++ b/internal/command/setup_step21.go
@@ -0,0 +1,103 @@
+package command
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/domain"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/org"
+ "github.com/caos/zitadel/internal/repository/user"
+)
+
+type Step21 struct{}
+
+func (s *Step21) Step() domain.Step {
+ return domain.Step21
+}
+
+func (s *Step21) execute(ctx context.Context, commandSide *Commands) error {
+ return commandSide.SetupStep21(ctx, s)
+}
+
+func (c *Commands) SetupStep21(ctx context.Context, step *Step21) error {
+ fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
+ events := make([]eventstore.EventPusher, 0)
+ globalMembers := newGlobalOrgMemberWriteModel(iam.GlobalOrgID, domain.RoleOrgProjectCreator)
+ orgAgg := OrgAggregateFromWriteModel(&globalMembers.WriteModel)
+ if err := c.eventstore.FilterToQueryReducer(ctx, globalMembers); err != nil {
+ return nil, err
+ }
+ for userID, roles := range globalMembers.members {
+ for i, role := range roles {
+ if role == domain.RoleOrgProjectCreator {
+ roles[i] = domain.RoleSelfManagementGlobal
+ }
+ }
+ events = append(events, org.NewMemberChangedEvent(ctx, orgAgg, userID, roles...))
+ }
+ return events, nil
+ }
+ return c.setup(ctx, step, fn)
+}
+
+type globalOrgMembersWriteModel struct {
+ eventstore.WriteModel
+
+ orgID string
+ role string
+ members map[string][]string
+}
+
+func newGlobalOrgMemberWriteModel(orgID, role string) *globalOrgMembersWriteModel {
+ return &globalOrgMembersWriteModel{
+ orgID: orgID,
+ role: role,
+ members: make(map[string][]string),
+ }
+}
+
+func (wm *globalOrgMembersWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *org.MemberAddedEvent:
+ for _, role := range e.Roles {
+ if wm.role == role {
+ wm.members[e.UserID] = e.Roles
+ break
+ }
+ }
+ case *org.MemberChangedEvent:
+ delete(wm.members, e.UserID)
+ for _, role := range e.Roles {
+ if wm.role == role {
+ wm.members[e.UserID] = e.Roles
+ break
+ }
+ }
+ case *org.MemberRemovedEvent:
+ delete(wm.members, e.UserID)
+ case *org.MemberCascadeRemovedEvent:
+ delete(wm.members, e.UserID)
+ case *user.UserRemovedEvent:
+ delete(wm.members, e.Aggregate().ID)
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *globalOrgMembersWriteModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ AddQuery().
+ AggregateTypes(org.AggregateType).
+ AggregateIDs(wm.orgID).
+ EventTypes(
+ org.MemberAddedEventType,
+ org.MemberChangedEventType,
+ org.MemberRemovedEventType,
+ org.MemberCascadeRemovedEventType,
+ ).
+ Or().
+ AggregateTypes(user.AggregateType).
+ EventTypes(user.UserRemovedType).
+ Builder()
+}
diff --git a/internal/domain/roles.go b/internal/domain/roles.go
index ade3a7feec..adc25aaae9 100644
--- a/internal/domain/roles.go
+++ b/internal/domain/roles.go
@@ -7,15 +7,16 @@ import (
)
const (
- IAMRolePrefix = "IAM"
- OrgRolePrefix = "ORG"
- ProjectRolePrefix = "PROJECT"
- ProjectGrantRolePrefix = "PROJECT_GRANT"
- RoleOrgOwner = "ORG_OWNER"
- RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
- RoleIAMOwner = "IAM_OWNER"
- RoleProjectOwner = "PROJECT_OWNER"
- RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
+ IAMRolePrefix = "IAM"
+ OrgRolePrefix = "ORG"
+ ProjectRolePrefix = "PROJECT"
+ ProjectGrantRolePrefix = "PROJECT_GRANT"
+ RoleOrgOwner = "ORG_OWNER"
+ RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
+ RoleIAMOwner = "IAM_OWNER"
+ RoleProjectOwner = "PROJECT_OWNER"
+ RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
+ RoleSelfManagementGlobal = "SELF_MANAGEMENT_GLOBAL"
)
func CheckForInvalidRoles(roles []string, rolePrefix string, validRoles []authz.RoleMapping) []string {
diff --git a/internal/domain/step.go b/internal/domain/step.go
index 883682306b..dc3641389b 100644
--- a/internal/domain/step.go
+++ b/internal/domain/step.go
@@ -23,6 +23,7 @@ const (
Step18
Step19
Step20
+ Step21
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount
)
diff --git a/internal/repository/project/key.go b/internal/repository/project/key.go
index eff24e1eab..3c2fa6a054 100644
--- a/internal/repository/project/key.go
+++ b/internal/repository/project/key.go
@@ -3,9 +3,10 @@ package project
import (
"context"
"encoding/json"
- "github.com/caos/zitadel/internal/eventstore"
"time"
+ "github.com/caos/zitadel/internal/eventstore"
+
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/repository"
diff --git a/internal/setup/config.go b/internal/setup/config.go
index 3d75ba14e6..83da2438fa 100644
--- a/internal/setup/config.go
+++ b/internal/setup/config.go
@@ -26,6 +26,7 @@ type IAMSetUp struct {
Step18 *command.Step18
Step19 *command.Step19
Step20 *command.Step20
+ Step21 *command.Step21
}
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
@@ -52,6 +53,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
setup.Step18,
setup.Step19,
setup.Step20,
+ setup.Step21,
} {
if step.Step() <= currentDone {
continue
diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go
index 436e98fca6..eadbabf272 100644
--- a/internal/ui/login/handler/external_login_handler.go
+++ b/internal/ui/login/handler/external_login_handler.go
@@ -259,7 +259,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
}
resourceOwner := iam.GlobalOrgID
- memberRoles := []string{domain.RoleOrgProjectCreator}
+ memberRoles := []string{domain.RoleSelfManagementGlobal}
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID {
memberRoles = nil
diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go
index 44ffc0ffd0..96db3500b4 100644
--- a/internal/ui/login/handler/external_register_handler.go
+++ b/internal/ui/login/handler/external_register_handler.go
@@ -136,7 +136,7 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.UserIDPLink) {
resourceOwner := iam.GlobalOrgID
- memberRoles := []string{domain.RoleOrgProjectCreator}
+ memberRoles := []string{domain.RoleSelfManagementGlobal}
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
memberRoles = nil
@@ -196,7 +196,7 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque
return
}
resourceOwner := iam.GlobalOrgID
- memberRoles := []string{domain.RoleOrgProjectCreator}
+ memberRoles := []string{domain.RoleSelfManagementGlobal}
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID {
memberRoles = nil
diff --git a/internal/ui/login/handler/register_handler.go b/internal/ui/login/handler/register_handler.go
index 7f009bb5d0..3a1946e557 100644
--- a/internal/ui/login/handler/register_handler.go
+++ b/internal/ui/login/handler/register_handler.go
@@ -68,7 +68,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
}
resourceOwner := iam.GlobalOrgID
- memberRoles := []string{domain.RoleOrgProjectCreator}
+ memberRoles := []string{domain.RoleSelfManagementGlobal}
if authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != iam.GlobalOrgID {
memberRoles = nil
diff --git a/proto/zitadel/auth.proto b/proto/zitadel/auth.proto
index 0fcb510b8f..c06b22c862 100644
--- a/proto/zitadel/auth.proto
+++ b/proto/zitadel/auth.proto
@@ -83,6 +83,17 @@ service AuthService {
};
}
+ // Changes the user state to deleted
+ rpc RemoveMyUser(RemoveMyUserRequest) returns (RemoveMyUserResponse) {
+ option (google.api.http) = {
+ delete: "/users/me"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "user.self.delete"
+ };
+ }
+
// Returns the history of the authorized user (each event)
rpc ListMyUserChanges(ListMyUserChangesRequest) returns (ListMyUserChangesResponse) {
option (google.api.http) = {
@@ -614,6 +625,14 @@ message GetMyUserResponse {
];
}
+//This is an empty request
+// the request parameters are read from the token-header
+message RemoveMyUserRequest {}
+
+message RemoveMyUserResponse{
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message ListMyUserChangesRequest {
zitadel.change.v1.ChangeQuery query = 1;
}