Merge branch 'v2-alpha' into v2-alpha-run-e2e-tests

This commit is contained in:
Elio Bischof 2022-07-08 11:27:43 +02:00
commit 2cb47b7877
No known key found for this signature in database
GPG Key ID: 7B383FDE4DDBF1BD
49 changed files with 8954 additions and 3443 deletions

View File

@ -3,7 +3,7 @@ module.exports = {
{name: 'main'}, {name: 'main'},
{name: '1.x.x', range: '1.x.x', channel: '1.x.x'}, {name: '1.x.x', range: '1.x.x', channel: '1.x.x'},
{name: 'v2-alpha', prerelease: true}, {name: 'v2-alpha', prerelease: true},
{name: 'notify-users', prerelease: true}, {name: 'auth-users', prerelease: true},
], ],
plugins: [ plugins: [
"@semantic-release/commit-analyzer" "@semantic-release/commit-analyzer"

View File

@ -22,9 +22,17 @@
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"], "assets": [
"styles": ["src/styles.scss"], "src/favicon.ico",
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"], "src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": [
"./node_modules/tinycolor2/dist/tinycolor-min.js"
],
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"@angular/common/locales/de", "@angular/common/locales/de",
"codemirror/mode/javascript/javascript", "codemirror/mode/javascript/javascript",
@ -122,15 +130,27 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"], "assets": [
"styles": ["./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css", "src/styles.scss"], "src/favicon.ico",
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"] "src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
"src/styles.scss"
],
"scripts": [
"./node_modules/tinycolor2/dist/tinycolor-min.js"
]
} }
}, },
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] "lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
} }
}, },
"e2e": { "e2e": {

11242
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,18 +12,18 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.1", "@angular/animations": "^14.0.4",
"@angular/cdk": "^14.0.1", "@angular/cdk": "^14.0.4",
"@angular/common": "^14.0.1", "@angular/common": "^14.0.4",
"@angular/compiler": "^14.0.1", "@angular/compiler": "^14.0.4",
"@angular/core": "^14.0.1", "@angular/core": "^14.0.4",
"@angular/forms": "^14.0.1", "@angular/forms": "^14.0.4",
"@angular/material": "^14.0.1", "@angular/material": "^14.0.4",
"@angular/material-moment-adapter": "^14.0.1", "@angular/material-moment-adapter": "^14.0.4",
"@angular/platform-browser": "^14.0.1", "@angular/platform-browser": "^14.0.4",
"@angular/platform-browser-dynamic": "^14.0.1", "@angular/platform-browser-dynamic": "^14.0.4",
"@angular/router": "^14.0.1", "@angular/router": "^14.0.4",
"@angular/service-worker": "^14.0.1", "@angular/service-worker": "^14.0.4",
"@ctrl/ngx-codemirror": "^5.1.1", "@ctrl/ngx-codemirror": "^5.1.1",
"@grpc/grpc-js": "^1.5.7", "@grpc/grpc-js": "^1.5.7",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
@ -36,7 +36,7 @@
"codemirror": "^5.65.0", "codemirror": "^5.65.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"google-proto-files": "^2.5.0", "google-proto-files": "^3.0.0",
"google-protobuf": "^3.19.4", "google-protobuf": "^3.19.4",
"grpc-web": "^1.3.0", "grpc-web": "^1.3.0",
"libphonenumber-js": "^1.10.6", "libphonenumber-js": "^1.10.6",
@ -52,32 +52,32 @@
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.0.1", "@angular-devkit/build-angular": "^14.0.4",
"@angular-eslint/builder": "^14.0.0-alpha.3", "@angular-eslint/builder": "^14.0.0",
"@angular-eslint/eslint-plugin": "^14.0.0-alpha.3", "@angular-eslint/eslint-plugin": "^14.0.0",
"@angular-eslint/eslint-plugin-template": "^14.0.0-alpha.3", "@angular-eslint/eslint-plugin-template": "^14.0.0",
"@angular-eslint/schematics": "^14.0.0-alpha.3", "@angular-eslint/schematics": "^14.0.0",
"@angular-eslint/template-parser": "^14.0.0-alpha.3", "@angular-eslint/template-parser": "^14.0.0",
"@angular/cli": "^14.0.1", "@angular/cli": "^14.0.4",
"@angular/compiler-cli": "^14.0.1", "@angular/compiler-cli": "^14.0.4",
"@angular/language-service": "^14.0.1", "@angular/language-service": "^14.0.4",
"@types/jasmine": "~4.0.3", "@types/jasmine": "~4.0.3",
"@types/jasminewd2": "~2.0.10", "@types/jasminewd2": "~2.0.10",
"@types/jsonwebtoken": "^8.5.5", "@types/jsonwebtoken": "^8.5.5",
"@types/node": "^17.0.42", "@types/node": "^17.0.42",
"@typescript-eslint/eslint-plugin": "5.25.0", "@typescript-eslint/eslint-plugin": "5.30.4",
"@typescript-eslint/parser": "5.27.0", "@typescript-eslint/parser": "5.30.4",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"cypress": "^10.1.0", "cypress": "^10.1.0",
"cypress-terminal-report": "^4.0.1", "cypress-terminal-report": "^4.0.1",
"eslint": "^8.17.0", "eslint": "^8.18.0",
"jasmine-core": "~4.1.1", "jasmine-core": "~4.2.0",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"karma": "~6.3.16", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~5.0.1", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.0.0", "karma-jasmine-html-reporter": "^2.0.0",
"mochawesome": "^7.1.2", "mochawesome": "^7.1.2",
"prettier": "^2.4.1", "prettier": "^2.4.1",

View File

@ -13,6 +13,10 @@ const routes: Routes = [
loadChildren: () => import('./pages/home/home.module').then((m) => m.HomeModule), loadChildren: () => import('./pages/home/home.module').then((m) => m.HomeModule),
canActivate: [AuthGuard], canActivate: [AuthGuard],
}, },
{
path: 'signedout',
loadChildren: () => import('./pages/signedout/signedout.module').then((m) => m.SignedoutModule),
},
{ {
path: 'orgs', path: 'orgs',
loadChildren: () => import('./pages/org-list/org-list.module').then((m) => m.OrgListModule), loadChildren: () => import('./pages/org-list/org-list.module').then((m) => m.OrgListModule),
@ -38,12 +42,7 @@ const routes: Routes = [
{ {
path: 'users', path: 'users',
canActivate: [AuthGuard], canActivate: [AuthGuard],
children: [ loadChildren: () => import('src/app/pages/users/users.module').then((m) => m.UsersModule),
{
path: '',
loadChildren: () => import('src/app/pages/users/users.module').then((m) => m.UsersModule),
},
],
}, },
{ {
path: 'instance', path: 'instance',
@ -170,10 +169,6 @@ const routes: Routes = [
roles: ['policy.read'], roles: ['policy.read'],
}, },
}, },
{
path: 'signedout',
loadChildren: () => import('./pages/signedout/signedout.module').then((m) => m.SignedoutModule),
},
{ {
path: '**', path: '**',
redirectTo: '/', redirectTo: '/',

View File

@ -1,32 +1,31 @@
<ng-container *ngIf="(authService.user | async) || undefined as user"> <div class="main-container">
<ng-container *ngIf="['iam.read$', 'iam.write$'] | hasRole as iamuser$"> <ng-container *ngIf="(authService.user | async) || {} as user">
<div class="main-container"> <cnsl-header
<cnsl-header *ngIf="user && user !== {}"
*ngIf="user" [org]="org"
[org]="org" [user]="$any(user)"
[user]="user" [isDarkTheme]="componentCssClass === 'dark-theme'"
[isDarkTheme]="componentCssClass === 'dark-theme'" [labelpolicy]="labelpolicy"
[labelpolicy]="labelpolicy" (changedActiveOrg)="changedOrg($event)"
(changedActiveOrg)="changedOrg($event)" ></cnsl-header>
></cnsl-header>
<cnsl-nav <cnsl-nav
id="mainnav" id="mainnav"
class="nav" class="nav"
[ngClass]="{ shadow: yoffset > 60 }" [ngClass]="{ shadow: yoffset > 60 }"
*ngIf="user" *ngIf="user && user !== {}"
[org]="org" [org]="org"
[user]="user" [user]="$any(user)"
[isDarkTheme]="componentCssClass === 'dark-theme'" [isDarkTheme]="componentCssClass === 'dark-theme'"
[labelpolicy]="labelpolicy" [labelpolicy]="labelpolicy"
></cnsl-nav> ></cnsl-nav>
<div class="router-container" [@routeAnimations]="prepareRoute(outlet)">
<div class="outlet">
<router-outlet class="outlet" #outlet="outlet"></router-outlet>
</div>
</div>
<span class="fill-space"></span>
<cnsl-footer [privateLabelPolicy]="labelpolicy"></cnsl-footer>
</div>
</ng-container> </ng-container>
</ng-container>
<div class="router-container" [@routeAnimations]="prepareRoute(outlet)">
<div class="outlet">
<router-outlet class="outlet" #outlet="outlet"></router-outlet>
</div>
</div>
<span class="fill-space"></span>
<cnsl-footer [privateLabelPolicy]="labelpolicy"></cnsl-footer>
</div>

View File

@ -69,6 +69,7 @@ export class AppComponent implements OnDestroy {
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
@Inject(DOCUMENT) private document: Document, @Inject(DOCUMENT) private document: Document,
) { ) {
this.themeService.loadPrivateLabelling(true);
console.log( console.log(
'%cWait!', '%cWait!',
'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5469D4; font-size: 50px', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5469D4; font-size: 50px',

View File

@ -26,7 +26,6 @@ import { HeaderModule } from './modules/header/header.module';
import { KeyboardShortcutsModule } from './modules/keyboard-shortcuts/keyboard-shortcuts.module'; import { KeyboardShortcutsModule } from './modules/keyboard-shortcuts/keyboard-shortcuts.module';
import { NavModule } from './modules/nav/nav.module'; import { NavModule } from './modules/nav/nav.module';
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module'; import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
import { SignedoutComponent } from './pages/signedout/signedout.component';
import { HasRolePipeModule } from './pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from './pipes/has-role-pipe/has-role-pipe.module';
import { AdminService } from './services/admin.service'; import { AdminService } from './services/admin.service';
import { AuthenticationService } from './services/authentication.service'; import { AuthenticationService } from './services/authentication.service';
@ -79,7 +78,7 @@ const authConfig: AuthConfig = {
}; };
@NgModule({ @NgModule({
declarations: [AppComponent, SignedoutComponent], declarations: [AppComponent],
imports: [ imports: [
AppRoutingModule, AppRoutingModule,
CommonModule, CommonModule,

View File

@ -4,13 +4,12 @@ import { AuthConfig } from 'angular-oauth2-oidc';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
import { GrpcAuthService } from '../services/grpc-auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private auth: AuthenticationService, private authService: GrpcAuthService) {} constructor(private auth: AuthenticationService) {}
public canActivate( public canActivate(
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,

View File

@ -196,55 +196,56 @@
</a> </a>
</div> </div>
<div class="account-card-wrapper"> <ng-container
<button *ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
cdkOverlayOrigin
#accounttrigger="cdkOverlayOrigin"
class="icon-container"
(click)="showAccount = !showAccount"
[ngClass]="{ 'iam-user': (['iam.write$'] | hasRole | async) }"
>
<cnsl-avatar
id="avatartoggle"
*ngIf="
user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))
"
class="avatar-toggle dontcloseonclick"
[active]="showAccount"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName || ''"
[name]="
user.human?.profile?.displayName
? user.human?.profile?.displayName ?? ''
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
"
[size]="38"
>
</cnsl-avatar>
</button>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="accounttrigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="accountCardPositions"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
[cdkConnectedOverlayOpen]="showAccount"
(backdropClick)="showAccount = false"
(detach)="showAccount = false"
> >
<cnsl-accounts-card <div class="account-card-wrapper">
@accounts <button
class="a_card" cdkOverlayOrigin
*ngIf="showAccount" #accounttrigger="cdkOverlayOrigin"
(closedCard)="showAccount = false" class="icon-container"
[user]="user" (click)="showAccount = !showAccount"
[iamuser]="['iam.write$'] | hasRole | async" [ngClass]="{ 'iam-user': (['iam.write$'] | hasRole | async) }"
>
<cnsl-avatar
id="avatartoggle"
class="avatar-toggle dontcloseonclick"
[active]="showAccount"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName || ''"
[name]="
user.human?.profile?.displayName
? user.human?.profile?.displayName ?? ''
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
"
[size]="38"
>
</cnsl-avatar>
</button>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="accounttrigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="accountCardPositions"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
[cdkConnectedOverlayOpen]="showAccount"
(backdropClick)="showAccount = false"
(detach)="showAccount = false"
> >
</cnsl-accounts-card> <cnsl-accounts-card
</ng-template> @accounts
class="a_card"
*ngIf="showAccount"
(closedCard)="showAccount = false"
[user]="user"
[iamuser]="['iam.write$'] | hasRole | async"
>
</cnsl-accounts-card>
</ng-template>
</ng-container>
</div> </div>
</mat-toolbar> </mat-toolbar>

View File

@ -5,7 +5,25 @@
[isInactive]="org?.state === OrgState.ORG_STATE_INACTIVE" [isInactive]="org?.state === OrgState.ORG_STATE_INACTIVE"
[hasContributors]="true" [hasContributors]="true"
stateTooltip="{{ 'ORG.STATE.' + org?.state | translate }}" stateTooltip="{{ 'ORG.STATE.' + org?.state | translate }}"
[hasActions]="['org.write:' + org?.id, 'org.write$'] | hasRole | async"
> >
<ng-template topActions cnslHasRole [hasRole]="['org.write:' + org?.id, 'org.write$']">
<button
mat-menu-item
*ngIf="org?.state === OrgState.ORG_STATE_ACTIVE"
(click)="changeState(OrgState.ORG_STATE_INACTIVE)"
>
{{ 'ORG.PAGES.DEACTIVATE' | translate }}
</button>
<button
mat-menu-item
*ngIf="org?.state === OrgState.ORG_STATE_INACTIVE"
(click)="changeState(OrgState.ORG_STATE_ACTIVE)"
>
{{ 'ORG.PAGES.REACTIVATE' | translate }}
</button>
</ng-template>
<cnsl-contributors <cnsl-contributors
topContributors topContributors
[totalResult]="totalMemberResult" [totalResult]="totalMemberResult"

View File

@ -7,6 +7,7 @@ import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-m
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component'; import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum'; import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb'; import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb'; import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb'; import { User } from 'src/app/proto/generated/zitadel/user_pb';
@ -62,6 +63,56 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.destroy$.complete(); this.destroy$.complete();
} }
public changeState(newState: OrgState): void {
if (newState === OrgState.ORG_STATE_ACTIVE) {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.REACTIVATE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'ORG.DIALOG.REACTIVATE.TITLE',
descriptionKey: 'ORG.DIALOG.REACTIVATE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
this.mgmtService
.reactivateOrg()
.then(() => {
this.toast.showInfo('ORG.TOAST.REACTIVATED', true);
this.org.state = OrgState.ORG_STATE_ACTIVE;
})
.catch((error) => {
this.toast.showError(error);
});
}
});
} else if (newState === OrgState.ORG_STATE_INACTIVE) {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DEACTIVATE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'ORG.DIALOG.DEACTIVATE.TITLE',
descriptionKey: 'ORG.DIALOG.DEACTIVATE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
this.mgmtService
.deactivateOrg()
.then(() => {
this.toast.showInfo('ORG.TOAST.DEACTIVATED', true);
this.org.state = OrgState.ORG_STATE_INACTIVE;
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
}
private async getData(): Promise<void> { private async getData(): Promise<void> {
this.mgmtService this.mgmtService
.getMyOrg() .getMyOrg()

View File

@ -4,14 +4,14 @@ import { RouterModule, Routes } from '@angular/router';
import { SignedoutComponent } from './signedout.component'; import { SignedoutComponent } from './signedout.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: SignedoutComponent, component: SignedoutComponent,
}, },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class SignedoutRoutingModule { } export class SignedoutRoutingModule {}

View File

@ -5,11 +5,18 @@
<ng-template #lighttheme> <ng-template #lighttheme>
<img alt="zitadel logo" src="../../../assets/images/zitadel-logo-dark.svg" /> <img alt="zitadel logo" src="../../../assets/images/zitadel-logo-dark.svg" />
</ng-template> </ng-template>
<p class="cnsl-secondary-text">{{'USER.SIGNEDOUT' | translate}}</p> <p class="cnsl-secondary-text">{{ 'USER.SIGNEDOUT' | translate }}</p>
<button matTooltip="{{'ACTIONS.LOGIN' | translate}}" color="primary" mat-raised-button <button
[routerLink]="[ '/users/me' ]">{{'USER.SIGNEDOUT_BTN' | class="cnsl-action-button"
translate}} <i class="las la-sign-in-alt"></i></button> matTooltip="{{ 'ACTIONS.LOGIN' | translate }}"
color="primary"
mat-raised-button
[routerLink]="['/users/me']"
>
<i class="las la-sign-in-alt"></i>
<span>{{ 'USER.SIGNEDOUT_BTN' | translate }}</span>
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,6 +9,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-top: 50px;
h1 { h1 {
font-size: 3rem; font-size: 3rem;
@ -24,16 +25,6 @@
img { img {
height: 100px; height: 100px;
max-width: 170px; max-width: 170px;
margin-bottom: 2rem;
}
button {
display: block;
padding: 0.5rem 4rem;
i {
margin-left: 0.5rem;
}
} }
} }
} }

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ThemeService } from 'src/app/services/theme.service';
@Component({ @Component({
selector: 'cnsl-signedout', selector: 'cnsl-signedout',
@ -8,7 +9,9 @@ import { Component } from '@angular/core';
export class SignedoutComponent { export class SignedoutComponent {
public dark: boolean = true; public dark: boolean = true;
constructor() { constructor(themeService: ThemeService) {
themeService.loadPrivateLabelling();
const theme = localStorage.getItem('theme'); const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true; this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
} }

View File

@ -1,15 +1,15 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from 'src/app/modules/shared/shared.module'; import { SharedModule } from 'src/app/modules/shared/shared.module';
import { SignedoutRoutingModule } from './signedout-routing.module'; import { SignedoutRoutingModule } from './signedout-routing.module';
import { SignedoutComponent } from './signedout.component';
@NgModule({ @NgModule({
declarations: [], declarations: [SignedoutComponent],
imports: [ imports: [CommonModule, SignedoutRoutingModule, MatButtonModule, MatTooltipModule, TranslateModule, SharedModule],
CommonModule,
SignedoutRoutingModule,
SharedModule,
],
}) })
export class SignedoutModule { } export class SignedoutModule {}

View File

@ -49,10 +49,10 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy {
switchMap((url: string) => { switchMap((url: string) => {
if (url.includes('?login_hint=')) { if (url.includes('?login_hint=')) {
const newUrl = this.removeParam('login_hint', url); const newUrl = this.removeParam('login_hint', url);
const urlWithoutBasePath = newUrl.startsWith('/ui/console') ? newUrl.replace('/ui/console', '') : newUrl; const urlWithoutBasePath = newUrl.includes('/ui/console') ? newUrl.replace('/ui/console', '') : newUrl;
return of(this.processor.createState(urlWithoutBasePath)); return of(this.processor.createState(urlWithoutBasePath));
} else if (url) { } else if (url) {
const urlWithoutBasePath = url.startsWith('/ui/console') ? url.replace('/ui/console', '') : url; const urlWithoutBasePath = url.includes('/ui/console') ? url.replace('/ui/console', '') : url;
return of(this.processor.createState(urlWithoutBasePath)); return of(this.processor.createState(urlWithoutBasePath));
} else { } else {
return of(undefined); return of(undefined);

View File

@ -140,70 +140,72 @@ export class ThemeService {
this.saveTextColor(lightText, false); this.saveTextColor(lightText, false);
}; };
public loadPrivateLabelling(): void { public loadPrivateLabelling(forceDefault: boolean = false): void {
this.setDefaultColors(); if (forceDefault) {
this.setDefaultColors();
} else {
const isDark = (color: string) => this.isDark(color);
const isLight = (color: string) => this.isLight(color);
const isDark = (color: string) => this.isDark(color); this.authService
const isLight = (color: string) => this.isLight(color); .getMyLabelPolicy()
.then((lpresp) => {
const labelpolicy = lpresp.policy;
this.authService const darkPrimary = labelpolicy?.primaryColorDark || '#bbbafa';
.getMyLabelPolicy() const lightPrimary = labelpolicy?.primaryColor || '#5469d4';
.then((lpresp) => {
const labelpolicy = lpresp.policy;
const darkPrimary = labelpolicy?.primaryColorDark || '#bbbafa'; const darkWarn = labelpolicy?.warnColorDark || '#ff3b5b';
const lightPrimary = labelpolicy?.primaryColor || '#5469d4'; const lightWarn = labelpolicy?.warnColor || '#cd3d56';
const darkWarn = labelpolicy?.warnColorDark || '#ff3b5b'; let darkBackground = labelpolicy?.backgroundColorDark;
const lightWarn = labelpolicy?.warnColor || '#cd3d56'; let lightBackground = labelpolicy?.backgroundColor;
let darkBackground = labelpolicy?.backgroundColorDark; let darkText = labelpolicy?.fontColorDark ?? '#ffffff';
let lightBackground = labelpolicy?.backgroundColor; let lightText = labelpolicy?.fontColor ?? '#000000';
let darkText = labelpolicy?.fontColorDark ?? '#ffffff'; this.savePrimaryColor(darkPrimary, true);
let lightText = labelpolicy?.fontColor ?? '#000000'; this.savePrimaryColor(lightPrimary, false);
this.savePrimaryColor(darkPrimary, true); this.saveWarnColor(darkWarn, true);
this.savePrimaryColor(lightPrimary, false); this.saveWarnColor(lightWarn, false);
this.saveWarnColor(darkWarn, true); if (darkBackground && !isDark(darkBackground)) {
this.saveWarnColor(lightWarn, false); console.info(
`Background (${darkBackground}) is not dark enough for a dark theme. Falling back to zitadel background`,
);
darkBackground = '#111827';
}
this.saveBackgroundColor(darkBackground || '#111827', true);
if (darkBackground && !isDark(darkBackground)) { if (lightBackground && !isLight(lightBackground)) {
console.info( console.info(
`Background (${darkBackground}) is not dark enough for a dark theme. Falling back to zitadel background`, `Background (${lightBackground}) is not light enough for a light theme. Falling back to zitadel background`,
); );
darkBackground = '#111827'; lightBackground = '#fafafa';
} }
this.saveBackgroundColor(darkBackground || '#111827', true); this.saveBackgroundColor(lightBackground || '#fafafa', false);
if (lightBackground && !isLight(lightBackground)) { if (darkText && !isLight(darkText)) {
console.info( console.info(
`Background (${lightBackground}) is not light enough for a light theme. Falling back to zitadel background`, `Text color (${darkText}) is not light enough for a dark theme. Falling back to zitadel's text color`,
); );
lightBackground = '#fafafa'; darkText = '#ffffff';
} }
this.saveBackgroundColor(lightBackground || '#fafafa', false); this.saveTextColor(darkText || '#ffffff', true);
if (darkText && !isLight(darkText)) { if (lightText && !isDark(lightText)) {
console.info( console.info(
`Text color (${darkText}) is not light enough for a dark theme. Falling back to zitadel's text color`, `Text color (${lightText}) is not dark enough for a light theme. Falling back to zitadel's text color`,
); );
darkText = '#ffffff'; lightText = '#000000';
} }
this.saveTextColor(darkText || '#ffffff', true); this.saveTextColor(lightText || '#000000', false);
})
if (lightText && !isDark(lightText)) { .catch((error) => {
console.info( console.error('could not load private labelling policy!', error);
`Text color (${lightText}) is not dark enough for a light theme. Falling back to zitadel's text color`, this.setDefaultColors();
); });
lightText = '#000000'; }
}
this.saveTextColor(lightText || '#000000', false);
})
.catch((error) => {
console.error('could not load private labelling policy!', error);
this.setDefaultColors();
});
} }
} }

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Wähle eine Organisation aus.", "LISTDESCRIPTION": "Wähle eine Organisation aus.",
"ACTIVE": "Aktiv", "ACTIVE": "Aktiv",
"CREATE": "Organisation erstellen", "CREATE": "Organisation erstellen",
"DEACTIVATE": "Organisation deaktivieren",
"REACTIVATE": "Organisation reaktivieren",
"NOPERMISSION": "Sie haben keine Berechtigung, auf Einstellungen der Organisation zuzugreifen.", "NOPERMISSION": "Sie haben keine Berechtigung, auf Einstellungen der Organisation zuzugreifen.",
"USERSELFACCOUNT": "Verwenden Sie Ihr persönliches Konto als Organisationsinhaber", "USERSELFACCOUNT": "Verwenden Sie Ihr persönliches Konto als Organisationsinhaber",
"ORGDETAIL_TITLE": "Gebe den Namen und die Domain für die neue Organisation ein.", "ORGDETAIL_TITLE": "Gebe den Namen und die Domain für die neue Organisation ein.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager entfernt.", "MEMBERREMOVED": "Manager entfernt.",
"MEMBERCHANGED": "Manager geändert.", "MEMBERCHANGED": "Manager geändert.",
"SETPRIMARY": "Primäre Domain gesetzt." "SETPRIMARY": "Primäre Domain gesetzt."
},
"DIALOG": {
"DEACTIVATE": {
"TITLE": "Organisation deaktivieren",
"DESCRIPTION": "Sie sind im Begriff Ihre Organisation zu deaktivieren. User können Sich danach nicht mehr anmelden? Wollen Sie fortfahren?"
},
"REACTIVATE": {
"TITLE": "Organisation reaktivieren",
"DESCRIPTION": "Sie sind im Begriff Ihre Organisation zu reaktivieren. User können Sich danach wieder anmelden? Wollen Sie fortfahren?"
}
} }
}, },
"SETTINGS": { "SETTINGS": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Choose an organization.", "LISTDESCRIPTION": "Choose an organization.",
"ACTIVE": "Active", "ACTIVE": "Active",
"CREATE": "Create Organization", "CREATE": "Create Organization",
"DEACTIVATE": "Deactivate Organization",
"REACTIVATE": "Reactivate Organization",
"NOPERMISSION": "You don't have the permission to access organization settings.", "NOPERMISSION": "You don't have the permission to access organization settings.",
"USERSELFACCOUNT": "Use your personal account as organization owner", "USERSELFACCOUNT": "Use your personal account as organization owner",
"ORGDETAIL_TITLE": "Enter the name and domain of your new organization.", "ORGDETAIL_TITLE": "Enter the name and domain of your new organization.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager removed.", "MEMBERREMOVED": "Manager removed.",
"MEMBERCHANGED": "Manager changed.", "MEMBERCHANGED": "Manager changed.",
"SETPRIMARY": "Primary domain set." "SETPRIMARY": "Primary domain set."
},
"DIALOG": {
"DEACTIVATE": {
"TITLE": "Deactivate organization",
"DESCRIPTION": "You are about to deactivate your organization. Users won't be able to login afterwards. Are you sure to proceed?"
},
"REACTIVATE": {
"TITLE": "Reactivate organization",
"DESCRIPTION": "You are about to reactivate your organization. Users will be able to login again. Are you sure to proceed?"
}
} }
}, },
"SETTINGS": { "SETTINGS": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Choisissez une organisation.", "LISTDESCRIPTION": "Choisissez une organisation.",
"ACTIVE": "Actif", "ACTIVE": "Actif",
"CREATE": "Créer une organisation", "CREATE": "Créer une organisation",
"DEACTIVATE": "Désactiver l'organisation",
"REACTIVATE": "Réactiver l'organisation",
"NOPERMISSION": "Vous n'avez pas la permission d'accéder aux paramètres de l'organisation.", "NOPERMISSION": "Vous n'avez pas la permission d'accéder aux paramètres de l'organisation.",
"USERSELFACCOUNT": "Utilisez votre compte personnel comme propriétaire de l'organisation", "USERSELFACCOUNT": "Utilisez votre compte personnel comme propriétaire de l'organisation",
"ORGDETAIL_TITLE": "Saisissez le nom et le domaine de votre nouvelle organisation.", "ORGDETAIL_TITLE": "Saisissez le nom et le domaine de votre nouvelle organisation.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Gestionnaire supprimé.", "MEMBERREMOVED": "Gestionnaire supprimé.",
"MEMBERCHANGED": "Gestionnaire modifié.", "MEMBERCHANGED": "Gestionnaire modifié.",
"SETPRIMARY": "Domaine primaire défini." "SETPRIMARY": "Domaine primaire défini."
},
"DIALOG": {
"DEACTIVATE": {
"TITLE": "Désactiver l'organisation",
"DESCRIPTION": "Vous êtes sur le point de désactiver votre organisation. Les utilisateurs ne peuvent plus se connecter ? Voulez-vous continuer ?"
},
"REACTIVATE": {
"TITLE": "Réactiver l'organisation",
"DESCRIPTION": "Vous êtes sur le point de réactiver votre organisation. Les utilisateurs peuvent ensuite se reconnecter ? Voulez-vous continuer ?"
}
} }
}, },
"SETTINGS": { "SETTINGS": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Scegli un'organizzazione.", "LISTDESCRIPTION": "Scegli un'organizzazione.",
"ACTIVE": "Attivo", "ACTIVE": "Attivo",
"CREATE": "Creare un'organizzazione", "CREATE": "Creare un'organizzazione",
"DEACTIVATE": "Disattiva organizzazione",
"REACTIVATE": "Riattiva organizzazione",
"NOPERMISSION": "Non hai l'autorizzazione per accedere alle impostazioni dell'organizzazione.", "NOPERMISSION": "Non hai l'autorizzazione per accedere alle impostazioni dell'organizzazione.",
"USERSELFACCOUNT": "Usa il tuo account personale come proprietario dell'organizzazione", "USERSELFACCOUNT": "Usa il tuo account personale come proprietario dell'organizzazione",
"ORGDETAIL_TITLE": "Inserisci il nome e il dominio della tua nuova organizzazione.", "ORGDETAIL_TITLE": "Inserisci il nome e il dominio della tua nuova organizzazione.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager rimosso con successo", "MEMBERREMOVED": "Manager rimosso con successo",
"MEMBERCHANGED": "Manager cambiato con successo", "MEMBERCHANGED": "Manager cambiato con successo",
"SETPRIMARY": "Dominio primario cambiato con successo" "SETPRIMARY": "Dominio primario cambiato con successo"
},
"DIALOG": {
"DEACTIVATE": {
"TITLE": "Disattivare l'organizzazione",
"DESCRIPTION": "Stai per disattivate la tua organizzazione. Utenti dell' organizzazione non possono più accedere in seguito. Sei sicuro di procedere?"
},
"REACTIVATE": {
"TITLE": "Riattivare l'organizzazione",
"DESCRIPTION": "Stai per riattivare la tua organizzazione. Utenti dell' organizzazione possono accedere nuovamente dopo l'attivazione. Vuoi procedere?"
}
} }
}, },
"SETTINGS": { "SETTINGS": {

View File

@ -1,34 +1,31 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>Console</title> <title>Console</title>
<base href="/"> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" href="./assets/icons/line-awesome/css/line-awesome.min.css" /> <link rel="stylesheet" href="./assets/icons/line-awesome/css/line-awesome.min.css" />
<link rel="manifest" href="manifest.webmanifest"> <link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#e6768b"> <meta name="theme-color" content="#e6768b" />
<meta property="description" content="Console Management Platform for ZITADEL IAM" /> <meta property="description" content="Console Management Platform for ZITADEL IAM" />
<meta property="og:url" content="https://console.zitadel.ch" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="ZITADEL Console" /> <meta property="og:title" content="ZITADEL Console" />
<meta property="og:description" content="Console Management Platform for ZITADEL IAM" /> <meta property="og:description" content="Console Management Platform for ZITADEL IAM" />
<meta property="description" content="Management Platform for ZITADEL IAM" /> <meta property="description" content="Management Platform for ZITADEL IAM" />
<meta property="og:image" content="https://www.zitadel.ch/zitadel-social-preview25.png" /> <meta property="og:image" content="https://www.zitadel.com/images/preview.png" />
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@zitadel_ch"> <meta name="twitter:site" content="@zitadel" />
<meta name="twitter:title" content="ZITADEL Console" /> <meta name="twitter:title" content="ZITADEL Console" />
<meta name="twitter:description" content="Management Platform for ZITADEL IAM" /> <meta name="twitter:description" content="Management Platform for ZITADEL IAM" />
<meta name="twitter:image" content="https://www.zitadel.ch/zitadel-social-preview25.png"> <meta name="twitter:image" content="https://www.zitadel.com/images/preview.png" />
</head> </head>
<body> <body>
<cnsl-root></cnsl-root> <cnsl-root></cnsl-root>
<noscript>Please enable JavaScript to continue using this application.</noscript> <noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>
</html>
</html>

View File

@ -0,0 +1,4 @@
Open your favorite internet browser and navigate to [http://localhost:8080/ui/console](http://localhost:8080/ui/console).
This is the default IAM admin users login:
- **username**: *zitadel-admin@<span></span>zitadel.localhost*
- **password**: *Password1!*

View File

@ -1,10 +1,7 @@
## Disclaimer
This guide is for development / demonstration purpose only and does NOT reflect a production setup.
## New Knative environment ## New Knative environment
### Download and run Knative quickstart ### Download and run Knative quickstart
Follow the Knative quickstart guide to get a local kind/minikube environment with Knative capabilities. Follow the [Knative quickstart guide](https://knative.dev/docs/getting-started/quickstart-install/) to get a local kind/minikube environment with Knative capabilities.
It is basically 4 commands on Mac: It is basically 4 commands on Mac:
@ -24,10 +21,6 @@ kn quickstart kind
That will get you a ready to go knative/kubernetes environment. That will get you a ready to go knative/kubernetes environment.
See Knative documentation here:
https://knative.dev/docs/install/quickstart-install/
## Database ## Database
start a single-node cockroachdb as statefulset start a single-node cockroachdb as statefulset
@ -35,14 +28,6 @@ start a single-node cockroachdb as statefulset
kubectl apply -f https://raw.githubusercontent.com/zitadel/zitadel/v2-alpha/deploy/knative/cockroachdb-statefulset-single-node.yaml kubectl apply -f https://raw.githubusercontent.com/zitadel/zitadel/v2-alpha/deploy/knative/cockroachdb-statefulset-single-node.yaml
``` ```
## Secret for TLS termination
create a secret with your certificates for TLS termination
```bash
#describe happy path
kubectl apply secret -f certs.yaml
```
## Start ZITADEL with Knative ## Start ZITADEL with Knative
```bash ```bash
@ -58,9 +43,7 @@ kn service create zitadel \
--env ZITADEL_TLS_ENABLED=false \ --env ZITADEL_TLS_ENABLED=false \
--env ZITADEL_EXTERNALDOMAIN=zitadel.default.127.0.0.1.sslip.io \ --env ZITADEL_EXTERNALDOMAIN=zitadel.default.127.0.0.1.sslip.io \
--env ZITADEL_S3DEFAULTINSTANCE_CUSTOMDOMAIN=zitadel.default.127.0.0.1.sslip.io \ --env ZITADEL_S3DEFAULTINSTANCE_CUSTOMDOMAIN=zitadel.default.127.0.0.1.sslip.io \
--arg "admin" --arg "start-from-init" --arg "--masterkey" --arg "MasterkeyNeedsToHave32Characters" \ --arg "start-from-init" --arg "--masterkey" --arg "MasterkeyNeedsToHave32Characters"
--mount /tls.secret=secret:certs/tls.secret \
--mount /tls.key=secret:certs/tls.key
``` ```
or use the knative service yaml or use the knative service yaml
@ -83,6 +66,6 @@ http://zitadel.default.127.0.0.1.sslip.io/ui/console
If you didn't configure something else, this is the default IAM admin users login: If you didn't configure something else, this is the default IAM admin users login:
* username: zitadel-admin@zitadel.zitadel.default.127.0.0.1.sslip.io * username: zitadel-admin@<span></span>zitadel.zitadel.default.127.0.0.1.sslip.io
* password: Password1! * password: Password1!

View File

@ -1,7 +1,3 @@
Open your favorite internet browser and navigate to [http://localhost:8080/ui/console](http://localhost:8080/ui/console).
This is the default IAM admin users login:
- **username**: *zitadel-admin@<span></span>zitadel.localhost*
- **password**: *Password1!*
## What's next ## What's next

View File

@ -11,6 +11,7 @@ import Compose from './compose.mdx'
import Helm from './helm.mdx' import Helm from './helm.mdx'
import Knative from './knative.mdx' import Knative from './knative.mdx'
import NextSelfHosted from './nextselfhosted.mdx' import NextSelfHosted from './nextselfhosted.mdx'
import DefaultUser from './defaultuser.mdx'
# Run ZITADEL # Run ZITADEL
@ -18,6 +19,16 @@ Choose your platform and run ZITADEL with the most minimal configuration possibl
For an easy self-hosted production setup, we recommend running ZITADEL on [Kubernetes](https://kubernetes.io/docs/home/), using our official [Helm](https://helm.sh/docs/) chart. For an easy self-hosted production setup, we recommend running ZITADEL on [Kubernetes](https://kubernetes.io/docs/home/), using our official [Helm](https://helm.sh/docs/) chart.
By default, it runs a highly available ZITADEL instance along with a secure and highly available [CockroachDB](https://www.cockroachlabs.com/docs/stable/) instance. By default, it runs a highly available ZITADEL instance along with a secure and highly available [CockroachDB](https://www.cockroachlabs.com/docs/stable/) instance.
## Disclaimer
This guide is for development / demonstration purpose only and does NOT reflect a production setup.
Things such as TLS termination and email verification will not be available unless you
- use an API gateway with valid certificates in front of the service
- configure an appropriate email server
see loadbalancing example [here](/docs/guides/installation/loadbalancing-example)
<!-- TODO: Destroy --> <!-- TODO: Destroy -->
<Tabs <Tabs
@ -37,21 +48,26 @@ By default, it runs a highly available ZITADEL instance along with a secure and
</TabItem> </TabItem>
<TabItem value="linux"> <TabItem value="linux">
<Linux/> <Linux/>
<DefaultUser/>
<NextSelfHosted/> <NextSelfHosted/>
</TabItem> </TabItem>
<TabItem value="macos"> <TabItem value="macos">
<MacOS/> <MacOS/>
<DefaultUser/>
<NextSelfHosted/> <NextSelfHosted/>
</TabItem> </TabItem>
<TabItem value="compose"> <TabItem value="compose">
<Compose/> <Compose/>
<DefaultUser/>
<NextSelfHosted/> <NextSelfHosted/>
</TabItem> </TabItem>
<TabItem value="k8s"> <TabItem value="k8s">
<Helm/> <Helm/>
<DefaultUser/>
<NextSelfHosted/> <NextSelfHosted/>
</TabItem> </TabItem>
<TabItem value="knative"> <TabItem value="knative">
<Knative/> <Knative/>
<NextSelfHosted/>
</TabItem> </TabItem>
</Tabs> </Tabs>

View File

@ -3,32 +3,32 @@ title: Angular
--- ---
This integration guide shows you the recommended way to integrate ZITADEL into your Angular application. This integration guide shows you the recommended way to integrate ZITADEL into your Angular application.
It shows how to add user login to your application and fetch some data from the user info endpoint. It shows how to add user login to your application and fetch some data from the user info endpoint.
At the end of the guide, your application has login functionality and has access to the current user's profile. At the end of the guide, your application has login functionality and has access to the current user's profile.
> This documentation refers to our [example](https://github.com/zitadel/zitadel-examples/tree/main/angular) in GitHub. Note that we've written ZITADEL Console in Angular, so you can also use that as a reference. > This documentation refers to our [example](https://github.com/zitadel/zitadel-examples/tree/main/angular) in GitHub. Note that we've written ZITADEL Console in Angular, so you can also use that as a reference.
## Setup Application and Get Keys ## Setup Application and Get Keys
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console. 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](https://console.zitadel.ch/projects), then add a new application at the top of the page. You will 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 Web application type and continue. Select Web application type and continue.
We recommend you use [Authorization Code](../../apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange (PKCE)](../../apis/openidoauth/grant-types#proof-key-for-code-exchange) for all web applications. We recommend you use [Authorization Code](../../apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange (PKCE)](../../apis/openidoauth/grant-types#proof-key-for-code-exchange) for all web applications.
![Create app in console](/img/angular/app-create-light.png) ![Create app in console](/img/angular/app-create.png)
### Redirect URIs ### Redirect URIs
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. 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.
> If you are following along with the [example](https://github.com/zitadel/zitadel-examples/tree/main/angular), set dev mode to `true` and the Redirect URIs to <http://localhost:4200/auth/callback>. > If you are following along with the [example](https://github.com/zitadel/zitadel-examples/tree/main/angular), set dev mode to `true` and the Redirect URIs to <http://localhost:4200/auth/callback>.
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 Logout URIs field. 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 Logout URIs field.
Continue and create the application. Continue and create the application.
### Client ID and Secret ### Client ID
After successful app creation, a pop-up will appear, showing the app's client ID. Copy the client ID, as you will need it to configure your Angular client. After successful app creation, a pop-up will appear, showing the app's client ID. Copy the client ID, as you will need it to configure your Angular client.
@ -58,7 +58,7 @@ const authConfig: AuthConfig = {
responseType: 'code', responseType: 'code',
oidc: true, oidc: true,
clientId: 'YOUR-CLIENT-ID', // replace with your appid clientId: 'YOUR-CLIENT-ID', // replace with your appid
issuer: 'https://issuer.zitadel.ch', issuer: 'https:/[your-domain]-[random-string].zitadel.cloud', // replace with your instance
redirectUri: 'http://localhost:4200/auth/callback', redirectUri: 'http://localhost:4200/auth/callback',
postLogoutRedirectUri: 'http://localhost:4200/signedout', // optional postLogoutRedirectUri: 'http://localhost:4200/signedout', // optional
requireHttps: false // required for running locally requireHttps: false // required for running locally
@ -68,14 +68,14 @@ const authConfig: AuthConfig = {
... ...
imports: [ imports: [
OAuthModule.forRoot(), OAuthModule.forRoot(),
HttpClientModule, HttpClientModule,
... ...
providers: [ providers: [
{ {
provide: AuthConfig, provide: AuthConfig,
useValue: authConfig useValue: authConfig
} }
... ...
``` ```
Set _openid_, _profile_ and _email_ as scope, _code_ as responseType, and oidc to _true_. Then create an authentication service to provide the functions to authenticate your user. Set _openid_, _profile_ and _email_ as scope, _code_ as responseType, and oidc to _true_. Then create an authentication service to provide the functions to authenticate your user.
@ -89,63 +89,62 @@ ng g service services/authentication
Copy the following code to your service. This code provides a function `authenticate()` which redirects the user to ZITADEL. After successful login, ZITADEL redirects the user back to the redirect URI configured in _AuthModule_ and ZITADEL Console. Make sure both correspond, otherwise ZITADEL throws an error. Copy the following code to your service. This code provides a function `authenticate()` which redirects the user to ZITADEL. After successful login, ZITADEL redirects the user back to the redirect URI configured in _AuthModule_ and ZITADEL Console. Make sure both correspond, otherwise ZITADEL throws an error.
```ts ```ts
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc'; import { AuthConfig, OAuthService } from "angular-oauth2-oidc";
import { BehaviorSubject, from, Observable } from 'rxjs'; import { BehaviorSubject, from, Observable } from "rxjs";
import { StatehandlerService } from './statehandler.service'; import { StatehandlerService } from "./statehandler.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: "root",
}) })
export class AuthenticationService { export class AuthenticationService {
private _authenticated: boolean = false; private _authenticated: boolean = false;
private readonly _authenticationChanged: BehaviorSubject< private readonly _authenticationChanged: BehaviorSubject<boolean> =
boolean new BehaviorSubject(this.authenticated);
> = new BehaviorSubject(this.authenticated);
constructor( constructor(
private oauthService: OAuthService, private oauthService: OAuthService,
private authConfig: AuthConfig, private authConfig: AuthConfig,
private statehandler: StatehandlerService, private statehandler: StatehandlerService
) { } ) {}
public get authenticated(): boolean { public get authenticated(): boolean {
return this._authenticated; return this._authenticated;
}
public get authenticationChanged(): Observable<boolean> {
return this._authenticationChanged;
}
public getOIDCUser(): Observable<any> {
return from(this.oauthService.loadUserProfile());
}
public async authenticate(setState: boolean = true): Promise<boolean> {
this.oauthService.configure(this.authConfig);
this.oauthService.strictDiscoveryDocumentValidation = false;
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated) {
const newState = setState
? await this.statehandler.createState().toPromise()
: undefined;
this.oauthService.initCodeFlow(newState);
} }
this._authenticationChanged.next(this.authenticated);
public get authenticationChanged(): Observable<boolean> { return this.authenticated;
return this._authenticationChanged; }
}
public getOIDCUser(): Observable<any> { public signout(): void {
return from(this.oauthService.loadUserProfile()); this.oauthService.logOut();
} this._authenticated = false;
this._authenticationChanged.next(false);
public async authenticate( }
setState: boolean = true,
): Promise<boolean> {
this.oauthService.configure(this.authConfig);
this.oauthService.strictDiscoveryDocumentValidation = false;
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated) {
const newState = setState ? await this.statehandler.createState().toPromise() : undefined;
this.oauthService.initCodeFlow(newState);
}
this._authenticationChanged.next(this.authenticated);
return this.authenticated;
}
public signout(): void {
this.oauthService.logOut();
this._authenticated = false;
this._authenticationChanged.next(false);
}
} }
``` ```
@ -215,26 +214,35 @@ ng g guard guards/auth
This code shows the _AuthGuard_ used in ZITADEL Console. This code shows the _AuthGuard_ used in ZITADEL Console.
```ts ```ts
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router'; import {
import { Observable } from 'rxjs'; ActivatedRouteSnapshot,
import { AuthenticationService } from '../services/authentication.service'; CanActivate,
RouterStateSnapshot,
UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
import { AuthenticationService } from "../services/authentication.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: "root",
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private auth: AuthenticationService) {}
constructor(private auth: AuthenticationService) { }
canActivate( canActivate(
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { state: RouterStateSnapshot
if (!this.auth.authenticated) { ):
return this.auth.authenticate(); | Observable<boolean | UrlTree>
} | Promise<boolean | UrlTree>
return this.auth.authenticated; | boolean
} | UrlTree {
if (!this.auth.authenticated) {
return this.auth.authenticate();
}
return this.auth.authenticated;
}
} }
``` ```
@ -267,14 +275,14 @@ const routes: Routes = [
Call `auth.signout()` for logging the current user out. Note that you can also configure a logout redirect URI if you want your users to be redirected after logout. Call `auth.signout()` for logging the current user out. Note that you can also configure a logout redirect URI if you want your users to be redirected after logout.
```ts ```ts
import { AuthenticationService } from 'src/app/services/authentication.service'; import { AuthenticationService } from "src/app/services/authentication.service";
export class SomeComponentWithLogout { export class SomeComponentWithLogout {
constructor(private authService: AuthenticationService){} constructor(private authService: AuthenticationService) {}
public signout(): Promise<void> { public signout(): Promise<void> {
return this.authService.signout(); return this.authService.signout();
} }
} }
``` ```
@ -297,7 +305,7 @@ And in your HTML file:
```html ```html
<div *ngIf="user$ | async as user"> <div *ngIf="user$ | async as user">
<p>{{user | json}}</p> <p>{{user | json}}</p>
</div> </div>
``` ```

View File

@ -45,14 +45,14 @@ You may want to change the Flutter SDK version in `pubspec.yaml` from
```yaml ```yaml
environment: environment:
sdk: '>=2.7.0 <3.0.0' sdk: ">=2.7.0 <3.0.0"
``` ```
to to
```yaml ```yaml
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: ">=2.12.0 <3.0.0"
``` ```
With this, you'll enable "nullable by default" mode in Flutter, as well as new language features. With this, you'll enable "nullable by default" mode in Flutter, as well as new language features.
@ -257,7 +257,7 @@ Future<void> _authenticate() async {
AuthorizationTokenRequest( AuthorizationTokenRequest(
'<<CLIENT_ID>>', // Client ID of the native application '<<CLIENT_ID>>', // Client ID of the native application
'<<CALLBACK_URL>>', // The registered url from zitadel (e.g. ch.myexample.app://signin) '<<CALLBACK_URL>>', // The registered url from zitadel (e.g. ch.myexample.app://signin)
issuer: '<<ISSUER>>', // most of the cases: https://issuer.zitadel.ch issuer: '<<ISSUER>>', // most of the cases: https:/[your-domain]-[random-string].zitadel.cloud
scopes: [ scopes: [
'openid', 'openid',
'profile', 'profile',
@ -268,7 +268,7 @@ Future<void> _authenticate() async {
); );
final userInfoResponse = await get( final userInfoResponse = await get(
Uri.parse('https://api.zitadel.ch/oauth/v2/userinfo'), Uri.parse('https://[your-instance].zitadel.cloud/oauth/v2/userinfo'),
headers: { headers: {
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}', HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8' HttpHeaders.acceptHeader: 'application/json; charset=UTF-8'
@ -333,7 +333,7 @@ class _MyHomePageState extends State<MyHomePage> {
); );
final userInfoResponse = await get( final userInfoResponse = await get(
Uri.parse('https://api.zitadel.ch/oauth/v2/userinfo'), Uri.parse('https:/[your-domain]-[random-string].zitadel.cloud/oauth/v2/userinfo'), // replace with your instance
headers: { headers: {
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}', HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8' HttpHeaders.acceptHeader: 'application/json; charset=UTF-8'
@ -407,10 +407,7 @@ class _MyHomePageState extends State<MyHomePage> {
If you run this application, you can authenticate with a valid ZITADEL user. If you run this application, you can authenticate with a valid ZITADEL user.
<div style={{display:'flex', 'justify-content': 'center'}}> <div style={{display: 'grid', 'grid-column-gap': '1rem', 'grid-template-columns': '1fr 1fr'}}>
<div style={{display:'flex', 'align-items': 'center'}}>
<img src="/img/flutter/not-authed.png" alt="Unauthenticated" height="500px" /> <img src="/img/flutter/not-authed.png" alt="Unauthenticated" height="500px" />
<span style={{padding:'1rem'}}>becomes</span>
<img src="/img/flutter/authed.png" alt="Flutter Authenticated" height="500px" /> <img src="/img/flutter/authed.png" alt="Flutter Authenticated" height="500px" />
</div>
</div> </div>

View File

@ -40,47 +40,47 @@ NextAuth.js exposes a REST API which is used by your client.
To setup your configuration, create a file called [...nextauth].tsx in `pages/api/auth`. To setup your configuration, create a file called [...nextauth].tsx in `pages/api/auth`.
```ts ```ts
import NextAuth from 'next-auth'; import NextAuth from "next-auth";
export const ZITADEL = { export const ZITADEL = {
id: "zitadel", id: "zitadel",
name: "zitadel", name: "zitadel",
type: "oauth", type: "oauth",
version: "2.0", version: "2",
scope: "openid profile email", wellKnown: process.env.ZITADEL_ISSUER,
params: { response_type: "code", grant_type: "authorization_code" }, authorization: {
authorizationParams: { grant_type: "authorization_code", response_type: "code" }, params: {
accessTokenUrl: "https://api.zitadel.dev/oauth/v2/token", scope: "openid email profile",
requestTokenUrl: "https://api.zitadel.dev/oauth/v2/token",
authorizationUrl: "https://accounts.zitadel.dev/oauth/v2/authorize",
profileUrl: "https://api.zitadel.dev/oauth/v2/userinfo",
protection: "pkce",
async profile(profile, tokens) {
console.log(profile, tokens);
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
};
},
clientId: process.env.ZITADEL_CLIENT_ID,
session: {
jwt: true,
}, },
},
idToken: true,
checks: ["pkce", "state"],
client: {
token_endpoint_auth_method: "none",
},
async profile(profile) {
return {
id: profile.sub,
name: profile.name,
firstName: profile.given_name,
lastName: profile.family_name,
email: profile.email,
loginName: profile.preferred_username,
image: profile.picture,
};
},
clientId: process.env.ZITADEL_CLIENT_ID,
}; };
export default NextAuth({ export default NextAuth({
providers: [ providers: [ZITADEL],
ZITADEL
],
}); });
``` ```
Replace the endpoints `https://api.zitadel.dev/` with `https://api.zitadel.ch/` if your using a ZITADEL CLOUD tier or your own endpoint if your using a self hosted ENTERPRISE tier respectively. Replace the endpoints `https:/[your-domain]-[random-string].zitadel.cloud` with your instance or if your using a ZITADEL CLOUD tier or your own endpoint if your using a self hosted ENTERPRISE tier respectively.
We recommend using the Authentication Code flow secured by PKCE for the Authentication flow. We recommend using the Authentication Code flow secured by PKCE for the Authentication flow.
To be able to connect to ZITADEL, navigate to your [Console Projects](https://console.zitadel.ch/projects) create or select an existing project and add your app selecting WEB, then PKCE, and then add `http://localhost:3000/api/auth/callback/zitadel` as redirect url to your app. To be able to connect to ZITADEL, navigate to your Console Projects, create or select an existing project and add your app selecting WEB, then PKCE, and then add `http://localhost:3000/api/auth/callback/zitadel` as redirect url to your app.
For simplicity reasons we set the default to the one that next-auth provides us. You'll be able to change the redirect later if you want to. For simplicity reasons we set the default to the one that next-auth provides us. You'll be able to change the redirect later if you want to.
@ -95,6 +95,7 @@ Create a file `.env` in the root of the project and add the following keys to it
``` ```
NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_URL=http://localhost:3000
ZITADEL_CLIENT_ID=[yourClientId] ZITADEL_CLIENT_ID=[yourClientId]
ZITADEL_ISSUER=https:/[your-domain]-[random-string].zitadel.cloud
``` ```
# User interface # User interface
@ -107,7 +108,7 @@ Note that signIn method requires the id of the provider we provided earlier, and
import { signIn, signOut, useSession } from 'next-auth/client'; import { signIn, signOut, useSession } from 'next-auth/client';
export default function Page() { export default function Page() {
const [session, loading] = useSession(); const { data: session } = useSession();
... ...
{!session && <> {!session && <>
Not signed in <br /> Not signed in <br />
@ -129,13 +130,14 @@ To allow session state to be shared between pages - which improves performance,
Take a loot at the template `_app.tsx`. Take a loot at the template `_app.tsx`.
```ts ```ts
import { Provider } from 'next-auth/client'; import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps }) { function MyApp({ Component, pageProps }) {
return <Provider return (
session={pageProps.session} > <SessionProvider session={pageProps.session}>
<Component {...pageProps} /> <Component {...pageProps} />
</Provider>; </SessionProvider>
);
} }
export default MyApp; export default MyApp;
@ -143,17 +145,19 @@ export default MyApp;
Last thing: create a `profile.tsx` in /pages which renders the callback page. Last thing: create a `profile.tsx` in /pages which renders the callback page.
## Learn More ```ts
import Link from "next/link";
To learn more about Next.js, take a look at the following resources: import styles from "../styles/Home.module.css";
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. export default function Profile() {
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. return (
<div className={styles.container}>
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! <h1>Login successful</h1>
<Link href="/">
## Deploy on Vercel <button>Back to Home</button>
</Link>
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. </div>
);
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. }
```

View File

@ -10,7 +10,7 @@ At the end of the guide you should have an application able to login a user and
## Setup Application and get Keys ## Setup Application and get Keys
Before we can start building our application we have to do a few configuration steps in ZITADEL Console. 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](https://console.zitadel.ch/projects) and add a new application at the top of the page. 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](https://docs.zitadel.com/docs/guides/authorization/oauth-recommended-flows#different-client-profiles). Select User Agent and continue. More about the different app types can you find [here](https://docs.zitadel.com/docs/guides/authorization/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. 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.
@ -54,38 +54,40 @@ This library helps integrating ZITADEL Authentication in your React Application.
With the installed oidc pakage you will need an AuthProvider which should contain the OIDC configuration. 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. The oidc configuration should contain **openid**, **profile** and **email** as scope and **code** as responseType.
In the code below the authority is already set to the issuer of zitadel.ch you can find this in the ZITADEL Console on you application. 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. Replace the clientId value 'YOUR-CLIENT-ID' with the generated client id of you application in ZITADEL Console.
```ts ```ts
import React from "react";
import React from 'react'; import { AuthProvider } from "oidc-react";
import { AuthProvider } from 'oidc-react'; import "./App.css";
import './App.css';
const oidcConfig = { const oidcConfig = {
onSignIn: async (response: any) => { onSignIn: async (response: any) => {
alert('You logged in :' + response.profile.given_name + ' ' + response.profile.family_name); alert(
window.location.hash = ''; "You logged in :" +
}, response.profile.given_name +
authority: 'https://issuer.zitadel.ch', " " +
clientId: response.profile.family_name
'YOUR-CLIENT-ID', );
responseType: 'code', window.location.hash = "";
redirectUri: 'http://localhost:3000/', },
scope: 'openid profile email' 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() { function App() {
return ( return (
<AuthProvider {...oidcConfig}> <AuthProvider {...oidcConfig}>
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">
<p>Hello World</p> <p>Hello World</p>
</header> </header>
</div> </div>
</AuthProvider> </AuthProvider>
); );
} }
export default App; export default App;
@ -100,7 +102,7 @@ npm start
``` ```
Your browser should automatically open the app site or just go to `http://localhost:3000/`. 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 zitadel.ch 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. After successfully authenticating your user, you will get back to you application.
It should show a popup which says: **You logged in {FirstName} {LastName}** It should show a popup which says: **You logged in {FirstName} {LastName}**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

BIN
docs/static/img/angular/app-create.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

View File

@ -44,7 +44,7 @@ func (s *Server) GetUserByLoginNameGlobal(ctx context.Context, req *mgmt_pb.GetU
if err != nil { if err != nil {
return nil, err return nil, err
} }
user, err := s.query.GetUser(ctx, loginName) user, err := s.query.GetUser(ctx, true, loginName)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,6 +3,7 @@ package middleware
import ( import (
"context" "context"
"github.com/zitadel/logging"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
@ -13,7 +14,11 @@ import (
func TranslationHandler() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { func TranslationHandler() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(ctx, req) resp, err := handler(ctx, req)
translator := newZitadelTranslator(authz.GetInstance(ctx).DefaultLanguage()) translator, translatorError := newZitadelTranslator(authz.GetInstance(ctx).DefaultLanguage())
if translatorError != nil {
logging.New().WithError(translatorError).Error("could not load translator")
return resp, err
}
if loc, ok := resp.(localizers); ok && resp != nil { if loc, ok := resp.(localizers); ok && resp != nil {
translateFields(ctx, loc, translator) translateFields(ctx, loc, translator)
} }

View File

@ -40,16 +40,13 @@ func translateError(ctx context.Context, err error, translator *i18n.Translator)
return err return err
} }
func newZitadelTranslator(defaultLanguage language.Tag) *i18n.Translator { func newZitadelTranslator(defaultLanguage language.Tag) (*i18n.Translator, error) {
return translatorFromNamespace("zitadel", defaultLanguage) return translatorFromNamespace("zitadel", defaultLanguage)
} }
func translatorFromNamespace(namespace string, defaultLanguage language.Tag) *i18n.Translator { func translatorFromNamespace(namespace string, defaultLanguage language.Tag) (*i18n.Translator, error) {
dir, err := fs.NewWithNamespace(namespace) dir, err := fs.NewWithNamespace(namespace)
logging.LogWithFields("ERROR-7usEW", "namespace", namespace).OnError(err).Panic("unable to get namespace") logging.WithFields("namespace", namespace).OnError(err).Panic("unable to get namespace")
translator, err := i18n.NewTranslator(dir, defaultLanguage, "") return i18n.NewTranslator(dir, defaultLanguage, "")
logging.Log("ERROR-Sk8sf").OnError(err).Panic("unable to get i18n translator")
return translator
} }

View File

@ -107,7 +107,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return
} }
user, err := l.query.GetUser(setContext(r.Context(), userOrg), loginName) user, err := l.query.GetUser(setContext(r.Context(), userOrg), false, loginName)
if err != nil { if err != nil {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return

View File

@ -23,7 +23,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return
} }
user, err := l.query.GetUser(setContext(r.Context(), authReq.UserOrgID), loginName) user, err := l.query.GetUser(setContext(r.Context(), authReq.UserOrgID), false, loginName)
if err != nil { if err != nil {
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
return return

View File

@ -12,14 +12,14 @@ Login:
NextButtonText: suivant NextButtonText: suivant
SelectAccount: SelectAccount:
Title: Select account Title: Sélectionner un compte
Description: Use your ZITADEL-Account Description: Utilisez votre compte ZITADEL
TitleLinking: Select account for user linking TitleLinking: Sélectionnez le compte pour le lien avec l'utilisateur
DescriptionLinking: Select your account to link with your external user. DescriptionLinking: Sélectionnez votre compte pour établir un lien avec votre utilisateur externe.
OtherUser: Other User OtherUser: Autre utilisateur
SessionState0: active SessionState0: actif
SessionState1: inactive SessionState1: inactif
MustBeMemberOfOrg: The user must be member of the {{.OrgName}} organisation. MustBeMemberOfOrg: L'utilisateur doit être membre de l'organisation {{.OrgName}}.
Password: Password:
Title: Mot de passe Title: Mot de passe

View File

@ -19,7 +19,7 @@
} }
a { a {
color: var(--zitadel-color-font-500); color: var(--zitadel-color-text-500);
} }
.lgn-logo-watermark { .lgn-logo-watermark {

View File

@ -50,17 +50,17 @@
--zitadel-color-input-border-active: var(--zitadel-color-primary-500); --zitadel-color-input-border-active: var(--zitadel-color-primary-500);
--zitadel-color-input-placeholder: var(--zitadel-color-grey-600); --zitadel-color-input-placeholder: var(--zitadel-color-grey-600);
--zitadel-color-font-50: rgb(0, 0, 0); --zitadel-color-text-50: rgb(0, 0, 0);
--zitadel-color-font-100: rgb(0, 0, 0); --zitadel-color-text-100: rgb(0, 0, 0);
--zitadel-color-font-200: rgb(0, 0, 0); --zitadel-color-text-200: rgb(0, 0, 0);
--zitadel-color-font-300: rgb(0, 0, 0); --zitadel-color-text-300: rgb(0, 0, 0);
--zitadel-color-font-400: rgb(0, 0, 0); --zitadel-color-text-400: rgb(0, 0, 0);
--zitadel-color-font-500: rgb(0, 0, 0); --zitadel-color-text-500: rgb(0, 0, 0);
--zitadel-color-font-600: rgb(0, 0, 0); --zitadel-color-text-600: rgb(0, 0, 0);
--zitadel-color-font-700: rgb(0, 0, 0); --zitadel-color-text-700: rgb(0, 0, 0);
--zitadel-color-font-800: rgb(0, 0, 0); --zitadel-color-text-800: rgb(0, 0, 0);
--zitadel-color-font-900: rgb(0, 0, 0); --zitadel-color-text-900: rgb(0, 0, 0);
--zitadel-color-font-contrast: rgb(255, 255, 255); --zitadel-color-text-contrast: rgb(255, 255, 255);
--zitadel-color-label: #0000008a; --zitadel-color-label: #0000008a;
--zitadel-color-account-selector-hover: rgba(0, 0, 0, 0.02); --zitadel-color-account-selector-hover: rgba(0, 0, 0, 0.02);

View File

@ -44,17 +44,17 @@
--zitadel-color-input-border-hover: #1a1b1b; --zitadel-color-input-border-hover: #1a1b1b;
--zitadel-color-input-border-active: var(--zitadel-color-primary-500); --zitadel-color-input-border-active: var(--zitadel-color-primary-500);
--zitadel-color-input-placeholder: var(--zitadel-color-grey-600); --zitadel-color-input-placeholder: var(--zitadel-color-grey-600);
--zitadel-color-font-50: rgb(0, 0, 0); --zitadel-color-text-50: rgb(0, 0, 0);
--zitadel-color-font-100: rgb(0, 0, 0); --zitadel-color-text-100: rgb(0, 0, 0);
--zitadel-color-font-200: rgb(0, 0, 0); --zitadel-color-text-200: rgb(0, 0, 0);
--zitadel-color-font-300: rgb(0, 0, 0); --zitadel-color-text-300: rgb(0, 0, 0);
--zitadel-color-font-400: rgb(0, 0, 0); --zitadel-color-text-400: rgb(0, 0, 0);
--zitadel-color-font-500: rgb(0, 0, 0); --zitadel-color-text-500: rgb(0, 0, 0);
--zitadel-color-font-600: rgb(0, 0, 0); --zitadel-color-text-600: rgb(0, 0, 0);
--zitadel-color-font-700: rgb(0, 0, 0); --zitadel-color-text-700: rgb(0, 0, 0);
--zitadel-color-font-800: rgb(0, 0, 0); --zitadel-color-text-800: rgb(0, 0, 0);
--zitadel-color-font-900: rgb(0, 0, 0); --zitadel-color-text-900: rgb(0, 0, 0);
--zitadel-color-font-contrast: rgb(255, 255, 255); --zitadel-color-text-contrast: rgb(255, 255, 255);
--zitadel-color-label: #0000008a; --zitadel-color-label: #0000008a;
--zitadel-color-account-selector-hover: rgba(0, 0, 0, 0.02); --zitadel-color-account-selector-hover: rgba(0, 0, 0, 0.02);
--zitadel-color-account-selector-active: rgba(0, 0, 0, 0.05); --zitadel-color-account-selector-active: rgba(0, 0, 0, 0.05);
@ -2819,7 +2819,7 @@ footer {
} }
} }
footer a { footer a {
color: var(--zitadel-color-font-500); color: var(--zitadel-color-text-500);
} }
footer .lgn-logo-watermark { footer .lgn-logo-watermark {
background: var(--zitadel-logo-powered-by) no-repeat; background: var(--zitadel-logo-powered-by) no-repeat;

View File

@ -40,7 +40,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
} }
idGenerator := id.SonyFlakeGenerator() idGenerator := id.SonyFlakeGenerator()
view, err := auth_view.StartView(dbClient, oidcEncryption, queries, idGenerator) view, err := auth_view.StartView(dbClient, oidcEncryption, queries, idGenerator, es)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,8 +1,12 @@
package view package view
import ( import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
usr_model "github.com/zitadel/zitadel/internal/user/model" usr_model "github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/user/repository/view/model"
@ -18,15 +22,77 @@ func (v *View) UserByID(userID, instanceID string) (*model.UserView, error) {
} }
func (v *View) UserByUsername(userName, instanceID string) (*model.UserView, error) { func (v *View) UserByUsername(userName, instanceID string) (*model.UserView, error) {
return view.UserByUserName(v.Db, userTable, userName, instanceID) query, err := query.NewUserUsernameSearchQuery(userName, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, query)
} }
func (v *View) UserByLoginName(loginName, instanceID string) (*model.UserView, error) { func (v *View) UserByLoginName(loginName, instanceID string) (*model.UserView, error) {
return view.UserByLoginName(v.Db, userTable, loginName, instanceID) loginNameQuery, err := query.NewUserLoginNamesSearchQuery(loginName)
if err != nil {
return nil, err
}
return v.userByID(instanceID, loginNameQuery)
} }
func (v *View) UserByLoginNameAndResourceOwner(loginName, resourceOwner, instanceID string) (*model.UserView, error) { func (v *View) UserByLoginNameAndResourceOwner(loginName, resourceOwner, instanceID string) (*model.UserView, error) {
return view.UserByLoginNameAndResourceOwner(v.Db, userTable, loginName, resourceOwner, instanceID) loginNameQuery, err := query.NewUserLoginNamesSearchQuery(loginName)
if err != nil {
return nil, err
}
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(instanceID, loginNameQuery, resourceOwnerQuery)
}
func (v *View) userByID(instanceID string, queries ...query.SearchQuery) (*model.UserView, error) {
ctx := authz.WithInstanceID(context.Background(), instanceID)
queriedUser, err := v.query.GetUser(ctx, true, queries...)
if err != nil {
return nil, err
}
user, err := view.UserByID(v.Db, userTable, queriedUser.ID, instanceID)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if err != nil {
user = new(model.UserView)
}
query, err := view.UserByIDQuery(queriedUser.ID, instanceID, user.Sequence)
if err != nil {
return nil, err
}
events, err := v.es.FilterEvents(ctx, query)
if err != nil && user.Sequence == 0 {
return nil, err
} else if err != nil {
return user, nil
}
userCopy := *user
for _, event := range events {
if err := user.AppendEvent(event); err != nil {
return &userCopy, nil
}
}
if user.State == int32(usr_model.UserStateDeleted) {
return nil, errors.ThrowNotFound(nil, "VIEW-r4y8r", "Errors.User.NotFound")
}
return user, nil
} }
func (v *View) UsersByOrgID(orgID, instanceID string) ([]*model.UserView, error) { func (v *View) UsersByOrgID(orgID, instanceID string) ([]*model.UserView, error) {

View File

@ -6,6 +6,7 @@ import (
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
eventstore "github.com/zitadel/zitadel/internal/eventstore/v1"
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
) )
@ -15,9 +16,10 @@ type View struct {
keyAlgorithm crypto.EncryptionAlgorithm keyAlgorithm crypto.EncryptionAlgorithm
idGenerator id.Generator idGenerator id.Generator
query *query.Queries query *query.Queries
es eventstore.Eventstore
} }
func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, queries *query.Queries, idGenerator id.Generator) (*View, error) { func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, queries *query.Queries, idGenerator id.Generator, es eventstore.Eventstore) (*View, error) {
gorm, err := gorm.Open("postgres", sqlClient) gorm, err := gorm.Open("postgres", sqlClient)
if err != nil { if err != nil {
return nil, err return nil, err
@ -27,6 +29,7 @@ func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, queri
keyAlgorithm: keyAlgorithm, keyAlgorithm: keyAlgorithm,
idGenerator: idGenerator, idGenerator: idGenerator,
query: queries, query: queries,
es: es,
}, nil }, nil
} }

View File

@ -33,6 +33,8 @@ func (wm *OrgWriteModel) Reduce() error {
wm.State = domain.OrgStateInactive wm.State = domain.OrgStateInactive
case *org.OrgReactivatedEvent: case *org.OrgReactivatedEvent:
wm.State = domain.OrgStateActive wm.State = domain.OrgStateActive
case *org.OrgRemovedEvent:
wm.State = domain.OrgStateRemoved
case *org.OrgChangedEvent: case *org.OrgChangedEvent:
wm.Name = e.Name wm.Name = e.Name
case *org.DomainPrimarySetEvent: case *org.DomainPrimarySetEvent:
@ -51,6 +53,9 @@ func (wm *OrgWriteModel) Query() *eventstore.SearchQueryBuilder {
EventTypes( EventTypes(
org.OrgAddedEventType, org.OrgAddedEventType,
org.OrgChangedEventType, org.OrgChangedEventType,
org.OrgDeactivatedEventType,
org.OrgReactivatedEventType,
org.OrgRemovedEventType,
org.OrgDomainPrimarySetEventType). org.OrgDomainPrimarySetEventType).
Builder() Builder()
} }

View File

@ -292,8 +292,8 @@ var (
} }
) )
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*User, error) { func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, queries ...SearchQuery) (*User, error) {
if shouldTriggered { if shouldTriggerBulk {
projection.UserProjection.TriggerBulk(ctx) projection.UserProjection.TriggerBulk(ctx)
} }
@ -314,7 +314,11 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID
return scan(row) return scan(row)
} }
func (q *Queries) GetUser(ctx context.Context, queries ...SearchQuery) (*User, error) { func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (*User, error) {
if shouldTriggerBulk {
projection.UserProjection.TriggerBulk(ctx)
}
instanceID := authz.GetInstance(ctx).InstanceID() instanceID := authz.GetInstance(ctx).InstanceID()
query, scan := prepareUserQuery(instanceID) query, scan := prepareUserQuery(instanceID)
for _, q := range queries { for _, q := range queries {

View File

@ -39,8 +39,8 @@ Errors:
NotFound: Générateur de secret non trouvé NotFound: Générateur de secret non trouvé
SMSConfig: SMSConfig:
NotFound: Configuration SMS non trouvée NotFound: Configuration SMS non trouvée
AlreadyActive: Configuration SMS déjà active AlreadyActive: Configuration SMS déjà active
AlreadyDeactivated: Configuration SMS déjà désactivée AlreadyDeactivated: Configuration SMS déjà désactivée
SMTPConfig: SMTPConfig:
NotFound: Configuration SMTP non trouvée NotFound: Configuration SMTP non trouvée
AlreadyExists: La configuration SMTP existe déjà AlreadyExists: La configuration SMTP existe déjà