Merge branch 'main' into next

# Conflicts:
#	docs/docs/self-hosting/manage/updating_scaling.md
#	docs/docs/support/advisory/a10008.md
#	docs/docs/support/technical_advisory.mdx
This commit is contained in:
Livio Spring 2024-02-12 07:44:07 +01:00
commit 24868240f0
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
351 changed files with 3833 additions and 1868 deletions

View File

@ -4,17 +4,39 @@ on:
issues:
types:
- opened
pull_request_target:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
name: Add issue and community pr to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.3.0
- name: add issue
uses: actions/add-to-project@v0.5.0
if: ${{ github.event_name == 'issues' }}
with:
# You can target a repository in a different organization
# to the issue
project-url: https://github.com/orgs/zitadel/projects/2
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: OKR
label-operator: NOT
- uses: tspascoal/get-user-teams-membership@v3
id: checkUserMember
with:
username: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }}
- name: add pr
uses: actions/add-to-project@v0.5.0
if: ${{ github.event_name == 'pull_request_target' && !contains(steps.checkUserMember.outputs.teams, 'engineers') && github.actor != 'app/dependabot'}}
with:
# You can target a repository in a different organization
# to the issue
project-url: https://github.com/orgs/zitadel/projects/2
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
- uses: actions-ecosystem/action-add-labels@v1.1.0
if: ${{ github.event_name == 'pull_request_target' && !contains(steps.checkUserMember.outputs.teams, 'staff') && github.actor != 'app/dependabot'}}
with:
github_token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labels: |
os-contribution

View File

@ -219,7 +219,7 @@ docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
In order to run the integrations tests for the gRPC API, PostgreSQL and CockroachDB must be started and initialized:
```bash
export INTEGRATION_DB_FLAVOR="cockroach" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
export INTEGRATION_DB_FLAVOR="postgres" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait ${INTEGRATION_DB_FLAVOR}
make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down

View File

@ -11,7 +11,7 @@
<img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" /></a>
<a href="https://github.com/zitadel/zitadel/actions" alt="ZITADEL Release">
<img alt="GitHub Workflow Status (with event)" src="https://img.shields.io/github/actions/workflow/status/zitadel/zitadel/build.yml?event=pull_request"></a>
<a href="https://github.com/zitadel/zitadel/releases" alt="Release">
<a href="https://zitadel.com/docs/support/software-release-cycles-support" alt="Release">
<img src="https://badgen.net/github/release/zitadel/zitadel/stable" /></a>
<a href="https://github.com/zitadel/zitadel/releases" alt="Release">
<img alt="Dynamic YAML Badge" src="https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fzitadel%2Fzitadel%2Fmain%2Frelease-channels.yaml&query=%24.stable&label=stable"></a>
@ -39,26 +39,16 @@ Look no further — ZITADEL is the identity infrastructure, simplified for you.
We provide you with a wide range of out-of-the-box features to accelerate your project, including:
:white_check_mark: Multi-tenancy with branding customization
:white_check_mark: Secure login
:white_check_mark: Self-service
:white_check_mark: OpenID Connect
:white_check_mark: OAuth2.x
:white_check_mark: SAML2
:white_check_mark: LDAP
:white_check_mark: Passkeys / FIDO2
:white_check_mark: OTP
:white_check_mark: U2F, and an unlimited audit trail is there for you, ready to use.
:white_check_mark: Multi-tenancy with team management
:white_check_mark: Secure login
:white_check_mark: Self-service
:white_check_mark: OpenID Connect
:white_check_mark: OAuth2.x
:white_check_mark: SAML2
:white_check_mark: LDAP
:white_check_mark: Passkeys / FIDO2
:white_check_mark: OTP
and an unlimited audit trail is there for you, ready to use.
With ZITADEL, you are assured of a robust and customizable turnkey solution for all your authentication and authorization needs.
@ -86,18 +76,18 @@ See all guides [here](https://zitadel.com/docs/self-hosting/deploy/overview)
### Setup ZITADEL Cloud (SaaS)
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.cloud).
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.com).
ZITADEL Cloud comes with a free tier, providing you with all the same features as the open-source version.
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
### Example applications
Clone one of our [example applications](https://zitadel.com/docs/examples/introduction#clone-a-sample-project) or deploy them directly to Vercel.
Clone one of our [example applications](https://zitadel.com/docs/sdk-examples/introduction) or deploy them directly to Vercel.
### SDKs
Use our [SDKs](https://zitadel.com/docs/examples/sdks) for your favorite language and framework.
Use our [SDKs](https://zitadel.com/docs/sdk-examples/introduction) for your favorite language and framework.
## Why choose ZITADEL
@ -115,6 +105,7 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
## Features
Authentication
- Single Sign On (SSO)
- Passkeys support (FIDO2 / WebAuthN)
- Username / Password
@ -128,12 +119,15 @@ Authentication
- [Machine-to-machine](https://zitadel.com/docs/guides/integrate/serviceusers) with JWT profile, Personal Access Tokens (PAT), and Client Credentials
Multi-Tenancy
- [Identity Brokering](https://zitadel.com/docs/guides/integrate/identity-brokering) with templates for popular identity providers
- [Customizable onboaring](https://zitadel.com/docs/guides/solution-scenarios/onboarding) for B2B and their users
- [Delegate role management to third-parties](https://zitadel.com/docs/guides/manage/console/projects)
- [Domain discovery](https://zitadel.com/docs/guides/solution-scenarios/domain-discovery)
Integration
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction)
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction) for every functionality and resource
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens
- [Role Based Access Control (RBAC)](https://zitadel.com/docs/guides/integrate/retrieve-user-roles)
@ -145,6 +139,7 @@ Self-Service
Deployment
- [Postgres](https://zitadel.com/docs/self-hosting/manage/database#postgres) (version >= 14) or [CockroachDB](https://zitadel.com/docs/self-hosting/manage/database#cockroach) (version latest stable)
- [Zero Downtime Updates](https://zitadel.com/docs/concepts/architecture/solution#zero-downtime-updates)
- [High scalability](https://zitadel.com/docs/self-hosting/manage/production)
Track upcoming features on our [roadmap](https://zitadel.com/roadmap).

View File

@ -337,6 +337,7 @@ OIDC:
TriggerIntrospectionProjections: false
# Allows fallback to the Legacy Introspection implementation
LegacyIntrospection: false
PublicKeyCacheMaxAge: 24h # ZITADEL_OIDC_PUBLICKEYCACHEMAXAGE
SAML:
ProviderConfig:

View File

@ -125,39 +125,6 @@ const routes: Routes = [
},
],
},
{
path: 'failed-events',
loadChildren: () => import('./pages/failed-events/failed-events.module'),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'views',
loadChildren: () => import('./pages/iam-views/iam-views.module'),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'events',
loadChildren: () => import('./pages/events/events.module'),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'settings',
loadChildren: () => import('./pages/instance-settings/instance-settings.module'),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read', 'iam.policy.read'],
requiresAll: true,
},
},
{
path: 'org-settings',
loadChildren: () => import('./pages/org-settings/org-settings.module'),

View File

@ -0,0 +1,129 @@
<h2>{{ 'IAM.EVENTS.TITLE' | translate }}</h2>
<p class="events-desc cnsl-secondary-text">{{ 'IAM.EVENTS.DESCRIPTION' | translate }}</p>
<cnsl-refresh-table
[hideRefresh]="true"
(refreshed)="refresh()"
[dataSize]="dataSource.data.length"
[loading]="_loading | async"
>
<div actions>
<cnsl-filter-events (requestChanged)="filterChanged($event)"></cnsl-filter-events>
</div>
<table
[dataSource]="dataSource"
mat-table
class="table views-table"
aria-label="Views"
matSort
(matSortChange)="sortChange($event)"
>
<ng-container matColumnDef="editor">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.EDITOR' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<div class="editor-row" *ngIf="event.editor as editor">
<!-- <cnsl-avatar
*ngIf="editor && editor.displayName; else cog"
class="avatar"
[name]="editor.displayName"
[avatarUrl]="editor.avatarUrl || ''"
[forColor]="editor.preferredLoginName ?? editor.displayName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="editor?.preferredLoginName ?? 'franz'" [isMachine]="true">
<i class="las la-robot"></i>
</cnsl-avatar> </ng-template
> -->
<span class="name" *ngIf="editor.displayName">{{ editor.displayName }}</span>
<span class="state" *ngIf="editor.service">{{ editor.service }}</span>
</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="aggregate">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.AGGREGATE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<div class="aggregate-row">
<span class="id">{{ event.aggregate.id }}</span
><span class="state" *ngIf="event.aggregate?.type?.localized?.localizedMessage">{{
event.aggregate.type.localized.localizedMessage
}}</span>
</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="resourceOwner">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.aggregate.resourceOwner">{{ event.aggregate.resourceOwner }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="sequence">
<th mat-header-cell *matHeaderCellDef>
{{ 'IAM.EVENTS.SEQUENCE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
{{ event.sequence }}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header [start]="'desc'" [disableClear]="true">
{{ 'IAM.EVENTS.CREATIONDATE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<span>{{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm:ss' }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.TYPE' | translate }}</th>
<td mat-cell *matCellDef="let event" data-e2e="event-type-cell">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.type?.localized?.localizedMessage">{{ event.type.localized.localizedMessage }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="payload">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.PAYLOAD' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | topayload as payload">
<span>{{ payload | json }}</span>
<div class="btn-wrapper">
<button class="open-in-dialog-btn" mat-icon-button (click)="openDialog(event)">
<mat-icon svgIcon="mdi_arrow_expand"></mat-icon>
</button>
</div>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<cnsl-paginator
#paginator
class="paginator"
[hidePagination]="true"
[showMoreButton]="true"
[disableShowMore]="_done | async"
(moreRequested)="more()"
[length]="dataSource.data.length"
>
</cnsl-paginator>
</cnsl-refresh-table>

View File

@ -61,10 +61,6 @@
}
}
.events-title {
margin: 2rem 0 0 0;
}
.events-desc {
font-size: 14px;
}

View File

@ -26,13 +26,11 @@ import { DisplayJsonDialogModule } from 'src/app/modules/display-json-dialog/dis
import { FilterEventsModule } from 'src/app/modules/filter-events/filter-events.module';
import { ToObjectPipeModule } from 'src/app/pipes/to-object/to-object.module';
import { ToPayloadPipeModule } from 'src/app/pipes/to-payload/to-payload.module';
import { EventsRoutingModule } from './events-routing.module';
import { EventsComponent } from './events.component';
@NgModule({
declarations: [EventsComponent],
imports: [
EventsRoutingModule,
CommonModule,
TableActionsModule,
MatIconModule,
@ -60,6 +58,6 @@ import { EventsComponent } from './events.component';
MatSortModule,
OverlayModule,
],
exports: [],
exports: [EventsComponent],
})
export default class IamViewsModule {}
export default class EventsModule {}

View File

@ -0,0 +1,68 @@
<h2>{{ 'IAM.FAILEDEVENTS.TITLE' | translate }}</h2>
<p class="failed-events-desc cnsl-secondary-text">{{ 'IAM.FAILEDEVENTS.DESCRIPTION' | translate }}</p>
<div class="table-wrapper">
<cnsl-refresh-table (refreshed)="loadEvents()" [dataSize]="eventDataSource.data.length" [loading]="loading$ | async">
<table [dataSource]="eventDataSource" mat-table class="table" aria-label="Elements">
<ng-container matColumnDef="viewName">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.VIEWNAME' | translate }}</th>
<td mat-cell *matCellDef="let event">{{ event.viewName }}</td>
</ng-container>
<ng-container matColumnDef="database">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.DATABASE' | translate }}</th>
<td mat-cell *matCellDef="let event">{{ event.database }}</td>
</ng-container>
<ng-container matColumnDef="failedSequence">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.FAILEDSEQUENCE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.failedSequence }}</span>
</td>
</ng-container>
<ng-container matColumnDef="failureCount">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.FAILURECOUNT' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.failureCount }}</span>
</td>
</ng-container>
<ng-container matColumnDef="lastFailed">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.LASTFAILED' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.lastFailed | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="errorMessage">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.ERRORMESSAGE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span class="failed-event-error-message">{{ event?.errorMessage }}</span>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td class="back" mat-cell *matCellDef="let event">
<cnsl-table-actions>
<button
actions
color="warn"
mat-icon-button
matTooltip="{{ 'IAM.FAILEDEVENTS.DELETE' | translate }}"
(click)="cancelEvent(event.viewName, event.database, event.failedSequence)"
>
<i class="las la-minus-circle"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="eventDisplayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: eventDisplayedColumns"></tr>
</table>
<cnsl-paginator #paginator class="paginator" [hidePagination]="true" [length]="eventDataSource.data.length || 0">
</cnsl-paginator>
</cnsl-refresh-table>
</div>

View File

@ -1,7 +1,3 @@
.failed-events-title {
margin: 2rem 0 0 0;
}
.failed-events-desc {
font-size: 14px;
}

View File

@ -18,13 +18,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { FailedEventsRoutingModule } from './failed-events-routing.module';
import { FailedEventsComponent } from './failed-events.component';
@NgModule({
declarations: [FailedEventsComponent],
imports: [
FailedEventsRoutingModule,
CommonModule,
TableActionsModule,
MatIconModule,
@ -44,5 +42,6 @@ import { FailedEventsComponent } from './failed-events.component';
MatTableModule,
MatSortModule,
],
exports: [FailedEventsComponent],
})
export default class FailedEventsModule {}

View File

@ -46,4 +46,27 @@
</div>
</div>
</div>
<div class="domain-query">
<mat-checkbox
id="domain"
class="cb"
[checked]="getSubFilter(SubQuery.DOMAIN)"
(change)="changeCheckbox(SubQuery.DOMAIN, $event)"
>{{ 'FILTER.PRIMARYDOMAIN' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.DOMAIN) as dq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dq.getMethod()" (selectionChange)="setMethod(dq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="dq.getDomain()" (change)="setValue(SubQuery.DOMAIN, dq, $event)" />
</cnsl-form-field>
</div>
</div>
</cnsl-filter>

View File

@ -3,7 +3,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
import { ActivatedRoute, Router } from '@angular/router';
import { take } from 'rxjs';
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
import { OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
import { OrgDomainQuery, OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
import { UserNameQuery } from 'src/app/proto/generated/zitadel/user_pb';
import { FilterComponent } from '../filter/filter.component';
@ -11,6 +11,7 @@ import { FilterComponent } from '../filter/filter.component';
enum SubQuery {
NAME,
STATE,
DOMAIN,
}
@Component({
@ -52,6 +53,13 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
orgStateQuery.setState(filter.stateQuery.state);
orgQuery.setStateQuery(orgStateQuery);
return orgQuery;
} else if (filter.domainQuery) {
const orgQuery = new OrgQuery();
const orgDomainQuery = new OrgDomainQuery();
orgDomainQuery.setDomain(filter.domainQuery.domain);
orgDomainQuery.setMethod(filter.domainQuery.method);
orgQuery.setDomainQuery(orgDomainQuery);
return orgQuery;
} else {
return undefined;
}
@ -83,6 +91,14 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
osq.setStateQuery(sq);
this.searchQueries.push(osq);
break;
case SubQuery.DOMAIN:
const dq = new OrgDomainQuery();
dq.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
dq.setDomain('');
const odq = new OrgQuery();
odq.setDomainQuery(dq);
this.searchQueries.push(odq);
break;
}
} else {
switch (subquery) {
@ -98,6 +114,12 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
this.searchQueries.splice(index_sn, 1);
}
break;
case SubQuery.DOMAIN:
const index_pdn = this.searchQueries.findIndex((q) => (q as OrgQuery).toObject().domainQuery !== undefined);
if (index_pdn > -1) {
this.searchQueries.splice(index_pdn, 1);
}
break;
}
}
}
@ -113,6 +135,10 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
(query as OrgStateQuery).setState(value);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.DOMAIN:
(query as OrgDomainQuery).setDomain(value);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
}
}
@ -132,6 +158,13 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
} else {
return undefined;
}
case SubQuery.DOMAIN:
const pdn = this.searchQueries.find((q) => (q as OrgQuery).toObject().domainQuery !== undefined);
if (pdn) {
return (pdn as OrgQuery).getDomainQuery();
} else {
return undefined;
}
}
}

View File

@ -0,0 +1,40 @@
<h2>{{ 'IAM.VIEWS.TITLE' | translate }}</h2>
<p class="views-desc cnsl-secondary-text">{{ 'IAM.VIEWS.DESCRIPTION' | translate }}</p>
<cnsl-refresh-table (refreshed)="loadViews()" [dataSize]="dataSource.data.length" [loading]="loading$ | async">
<table [dataSource]="dataSource" mat-table class="table views-table" aria-label="Views" matSort>
<ng-container matColumnDef="viewName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'IAM.VIEWS.VIEWNAME' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.viewName }}</td>
</ng-container>
<ng-container matColumnDef="database">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'IAM.VIEWS.DATABASE' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.database }}</td>
</ng-container>
<ng-container matColumnDef="sequence">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.SEQUENCE' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.processedSequence }}</td>
</ng-container>
<ng-container matColumnDef="eventTimestamp">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.EVENTTIMESTAMP' | translate }}</th>
<td mat-cell *matCellDef="let view">
<span>{{ view?.eventTimestamp | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="lastSuccessfulSpoolerRun">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.LASTSPOOL' | translate }}</th>
<td mat-cell *matCellDef="let view">
<span>{{ view?.lastSuccessfulSpoolerRun | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<cnsl-paginator #paginator class="paginator" [hidePagination]="true" [length]="dataSource.data.length || 0">
</cnsl-paginator>
</cnsl-refresh-table>

View File

@ -0,0 +1,3 @@
.views-desc {
font-size: 14px;
}

View File

@ -18,13 +18,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { IamViewsRoutingModule } from './iam-views-routing.module';
import { IamViewsComponent } from './iam-views.component';
@NgModule({
declarations: [IamViewsComponent],
imports: [
IamViewsRoutingModule,
CommonModule,
TableActionsModule,
MatIconModule,
@ -44,6 +42,6 @@ import { IamViewsComponent } from './iam-views.component';
MatTableModule,
MatSortModule,
],
exports: [],
exports: [IamViewsComponent],
})
export default class IamViewsModule {}

View File

@ -7,99 +7,12 @@
*ngIf="
breadc[breadc.length - 1] &&
!breadc[breadc.length - 1].hideNav &&
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER &&
breadc[breadc.length - 1].type !== BreadcrumbType.INSTANCE
"
[ngSwitch]="breadc[0].type"
>
<div class="nav-row" @navrow>
<ng-container *ngSwitchCase="BreadcrumbType.INSTANCE">
<div class="nav-row-abs" @navroworg>
<ng-template cnslHasRole [hasRole]="['iam.read']">
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/instance']"
>
<div class="c_label">
<span> {{ 'MENU.INSTANCEOVERVIEW' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/orgs']"
>
<div class="c_label">
<span> {{ 'MENU.ORGS' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/events']"
>
<div class="c_label">
<span> {{ 'MENU.EVENTS' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/views']"
>
<div class="c_label">
<span> {{ 'MENU.VIEWS' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/failed-events']"
>
<div class="c_label">
<span> {{ 'MENU.FAILEDEVENTS' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/settings']"
*ngIf="['iam.read', 'iam.policy.read'] | hasRole: true | async"
>
<div class="c_label">
<span> {{ 'MENU.SETTINGS' | translate }} </span>
</div>
</a>
<a
*ngIf="customerPortalLink$ | async as customerPortalLink"
class="nav-item external-link"
[href]="customerPortalLink"
target="_blank"
rel="noreferrer"
>
<div class="c_label">
<span> {{ 'MENU.CUSTOMERPORTAL' | translate }} </span>
</div>
<i class="las la-external-link-alt"></i>
</a>
</ng-template>
<template [ngTemplateOutlet]="shortcutKeyRef"></template>
</div>
</ng-container>
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
<div class="nav-row-abs" @navrowproject>
<a
@ -183,7 +96,7 @@
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/org-settings']"
*ngIf="['policy.read'] | hasRole | async"
*ngIf="(['policy.read'] | hasRole | async) && ((authService.cachedOrgs | async)?.length ?? 1) > 1"
>
<span class="label">{{ 'MENU.SETTINGS' | translate }}</span>
</a>

View File

@ -90,7 +90,6 @@ export class NavComponent implements OnDestroy {
private destroy$: Subject<void> = new Subject();
public BreadcrumbType: any = BreadcrumbType;
public customerPortalLink$ = this.envService.env.pipe(map((env) => env.customer_portal));
public positions: ConnectedPosition[] = [
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }, 0, 10),
@ -98,7 +97,6 @@ export class NavComponent implements OnDestroy {
];
constructor(
private envService: EnvironmentService,
public authService: GrpcAuthService,
public adminService: AdminService,
public authenticationService: AuthenticationService,

View File

@ -88,7 +88,7 @@ export class OrgTableComponent {
}
return from(
this.authService.listMyProjectOrgs(request.limit, request.offset, request.queries, sortingField, this.sort?.direction),
this.adminService.listOrgs(request.limit, request.offset, request.queries, sortingField, this.sort?.direction),
).pipe(
map((resp) => {
this.timestamp = resp.details?.viewTimestamp;

View File

@ -6,6 +6,12 @@
[settingsList]="settingsList"
queryParam="id"
>
<ng-container *ngIf="currentSetting === 'organizations'">
<h2>{{ 'ORG.PAGES.LIST' | translate }}</h2>
<p class="org-desc cnsl-secondary-text">{{ 'ORG.PAGES.LISTDESCRIPTION' | translate }}</p>
<cnsl-org-table></cnsl-org-table>
</ng-container>
<ng-container *ngIf="currentSetting === 'complexity'">
<cnsl-password-complexity-policy [serviceType]="serviceType"></cnsl-password-complexity-policy>
</ng-container>
@ -57,4 +63,13 @@
<ng-container *ngIf="currentSetting === 'languages' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-language-settings></cnsl-language-settings>
</ng-container>
<ng-container *ngIf="currentSetting === 'views' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-iam-views></cnsl-iam-views>
</ng-container>
<ng-container *ngIf="currentSetting === 'events' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-events></cnsl-events>
</ng-container>
<ng-container *ngIf="currentSetting === 'failedevents' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-iam-failed-events></cnsl-iam-failed-events>
</ng-container>
</cnsl-sidenav>

View File

@ -24,6 +24,11 @@ import { SecretGeneratorModule } from '../policies/secret-generator/secret-gener
import { SecurityPolicyModule } from '../policies/security-policy/security-policy.module';
import { SidenavModule } from '../sidenav/sidenav.module';
import { SettingsListComponent } from './settings-list.component';
import FailedEventsModule from '../failed-events/failed-events.module';
import IamViewsModule from '../iam-views/iam-views.module';
import EventsModule from '../events/events.module';
import OrgListModule from 'src/app/pages/org-list/org-list.module';
import { OrgTableModule } from '../org-table/org-table.module';
@NgModule({
declarations: [SettingsListComponent],
@ -44,6 +49,7 @@ import { SettingsListComponent } from './settings-list.component';
SecurityPolicyModule,
DomainsModule,
LoginTextsPolicyModule,
OrgTableModule,
DomainPolicyModule,
TranslateModule,
HasRolePipeModule,
@ -51,6 +57,9 @@ import { SettingsListComponent } from './settings-list.component';
NotificationSMSProviderModule,
OIDCConfigurationModule,
SecretGeneratorModule,
FailedEventsModule,
IamViewsModule,
EventsModule,
],
exports: [SettingsListComponent],
})

View File

@ -1,6 +1,15 @@
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { SidenavSetting } from '../sidenav/sidenav.component';
export const ORGANIZATIONS: SidenavSetting = {
id: 'organizations',
i18nKey: 'SETTINGS.LIST.ORGS',
groupI18nKey: 'SETTINGS.GROUPS.GENERAL',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.read'],
},
};
export const LANGUAGES: SidenavSetting = {
id: 'languages',
i18nKey: 'SETTINGS.LIST.LANGUAGES',
@ -33,6 +42,33 @@ export const SECURITY: SidenavSetting = {
},
};
export const VIEWS: SidenavSetting = {
id: 'views',
i18nKey: 'SETTINGS.LIST.VIEWS',
groupI18nKey: 'SETTINGS.GROUPS.STORAGE',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.read'],
},
};
export const FAILEDEVENTS: SidenavSetting = {
id: 'failedevents',
i18nKey: 'SETTINGS.LIST.FAILEDEVENTS',
groupI18nKey: 'SETTINGS.GROUPS.STORAGE',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.read'],
},
};
export const EVENTS: SidenavSetting = {
id: 'events',
i18nKey: 'SETTINGS.LIST.EVENTS',
groupI18nKey: 'SETTINGS.GROUPS.STORAGE',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['events.read'],
},
};
export const LOGIN: SidenavSetting = {
id: 'login',
i18nKey: 'SETTINGS.LIST.LOGIN',

View File

@ -109,7 +109,7 @@
<ng-container matColumnDef="org">
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.ORG' | translate }}</th>
<td mat-cell *matCellDef="let grant">
{{ grant.orgName }}
{{ grant.grantedOrgName }}
</td>
</ng-container>
@ -169,7 +169,7 @@
<i class="las la-trash"></i>
</button>
<button menuActions mat-menu-item [routerLink]="['/users', grant.userId]">
<button menuActions mat-menu-item (click)="showUser(grant)">
{{ 'ACTIONS.TABLE.SHOWUSER' | translate: { value: grant.displayName } }}
</button>
</cnsl-table-actions>

View File

@ -8,7 +8,7 @@ import { tap } from 'rxjs/operators';
import { enterAnimations } from 'src/app/animations';
import { UserGrant as AuthUserGrant } from 'src/app/proto/generated/zitadel/auth_pb';
import { Role } from 'src/app/proto/generated/zitadel/project_pb';
import { Type, UserGrant as MgmtUserGrant, UserGrantQuery } from 'src/app/proto/generated/zitadel/user_pb';
import { Type, UserGrant as MgmtUserGrant, UserGrantQuery, UserGrant } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@ -18,6 +18,7 @@ import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'
import { UserGrantRoleDialogComponent } from '../user-grant-role-dialog/user-grant-role-dialog.component';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { UserGrantContext, UserGrantsDataSource } from './user-grants-datasource';
import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
export enum UserGrantListSearchKey {
DISPLAY_NAME,
@ -68,6 +69,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
@Input() public type: Type | undefined = undefined;
public filterOpen: boolean = false;
public myOrgs: Array<Org.AsObject> = [];
constructor(
private authService: GrpcAuthService,
private userService: ManagementService,
@ -115,6 +117,9 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
}
this.loadGrantsPage(this.type);
this.authService.listMyProjectOrgs(undefined, 0).then((orgs) => {
this.myOrgs = orgs.resultList;
});
}
public ngAfterViewInit(): void {
@ -300,4 +305,21 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
this.loadGrantsPage(this.type);
}
}
public showUser(grant: UserGrant.AsObject) {
const org: Org.AsObject = {
id: grant.grantedOrgId,
name: grant.grantedOrgName,
state: OrgState.ORG_STATE_ACTIVE,
primaryDomain: grant.grantedOrgDomain,
};
// Check if user has permissions for that org before changing active org
if (this.myOrgs.find((org) => org.id === grant.grantedOrgId)) {
this.authService.setActiveOrg(org);
this.router.navigate(['/users', grant.userId]);
} else {
this.toast.showInfo('GRANTS.TOAST.CANTSHOWINFO', true);
}
}
}

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EventsComponent } from './events.component';
const routes: Routes = [
{
path: '',
component: EventsComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class EventsRoutingModule {}

View File

@ -1,131 +0,0 @@
<div class="max-width-container">
<h1 class="events-title">{{ 'IAM.EVENTS.TITLE' | translate }}</h1>
<p class="events-desc cnsl-secondary-text">{{ 'IAM.EVENTS.DESCRIPTION' | translate }}</p>
<cnsl-refresh-table
[hideRefresh]="true"
(refreshed)="refresh()"
[dataSize]="dataSource.data.length"
[loading]="_loading | async"
>
<div actions>
<cnsl-filter-events (requestChanged)="filterChanged($event)"></cnsl-filter-events>
</div>
<table
[dataSource]="dataSource"
mat-table
class="table views-table"
aria-label="Views"
matSort
(matSortChange)="sortChange($event)"
>
<ng-container matColumnDef="editor">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.EDITOR' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<div class="editor-row" *ngIf="event.editor as editor">
<!-- <cnsl-avatar
*ngIf="editor && editor.displayName; else cog"
class="avatar"
[name]="editor.displayName"
[avatarUrl]="editor.avatarUrl || ''"
[forColor]="editor.preferredLoginName ?? editor.displayName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="editor?.preferredLoginName ?? 'franz'" [isMachine]="true">
<i class="las la-robot"></i>
</cnsl-avatar> </ng-template
> -->
<span class="name" *ngIf="editor.displayName">{{ editor.displayName }}</span>
<span class="state" *ngIf="editor.service">{{ editor.service }}</span>
</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="aggregate">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.AGGREGATE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<div class="aggregate-row">
<span class="id">{{ event.aggregate.id }}</span
><span class="state" *ngIf="event.aggregate?.type?.localized?.localizedMessage">{{
event.aggregate.type.localized.localizedMessage
}}</span>
</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="resourceOwner">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.aggregate.resourceOwner">{{ event.aggregate.resourceOwner }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="sequence">
<th mat-header-cell *matHeaderCellDef>
{{ 'IAM.EVENTS.SEQUENCE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
{{ event.sequence }}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header [start]="'desc'" [disableClear]="true">
{{ 'IAM.EVENTS.CREATIONDATE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<span>{{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm:ss' }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.TYPE' | translate }}</th>
<td mat-cell *matCellDef="let event" data-e2e="event-type-cell">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.type?.localized?.localizedMessage">{{ event.type.localized.localizedMessage }}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="payload">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.PAYLOAD' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | topayload as payload">
<span>{{ payload | json }}</span>
<div class="btn-wrapper">
<button class="open-in-dialog-btn" mat-icon-button (click)="openDialog(event)">
<mat-icon svgIcon="mdi_arrow_expand"></mat-icon>
</button>
</div>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<cnsl-paginator
#paginator
class="paginator"
[hidePagination]="true"
[showMoreButton]="true"
[disableShowMore]="_done | async"
(moreRequested)="more()"
[length]="dataSource.data.length"
>
</cnsl-paginator>
</cnsl-refresh-table>
</div>

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FailedEventsComponent } from './failed-events.component';
const routes: Routes = [
{
path: '',
component: FailedEventsComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FailedEventsRoutingModule {}

View File

@ -1,70 +0,0 @@
<div class="max-width-container">
<h1 class="failed-events-title">{{ 'IAM.FAILEDEVENTS.TITLE' | translate }}</h1>
<p class="failed-events-desc cnsl-secondary-text">{{ 'IAM.FAILEDEVENTS.DESCRIPTION' | translate }}</p>
<div class="table-wrapper">
<cnsl-refresh-table (refreshed)="loadEvents()" [dataSize]="eventDataSource.data.length" [loading]="loading$ | async">
<table [dataSource]="eventDataSource" mat-table class="table" aria-label="Elements">
<ng-container matColumnDef="viewName">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.VIEWNAME' | translate }}</th>
<td mat-cell *matCellDef="let event">{{ event.viewName }}</td>
</ng-container>
<ng-container matColumnDef="database">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.DATABASE' | translate }}</th>
<td mat-cell *matCellDef="let event">{{ event.database }}</td>
</ng-container>
<ng-container matColumnDef="failedSequence">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.FAILEDSEQUENCE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.failedSequence }}</span>
</td>
</ng-container>
<ng-container matColumnDef="failureCount">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.FAILURECOUNT' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.failureCount }}</span>
</td>
</ng-container>
<ng-container matColumnDef="lastFailed">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.LASTFAILED' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span>{{ event?.lastFailed | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="errorMessage">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.ERRORMESSAGE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<span class="failed-event-error-message">{{ event?.errorMessage }}</span>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td class="back" mat-cell *matCellDef="let event">
<cnsl-table-actions>
<button
actions
color="warn"
mat-icon-button
matTooltip="{{ 'IAM.FAILEDEVENTS.DELETE' | translate }}"
(click)="cancelEvent(event.viewName, event.database, event.failedSequence)"
>
<i class="las la-minus-circle"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="eventDisplayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: eventDisplayedColumns"></tr>
</table>
<cnsl-paginator #paginator class="paginator" [hidePagination]="true" [length]="eventDataSource.data.length || 0">
</cnsl-paginator>
</cnsl-refresh-table>
</div>
</div>

View File

@ -56,7 +56,7 @@
<span>{{ 'HOME.GETSTARTED.TITLE' | translate }}</span>
</a>
<a href="https://zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item">
<a href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank" rel="noreferrer" class="grid-item">
<div
class="icon-wrapper"
[ngStyle]="{

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { IamViewsComponent } from './iam-views.component';
const routes: Routes = [
{
path: '',
component: IamViewsComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IamViewsRoutingModule {}

View File

@ -1,42 +0,0 @@
<div class="max-width-container">
<h1 class="views-title">{{ 'IAM.VIEWS.TITLE' | translate }}</h1>
<p class="views-desc cnsl-secondary-text">{{ 'IAM.VIEWS.DESCRIPTION' | translate }}</p>
<cnsl-refresh-table (refreshed)="loadViews()" [dataSize]="dataSource.data.length" [loading]="loading$ | async">
<table [dataSource]="dataSource" mat-table class="table views-table" aria-label="Views" matSort>
<ng-container matColumnDef="viewName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'IAM.VIEWS.VIEWNAME' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.viewName }}</td>
</ng-container>
<ng-container matColumnDef="database">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'IAM.VIEWS.DATABASE' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.database }}</td>
</ng-container>
<ng-container matColumnDef="sequence">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.SEQUENCE' | translate }}</th>
<td mat-cell *matCellDef="let view">{{ view.processedSequence }}</td>
</ng-container>
<ng-container matColumnDef="eventTimestamp">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.EVENTTIMESTAMP' | translate }}</th>
<td mat-cell *matCellDef="let view">
<span>{{ view?.eventTimestamp | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="lastSuccessfulSpoolerRun">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.VIEWS.LASTSPOOL' | translate }}</th>
<td mat-cell *matCellDef="let view">
<span>{{ view?.lastSuccessfulSpoolerRun | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<cnsl-paginator #paginator class="paginator" [hidePagination]="true" [length]="dataSource.data.length || 0">
</cnsl-paginator>
</cnsl-refresh-table>
</div>

View File

@ -1,7 +0,0 @@
.views-title {
margin: 2rem 0 0 0;
}
.views-desc {
font-size: 14px;
}

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { InstanceSettingsComponent } from './instance-settings.component';
const routes: Routes = [
{
path: '',
component: InstanceSettingsComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class InstanceSettingsRoutingModule {}

View File

@ -1,18 +0,0 @@
<div class="max-width-container">
<div class="settings-top-view">
<div>
<div class="settings-title-row">
<h1>{{ 'SETTINGS.INSTANCE.TITLE' | translate }}</h1>
</div>
<p class="desc cnsl-secondary-text">{{ 'SETTINGS.INSTANCE.DESCRIPTION' | translate }}</p>
</div>
<span class="fill-space"></span>
</div>
<ng-container *ngIf="settingsList | async as list">
<cnsl-settings-list
[selectedId]="id"
[serviceType]="PolicyComponentServiceType.ADMIN"
[settingsList]="list"
></cnsl-settings-list>
</ng-container>
</div>

View File

@ -1,34 +0,0 @@
.settings-top-view {
display: flex;
align-items: center;
padding-top: 2rem;
.settings-title-row {
display: flex;
align-items: center;
h1 {
margin: 0;
}
a i {
font-size: 1.2rem;
height: 1.2rem;
line-height: 1.2rem;
}
}
.fill-space {
flex: 1;
}
.actions {
display: flex;
align-items: center;
}
}
.desc {
margin-bottom: 2rem;
font-size: 14px;
}

View File

@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InstanceSettingsComponent } from './instance-settings.component';
describe('InstanceSettingsComponent', () => {
let component: InstanceSettingsComponent;
let fixture: ComponentFixture<InstanceSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InstanceSettingsComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InstanceSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,97 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Observable, of, Subject, takeUntil } from 'rxjs';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import {
BRANDING,
COMPLEXITY,
DOMAIN,
LANGUAGES,
IDP,
LOCKOUT,
LOGIN,
LOGINTEXTS,
MESSAGETEXTS,
NOTIFICATIONS,
OIDC,
PRIVACYPOLICY,
SECRETS,
SECURITY,
SMS_PROVIDER,
SMTP_PROVIDER,
} from '../../modules/settings-list/settings';
@Component({
selector: 'cnsl-instance-settings',
templateUrl: './instance-settings.component.html',
styleUrls: ['./instance-settings.component.scss'],
})
export class InstanceSettingsComponent implements OnInit, OnDestroy {
public id: string = '';
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public defaultSettingsList: SidenavSetting[] = [
// notifications
// { showWarn: true, ...NOTIFICATIONS },
NOTIFICATIONS,
SMTP_PROVIDER,
SMS_PROVIDER,
// login
LOGIN,
IDP,
COMPLEXITY,
LOCKOUT,
DOMAIN,
// appearance
BRANDING,
MESSAGETEXTS,
LOGINTEXTS,
// others
PRIVACYPOLICY,
LANGUAGES,
OIDC,
SECRETS,
SECURITY,
];
public settingsList: Observable<SidenavSetting[]> = of([]);
private destroy$: Subject<void> = new Subject();
constructor(
breadcrumbService: BreadcrumbService,
activatedRoute: ActivatedRoute,
public authService: GrpcAuthService,
) {
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.INSTANCE,
name: 'Instance',
routerLink: ['/instance'],
}),
];
breadcrumbService.setBreadcrumb(breadcrumbs);
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.id = id;
}
});
}
ngOnInit(): void {
this.settingsList = this.authService.isAllowedMapper(
this.defaultSettingsList,
(setting) => setting.requiredRoles.admin || [],
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,14 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { SettingsListModule } from 'src/app/modules/settings-list/settings-list.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InstanceSettingsRoutingModule } from './instance-settings-routing.module';
import { InstanceSettingsComponent } from './instance-settings.component';
@NgModule({
declarations: [InstanceSettingsComponent],
imports: [CommonModule, InstanceSettingsRoutingModule, SettingsListModule, HasRolePipeModule, TranslateModule],
})
export default class InstanceSettingsModule {}

View File

@ -7,29 +7,45 @@
[hasContributors]="true"
stateTooltip="{{ 'INSTANCE.STATE.' + instance?.state | translate }}"
>
<cnsl-contributors
topContributors
[totalResult]="totalMemberResult"
[loading]="loading$ | async"
[membersSubject]="membersSubject"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}"
(addClicked)="openAddMember()"
(showDetailClicked)="showDetail()"
(refreshClicked)="loadMembers()"
[disabled]="(['iam.member.write'] | hasRole | async) === false"
>
</cnsl-contributors>
<div topContributors class="instance-action-wrapper">
<a
mat-raised-button
color="primary"
*ngIf="customerPortalLink$ | async as customerPortalLink"
class="portal-link external-link"
[href]="customerPortalLink"
target="_blank"
rel="noreferrer"
>
<div class="cnsl-action-button">
<span class="portal-span">{{ 'MENU.CUSTOMERPORTAL' | translate }}</span>
<i class="las la-external-link-alt"></i>
</div>
</a>
<cnsl-contributors
[totalResult]="totalMemberResult"
[loading]="loading$ | async"
[membersSubject]="membersSubject"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}"
(addClicked)="openAddMember()"
(showDetailClicked)="showDetail()"
(refreshClicked)="loadMembers()"
[disabled]="(['iam.member.write'] | hasRole | async) === false"
>
</cnsl-contributors>
</div>
<cnsl-info-row topContent *ngIf="instance" [instance]="instance"></cnsl-info-row>
</cnsl-top-view>
<div class="max-width-container">
<h2 class="instance-table-title">{{ 'ORG.LIST.TITLE' | translate }}</h2>
<p class="instance-table-desc cnsl-secondary-text">{{ 'ORG.LIST.DESCRIPTION' | translate }}</p>
<cnsl-org-table></cnsl-org-table>
<cnsl-settings-grid [type]="PolicyComponentServiceType.ADMIN"></cnsl-settings-grid>
<div class="instance-settings-wrapper">
<ng-container *ngIf="settingsList | async as list">
<cnsl-settings-list
[selectedId]="id"
[serviceType]="PolicyComponentServiceType.ADMIN"
[settingsList]="list"
></cnsl-settings-list>
</ng-container>
</div>
</div>

View File

@ -11,6 +11,23 @@
margin-top: 2rem;
}
.instance-action-wrapper {
display: flex;
align-items: center;
.portal-link {
margin-right: 1rem;
.portal-span {
margin-right: 0.5rem;
}
}
}
.instance-table-desc {
font-size: 14px;
}
.instance-settings-wrapper {
margin-top: 2rem;
}

View File

@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
import { catchError, finalize, map, takeUntil } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { InstanceDetail, State } from 'src/app/proto/generated/zitadel/instance_pb';
@ -11,7 +11,31 @@ import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ToastService } from 'src/app/services/toast.service';
import {
BRANDING,
COMPLEXITY,
DOMAIN,
LANGUAGES,
IDP,
LOCKOUT,
LOGIN,
LOGINTEXTS,
MESSAGETEXTS,
NOTIFICATIONS,
OIDC,
PRIVACYPOLICY,
SECRETS,
SECURITY,
SMS_PROVIDER,
SMTP_PROVIDER,
VIEWS,
FAILEDEVENTS,
EVENTS,
ORGANIZATIONS,
} from '../../modules/settings-list/settings';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { EnvironmentService } from 'src/app/services/environment.service';
@Component({
selector: 'cnsl-instance',
templateUrl: './instance.component.html',
@ -25,12 +49,51 @@ export class InstanceComponent {
public totalMemberResult: number = 0;
public membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
public State: any = State;
public id: string = '';
public defaultSettingsList: SidenavSetting[] = [
ORGANIZATIONS,
// notifications
// { showWarn: true, ...NOTIFICATIONS },
NOTIFICATIONS,
SMTP_PROVIDER,
SMS_PROVIDER,
// login
LOGIN,
IDP,
COMPLEXITY,
LOCKOUT,
DOMAIN,
// appearance
BRANDING,
MESSAGETEXTS,
LOGINTEXTS,
// storage
VIEWS,
EVENTS,
FAILEDEVENTS,
// others
PRIVACYPOLICY,
LANGUAGES,
OIDC,
SECRETS,
SECURITY,
];
public settingsList: Observable<SidenavSetting[]> = of([]);
public customerPortalLink$ = this.envService.env.pipe(map((env) => env.customer_portal));
private destroy$: Subject<void> = new Subject();
constructor(
public adminService: AdminService,
private dialog: MatDialog,
private toast: ToastService,
breadcrumbService: BreadcrumbService,
private router: Router,
private authService: GrpcAuthService,
private envService: EnvironmentService,
activatedRoute: ActivatedRoute,
) {
this.loadMembers();
@ -52,6 +115,13 @@ export class InstanceComponent {
.catch((error) => {
this.toast.showError(error);
});
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.id = id;
}
});
}
public loadMembers(): void {
@ -113,4 +183,16 @@ export class InstanceComponent {
public showDetail(): void {
this.router.navigate(['/instance', 'members']);
}
ngOnInit(): void {
this.settingsList = this.authService.isAllowedMapper(
this.defaultSettingsList,
(setting) => setting.requiredRoles.admin || [],
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -29,6 +29,7 @@ import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/
import { IamRoutingModule } from './instance-routing.module';
import { InstanceComponent } from './instance.component';
import { SettingsListModule } from 'src/app/modules/settings-list/settings-list.module';
@NgModule({
declarations: [InstanceComponent],
@ -61,6 +62,7 @@ import { InstanceComponent } from './instance.component';
TimestampToDatePipeModule,
RefreshTableModule,
HasRolePipeModule,
SettingsListModule,
MatSortModule,
SettingsGridModule,
],

View File

@ -21,6 +21,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { LanguagesService } from '../../services/languages.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Component({
selector: 'cnsl-org-create',
@ -59,6 +60,7 @@ export class OrgCreateComponent {
private _location: Location,
private fb: UntypedFormBuilder,
private mgmtService: ManagementService,
private authService: GrpcAuthService,
public langSvc: LanguagesService,
breadcrumbService: BreadcrumbService,
) {
@ -101,6 +103,7 @@ export class OrgCreateComponent {
this.adminService
.SetUpOrg(createOrgRequest, humanRequest)
.then(() => {
this.authService.revalidateOrgs();
this.router.navigate(['/orgs']);
})
.catch((error) => {
@ -191,6 +194,7 @@ export class OrgCreateComponent {
this.mgmtService
.addOrg(this.name.value)
.then(() => {
this.authService.revalidateOrgs();
this.router.navigate(['/orgs']);
})
.catch((error) => {

View File

@ -9,5 +9,6 @@ import { OrgListComponent } from './org-list.component';
@NgModule({
declarations: [OrgListComponent],
imports: [CommonModule, OrgListRoutingModule, OrgTableModule, TranslateModule],
exports: [OrgListComponent],
})
export default class OrgListModule {}

View File

@ -159,6 +159,8 @@ import {
ListLoginPolicySecondFactorsResponse,
ListMilestonesRequest,
ListMilestonesResponse,
ListOrgsRequest,
ListOrgsResponse,
ListProvidersRequest,
ListProvidersResponse,
ListSecretGeneratorsRequest,
@ -314,6 +316,8 @@ import {
MilestoneQuery,
MilestoneType,
} from '../proto/generated/zitadel/milestone/v1/milestone_pb';
import { OrgFieldName, OrgQuery } from '../proto/generated/zitadel/org_pb';
import { SortDirection } from '@angular/material/sort';
export interface OnboardingActions {
order: number;
@ -1303,4 +1307,33 @@ export class AdminService {
public listMilestones(req: ListMilestonesRequest): Promise<ListMilestonesResponse.AsObject> {
return this.grpcService.admin.listMilestones(req, null).then((resp) => resp.toObject());
}
public listOrgs(
limit: number,
offset: number,
queriesList?: OrgQuery[],
sortingColumn?: OrgFieldName,
sortingDirection?: SortDirection,
): Promise<ListOrgsResponse.AsObject> {
const req = new ListOrgsRequest();
const query = new ListQuery();
if (limit) {
query.setLimit(limit);
}
if (offset) {
query.setOffset(offset);
}
if (sortingDirection) {
query.setAsc(sortingDirection === 'asc');
}
req.setQuery(query);
if (sortingColumn) {
req.setSortingColumn(sortingColumn);
}
if (queriesList) {
req.setQueriesList(queriesList);
}
return this.grpcService.admin.listOrgs(req, null).then((resp) => resp.toObject());
}
}

View File

@ -140,14 +140,13 @@ export class GrpcAuthService {
public zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private cachedOrgs: Org.AsObject[] = [];
public cachedOrgs: BehaviorSubject<Org.AsObject[]> = new BehaviorSubject<Org.AsObject[]>([]);
private cachedLabelPolicies: { [orgId: string]: LabelPolicy.AsObject } = {};
constructor(
private readonly grpcService: GrpcService,
private oauthService: OAuthService,
private storage: StorageService,
themeService: ThemeService,
) {
this.zitadelPermissions$.subscribe(this.zitadelPermissions);
@ -221,15 +220,14 @@ export class GrpcAuthService {
public async getActiveOrg(id?: string): Promise<Org.AsObject> {
if (id) {
const find = this.cachedOrgs.find((tmp) => tmp.id === id);
const find = this.cachedOrgs.getValue().find((tmp) => tmp.id === id);
if (find) {
this.setActiveOrg(find);
return Promise.resolve(find);
} else {
const orgs = (await this.listMyProjectOrgs(10, 0)).resultList;
this.cachedOrgs = orgs;
const toFind = this.cachedOrgs.find((tmp) => tmp.id === id);
this.cachedOrgs.next(orgs);
const toFind = orgs.find((tmp) => tmp.id === id);
if (toFind) {
this.setActiveOrg(toFind);
return Promise.resolve(toFind);
@ -238,10 +236,10 @@ export class GrpcAuthService {
}
}
} else {
let orgs = this.cachedOrgs;
let orgs = this.cachedOrgs.getValue();
if (orgs.length === 0) {
orgs = (await this.listMyProjectOrgs()).resultList;
this.cachedOrgs = orgs;
this.cachedOrgs.next(orgs);
}
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization, StorageLocation.local);
@ -373,6 +371,11 @@ export class GrpcAuthService {
return this.grpcService.auth.listMyAuthFactors(new ListMyAuthFactorsRequest(), null).then((resp) => resp.toObject());
}
public async revalidateOrgs() {
const orgs = (await this.listMyProjectOrgs()).resultList;
this.cachedOrgs.next(orgs);
}
public listMyProjectOrgs(
limit?: number,
offset?: number,

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Инстанция",
"INSTANCE": "настройките по подразбиране",
"DASHBOARD": "Табло",
"PERSONAL_INFO": "Лична информация",
"DOCUMENTATION": "Документация",
@ -221,6 +221,7 @@
"EMAIL": "електронна поща",
"USERNAME": "Потребителско име",
"ORGNAME": "Наименование на организацията",
"PRIMARYDOMAIN": "Основен домейн",
"PROJECTNAME": "Име на проекта",
"RESOURCEOWNER": "Собственик на ресурс",
"METHODS": {
@ -1016,6 +1017,7 @@
"DESCRIPTION": "Тези настройки разширяват и презаписват настройките на вашия екземпляр."
},
"LIST": {
"ORGS": "Организации",
"LANGUAGES": "Езици",
"LOGIN": "Поведение при влизане и сигурност",
"LOCKOUT": "Блокиране",
@ -1033,15 +1035,20 @@
"PRIVACYPOLICY": "Политика за бедност",
"OIDC": "Живот и изтичане на OIDC Token",
"SECRETS": "Тайна поява",
"SECURITY": "Настройки на сигурността"
"SECURITY": "Настройки на сигурността",
"EVENTS": "Събития",
"FAILEDEVENTS": "Неуспешни събития",
"VIEWS": "Изгледи"
},
"GROUPS": {
"GENERAL": "Главна информация",
"NOTIFICATIONS": "Известия",
"LOGIN": "Вход и достъп",
"DOMAIN": "Домейн",
"TEXTS": "Текстове и езици",
"APPEARANCE": "Външен вид",
"OTHER": "други"
"OTHER": "други",
"STORAGE": "Съхранение"
}
},
"SETTING": {
@ -2227,7 +2234,8 @@
"TOAST": {
"UPDATED": "Упълномощаването е актуализирано.",
"REMOVED": "Упълномощаването е премахнато",
"BULKREMOVED": "Разрешенията са премахнати."
"BULKREMOVED": "Разрешенията са премахнати.",
"CANTSHOWINFO": "Не можете да посетите профила на този потребител, тъй като не сте член на организацията, към която принадлежи този потребител"
},
"DIALOG": {
"DELETE_TITLE": "Изтриване на разрешението",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instance",
"INSTANCE": "Výchozí nastavení",
"DASHBOARD": "Domů",
"PERSONAL_INFO": "Osobní informace",
"DOCUMENTATION": "Dokumentace",
@ -228,6 +228,7 @@
"EMAIL": "Email",
"USERNAME": "Uživatelské jméno",
"ORGNAME": "Název organizace",
"PRIMARYDOMAIN": "Primární doména",
"PROJECTNAME": "Název projektu",
"RESOURCEOWNER": "Vlastník zdroje",
"METHODS": {
@ -1023,6 +1024,7 @@
"DESCRIPTION": "Tato nastavení rozšiřují a přepisují nastavení vaší instance."
},
"LIST": {
"ORGS": "Organizace",
"LANGUAGES": "Jazyky",
"LOGIN": "Chování při přihlášení a bezpečnost",
"LOCKOUT": "Blokování",
@ -1040,15 +1042,20 @@
"PRIVACYPOLICY": "Zásady ochrany osobních údajů",
"OIDC": "Životnost a expirace OIDC tokenu",
"SECRETS": "Generátor tajemství",
"SECURITY": "Bezpečnostní nastavení"
"SECURITY": "Bezpečnostní nastavení",
"EVENTS": "Události",
"FAILEDEVENTS": "Selhané události",
"VIEWS": "Pohledy"
},
"GROUPS": {
"GENERAL": "Obecné informace",
"NOTIFICATIONS": "Oznámení",
"LOGIN": "Přihlášení a přístup",
"DOMAIN": "Doména",
"TEXTS": "Texty a jazyky",
"APPEARANCE": "Vzhled",
"OTHER": "Ostatní"
"OTHER": "Ostatní",
"STORAGE": "Data"
}
},
"SETTING": {
@ -2246,7 +2253,8 @@
"TOAST": {
"UPDATED": "Autorizace aktualizována.",
"REMOVED": "Autorizace odebrána",
"BULKREMOVED": "Autorizace odebrány."
"BULKREMOVED": "Autorizace odebrány.",
"CANTSHOWINFO": "Nemůžete navštívit profil tohoto uživatele, protože nejste členem organizace, do které tento uživatel patří"
},
"DIALOG": {
"DELETE_TITLE": "Smazat autorizaci",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instanz",
"INSTANCE": "Standardeinstellungen",
"DASHBOARD": "Startseite",
"PERSONAL_INFO": "Persönliche Informationen",
"DOCUMENTATION": "Dokumentation",
@ -227,6 +227,7 @@
"EMAIL": "Email",
"USERNAME": "Nutzername",
"ORGNAME": "Organisationsname",
"PRIMARYDOMAIN": "Primäre Domäne",
"PROJECTNAME": "Projektname",
"RESOURCEOWNER": "Ressourcenbesitzer",
"METHODS": {
@ -1022,6 +1023,7 @@
"DESCRIPTION": "Diese Einstellungen erweitern bzw. überschreiben die Einstellungen Ihrer Instanz."
},
"LIST": {
"ORGS": "Organisationen",
"LANGUAGES": "Sprachen",
"LOGIN": "Loginverhalten und Sicherheit",
"LOCKOUT": "Sperrmechanismen",
@ -1039,15 +1041,20 @@
"PRIVACYPOLICY": "Datenschutzrichtlinie",
"OIDC": "OIDC Token Lifetime und Expiration",
"SECRETS": "Secret Generator",
"SECURITY": "Sicherheitseinstellungen"
"SECURITY": "Sicherheitseinstellungen",
"EVENTS": "Events",
"FAILEDEVENTS": "Fehlerhafte Events",
"VIEWS": "Views"
},
"GROUPS": {
"GENERAL": "Allgemein",
"NOTIFICATIONS": "Benachrichtigungen",
"LOGIN": "Login und Zugriff",
"DOMAIN": "Domain",
"TEXTS": "Texte und Sprachen",
"APPEARANCE": "Erscheinungsbild",
"OTHER": "Anderes"
"OTHER": "Anderes",
"STORAGE": "Speicher"
}
},
"SETTING": {
@ -2236,7 +2243,8 @@
"TOAST": {
"UPDATED": "Berechtigung geändert.",
"REMOVED": "Berechtigung entfernt.",
"BULKREMOVED": "Berechtigungen entfernt."
"BULKREMOVED": "Berechtigungen entfernt.",
"CANTSHOWINFO": "Sie können das Profil dieses Benutzers nicht besuchen, da Sie kein Mitglied der Organisation sind, zu der dieser Benutzer gehört"
},
"DIALOG": {
"DELETE_TITLE": "Authorisierung löschen",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instance",
"INSTANCE": "Default settings",
"DASHBOARD": "Home",
"PERSONAL_INFO": "Personal Information",
"DOCUMENTATION": "Documentation",
@ -228,6 +228,7 @@
"EMAIL": "Email",
"USERNAME": "User Name",
"ORGNAME": "Organization Name",
"PRIMARYDOMAIN": "Primary Domain",
"PROJECTNAME": "Project Name",
"RESOURCEOWNER": "Resource Owner",
"METHODS": {
@ -1023,6 +1024,7 @@
"DESCRIPTION": "These settings extend and overwrite your instance settings."
},
"LIST": {
"ORGS": "Organizations",
"LANGUAGES": "Languages",
"LOGIN": "Login Behavior and Security",
"LOCKOUT": "Lockout",
@ -1040,15 +1042,20 @@
"PRIVACYPOLICY": "Privacy Policy",
"OIDC": "OIDC Token lifetime and expiration",
"SECRETS": "Secret Generator",
"SECURITY": "Security settings"
"SECURITY": "Security settings",
"EVENTS": "Events",
"FAILEDEVENTS": "Failed Events",
"VIEWS": "Views"
},
"GROUPS": {
"GENERAL": "General Information",
"NOTIFICATIONS": "Notifications",
"LOGIN": "Login and Access",
"DOMAIN": "Domain",
"TEXTS": "Texts and Languages",
"APPEARANCE": "Appearance",
"OTHER": "Other"
"OTHER": "Other",
"STORAGE": "Storage"
}
},
"SETTING": {
@ -2255,7 +2262,8 @@
"TOAST": {
"UPDATED": "Authorization updated.",
"REMOVED": "Authorization removed",
"BULKREMOVED": "Authorizations removed."
"BULKREMOVED": "Authorizations removed.",
"CANTSHOWINFO": "You cannot visit this user's profile as you are not a member of the organization where this user belongs to"
},
"DIALOG": {
"DELETE_TITLE": "Delete authorization",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instancia",
"INSTANCE": "configuración por defecto",
"DASHBOARD": "Inicio",
"PERSONAL_INFO": "Información personal",
"DOCUMENTATION": "Documentación",
@ -228,6 +228,7 @@
"EMAIL": "Email",
"USERNAME": "Nombre de usuario",
"ORGNAME": "Nombre de organización",
"PRIMARYDOMAIN": "Dominio primario",
"PROJECTNAME": "Nombre de proyecto",
"RESOURCEOWNER": "Propietario del recurso",
"METHODS": {
@ -1024,6 +1025,7 @@
"DESCRIPTION": "Estas configuraciones amplían y sobrescriben tus configuraciones de instancia."
},
"LIST": {
"ORGS": "Organizaciones",
"LANGUAGES": "Idiomas",
"LOGIN": "Comportamiento del inicio de sesión y de la seguridad",
"LOCKOUT": "Bloqueo",
@ -1041,15 +1043,20 @@
"PRIVACYPOLICY": "Política de privacidad",
"OIDC": "OIDC Token lifetime and expiration",
"SECRETS": "Apariencia del secreto",
"SECURITY": "Ajustes de seguridad"
"SECURITY": "Ajustes de seguridad",
"EVENTS": "Eventos",
"FAILEDEVENTS": "Eventos fallidos",
"VIEWS": "Vistas"
},
"GROUPS": {
"GENERAL": "General",
"NOTIFICATIONS": "Notificaciones",
"LOGIN": "Inicio de sesión y acceso",
"DOMAIN": "Dominio",
"TEXTS": "Textos e idiomas",
"APPEARANCE": "Apariencia",
"OTHER": "Otros"
"OTHER": "Otros",
"STORAGE": "Datos"
}
},
"SETTING": {
@ -2234,7 +2241,8 @@
"TOAST": {
"UPDATED": "Autorización actualizada.",
"REMOVED": "Autorización eliminada",
"BULKREMOVED": "Autorizaciones eliminadas."
"BULKREMOVED": "Autorizaciones eliminadas.",
"CANTSHOWINFO": "No puedes visitar el perfil de este usuario porque no eres miembro de la organización a la que pertenece este usuario."
},
"DIALOG": {
"DELETE_TITLE": "Borrar autorización",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instance",
"INSTANCE": "paramètres par défaut",
"DASHBOARD": "Accueil",
"PERSONAL_INFO": "Informations personnelles",
"DOCUMENTATION": "Documentation",
@ -227,6 +227,7 @@
"EMAIL": "Courriel",
"USERNAME": "Nom de l'utilisateur",
"ORGNAME": "Nom de l'organisation",
"PRIMARYDOMAIN": "Domaine principal",
"PROJECTNAME": "Nom du projet",
"RESOURCEOWNER": "Propriétaire des ressources",
"METHODS": {
@ -1022,6 +1023,7 @@
"DESCRIPTION": "Ces paramètres étendent et remplacent les paramètres de votre instance."
},
"LIST": {
"ORGS": "Organisations",
"LANGUAGES": "Langues",
"LOGIN": "Comportement de connexion et sécurité",
"LOCKOUT": "Verrouillage",
@ -1039,15 +1041,20 @@
"PRIVACYPOLICY": "Politique de confidentialité",
"OIDC": "Durée de vie et expiration des jetons OIDC",
"SECRETS": "Apparence secrète",
"SECURITY": "Paramètres de sécurité"
"SECURITY": "Paramètres de sécurité",
"EVENTS": "Événements",
"FAILEDEVENTS": "Événements échoués",
"VIEWS": "Vues"
},
"GROUPS": {
"GENERAL": "Général",
"NOTIFICATIONS": "Notifications",
"LOGIN": "Connexion et accès",
"DOMAIN": "Domaine",
"TEXTS": "Textes et langues",
"APPEARANCE": "Apparence",
"OTHER": "Autres"
"OTHER": "Autres",
"STORAGE": "Stockage"
}
},
"SETTING": {
@ -2237,7 +2244,8 @@
"TOAST": {
"UPDATED": "Autorisation mise à jour.",
"REMOVED": "Autorisation supprimée",
"BULKREMOVED": "Autorisations supprimées."
"BULKREMOVED": "Autorisations supprimées.",
"CANTSHOWINFO": "Vous ne pouvez pas visiter le profil de cet utilisateur car vous n'êtes pas membre de l'organisation à laquelle appartient cet utilisateur."
},
"DIALOG": {
"DELETE_TITLE": "Supprimer une autorisation",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Istanza",
"INSTANCE": "Impostazioni default",
"DASHBOARD": "Pagina iniziale",
"PERSONAL_INFO": "Informazioni personali",
"DOCUMENTATION": "Documentazione",
@ -226,6 +226,7 @@
"EMAIL": "Email",
"USERNAME": "User Name",
"ORGNAME": "Nome organizzazione",
"PRIMARYDOMAIN": "Dominio primario",
"PROJECTNAME": "Nome del progetto",
"RESOURCEOWNER": "Resource Owner",
"METHODS": {
@ -1022,6 +1023,7 @@
"DESCRIPTION": "Queste impostazioni si applicheranno alla organizzazione corrente."
},
"LIST": {
"ORGS": "Organizzazioni",
"LANGUAGES": "Lingue",
"LOGIN": "Comportamento login e sicurezza",
"LOCKOUT": "Meccanismi di bloccaggio",
@ -1039,15 +1041,20 @@
"PRIVACYPOLICY": "Informativa sulla privacy e TOS",
"OIDC": "OIDC Token lifetime e scadenza",
"SECRETS": "Aspetto dei segreti",
"SECURITY": "Impostazioni di sicurezza"
"SECURITY": "Impostazioni di sicurezza",
"EVENTS": "Eventi",
"FAILEDEVENTS": "Eventi falliti",
"VIEWS": "Views"
},
"GROUPS": {
"GENERAL": "Generale",
"NOTIFICATIONS": "Notifiche",
"LOGIN": "Accesso e login",
"DOMAIN": "Dominio",
"TEXTS": "Testi e lingue",
"APPEARANCE": "Aspetto",
"OTHER": "Altro"
"OTHER": "Altro",
"STORAGE": "Dati"
}
},
"SETTING": {
@ -2237,7 +2244,8 @@
"TOAST": {
"UPDATED": "Autorizzazione aggiornata.",
"REMOVED": "Autorizzazione rimossa con successo.",
"BULKREMOVED": "Autorizzazioni rimossi con successo."
"BULKREMOVED": "Autorizzazioni rimossi con successo.",
"CANTSHOWINFO": "Non puoi visitare il profilo di questo utente poiché non sei un membro dell'organizzazione a cui appartiene questo utente"
},
"DIALOG": {
"DELETE_TITLE": "Elima autorizzazione",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "インスタンス",
"INSTANCE": "デフォルトの設定",
"DASHBOARD": "ホーム",
"PERSONAL_INFO": "個人情報",
"DOCUMENTATION": "ドキュメント",
@ -228,6 +228,7 @@
"EMAIL": "Eメール",
"USERNAME": "ユーザー名",
"ORGNAME": "組織名",
"PRIMARYDOMAIN": "プライマリドメイン",
"PROJECTNAME": "プロジェクト名",
"RESOURCEOWNER": "リソース所有者",
"METHODS": {
@ -1023,6 +1024,7 @@
"DESCRIPTION": "これらの設定は、インスタンス設定を拡張・上書きします。"
},
"LIST": {
"ORGS": "組織",
"LANGUAGES": "一般設定",
"LOGIN": "ログイン動作とセキュリティ",
"LOCKOUT": "ロックアウト",
@ -1040,15 +1042,20 @@
"PRIVACYPOLICY": "プライバシーポリシー",
"OIDC": "OIDCトークンのライフタイムと有効期限",
"SECRETS": "シークレット設定",
"SECURITY": "セキュリティ設定"
"SECURITY": "セキュリティ設定",
"EVENTS": "イベント",
"FAILEDEVENTS": "失敗したイベント",
"VIEWS": "ビュー"
},
"GROUPS": {
"GENERAL": "一般",
"NOTIFICATIONS": "通知",
"LOGIN": "ログインとアクセス",
"DOMAIN": "ドメイン",
"TEXTS": "テキストと言語",
"APPEARANCE": "設定",
"OTHER": "その他"
"OTHER": "その他",
"STORAGE": "ストレージ"
}
},
"SETTING": {
@ -2228,7 +2235,8 @@
"TOAST": {
"UPDATED": "認可が更新されました。",
"REMOVED": "認可が削除されました",
"BULKREMOVED": "認可が削除されました。"
"BULKREMOVED": "認可が削除されました。",
"CANTSHOWINFO": "このユーザーが所属する組織のメンバーではないため、このユーザーのプロフィールにアクセスできません"
},
"DIALOG": {
"DELETE_TITLE": "認可の削除",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Инстанца",
"INSTANCE": "стандардни поставки",
"DASHBOARD": "Дома",
"PERSONAL_INFO": "Лични информации",
"DOCUMENTATION": "Документација",
@ -228,6 +228,7 @@
"EMAIL": "Е-пошта",
"USERNAME": "Корисничко име",
"ORGNAME": "Име на организацијата",
"PRIMARYDOMAIN": "Примарен домен",
"PROJECTNAME": "Име на проектот",
"RESOURCEOWNER": "Сопственик на ресурсот",
"METHODS": {
@ -1024,6 +1025,7 @@
"DESCRIPTION": "Овие подесувања ги прошируваат и препишуваат подесувањата на вашата инстанца."
},
"LIST": {
"ORGS": "Организации",
"LANGUAGES": "Општо",
"LOGIN": "Правила и безбедност при најава",
"LOCKOUT": "Забрана на пристап",
@ -1041,15 +1043,20 @@
"PRIVACYPOLICY": "Политика за приватност",
"OIDC": "OIDC времетраење и истекување на токени",
"SECRETS": "Изглед на тајни",
"SECURITY": "Подесувања за безбедност"
"SECURITY": "Подесувања за безбедност",
"EVENTS": "Настани",
"FAILEDEVENTS": "Неуспешни настани",
"VIEWS": "Прегледи"
},
"GROUPS": {
"GENERAL": "Општи информации",
"NOTIFICATIONS": "Известувања",
"LOGIN": "Најава и пристап",
"DOMAIN": "Домен",
"TEXTS": "Текстови и јазици",
"APPEARANCE": "Изглед",
"OTHER": "Друго"
"OTHER": "Друго",
"STORAGE": "складирање"
}
},
"SETTING": {
@ -2234,7 +2241,8 @@
"TOAST": {
"UPDATED": "Овластувањето е ажурирано.",
"REMOVED": "Овластувањето е отстрането",
"BULKREMOVED": "Овластувањата се отстранети."
"BULKREMOVED": "Овластувањата се отстранети.",
"CANTSHOWINFO": "Не можете да го посетите профилот на овој корисник бидејќи не сте член на организацијата каде што припаѓа овој корисник"
},
"DIALOG": {
"DELETE_TITLE": "Избриши овластување",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instantie",
"INSTANCE": "Standaard instellingen",
"DASHBOARD": "Home",
"PERSONAL_INFO": "Persoonlijke informatie",
"DOCUMENTATION": "Documentatie",
@ -228,6 +228,7 @@
"EMAIL": "E-mail",
"USERNAME": "Gebruikersnaam",
"ORGNAME": "Organisatienaam",
"PRIMARYDOMAIN": "Primair domein",
"PROJECTNAME": "Projectnaam",
"RESOURCEOWNER": "Eigenaar van de bron",
"METHODS": {
@ -1023,6 +1024,7 @@
"DESCRIPTION": "Deze instellingen breiden uw instantie instellingen uit en overschrijven deze."
},
"LIST": {
"ORGS": "Organisaties",
"LANGUAGES": "Talen",
"LOGIN": "Login Gedrag en Beveiliging",
"LOCKOUT": "Lockout",
@ -1040,15 +1042,20 @@
"PRIVACYPOLICY": "Privacybeleid",
"OIDC": "OIDC Token levensduur en vervaldatum",
"SECRETS": "Secret Generator",
"SECURITY": "Beveiligingsinstellingen"
"SECURITY": "Beveiligingsinstellingen",
"EVENTS": "Evenementen",
"FAILEDEVENTS": "Mislukte evenementen",
"VIEWS": "Weergaves"
},
"GROUPS": {
"GENERAL": "Algemeen",
"NOTIFICATIONS": "Notificaties",
"LOGIN": "Login en Toegang",
"DOMAIN": "Domein",
"TEXTS": "Teksten en Talen",
"APPEARANCE": "Verschijning",
"OTHER": "Andere"
"OTHER": "Andere",
"STORAGE": "opslag"
}
},
"SETTING": {
@ -2255,7 +2262,8 @@
"TOAST": {
"UPDATED": "Autorisatie bijgewerkt.",
"REMOVED": "Autorisatie verwijderd",
"BULKREMOVED": "Autorisaties verwijderd."
"BULKREMOVED": "Autorisaties verwijderd.",
"CANTSHOWINFO": "U kunt het profiel van deze gebruiker niet bezoeken omdat u geen lid bent van de organisatie waartoe deze gebruiker behoort"
},
"DIALOG": {
"DELETE_TITLE": "Verwijder autorisatie",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instancja",
"INSTANCE": "Ustawienia domyślne",
"DASHBOARD": "Strona główna",
"PERSONAL_INFO": "Informacje Osobiste",
"DOCUMENTATION": "Dokumentacja",
@ -227,6 +227,7 @@
"EMAIL": "Email",
"USERNAME": "Nazwa Użytkownika",
"ORGNAME": "Nazwa Organizacji",
"PRIMARYDOMAIN": "Domena podstawowa",
"PROJECTNAME": "Nazwa Projektu",
"RESOURCEOWNER": "Właściciel Zasobu",
"METHODS": {
@ -1022,6 +1023,7 @@
"DESCRIPTION": "Te ustawienia rozszerzają i nadpisują ustawienia instancji."
},
"LIST": {
"ORGS": "Organizacje",
"LANGUAGES": "Języki",
"LOGIN": "Zachowanie logowania i bezpieczeństwo",
"LOCKOUT": "Blokada",
@ -1039,15 +1041,20 @@
"PRIVACYPOLICY": "Polityka prywatności",
"OIDC": "Czas trwania tokenów OIDC i wygaśnięcie",
"SECRETS": "Wygląd sekretów",
"SECURITY": "Ustawienia bezpieczeństwa"
"SECURITY": "Ustawienia bezpieczeństwa",
"EVENTS": "Zdarzenia",
"FAILEDEVENTS": "Nieudane Zdarzenia",
"VIEWS": "Widoki"
},
"GROUPS": {
"GENERAL": "Informacje ogólne",
"NOTIFICATIONS": "Powiadomienia",
"LOGIN": "Logowanie i dostęp",
"DOMAIN": "Domena",
"TEXTS": "Teksty i języki",
"APPEARANCE": "Wygląd",
"OTHER": "Inne"
"OTHER": "Inne",
"STORAGE": "składowanie"
}
},
"SETTING": {
@ -2237,7 +2244,8 @@
"TOAST": {
"UPDATED": "Autoryzacja zaktualizowana.",
"REMOVED": "Autoryzacja usunięta",
"BULKREMOVED": "Autoryzacje usunięte."
"BULKREMOVED": "Autoryzacje usunięte.",
"CANTSHOWINFO": "Nie możesz odwiedzić profilu tego użytkownika, ponieważ nie jesteś członkiem organizacji, do której należy ten użytkownik"
},
"DIALOG": {
"DELETE_TITLE": "Usuń autoryzację",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "Instância",
"INSTANCE": "Configurações padrão",
"DASHBOARD": "Início",
"PERSONAL_INFO": "Informações Pessoais",
"DOCUMENTATION": "Documentação",
@ -228,6 +228,7 @@
"EMAIL": "E-mail",
"USERNAME": "Nome de Usuário",
"ORGNAME": "Nome da Organização",
"PRIMARYDOMAIN": "Domínio primário",
"PROJECTNAME": "Nome do Projeto",
"RESOURCEOWNER": "Proprietário do Recurso",
"METHODS": {
@ -1024,6 +1025,7 @@
"DESCRIPTION": "Essas configurações estendem e sobrescrevem as configurações da sua instância."
},
"LIST": {
"ORGS": "Organizações",
"LANGUAGES": "Idiomas",
"LOGIN": "Comportamento de Login e Segurança",
"LOCKOUT": "Bloqueio",
@ -1041,15 +1043,20 @@
"PRIVACYPOLICY": "Política de Privacidade",
"OIDC": "Tempo de Vida e Expiração do Token OIDC",
"SECRETS": "Aparência de Segredo",
"SECURITY": "Configurações de Segurança"
"SECURITY": "Configurações de Segurança",
"EVENTS": "Eventos",
"FAILEDEVENTS": "Eventos com Falha",
"VIEWS": "Visualizações"
},
"GROUPS": {
"GENERAL": "Geral",
"NOTIFICATIONS": "Notificações",
"LOGIN": "Login e Acesso",
"DOMAIN": "Domínio",
"TEXTS": "Textos e Idiomas",
"APPEARANCE": "Aparência",
"OTHER": "Outro"
"OTHER": "Outro",
"STORAGE": "armazenar"
}
},
"SETTING": {
@ -2232,7 +2239,8 @@
"TOAST": {
"UPDATED": "Autorização atualizada.",
"REMOVED": "Autorização removida",
"BULKREMOVED": "Autorizações removidas."
"BULKREMOVED": "Autorizações removidas.",
"CANTSHOWINFO": "Você não pode visitar o perfil deste usuário porque não é membro da organização à qual esse usuário pertence"
},
"DIALOG": {
"DELETE_TITLE": "Excluir autorização",

View File

@ -85,7 +85,7 @@
}
},
"MENU": {
"INSTANCE": "Система",
"INSTANCE": "настройки по умолчанию",
"DASHBOARD": "Главная",
"PERSONAL_INFO": "Персональная информация",
"DOCUMENTATION": "Документация",
@ -224,6 +224,7 @@
"EMAIL": "Электронная почта",
"USERNAME": "Имя пользователя",
"ORGNAME": "Название организации",
"PRIMARYDOMAIN": "Основной домен",
"PROJECTNAME": "название проекта",
"RESOURCEOWNER": "Владелец ресурса",
"METHODS": {
@ -1019,6 +1020,7 @@
"DESCRIPTION": "Эти настройки расширяют и перезаписывают настройки вашего экземпляра."
},
"LIST": {
"ORGS": "Организации",
"LANGUAGES": "Языки",
"LOGIN": "Поведение при входе и безопасность",
"LOCKOUT": "Блокировка",
@ -1033,15 +1035,20 @@
"PRIVACYPOLICY": "Политика конфиденциальности",
"OIDC": "Срок действия и срок действия токена OIDC",
"SECRETS": "Тайное появление",
"SECURITY": "Настройки безопасности"
"SECURITY": "Настройки безопасности",
"EVENTS": "События",
"FAILEDEVENTS": "Неудачные события",
"VIEWS": "Отображение"
},
"GROUPS": {
"GENERAL": "Общая информация",
"NOTIFICATIONS": "Уведомления",
"LOGIN": "Вход и доступ",
"DOMAIN": "Домен",
"TEXTS": "Тексты и языки",
"APPEARANCE": "Внешний вид",
"OTHER": "Другой"
"OTHER": "Другой",
"STORAGE": "хранилище"
}
},
"SETTING": {
@ -2226,7 +2233,8 @@
"TOAST": {
"UPDATED": "Авторизация обновлена.",
"REMOVED": "Авторизация удалена",
"BULKREMOVED": "Авторизации удалены."
"BULKREMOVED": "Авторизации удалены.",
"CANTSHOWINFO": "Вы не можете посетить профиль этого пользователя, поскольку вы не являетесь членом организации, к которой принадлежит этот пользователь."
},
"DIALOG": {
"DELETE_TITLE": "Удалить авторизацию",

View File

@ -90,7 +90,7 @@
}
},
"MENU": {
"INSTANCE": "实例",
"INSTANCE": "默认设置",
"DASHBOARD": "首页",
"PERSONAL_INFO": "个人信息",
"DOCUMENTATION": "文档",
@ -227,6 +227,7 @@
"EMAIL": "邮箱",
"USERNAME": "用户名",
"ORGNAME": "组织名称",
"PRIMARYDOMAIN": "主域",
"PROJECTNAME": "项目名称",
"RESOURCEOWNER": "资源所有者",
"METHODS": {
@ -1022,6 +1023,7 @@
"DESCRIPTION": "这些设置将扩展或覆盖您的实例设置。"
},
"LIST": {
"ORGS": "组织",
"LANGUAGES": "语言",
"LOGIN": "登录行为和安全",
"LOCKOUT": "安全锁策略",
@ -1039,15 +1041,20 @@
"PRIVACYPOLICY": "隐私政策",
"OIDC": "OIDC 令牌有效期和过期时间",
"SECRETS": "验证码外观",
"SECURITY": "安全设置"
"SECURITY": "安全设置",
"EVENTS": "活动",
"FAILEDEVENTS": "失败事件",
"VIEWS": "数据表"
},
"GROUPS": {
"GENERAL": "通用",
"NOTIFICATIONS": "通知",
"LOGIN": "登录和访问",
"DOMAIN": "域名",
"TEXTS": "文本和语言",
"APPEARANCE": "外观",
"OTHER": "其他"
"OTHER": "其他",
"STORAGE": "贮存"
}
},
"SETTING": {
@ -2236,7 +2243,8 @@
"TOAST": {
"UPDATED": "授权已更新。",
"REMOVED": "已移除授权。",
"BULKREMOVED": "已移除授权。"
"BULKREMOVED": "已移除授权。",
"CANTSHOWINFO": "您无法访问该用户的个人资料,因为您不是该用户所属组织的成员"
},
"DIALOG": {
"DELETE_TITLE": "删除授权",

View File

@ -57,7 +57,7 @@
@import 'src/app/modules/shortcuts/shortcuts.component.scss';
@import 'src/app/modules/policies/idp-settings/idp-settings.component.scss';
@import 'src/app/modules/project-role-chip/project-role-chip.component';
@import 'src/app/pages/events/events.component';
@import 'src/app/modules/events/events.component';
@import 'src/app/pages/home/home.component.scss';
@import 'src/app/modules/policies/security-policy/security-policy.component.scss';
@import 'src/app/modules/idp-table/idp-table.component.scss';

View File

@ -18,6 +18,8 @@ This trigger is called before userinfo are set in the id_token or userinfo and i
- `user`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `grants` [*UserGrantList*](./objects#user-grant-list)
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields:
- `v1`
@ -46,6 +48,8 @@ This trigger is called before the claims are set in the access token and the tok
- `user`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `grants` [*UserGrantList*](./objects#user-grant-list)
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields:
- `v1`

View File

@ -17,6 +17,8 @@ This trigger is called before attributes are set in the SAMLResponse.
- `user`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `grants` [*UserGrantList*](./objects#user-grant-list)
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields:
- `v1`

View File

@ -221,7 +221,7 @@ The last API (assets) is only a REST API because ZITADEL uses multipart form dat
### SDKs
ZITADEL provides some [official and community supported SDKs](/docs/examples/sdks) for multiple languages and frameworks.
ZITADEL provides some [official and community supported SDKs](/docs/sdk-examples/introduction) for multiple languages and frameworks.
Most languages allow you to build a client from proto definitions, which allows you to build your own client in case an SDK is missing.
### Proto

View File

@ -125,4 +125,4 @@ This can be achieved by adding the scope `urn:zitadel:iam:org:project:id:zitadel
Please refer to our [guide](/guides/integrate/login-users) on how to login users.
OpenID Connect certified libraries should allow you to customize the parameters and define scopes for the authorization request. You can also continue by using one of our [example applications](/examples/introduction).
OpenID Connect certified libraries should allow you to customize the parameters and define scopes for the authorization request. You can also continue by using one of our [example applications](/docs/sdk-examples/introduction).

View File

@ -569,7 +569,7 @@ curl --request POST \
The endpoint has to be opened in the user agent (browser) to terminate the user sessions.
No parameters are needed apart from the user agent cookie, but you can provide the following to customize the behaviour:
No parameters are needed apart from the user agent cookie, but you can provide the following to customize the behavior:
| Parameter | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |

View File

@ -0,0 +1,53 @@
1. Go to your Project and click on the **New** button as shown below.
![Register the API](/img/examples/secure-api/app-jwt/1.png)
2. Give a name to your application (Test API is the name given below) and select type **API**.
![Register the API](/img/examples/secure-api/app-jwt/2.png)
3. Select **JWT** as the authentication method and click **Continue**.
![Register the API](/img/examples/secure-api/app-jwt/3.png)
4. Now review your configuration and click **Create**.
![Register the API](/img/examples/secure-api/app-jwt/4.png)
5. You will now see the APIs **Client ID**. You will not see a Client Secret because we are using a private JWT key.
![Register the API](/img/examples/secure-api/app-jwt/5.png)
6. Next, we must create the key pairs. Click on **New**.
![Register the API](/img/examples/secure-api/app-jwt/6.png)
7. Select **JSON** as the type of key. You can also set an expiration time for the key or leave it empty. Click on **Add**.
![Register the API](/img/examples/secure-api/app-jwt/7.png)
8. Download the created key by clicking the **Download** button and then click **Close**.
![Register the API](/img/examples/secure-api/app-jwt/8.png)
9. The key will be downloaded.
![Register the API](/img/examples/secure-api/app-jwt/9.png)
10. When you click on URLs on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
![Register the API](/img/examples/secure-api/app-jwt/10.png)
11. The key that you downloaded will be of the following format.
```
{
"type":"application",
"keyId":"<YOUR_KEY_ID>",
"key":"-----BEGIN RSA PRIVATE KEY-----\<YOUR_PRIVATE_KEY>\n-----END RSA PRIVATE KEY-----\n",
"appId":"<YOUR_APP_ID>",
"clientId":"<YOUR_CLIENT_ID>"
}
```
12. Also note down the **Resource ID** of your project.
![Register the API](/img/examples/secure-api/app-jwt/11.png)

View File

@ -0,0 +1,49 @@
1. Go to the **Users** tab in your organization as shown below and click on the **Service Users** tab.
![Register the API](/img/examples/secure-api/service-user-jwt/1.png)
2. To add a service user, click on the **New** button.
![Register the API](/img/examples/secure-api/service-user-jwt/2.png)
3. Next, add the details of the service user and select either **Bearer** or **JWT** for **Access Token Type** and click on **Create**. For this example, we will select **JWT**.
![Register the API](/img/examples/secure-api/service-user-jwt/3.png)
4. Now you will see the saved details.
![Register the API](/img/examples/secure-api/service-user-jwt/4.png)
5. Next, we need to generate a private-public key pair in ZITADEL and you must get the private key to sign your JWT. Go to **Keys** and click on **New**.
![Register the API](/img/examples/secure-api/service-user-jwt/5.png)
6. Select type **JSON** and click **Add**.
![Register the API](/img/examples/secure-api/service-user-jwt/6.png)
7. Download the key by clicking **Download**. After the download, click **Close**.
![Register the API](/img/examples/secure-api/service-user-jwt/7.png)
8. You will see the following screen afterwards.
![Register the API](/img/examples/secure-api/service-user-jwt/8.png)
9. The downloaded key will be of the following format:
```
{
"type":"serviceaccount",
"keyId":"<YOUR_KEY_ID>",
"key":"-----BEGIN RSA PRIVATE KEY-----\n<YOUR_KEY>\n-----END RSA PRIVATE KEY-----\n",
"userId":"<YOUR_USER_ID>"
}
```

View File

@ -0,0 +1,36 @@
In order to access this route, you must create the role `read:messages` in your ZITADEL project and also create an authorization for the service user you created by adding the role to the user. Follow these steps to do so:
1. Go to your project and select **Roles**. Click **New**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/1.png)
2. Add the `read:messages` role as shown below and click **Save**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/2.png)
3. You will see the created role listed.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/3.png)
4. To assign this role to a user, click on **Authorizations**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/4.png)
5. Select the user you want to assign the role to.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/5.png)
6. Select the project where this authorization is applicable.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/6.png)
7. Click **Continue**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/7.png)
8. Select the role **read:messages** and click **Save**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/8.png)
9. You will now see the your service user has been assigned the role **read:messages**.
![Register the API](/img/examples/secure-api/service-user-jwt/scopes/9.png)

View File

@ -0,0 +1,9 @@
To install Django:
```bash
python -m pip install Django
```
Then in your folder of choice call the following command to create a Django base:
```bash
django-admin startproject mysite .
```

View File

@ -0,0 +1,5 @@
As we use local environmental variables please install dotenv:
```bash
python -m pip install python-dotenv
```

View File

@ -0,0 +1 @@
You have to install Python as described in [their documentation](https://wiki.python.org/moin/BeginnersGuide/Download).

View File

@ -1,209 +0,0 @@
---
title: Overview of ZITADEL example applications and quickstarts
sidebar_label: Overview
---
Our examples cover a range of programming languages and frameworks, so no matter what you're into, we've got you covered.
## Frontend
### Single page application
<table>
<tr>
<th></th>
<th>Language</th>
<th>Example</th>
<th>Quickstart</th>
<th>SDK</th>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/angular.svg" alt="angular"/>
</td>
<td>Angular</td>
<td><a href="https://github.com/zitadel/zitadel-angular" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/angular">Guide</a></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/react.png" alt="react"/>
</td>
<td>React</td>
<td><a href="https://github.com/zitadel/react-user-authentication" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/react">Guide</a></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/vue.svg" alt="vue"/>
</td>
<td>Vue</td>
<td><a href="https://github.com/zitadel/zitadel/issues/5223" target="_blank">🚧</a></td>
<td></td>
<td></td>
</tr>
</table>
### Native/mobile app
<table>
<tr>
<th></th>
<th>Language</th>
<th>Example</th>
<th>Quickstart</th>
<th>SDK</th>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/flutter.svg" alt="flutter"/>
</td>
<td>Flutter</td>
<td><a href="https://github.com/zitadel/zitadel_flutter" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/flutter">Guide</a></td>
<td></td>
</tr>
</table>
### Regular web app
<table>
<tr>
<th></th>
<th>Language</th>
<th>Example</th>
<th>Quickstart</th>
<th>SDK</th>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/nextjs.svg" alt="nextjs"/>
</td>
<td>NextJS</td>
<td><a href="https://github.com/zitadel/zitadel-nextjs" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/nextjs">Guide</a></td>
<td><a href="./sdks#more">NextAuth Provider</a></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/golang.svg" alt="golang"/>
</td>
<td>Go Web</td>
<td><a href="https://github.com/zitadel/zitadel-go/tree/next/example/app" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/go">Guide</a></td>
<td><a href="https://github.com/zitadel/zitadel-go/tree/next" target="_blank">SDK</a></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/java.svg" alt="java"/>
</td>
<td>Java Spring Boot Web</td>
<td><a href="https://github.com/zitadel/zitadel-java" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/java-spring">Guide</a></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/php.svg" alt="php"/>
</td>
<td>Symfony PHP Framework</td>
<td><a href="https://github.com/zitadel/example-symfony-oidc" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/symfony">Guide</a></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/python.svg" alt="python"/>
</td>
<td>Python3 Flask Web</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/dotnet.svg" alt="dotnet"/>
</td>
<td>ASP.NET Core MVC Web</td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
## Backend
<table>
<tr>
<th></th>
<th>Language</th>
<th>Example</th>
<th>Quickstart</th>
<th>SDK</th>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/golang.svg" alt="golang"/>
</td>
<td>Golang</td>
<td><a href="https://github.com/zitadel/zitadel-go/tree/next/example/api" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="./secure-api/go">Guide</a></td>
<td><a href="https://github.com/zitadel/zitadel-go/tree/next" target="_blank">SDK</a></td>
</tr>
<tr>
<td>
<img src="/docs/img/tech/python.svg" alt="phyton"/>
</td>
<td>Python Flask</td>
<td><a href="https://github.com/zitadel/example-api-python3-flask" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="./secure-api/python-flask">Guide</a></td>
<td></td>
</tr>
<tr>
<td>
<img src="/docs/img/tech/dotnet.svg" alt="dotnet"/>
</td>
<td>ASP.NET Core WebAPI</td>
<td></td>
<td><a href="./call-zitadel-api/dot-net">Guide</a></td>
<td><a href="https://github.com/smartive/zitadel-net">SDK</a></td>
</tr>
<tr>
<td>
<img src="/docs/img/tech/nodejs.svg" alt="node"/>
</td>
<td>Node.js NestJS API</td>
<td><a href="https://github.com/ehwplus/zitadel-nodejs-nestjs" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="./secure-api/nodejs-nestjs">Guide</a></td>
<td></td>
</tr>
<tr>
<td width="100px">
<img src="/docs/img/tech/php.svg" alt="php"/>
</td>
<td>PHP API</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>
<img src="/docs/img/tech/java.svg" alt="java"/>
</td>
<td>Java Spring Boot API</td>
<td><a href="https://github.com/zitadel/zitadel-java" target="_blank"><i class="lab la-github"></i></a></td>
<td><a href="/examples/login/java-spring">Guide</a></td>
<td></td>
</tr>
</table>
## Other example applications
- [B2B customer portal](https://github.com/zitadel/zitadel-nextjs-b2b): Showcase the use of personal access tokens in a B2B environment. Uses NextJS Framework.
- [Frontend with backend API](https://github.com/zitadel/example-quote-generator-app): A simple web application using a React front-end and a Python back-end API, both secured using ZITADEL
- [Introspection](https://github.com/zitadel/examples-api-access-and-token-introspection): Python examples for securing an API and invoking it as a service user
- [Fine-grained authorization](https://github.com/zitadel/example-fine-grained-authorization): Leverage actions, custom metadata, and claims for attribute-based access control
Search for the "example" tag in our repository to [explore all examples](https://github.com/search?q=topic%3Aexamples+org%3Azitadel&type=repositories).

View File

@ -0,0 +1,241 @@
---
title: ZITADEL with Django Python
sidebar_label: Django
---
import SetupPython from '../imports/_setup_python.mdx';
import SetupDjango from '../imports/_setup_django.mdx';
import SetupDotenv from '../imports/_setup_dotenv.mdx';
This integration guide demonstrates the recommended way to incorporate ZITADEL into your Django Python application.
It explains how to enable user login in your application and how to incorporate the ZITADEL users into the existing AuthenticationBackend.
By the end of this guide, your application will have login functionality with basic role mapping, admin console and polls as described in the Django guide.
:::info
This documentation references our [example](https://github.com/zitadel/example-django-python-oidc) on GitHub.
:::
## ZITADEL setup
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
### Project roles
The Example expects [user roles](guides/integrate/retrieve-user-roles) to be returned after login.
This example expects 3 different roles:
- `admin`: superuser with permissions to use the admin console
- `staff`: user with permissions to see results of the polls
- `user`: normal user with permission to vote on the existing polls
In your project settings make sure the "Assert Roles On Authentication" is enabled.
![Project settings in console](/img/django/project-settings.png)
In the project Role tab, add 3 special roles:
- `admin`
- `staff`
- `user`
If none of the roles is provided as a user, the user in Django will not be created.
![Project roles in console](/img/django/project-roles.png)
Finally, we can assign the roles to users in the project's authorizations tab.
![Project authorizations in console](/img/django/project-authorizations.png)
### Set up application and obtain secrets
Next you will need to provide some information about your app.
In your Project, add a new application at the top of the page.
Select Web application type and continue.
We use [Authorization Code](/apis/openidoauth/grant-types#authorization-code) for our Django application.
![Create app in console](/img/django/app-create.png)
Select `CODE` in the next step. This makes sure you still get a secret. Note that the secret never gets exposed on the browser and is therefore kept in a confidential environment. Safe the generated secret for later use.
![Configure app authentication method in console](/img/django/app-auth-method.png)
With the Redirect URIs field, you tell ZITADEL where it is allowed to redirect users to after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
For the example application we are writing use:
- `http://localhost:8000/oidc/callback/` as Redirect URI
- `http://localhost:8000/oidc/logout/` as post-logout URI.
![Configure app redirects console](/img/django/app-redirects.png)
After the final step you are presented with a client ID and secret.
Copy and paste them to a safe location for later use by the application.
The secret will not be displayed again, but you can regenerate one if you loose it.
## Setup new Django application
### Setup Python
<SetupPython/>
### Install dependencies
For this example we need the following dependencies:
- `django`: to create an API with django
- `python-dotenv`: to use environment variables in the configuration
- `mozilla-django-oidc`: client-side OIDC functionality
For the dependencies we need a requirements.txt-file with the following content:
```python reference
https://github.com/zitadel/example-python-django-oidc/blob/main/requirements.txt
```
Then install all dependencies with:
```bash
python -m pip install -U requirements.txt
```
The used base is the "Writing your first Django app" from the Django documentation under [https://docs.djangoproject.com/en/5.0/intro/](https://docs.djangoproject.com/en/5.0/intro/),
which has documented additional parts in to use [mozilla-django-oidc](https://github.com/mozilla/mozilla-django-oidc) to integrate ZITADEL as AuthenticationBackend.
:::info
Skip this step if you are connecting ZITADEL to an existing application.
:::
## Define the Django app
### Create the settings.py to include mozilla-django-oidc
To use the mozilla-django-oidc as AuthenticationBackend, there are several things to add to the settings.py, as described in the [documentation "Add settings to settings.py"](https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html#add-settings-to-settings-py):
Add INSTALLED_APPS:
```python
INSTALLED_APPS = [
...
"mozilla_django_oidc", # Load after auth
...
]
```
Add MIDDLEWARE:
```python
MIDDLEWARE = [
#...
"mozilla_django_oidc.middleware.SessionRefresh",
]
```
Add AUTHENTICATION_BACKENDS:
```python
AUTHENTICATION_BACKENDS = (
"mysite.backend.PermissionBackend",
)
```
Add configuration:
```python reference
https://github.com/zitadel/example-python-django-oidc/blob/main/mysite/settings.py#L130-L174
```
and create a ".env"-file in the root folder with the configuration:
```bash
ZITADEL_PROJECT = "ID of the project you created the application in ZITADEL"
OIDC_RP_CLIENT_ID = "ClientID provided by the created application in ZITADEL"
OIDC_RP_CLIENT_SECRET = "ClientSecret provided by the created application in ZITADEL"
OIDC_OP_BASE_URL = "Base URL to the ZITADEL instance"
```
which should then look something like this:
```bash
ZITADEL_PROJECT = "249703732336418457"
OIDC_RP_CLIENT_ID = "249703852243222581@python"
OIDC_RP_CLIENT_SECRET = "Zy3OOHaMBTj2sfamW77Vak5BeQ3nEpOf7suPKTnJKaScMh0lPJqUeDOZmgL3bds0"
OIDC_OP_BASE_URL = "https://example.zitadel.cloud"
```
### AuthenticationBackend definition
To create and update the users regarding the roles given in the authentications in ZITADEL a Subclass of OIDCAuthenticationBackend has to be created:
```python reference
https://github.com/zitadel/example-python-django-oidc/blob/main/mysite/backend.py
```
Which handles the users differently depending on if there are roles associated to:
- `admin` -> superuser
- `staff` -> staff
- `user` -> user
- `no role` -> no user gets created
### URLs
To handle the callback and logout the urls have to be added to the urls.py:
```python
urlpatterns = [
#...
path("oidc/", include("mozilla_django_oidc.urls")),
]
```
So it should like something like this:
```python reference
https://github.com/zitadel/example-python-django-oidc/blob/main/mysite/urls.py#L21-L28
```
## Configure and run the application
:::warning
Never store and commit secrets in the ".env" or settings.py file
:::
### Authentication and authorization
To check the authentication and authorization the views in the polls application are extended with decorators:
```python reference
https://github.com/zitadel/example-python-django-oidc/blob/main/mysite/views.py
```
- `@method_decorator(login_required, name="dispatch")`: means that the user has to be logged in Django, which only happens if you have one of the 3 roles("admin", "staff" or "user")
- `@method_decorator(staff_member_required, name="dispatch")`: means you have to have at least a staff user ("admin" or "staff")
- `/admin/`: all admin sides are only accessible if you have a superuser with the role "admin"
:::info
Additional permission checks could be done with "permission_required" from "django.contrib.auth.decorators" also described in the [Django documentation](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#custom-permissions).
:::
### DB
Create and run migrations:
```bash
python manage.py migrate
```
### Run
You can use a local Django server to test the application.
```bash
python manage.py runserver
```
Visit http://localhost:8000/polls or http://localhost:8000/admin and click around.
## Completion
Congratulations! You have successfully integrated your Python Django application with ZITADEL!
If you get stuck, consider checking out our [example](https://github.com/zitadel/example-python-django-oidc) application. This application includes all the functionalities mentioned in this quick-start. You can start by cloning the repository and defining the settings in the settings.py. If you face issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/example-python-django-oidc/issues).
### What's next?
Now that you have enabled authentication, it's time for you to add more authorizations to your application using ZITADEL APIs. To do this, you can refer to the [docs](/apis/introduction) or check out the ZITADEL Console code on [GitHub](https://github.com/zitadel/zitadel) which uses gRPC and OpenAPI to access data.

View File

@ -0,0 +1,120 @@
---
title: ZITADEL with React
sidebar_label: React
---
This integration guide demonstrates the recommended way to incorporate ZITADEL into your React application.
It explains how to enable user login in your application and how to fetch data from the user info endpoint.
By the end of this guide, your application will have login functionality and will be able to access the current user's profile.
:::tip
This documentation references our [example](https://github.com/zitadel/zitadel-react) on GitHub.
It also uses the @zitadel/react package with its default configuration.
:::
## Set up application and obtain keys
Before we begin developing our application, we need to perform a few configuration steps in the ZITADEL Console.
You'll need to provide some information about your app.
We recommend creating a new app to start from scratch.
Navigate to your project, then add a new application at the top of the page.
Select the **User Agent** application type and continue.
We recommend that you use [Proof Key for Code Exchange (PKCE)](/apis/openidoauth/grant-types#proof-key-for-code-exchange) for all single page applications.
![Create app in console](/img/react/app-create.png)
### Redirect URIs
The redirect URIs field tells ZITADEL where it's allowed to redirect users after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
The post logout redirect sends your users back to a public route on your application after they have logged out.
:::tip
If you are following along with the [example](https://github.com/zitadel/zitadel-react), set the dev mode switch to `true`.
Configure a redirect URIs to \http://localhost:3000/callback and a post redirect URI to \http://localhost:3000/.
:::
Continue and create the application.
### Copy Client ID
After successful creation of the app, make sure copy the client ID, as you will need it to configure your React client.
## Create a project role "admin" and assign it to your user
Also note the projects resource ID, as you will need it to configure your React client.
![Create project role "admin"](/img/react/project-role.png)
![Assign the "admin" role to your user](/img/react/project-authz.png)
If you want to read your users roles from user info endpoint, make sure to enable the checkbox in your project.
## React setup
Now that you have configured your web application on the ZITADEL side, you can proceed with the integration of your React client.
### Install React dependencies
To conveniently connect with ZITADEL, you can install the [@zitadel/react NPM package](https://www.npmjs.com/package/@zitadel/react). Run the following command:
```bash
yarn add @zitadel/react
```
### Create and configure the auth service
The @zitadel/react package provides a `createZitadelAuth()` function which sets some defaults and initializes the underlying [oidc-client-ts](https://github.com/authts/oidc-client-ts) `UserManager` class.
You can overwrite all the defaults with the aguments you pass to `createZitadelAuth()`.
Export the object returned from `createZitadelAuth()`
### Initialize user manager
```ts reference
https://github.com/zitadel/zitadel-react/blob/main/src/App.tsx
```
### Add two new components to your application
First, add the component which prompts the user to login.
```ts reference
https://github.com/zitadel/zitadel-react/blob/main/src/components/Login.tsx
```
Then create the component for the page where the users will be redirected.
It loads the user info endpoint once the code flow completes and prints all the information.
```ts reference
https://github.com/zitadel/zitadel-react/blob/main/src/components/Callback.tsx
```
You can now read a user's role to show protected areas of the application.
### Run
Finally, you can start your application by running the following:
```
yarn start
```
## Completion
Congratulations! You have successfully integrated your React application with ZITADEL!
If you get stuck, consider checking out the [ZITADEL React example application](https://github.com/zitadel/zitadel-react).
This application includes all the functionalities mentioned in this quickstart.
You can start by cloning the repository and changing the arguments to `createZitadelAuth` to fit your requirements.
If you face issues, contact us or [raise an issue on GitHub](https://github.com/zitadel/zitadel-react/issues).
![App in console](/img/react/app-screen.png)
### What's next?
Now that you have enabled authentication, you are ready to add authorization to your application by using ZITADEL APIs.
To do this, [refer to the API docs](/apis/introduction) or check out [the ZITADEL Console code on GitHub](https://github.com/zitadel/zitadel) which uses gRPC to access data.
For more information on how to create a React application, you can refer to [Create React App](https://github.com/facebook/create-react-app).
If you want to learn more about the libraries wrapped by [@zitadel/react](https://www.npmjs.com/package/@zitadel/react), read the docs for [oidc-client-ts](https://github.com/authts/oidc-client-ts).

View File

@ -1,118 +0,0 @@
---
title: ZITADEL with React
sidebar_label: React
---
This Integration guide shows you the recommended way to integrate **ZITADEL** into your React Application.
It demonstrates how to add a user login to your application and fetch some data from the user info endpoint.
At the end of the guide you should have an application able to login a user and read the user profile.
## Setup Application and get Keys
Before we can start building our application we have to do a few configuration steps in ZITADEL Console.
You will need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project and add a new application at the top of the page.
Select User Agent and continue. More about the different app types can you find [here](/guides/integrate/oauth-recommended-flows#different-client-profiles).
We recommend that you use [Authorization Code](/apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange](/apis/openidoauth/grant-types#proof-key-for-code-exchange) for all web applications.
### Redirect URLs
A redirect URL is a URL in your application where ZITADEL redirects the user after they have authenticated. Set your url to the domain the app will be deployed to or use `http://localhost:3000/` because this will be the default of npm.
> You should set dev mode to `true` as this will enable unsecure http for the moment.
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the post redirectURI field.
Continue and Create the application.
### Client ID
After successful app creation a popup will appear showing you your clientID.
Copy your client ID as it will be needed in the next step.
## React Setup
### Create React app
Create a new React app with the following command
```bash
npx create-react-app my-app
```
### Install oidc client
You need to install an oauth / oidc client to connect with ZITADEL. Run the following command:
```bash
npm install oidc-react
```
This library helps integrating ZITADEL Authentication in your React Application.
### Create and configure Auth Module
With the installed oidc pakage you will need an AuthProvider which should contain the OIDC configuration.
The oidc configuration should contain **openid**, **profile** and **email** as scope and **code** as responseType.
In the code below make sure to change the issuer to your instance url. You can find this in the ZITADEL Console on you application.
Replace the clientId value 'YOUR-CLIENT-ID' with the generated client id of you application in ZITADEL Console.
```ts
import React from "react";
import { AuthProvider } from "oidc-react";
import "./App.css";
const oidcConfig = {
onSignIn: async (response: any) => {
alert(
"You logged in :" +
response.profile.given_name +
" " +
response.profile.family_name
);
window.location.hash = "";
},
authority: "https:/[your-domain]-[random-string].zitadel.cloud", // replace with your instance
clientId: "YOUR-CLIENT-ID",
responseType: "code",
redirectUri: "http://localhost:3000/",
scope: "openid profile email",
};
function App() {
return (
<AuthProvider {...oidcConfig}>
<div className="App">
<header className="App-header">
<p>Hello World</p>
</header>
</div>
</AuthProvider>
);
}
export default App;
```
### Run application
Start your react application with the following command
```bash
npm start
```
Your browser should automatically open the app site or just go to `http://localhost:3000/`.
On opening the app in the browser you will be redirected to the login of your instance.
After successfully authenticating your user, you will get back to you application.
It should show a popup which says: **You logged in {FirstName} {LastName}**
## Completion
You have successfully integrated ZITADEL in your React Application!
### Whats next?
Now you can proceed implementing our APIs to include Authorization. You can find our API Docs [here](/apis/introduction)
For more information about creating a React application we refer to [React](https://reactjs.org/docs/getting-started.html) and for more information about the used oauth/oidc library consider reading their docs at [oidc-react](https://www.npmjs.com/package/oidc-react).

View File

@ -1,108 +0,0 @@
---
title: ZITADEL SDKs
sidebar_label: SDKs
---
On this page you find our official SDKs, links to supporting frameworks and providers, and resources to help with SDKs.
The SDKs wrap either our [gRPC or REST APIs](/docs/apis/introduction) to provide the client with User Authentication and
Management for resources.
## ZITADEL SDKs
| Language / Framework | Link Github | User Authentication | Manage resources | Notes |
|----------------------|---------------------------------------------------------------|-----------------------------------------------------------|------------------|-------------------|
| Go | [zitadel-go](https://github.com/zitadel/zitadel-go) | 🚧 [WIP](https://github.com/zitadel/zitadel-go/tree/next) | ✔️ | `official` |
| Vue | [zitadel-vue](https://github.com/zitadel/zitadel-vue) | ✔️ | ❌ | `official` |
| .NET | [zitadel-net](https://github.com/smartive/zitadel-net) | ✔️ | ✔️ | `community` |
| Elixir | [zitadel_api](https://github.com/jshmrtn/zitadel_api) | ✔️ | ✔️ | `community` |
| NodeJS | [@zitadel/node](https://www.npmjs.com/package/@zitadel/node) | ❌ | ✔️ | `community` |
| Dart | [zitadel-dart](https://github.com/smartive/zitadel-dart) | ❌ | ✔️ | `community` |
| Rust | [zitadel-rust](https://github.com/smartive/zitadel-rust) | ✔️ | ✔️ | `community` |
| JVM | 🚧 [WIP](https://github.com/zitadel/zitadel/discussions/3650) | ❓ | ❓ | TBD |
| Python | 🚧 [WIP](https://github.com/zitadel/zitadel/issues/3675) | ❓ | ❓ | TBD |
## Missing SDK
Is your language/framework missing? Fear not, you can generate your gRPC API Client with ease.
1. Make sure to install [buf](https://buf.build/docs/installation/)
2. Create a `buf.gen.yaml` and configure the [plugins](https://buf.build/plugins) you need
3. Run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` (change the versions to your needs)
Let us make an example with Ruby. Any other supported language by buf will work as well. Consult
the [buf plugin registry](https://buf.build/plugins) for more ideas.
### Example with Ruby
With gRPC we usually need to generate the client stub and the messages/types. This is why we need two plugins.
The plugin `grpc/ruby` generates the client stub and the plugin `protocolbuffers/ruby` takes care of the messages/types.
```yaml
version: v1
plugins:
- plugin: buf.build/grpc/ruby
out: gen
- plugin: buf.build/protocolbuffers/ruby
out: gen
```
If you now run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` in the folder where
your `buf.gen.yaml` is located you should see the folder `gen` appear.
If you run `ls -la gen/zitadel/` you should see something like this:
```bash
ffo@ffo-pc:~/git/zitadel/ruby$ ls -la gen/zitadel/
total 704
drwxr-xr-x 2 ffo ffo 4096 Apr 11 16:49 .
drwxr-xr-x 3 ffo ffo 4096 Apr 11 16:49 ..
-rw-r--r-- 1 ffo ffo 4397 Apr 11 16:49 action_pb.rb
-rw-r--r-- 1 ffo ffo 141097 Apr 11 16:49 admin_pb.rb
-rw-r--r-- 1 ffo ffo 25151 Apr 11 16:49 admin_services_pb.rb
-rw-r--r-- 1 ffo ffo 6537 Apr 11 16:49 app_pb.rb
-rw-r--r-- 1 ffo ffo 1134 Apr 11 16:49 auth_n_key_pb.rb
-rw-r--r-- 1 ffo ffo 32881 Apr 11 16:49 auth_pb.rb
-rw-r--r-- 1 ffo ffo 6896 Apr 11 16:49 auth_services_pb.rb
-rw-r--r-- 1 ffo ffo 1571 Apr 11 16:49 change_pb.rb
-rw-r--r-- 1 ffo ffo 2488 Apr 11 16:49 event_pb.rb
-rw-r--r-- 1 ffo ffo 14782 Apr 11 16:49 idp_pb.rb
-rw-r--r-- 1 ffo ffo 5031 Apr 11 16:49 instance_pb.rb
-rw-r--r-- 1 ffo ffo 223348 Apr 11 16:49 management_pb.rb
-rw-r--r-- 1 ffo ffo 44402 Apr 11 16:49 management_services_pb.rb
-rw-r--r-- 1 ffo ffo 3020 Apr 11 16:49 member_pb.rb
-rw-r--r-- 1 ffo ffo 855 Apr 11 16:49 message_pb.rb
-rw-r--r-- 1 ffo ffo 1445 Apr 11 16:49 metadata_pb.rb
-rw-r--r-- 1 ffo ffo 2370 Apr 11 16:49 object_pb.rb
-rw-r--r-- 1 ffo ffo 621 Apr 11 16:49 options_pb.rb
-rw-r--r-- 1 ffo ffo 4425 Apr 11 16:49 org_pb.rb
-rw-r--r-- 1 ffo ffo 8538 Apr 11 16:49 policy_pb.rb
-rw-r--r-- 1 ffo ffo 8223 Apr 11 16:49 project_pb.rb
-rw-r--r-- 1 ffo ffo 1022 Apr 11 16:49 quota_pb.rb
-rw-r--r-- 1 ffo ffo 5872 Apr 11 16:49 settings_pb.rb
-rw-r--r-- 1 ffo ffo 20985 Apr 11 16:49 system_pb.rb
-rw-r--r-- 1 ffo ffo 4784 Apr 11 16:49 system_services_pb.rb
-rw-r--r-- 1 ffo ffo 28759 Apr 11 16:49 text_pb.rb
-rw-r--r-- 1 ffo ffo 24170 Apr 11 16:49 user_pb.rb
-rw-r--r-- 1 ffo ffo 13568 Apr 11 16:49 v1_pb.rb
```
Import these files into your project to start interacting with ZITADEL's APIs.
## More
While we are not actively maintaining the following projects, it is worth checking out if you're interested in exploring
ZITADEL in different programming languages or frameworks.
- [NodeJS passport](https://github.com/buehler/node-passport-zitadel) authentication helper
- [NextAuth Provider for ZITADEL](https://next-auth.js.org/providers/zitadel)
If we do not provide an example, SDK or guide, we strongly recommend using existing authentication libraries for your
language or framework instead of building your own.
Certified libraries have undergone rigorous testing and validation to ensure high security and reliability.
There are many recommended libraries available, this saves time and ensures that users' data is well-protected.
You might want to check out the following links to find a good library:
- [awesome-auth](https://github.com/casbin/awesome-auth)
- [OpenID General References](https://openid.net/developers/libraries/)
- [OpenID certified developer tools](https://openid.net/certified-open-id-developer-tools/)

View File

@ -1,5 +0,0 @@
---
title: ZITADEL with .NET
sidebar_label: .NET
---
Coming soon

View File

@ -38,7 +38,7 @@ If your starting from scratch, you can use the Spring Initializer with the [foll
### Support class
To be able to take the most out of ZITADELs RBAC, we first need to create a CustomAuthorityOpaqueTokenIntrospector, that will
customize the introspection behaviour and map the role claims (`urn:zitadel:iam:org:project:roles`)
customize the introspection behavior and map the role claims (`urn:zitadel:iam:org:project:roles`)
into Spring Security `authiorities`, which can be used later on to determine the granted permissions.
So in your application, create a `support/zitadel` package and in there the `CustomAuthorityOpaqueTokenIntrospector.java`:

View File

@ -1,6 +1,6 @@
---
title: ZITADEL with Node.js
sidebar_label: Node.js
sidebar_label: NestJS
---
# ZITADEL with Node.js (NestJS)

View File

@ -0,0 +1,166 @@
---
title: ZITADEL with Django Python
sidebar_label: Django
---
import AppJWT from '../imports/_app_jwt.mdx';
import ServiceuserJWT from '../imports/_serviceuser_jwt.mdx';
import ServiceuserRole from '../imports/_serviceuser_role.mdx';
import SetupPython from '../imports/_setup_python.mdx';
This integration guide demonstrates the recommended way to incorporate ZITADEL into your Django Python application.
It explains how to check the token validity in the API and how to check for permissions.
By the end of this guide, your application will have three different endpoint which are public, private(valid token) and private-scoped(valid token with specific role).
:::info
This documentation references our [example](https://github.com/zitadel/example-django-python-oauth) on GitHub.
:::
## ZITADEL setup
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
### Create application
<AppJWT/>
### Create Serviceuser
<ServiceuserJWT/>
### Give Serviceuser an authorization
<ServiceuserRole/>
### Prerequisites
At the end you should have the following for the API:
- Issuer, something like `https://example.zitadel.cloud` or `http://localhost:8080`
- Introspection URL, something like `https://example.zitadel.cloud/oauth/v2/introspect`
- Token URL, something like `https://example.zitadel.cloud/oauth/v2/token`
- `.json`-key-file for the API, from the application
- ID of the project
And the following from the Serviceuser:
- `.json`-key-file from the serviceuser
## Setup new Django application
### Setup Python
<SetupPython/>
### Install dependencies
For this example we need the following dependencies:
- `django`: to create an API with django
- `python-dotenv`: to use environment variables in the configuration
- `authlib`: client-side OAuth functionality
- `requests`: HTTP requests for the introspection
For the dependencies we need a requirements.txt-file with the following content:
```python reference
https://github.com/zitadel/example-python-django-oauth/blob/main/requirements.txt
```
Then install all dependencies with:
```bash
python -m pip install -U requirements.txt
```
Then in your folder of choice, call the following command to create a Django base:
```bash
django-admin startproject myapi .
```
## Define the Django API
### Add to the settings.py to include ZITADEL info
There is info needed for the introspection calls, which we put into the settings.py:
```python reference
https://github.com/zitadel/example-python-django-oauth/blob/main/myapi/settings.py#L125-L133
```
and create a ".env"-file in the root folder with the configuration as an example:
```bash
ZITADEL_INTROSPECTION_URL = 'URL to the introspection endpoint to verify the provided token'
ZITADEL_DOMAIN = 'Domain used as audience in the token verification'
API_PRIVATE_KEY_FILE_PATH = 'Path to the key.json created in ZITADEL'
```
I should look something like this:
```bash
ZITADEL_INTROSPECTION_URL = 'https://example.zitadel.cloud/oauth/v2/introspect'
ZITADEL_DOMAIN = 'https://example.zitadel.cloud'
API_PRIVATE_KEY_FILE_PATH = '/tmp/example/250719519163548112.json'
```
### Validator definition
To validate the tokens, we need a validator which can be called in the event of API-calls.
validator.py:
```python reference
https://github.com/zitadel/example-python-django-oauth/blob/main/myapi/validator.py
```
### Requests and URLs
We define 3 different endpoints which differ in terms of requirements.
views.py:
```python reference
https://github.com/zitadel/example-python-django-oauth/blob/main/myapi/views.py
```
To handle endpoints the urls have to be added to the urls.py:
```python reference
https://github.com/zitadel/example-python-django-oauth/blob/main/myapi/urls.py
```
### DB
Create and run migrations:
```bash
python manage.py migrate
```
### Run
You can use a local Django server to test the application.
```bash
python manage.py runserver
```
### Call the API
To call the API you need an access token, which is then verified by ZITADEL.
Please follow [this guide here](https://zitadel.com/docs/guides/integrate/private-key-jwt#get-an-access-token), ignoring the first step as we already have the `.json`-key-file from the serviceaccount.
Optionally set the token as an environment variable:
```
export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
With the access token, you can then do the following calls:
```
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/public
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/private
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8000/api/private-scoped
```
## Completion
Congratulations! You have successfully integrated your Django API with ZITADEL!
If you get stuck, consider checking out our [example](https://github.com/zitadel/example-python-django-oauth) application. This application includes all the functionalities mentioned in this quick-start. You can start by cloning the repository and defining the settings in the settings.py. If you face issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/example-python-django-oauth/issues).

View File

@ -19,7 +19,7 @@ on /authorize with at least the following parameters:
We recommend always using two additional parameters `state` and `nonce`. The former enables you to transfer a state through
the authentication process. The latter is used to bind the client session with the id_token and to mitigate replay attacks.
PKCE stands for Proof Key for Code Exchange. So other than "normal" code exchange, the does not authenticate using
PKCE stands for Proof Key for Code Exchange. So other than "normal" code exchange, the PKCE does not authenticate using
client_id and client_secret but an additional code. You will have to generate a random string, hash it and send this hash
on the authorization_endpoint. On the token_endpoint you will then send the plain string for the authorization to compute
the hash as well and to verify it's correct. In order to do so you're required to send the following two parameters as well:

View File

@ -1,6 +1,6 @@
The login policy can be configured on two levels. Once as default on the instance and this can be overwritten for each organization.
The only difference is where you configure it. Go either to the settings page of a specific organization or to the settings page of your instance.
Instance: $YOUR-DOMAIN/ui/console/settings?id=general
Instance: $YOUR-DOMAIN/ui/console/instance?id=general
Organization: Choose the organization in the menu and go to $YOUR-DOMAIN/ui/console/org-settings?id=login
1. Go to the Settings

View File

@ -1,4 +1,3 @@
import CodeBlock from '@theme/CodeBlock';
<p>You can use a ZITADEL action if you want to prefill the fields {props.fields} with {props.provider} data.</p>

View File

@ -0,0 +1,15 @@
---
title: Additional Information
sidebar_label: Additional Information
---
## Automatically prefill user data
import PrefillAction from './_prefill_action.mdx';
<PrefillAction fields="specific fields like firstname, lastname and email verified" provider="your providers"/>
This action is an example for OKTA. You can also use it for any other provider
```js reference
https://github.com/zitadel/actions/blob/main/examples/okta_identity_provider.js
```

View File

@ -0,0 +1,6 @@
When adding a new manager, you can select multiple roles some of which are only allowed to read data.
This can be especially useful if you add service users for one of your projects where you only need read access.
Per default you will only search for users within the selected organization. If you like to give a role to a user outside the organization you need to switch to the global search and type the exact loginname of the users. This will prevent users from guessing users from other organizations.
<img alt="Managers" src="/docs/img/guides/console/addmanager.png" width="390px" />

View File

@ -0,0 +1,27 @@
To create a new user, go to Users and click on **New**. Enter the required contact details and save by clicking “Create”.
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
<Tabs>
<TabItem value="human" label="Human User" default>
<img src="/docs/img/guides/console/addhuman.png" width="680px" alt="Add Human" />
</TabItem>
<TabItem value="service" label="Service User">
<img
src="/docs/img/guides/console/addmachine.png"
width="540px"
alt="Add Service User"
/>
</TabItem>
</Tabs>
After a human user is created, by default, an initialization mail with a code is sent to the registered email. This code then has to be verified on first login.
If you want to omit this mail, you can check the **email verified** and **set initial password** toggle.
If no password is set initially, the initialization mail prompting the user to set his password is sent.
You can prompt the user to add a second factor method too by checking the **Force MFA** toggle in [Login behaviour settings](/docs/guides/manage/console/instance-settings#login-behavior-and-access).
When logged in, a user can then manage his profile in console himself, adding a profile picture, external IDPs and Passwordless authentication devices.
<img src="/docs/img/guides/console/myprofile.png" alt="Profile Self Manage" />

View File

@ -23,4 +23,4 @@ This could for example be a **External Authentication** Flow, with a **Post Auth
<img src="/docs/img/guides/console/flow.png" alt="Flow" width="400px" />
Now whenever a user gets authenticated externally with an IDP, a action is triggered after the authentication itself.
If you want to know more where actions can be useful, take a look at the feature [here](/concepts/features/actions) or directly jump to an example of a custom behaviour [here](/guides/manage/customize/behavior).
If you want to know more where actions can be useful, take a look at the feature [here](/concepts/features/actions) or directly jump to an example of a custom behavior [here](/guides/manage/customize/behavior).

View File

@ -3,7 +3,7 @@ title: ZITADEL Instance Settings
sidebar_label: Instance Settings
---
Instance settings work as default or fallback settings for your organizational settings. Most of the time you only have to set instance settings for the cases where you don't need specific behaviour in the organizations themselves or you only have one organization.
Instance settings work as default or fallback settings for your organizational settings. Most of the time you only have to set instance settings for the cases where you don't need specific behavior in the organizations themselves or you only have one organization.
To access instance settings, use the instance page at `{instanceDomain}/ui/console/settings` or click at the instance button on the **top-right** of the page and then navigate to settings in the navigation.
@ -17,7 +17,7 @@ When you configure your instance, you can set the following:
- **General**: Default Language for the UI
- [**Notification settings**](#notification-providers-and-smtp): Notification and Email Server settings, so initialization-, verification- and other mails are sent from your own domain. For SMS, Twilio is supported as notification provider.
- [**Login Behaviour and Access**](#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
- [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
- [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
- [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked.
@ -43,7 +43,7 @@ In the Branding settings, you can upload you Logo for the login interface, set y
| Icon | Upload your icon for the light and the dark design. Icons are used for smaller components. For example in console on the top left as the home button. |
| Colors | You can set four different colors to design your login page and email. (Background-, Primary-, Warn- and Font Color) |
| Font | Upload your custom font |
| Advanced Behaviour | **Hide Loginname suffix**: If enabled, your loginname suffix (Domain) will not be shown in the login page. **Disable Watermark**: If you disable the watermark you will not see the "Powered by ZITADEL" in the login page |
| Advanced Behavior | **Hide Loginname suffix**: If enabled, your loginname suffix (Domain) will not be shown in the login page. **Disable Watermark**: If you disable the watermark you will not see the "Powered by ZITADEL" in the login page |
Make sure you click the "Apply configuration" button after you finish your configuration. This will ensure your design is visible for your customers.
@ -86,7 +86,7 @@ No default provider is configured to send some SMS to your users. If you like to
<img src="/docs/img/guides/console/twilio.png" alt="Twilio" width="400px" />
## Login Behaviour and Access
## Login Behavior and Access
The Login Policy defines how the login process should look like and which authentication options a user has to authenticate.

View File

@ -12,12 +12,10 @@ In the right part of the console you can finde **MANAGERS** in the details part.
<img alt="Managers" src="/docs/img/guides/console/managers.png" width="200px" />
When adding a new manager, you can select multiple roles some of which are only allowed to read data.
This can be especially useful if you add service users for one of your projects where you only need read access.
import AddManager from "./_add_manager.mdx";
Per default you will only search for users within the selected organization. If you like to give a role to a user outside the organization you need to switch to the global search and type the exact loginname of the users. This will prevent allowing users to guess users from other organizations.
<AddManager name="AddManager" />
<img alt="Managers" src="/docs/img/guides/console/addmanager.png" width="390px" />
## Roles

View File

@ -39,7 +39,7 @@ At the moment the username only allows e-mail formatted input. (This will be cha
### User Loginname must contain orgdomain
If this behaviour is not suitable for you, ZITADEL has the option to suffix the usernames with the organization domain.
If this behavior is not suitable for you, ZITADEL has the option to suffix the usernames with the organization domain.
This setting is called **User Loginname must contain orgdomain** and is part of your [Domain settings](./instance-settings#domain-settings).
Those loginnames consist of the format `{username}@{domainname}.{zitadeldomain}`.
@ -105,7 +105,7 @@ Those settings are the same as on your instance.
> Note: that the following links, redirect to instance settings to omit redundancy.
- [**Login Behaviour and Access**](./instance-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
- [**Login Behavior and Access**](./instance-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
- [**Identity Providers**](./instance-settings#identity-providers): Define IDPs which are available for all organizations
- [**Password Complexity**](./instance-settings#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
- [**Lockout**](./instance-settings#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked.
@ -118,7 +118,7 @@ Those settings are the same as on your instance.
If you need custom branding on a organization (for example in a B2B scenario, where organizations are allowed to use their custom design), navigate back to the home page, choose your organization in the header above, navigate to the organization settings and set the custom design here.
The behaviour of the login page, applyling custom design, is then defined on your projects detail page. Read more about it [here](./projects#branding)
The behavior of the login page, applying custom design, is then defined on your projects detail page. Read more about it [here](./projects#branding)
## Show Organization Login

View File

@ -64,7 +64,7 @@ Organizations can then create authorizations for their users on their own. The p
### Branding
If you have different designs for your organizations or probably and use project grants, you can define the login behaviour on the project detail page.
If you have different designs for your organizations or probably and use project grants, you can define the login behavior on the project detail page.
<img
src="/docs/img/guides/console/projectbranding.png"
@ -84,7 +84,7 @@ In a B2B use case, you would typically use the organization setting. If you want
### Role settings
Below the branding settings, you can check different checkboxes to get even more custom behaviour on authentication.
Below the branding settings, you can check different checkboxes to get even more custom behavior on authentication.
- **Assert Roles on Authentication**:
Role information is sent from Userinfo endpoint and depending on your application settings in tokens and other types.

View File

@ -21,33 +21,9 @@ To get an understanding on how service users are used, take a look at our NextJS
## Create User
To create a new user, go to Users and click on **New**. Enter the required contact details and save by clicking “Create”.
import CreateUser from './_create-user.mdx';
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
<Tabs>
<TabItem value="human" label="Human User" default>
<img src="/docs/img/guides/console/addhuman.png" width="680px" alt="Add Human" />
</TabItem>
<TabItem value="service" label="Service User">
<img
src="/docs/img/guides/console/addmachine.png"
width="540px"
alt="Add Service User"
/>
</TabItem>
</Tabs>
After a human user is created, by default, an initialization mail with a code is sent to the registered email. This code then has to be verified on first login.
If you want to omit this mail, you can check the **email verified** and **set initial password** toggle.
If no password is set initially, the initialization mail prompting the user to set his password is sent.
You can prompt the user to add a second factor method too by checking the **Force MFA** toggle in [Login behaviour settings](./instance-settings#login-behaviour-and-access).
When logged in, a user can then manage his profile in console himself, adding a profile picture, external IDPs and Passwordless authentication devices.
<img src="/docs/img/guides/console/myprofile.png" alt="Profile Self Manage" />
<CreateUser/>
## Metadata

Some files were not shown because too many files have changed in this diff Show More