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: '1.x.x', range: '1.x.x', channel: '1.x.x'},
{name: 'v2-alpha', prerelease: true},
{name: 'notify-users', prerelease: true},
{name: 'auth-users', prerelease: true},
],
plugins: [
"@semantic-release/commit-analyzer"

View File

@ -22,9 +22,17 @@
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"styles": ["src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": [
"./node_modules/tinycolor2/dist/tinycolor-min.js"
],
"allowedCommonJsDependencies": [
"@angular/common/locales/de",
"codemirror/mode/javascript/javascript",
@ -122,15 +130,27 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "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"]
"assets": [
"src/favicon.ico",
"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": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
},
"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,
"dependencies": {
"@angular/animations": "^14.0.1",
"@angular/cdk": "^14.0.1",
"@angular/common": "^14.0.1",
"@angular/compiler": "^14.0.1",
"@angular/core": "^14.0.1",
"@angular/forms": "^14.0.1",
"@angular/material": "^14.0.1",
"@angular/material-moment-adapter": "^14.0.1",
"@angular/platform-browser": "^14.0.1",
"@angular/platform-browser-dynamic": "^14.0.1",
"@angular/router": "^14.0.1",
"@angular/service-worker": "^14.0.1",
"@angular/animations": "^14.0.4",
"@angular/cdk": "^14.0.4",
"@angular/common": "^14.0.4",
"@angular/compiler": "^14.0.4",
"@angular/core": "^14.0.4",
"@angular/forms": "^14.0.4",
"@angular/material": "^14.0.4",
"@angular/material-moment-adapter": "^14.0.4",
"@angular/platform-browser": "^14.0.4",
"@angular/platform-browser-dynamic": "^14.0.4",
"@angular/router": "^14.0.4",
"@angular/service-worker": "^14.0.4",
"@ctrl/ngx-codemirror": "^5.1.1",
"@grpc/grpc-js": "^1.5.7",
"@ngx-translate/core": "^14.0.0",
@ -36,7 +36,7 @@
"codemirror": "^5.65.0",
"cors": "^2.8.5",
"file-saver": "^2.0.5",
"google-proto-files": "^2.5.0",
"google-proto-files": "^3.0.0",
"google-protobuf": "^3.19.4",
"grpc-web": "^1.3.0",
"libphonenumber-js": "^1.10.6",
@ -52,32 +52,32 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.0.1",
"@angular-eslint/builder": "^14.0.0-alpha.3",
"@angular-eslint/eslint-plugin": "^14.0.0-alpha.3",
"@angular-eslint/eslint-plugin-template": "^14.0.0-alpha.3",
"@angular-eslint/schematics": "^14.0.0-alpha.3",
"@angular-eslint/template-parser": "^14.0.0-alpha.3",
"@angular/cli": "^14.0.1",
"@angular/compiler-cli": "^14.0.1",
"@angular/language-service": "^14.0.1",
"@angular-devkit/build-angular": "^14.0.4",
"@angular-eslint/builder": "^14.0.0",
"@angular-eslint/eslint-plugin": "^14.0.0",
"@angular-eslint/eslint-plugin-template": "^14.0.0",
"@angular-eslint/schematics": "^14.0.0",
"@angular-eslint/template-parser": "^14.0.0",
"@angular/cli": "^14.0.4",
"@angular/compiler-cli": "^14.0.4",
"@angular/language-service": "^14.0.4",
"@types/jasmine": "~4.0.3",
"@types/jasminewd2": "~2.0.10",
"@types/jsonwebtoken": "^8.5.5",
"@types/node": "^17.0.42",
"@typescript-eslint/eslint-plugin": "5.25.0",
"@typescript-eslint/parser": "5.27.0",
"@typescript-eslint/eslint-plugin": "5.30.4",
"@typescript-eslint/parser": "5.30.4",
"codelyzer": "^6.0.0",
"cypress": "^10.1.0",
"cypress-terminal-report": "^4.0.1",
"eslint": "^8.17.0",
"jasmine-core": "~4.1.1",
"eslint": "^8.18.0",
"jasmine-core": "~4.2.0",
"jasmine-spec-reporter": "~7.0.0",
"jsonwebtoken": "^8.5.1",
"karma": "~6.3.16",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~5.0.1",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.0.0",
"mochawesome": "^7.1.2",
"prettier": "^2.4.1",

View File

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

View File

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

View File

@ -69,6 +69,7 @@ export class AppComponent implements OnDestroy {
private activatedRoute: ActivatedRoute,
@Inject(DOCUMENT) private document: Document,
) {
this.themeService.loadPrivateLabelling(true);
console.log(
'%cWait!',
'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 { NavModule } from './modules/nav/nav.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 { AdminService } from './services/admin.service';
import { AuthenticationService } from './services/authentication.service';
@ -79,7 +78,7 @@ const authConfig: AuthConfig = {
};
@NgModule({
declarations: [AppComponent, SignedoutComponent],
declarations: [AppComponent],
imports: [
AppRoutingModule,
CommonModule,

View File

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

View File

@ -196,6 +196,9 @@
</a>
</div>
<ng-container
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
>
<div class="account-card-wrapper">
<button
cdkOverlayOrigin
@ -206,9 +209,6 @@
>
<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 || ''"
@ -246,5 +246,6 @@
>
</cnsl-accounts-card>
</ng-template>
</ng-container>
</div>
</mat-toolbar>

View File

@ -5,7 +5,25 @@
[isInactive]="org?.state === OrgState.ORG_STATE_INACTIVE"
[hasContributors]="true"
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
topContributors
[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 { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
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 { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
@ -62,6 +63,56 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
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> {
this.mgmtService
.getMyOrg()

View File

@ -14,4 +14,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SignedoutRoutingModule { }
export class SignedoutRoutingModule {}

View File

@ -5,11 +5,18 @@
<ng-template #lighttheme>
<img alt="zitadel logo" src="../../../assets/images/zitadel-logo-dark.svg" />
</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
[routerLink]="[ '/users/me' ]">{{'USER.SIGNEDOUT_BTN' |
translate}} <i class="las la-sign-in-alt"></i></button>
<button
class="cnsl-action-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>

View File

@ -9,6 +9,7 @@
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
h1 {
font-size: 3rem;
@ -24,16 +25,6 @@
img {
height: 100px;
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 { ThemeService } from 'src/app/services/theme.service';
@Component({
selector: 'cnsl-signedout',
@ -8,7 +9,9 @@ import { Component } from '@angular/core';
export class SignedoutComponent {
public dark: boolean = true;
constructor() {
constructor(themeService: ThemeService) {
themeService.loadPrivateLabelling();
const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
}

View File

@ -1,15 +1,15 @@
import { CommonModule } from '@angular/common';
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 { SignedoutRoutingModule } from './signedout-routing.module';
import { SignedoutComponent } from './signedout.component';
@NgModule({
declarations: [],
imports: [
CommonModule,
SignedoutRoutingModule,
SharedModule,
],
declarations: [SignedoutComponent],
imports: [CommonModule, SignedoutRoutingModule, MatButtonModule, MatTooltipModule, TranslateModule, SharedModule],
})
export class SignedoutModule { }
export class SignedoutModule {}

View File

@ -49,10 +49,10 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy {
switchMap((url: string) => {
if (url.includes('?login_hint=')) {
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));
} 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));
} else {
return of(undefined);

View File

@ -140,9 +140,10 @@ export class ThemeService {
this.saveTextColor(lightText, false);
};
public loadPrivateLabelling(): void {
public loadPrivateLabelling(forceDefault: boolean = false): void {
if (forceDefault) {
this.setDefaultColors();
} else {
const isDark = (color: string) => this.isDark(color);
const isLight = (color: string) => this.isLight(color);
@ -206,4 +207,5 @@ export class ThemeService {
this.setDefaultColors();
});
}
}
}

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Wähle eine Organisation aus.",
"ACTIVE": "Aktiv",
"CREATE": "Organisation erstellen",
"DEACTIVATE": "Organisation deaktivieren",
"REACTIVATE": "Organisation reaktivieren",
"NOPERMISSION": "Sie haben keine Berechtigung, auf Einstellungen der Organisation zuzugreifen.",
"USERSELFACCOUNT": "Verwenden Sie Ihr persönliches Konto als Organisationsinhaber",
"ORGDETAIL_TITLE": "Gebe den Namen und die Domain für die neue Organisation ein.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager entfernt.",
"MEMBERCHANGED": "Manager geändert.",
"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": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Choose an organization.",
"ACTIVE": "Active",
"CREATE": "Create Organization",
"DEACTIVATE": "Deactivate Organization",
"REACTIVATE": "Reactivate Organization",
"NOPERMISSION": "You don't have the permission to access organization settings.",
"USERSELFACCOUNT": "Use your personal account as organization owner",
"ORGDETAIL_TITLE": "Enter the name and domain of your new organization.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager removed.",
"MEMBERCHANGED": "Manager changed.",
"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": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Choisissez une organisation.",
"ACTIVE": "Actif",
"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.",
"USERSELFACCOUNT": "Utilisez votre compte personnel comme propriétaire de l'organisation",
"ORGDETAIL_TITLE": "Saisissez le nom et le domaine de votre nouvelle organisation.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Gestionnaire supprimé.",
"MEMBERCHANGED": "Gestionnaire modifié.",
"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": {

View File

@ -757,6 +757,8 @@
"LISTDESCRIPTION": "Scegli un'organizzazione.",
"ACTIVE": "Attivo",
"CREATE": "Creare un'organizzazione",
"DEACTIVATE": "Disattiva organizzazione",
"REACTIVATE": "Riattiva organizzazione",
"NOPERMISSION": "Non hai l'autorizzazione per accedere alle impostazioni dell'organizzazione.",
"USERSELFACCOUNT": "Usa il tuo account personale come proprietario dell'organizzazione",
"ORGDETAIL_TITLE": "Inserisci il nome e il dominio della tua nuova organizzazione.",
@ -821,6 +823,16 @@
"MEMBERREMOVED": "Manager rimosso con successo",
"MEMBERCHANGED": "Manager 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": {

View File

@ -1,34 +1,31 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>Console</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<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="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#e6768b">
<link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#e6768b" />
<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:title" content="ZITADEL Console" />
<meta property="og:description" content="Console 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 name="twitter:card" content="summary">
<meta name="twitter:site" content="@zitadel_ch">
<meta property="og:image" content="https://www.zitadel.com/images/preview.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@zitadel" />
<meta name="twitter:title" content="ZITADEL Console" />
<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>
<body>
<cnsl-root></cnsl-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</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
### 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:
@ -24,10 +21,6 @@ kn quickstart kind
That will get you a ready to go knative/kubernetes environment.
See Knative documentation here:
https://knative.dev/docs/install/quickstart-install/
## Database
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
```
## 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
```bash
@ -58,9 +43,7 @@ kn service create zitadel \
--env ZITADEL_TLS_ENABLED=false \
--env ZITADEL_EXTERNALDOMAIN=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" \
--mount /tls.secret=secret:certs/tls.secret \
--mount /tls.key=secret:certs/tls.key
--arg "start-from-init" --arg "--masterkey" --arg "MasterkeyNeedsToHave32Characters"
```
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:
* 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!

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

View File

@ -11,6 +11,7 @@ import Compose from './compose.mdx'
import Helm from './helm.mdx'
import Knative from './knative.mdx'
import NextSelfHosted from './nextselfhosted.mdx'
import DefaultUser from './defaultuser.mdx'
# 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.
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 -->
<Tabs
@ -37,21 +48,26 @@ By default, it runs a highly available ZITADEL instance along with a secure and
</TabItem>
<TabItem value="linux">
<Linux/>
<DefaultUser/>
<NextSelfHosted/>
</TabItem>
<TabItem value="macos">
<MacOS/>
<DefaultUser/>
<NextSelfHosted/>
</TabItem>
<TabItem value="compose">
<Compose/>
<DefaultUser/>
<NextSelfHosted/>
</TabItem>
<TabItem value="k8s">
<Helm/>
<DefaultUser/>
<NextSelfHosted/>
</TabItem>
<TabItem value="knative">
<Knative/>
<NextSelfHosted/>
</TabItem>
</Tabs>

View File

@ -12,11 +12,11 @@ At the end of the guide, your application has login functionality and has access
## 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](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.
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
@ -28,7 +28,7 @@ If you want to redirect the users back to a route on your application after they
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.
@ -58,7 +58,7 @@ const authConfig: AuthConfig = {
responseType: 'code',
oidc: true,
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',
postLogoutRedirectUri: 'http://localhost:4200/signedout', // optional
requireHttps: false // required for running locally
@ -89,26 +89,25 @@ 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.
```ts
import { Injectable } from '@angular/core';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { Injectable } from "@angular/core";
import { AuthConfig, OAuthService } from "angular-oauth2-oidc";
import { BehaviorSubject, from, Observable } from "rxjs";
import { StatehandlerService } from './statehandler.service';
import { StatehandlerService } from "./statehandler.service";
@Injectable({
providedIn: 'root'
providedIn: "root",
})
export class AuthenticationService {
private _authenticated: boolean = false;
private readonly _authenticationChanged: BehaviorSubject<
boolean
> = new BehaviorSubject(this.authenticated);
private readonly _authenticationChanged: BehaviorSubject<boolean> =
new BehaviorSubject(this.authenticated);
constructor(
private oauthService: OAuthService,
private authConfig: AuthConfig,
private statehandler: StatehandlerService,
) { }
private statehandler: StatehandlerService
) {}
public get authenticated(): boolean {
return this._authenticated;
@ -122,9 +121,7 @@ export class AuthenticationService {
return from(this.oauthService.loadUserProfile());
}
public async authenticate(
setState: boolean = true,
): Promise<boolean> {
public async authenticate(setState: boolean = true): Promise<boolean> {
this.oauthService.configure(this.authConfig);
this.oauthService.strictDiscoveryDocumentValidation = false;
@ -133,7 +130,9 @@ export class AuthenticationService {
this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated) {
const newState = setState ? await this.statehandler.createState().toPromise() : undefined;
const newState = setState
? await this.statehandler.createState().toPromise()
: undefined;
this.oauthService.initCodeFlow(newState);
}
this._authenticationChanged.next(this.authenticated);
@ -215,21 +214,30 @@ ng g guard guards/auth
This code shows the _AuthGuard_ used in ZITADEL Console.
```ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthenticationService } from '../services/authentication.service';
import { Injectable } from "@angular/core";
import {
ActivatedRouteSnapshot,
CanActivate,
RouterStateSnapshot,
UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
import { AuthenticationService } from "../services/authentication.service";
@Injectable({
providedIn: 'root'
providedIn: "root",
})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthenticationService) { }
constructor(private auth: AuthenticationService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
if (!this.auth.authenticated) {
return this.auth.authenticate();
}
@ -267,10 +275,10 @@ 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.
```ts
import { AuthenticationService } from 'src/app/services/authentication.service';
import { AuthenticationService } from "src/app/services/authentication.service";
export class SomeComponentWithLogout {
constructor(private authService: AuthenticationService){}
constructor(private authService: AuthenticationService) {}
public signout(): Promise<void> {
return this.authService.signout();

View File

@ -45,14 +45,14 @@ You may want to change the Flutter SDK version in `pubspec.yaml` from
```yaml
environment:
sdk: '>=2.7.0 <3.0.0'
sdk: ">=2.7.0 <3.0.0"
```
to
```yaml
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.
@ -257,7 +257,7 @@ Future<void> _authenticate() async {
AuthorizationTokenRequest(
'<<CLIENT_ID>>', // Client ID of the native application
'<<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: [
'openid',
'profile',
@ -268,7 +268,7 @@ Future<void> _authenticate() async {
);
final userInfoResponse = await get(
Uri.parse('https://api.zitadel.ch/oauth/v2/userinfo'),
Uri.parse('https://[your-instance].zitadel.cloud/oauth/v2/userinfo'),
headers: {
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8'
@ -333,7 +333,7 @@ class _MyHomePageState extends State<MyHomePage> {
);
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: {
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
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.
<div style={{display:'flex', 'justify-content': 'center'}}>
<div style={{display:'flex', 'align-items': 'center'}}>
<div style={{display: 'grid', 'grid-column-gap': '1rem', 'grid-template-columns': '1fr 1fr'}}>
<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" />
</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`.
```ts
import NextAuth from 'next-auth';
import NextAuth from "next-auth";
export const ZITADEL = {
id: "zitadel",
name: "zitadel",
type: "oauth",
version: "2.0",
scope: "openid profile email",
params: { response_type: "code", grant_type: "authorization_code" },
authorizationParams: { grant_type: "authorization_code", response_type: "code" },
accessTokenUrl: "https://api.zitadel.dev/oauth/v2/token",
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);
version: "2",
wellKnown: process.env.ZITADEL_ISSUER,
authorization: {
params: {
scope: "openid email profile",
},
},
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,
image: profile.picture
loginName: profile.preferred_username,
image: profile.picture,
};
},
clientId: process.env.ZITADEL_CLIENT_ID,
session: {
jwt: true,
},
};
export default NextAuth({
providers: [
ZITADEL
],
providers: [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.
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.
@ -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
ZITADEL_CLIENT_ID=[yourClientId]
ZITADEL_ISSUER=https:/[your-domain]-[random-string].zitadel.cloud
```
# 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';
export default function Page() {
const [session, loading] = useSession();
const { data: session } = useSession();
...
{!session && <>
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`.
```ts
import { Provider } from 'next-auth/client';
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps }) {
return <Provider
session={pageProps.session} >
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</Provider>;
</SessionProvider>
);
}
export default MyApp;
@ -143,17 +145,19 @@ export default MyApp;
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.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
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.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
export default function Profile() {
return (
<div className={styles.container}>
<h1>Login successful</h1>
<Link href="/">
<button>Back to Home</button>
</Link>
</div>
);
}
```

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
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).
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,26 +54,28 @@ 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.
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.
```ts
import React from 'react';
import { AuthProvider } from 'oidc-react';
import './App.css';
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 = '';
alert(
"You logged in :" +
response.profile.given_name +
" " +
response.profile.family_name
);
window.location.hash = "";
},
authority: 'https://issuer.zitadel.ch',
clientId:
'YOUR-CLIENT-ID',
responseType: 'code',
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() {
@ -85,7 +87,7 @@ function App() {
</header>
</div>
</AuthProvider>
);
);
}
export default App;
@ -100,7 +102,7 @@ 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 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.
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 {
return nil, err
}
user, err := s.query.GetUser(ctx, loginName)
user, err := s.query.GetUser(ctx, true, loginName)
if err != nil {
return nil, err
}

View File

@ -3,6 +3,7 @@ package middleware
import (
"context"
"github.com/zitadel/logging"
"google.golang.org/grpc"
"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) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
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 {
translateFields(ctx, loc, translator)
}

View File

@ -40,16 +40,13 @@ func translateError(ctx context.Context, err error, translator *i18n.Translator)
return err
}
func newZitadelTranslator(defaultLanguage language.Tag) *i18n.Translator {
func newZitadelTranslator(defaultLanguage language.Tag) (*i18n.Translator, error) {
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)
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, "")
logging.Log("ERROR-Sk8sf").OnError(err).Panic("unable to get i18n translator")
return translator
return i18n.NewTranslator(dir, defaultLanguage, "")
}

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)
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 {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
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)
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 {
l.renderPasswordResetDone(w, r, authReq, err)
return

View File

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

View File

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

View File

@ -50,17 +50,17 @@
--zitadel-color-input-border-active: var(--zitadel-color-primary-500);
--zitadel-color-input-placeholder: var(--zitadel-color-grey-600);
--zitadel-color-font-50: rgb(0, 0, 0);
--zitadel-color-font-100: rgb(0, 0, 0);
--zitadel-color-font-200: rgb(0, 0, 0);
--zitadel-color-font-300: rgb(0, 0, 0);
--zitadel-color-font-400: rgb(0, 0, 0);
--zitadel-color-font-500: rgb(0, 0, 0);
--zitadel-color-font-600: rgb(0, 0, 0);
--zitadel-color-font-700: rgb(0, 0, 0);
--zitadel-color-font-800: rgb(0, 0, 0);
--zitadel-color-font-900: rgb(0, 0, 0);
--zitadel-color-font-contrast: rgb(255, 255, 255);
--zitadel-color-text-50: rgb(0, 0, 0);
--zitadel-color-text-100: rgb(0, 0, 0);
--zitadel-color-text-200: rgb(0, 0, 0);
--zitadel-color-text-300: rgb(0, 0, 0);
--zitadel-color-text-400: rgb(0, 0, 0);
--zitadel-color-text-500: rgb(0, 0, 0);
--zitadel-color-text-600: rgb(0, 0, 0);
--zitadel-color-text-700: rgb(0, 0, 0);
--zitadel-color-text-800: rgb(0, 0, 0);
--zitadel-color-text-900: rgb(0, 0, 0);
--zitadel-color-text-contrast: rgb(255, 255, 255);
--zitadel-color-label: #0000008a;
--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-active: var(--zitadel-color-primary-500);
--zitadel-color-input-placeholder: var(--zitadel-color-grey-600);
--zitadel-color-font-50: rgb(0, 0, 0);
--zitadel-color-font-100: rgb(0, 0, 0);
--zitadel-color-font-200: rgb(0, 0, 0);
--zitadel-color-font-300: rgb(0, 0, 0);
--zitadel-color-font-400: rgb(0, 0, 0);
--zitadel-color-font-500: rgb(0, 0, 0);
--zitadel-color-font-600: rgb(0, 0, 0);
--zitadel-color-font-700: rgb(0, 0, 0);
--zitadel-color-font-800: rgb(0, 0, 0);
--zitadel-color-font-900: rgb(0, 0, 0);
--zitadel-color-font-contrast: rgb(255, 255, 255);
--zitadel-color-text-50: rgb(0, 0, 0);
--zitadel-color-text-100: rgb(0, 0, 0);
--zitadel-color-text-200: rgb(0, 0, 0);
--zitadel-color-text-300: rgb(0, 0, 0);
--zitadel-color-text-400: rgb(0, 0, 0);
--zitadel-color-text-500: rgb(0, 0, 0);
--zitadel-color-text-600: rgb(0, 0, 0);
--zitadel-color-text-700: rgb(0, 0, 0);
--zitadel-color-text-800: rgb(0, 0, 0);
--zitadel-color-text-900: rgb(0, 0, 0);
--zitadel-color-text-contrast: rgb(255, 255, 255);
--zitadel-color-label: #0000008a;
--zitadel-color-account-selector-hover: rgba(0, 0, 0, 0.02);
--zitadel-color-account-selector-active: rgba(0, 0, 0, 0.05);
@ -2819,7 +2819,7 @@ footer {
}
}
footer a {
color: var(--zitadel-color-font-500);
color: var(--zitadel-color-text-500);
}
footer .lgn-logo-watermark {
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()
view, err := auth_view.StartView(dbClient, oidcEncryption, queries, idGenerator)
view, err := auth_view.StartView(dbClient, oidcEncryption, queries, idGenerator, es)
if err != nil {
return nil, err
}

View File

@ -1,8 +1,12 @@
package view
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
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/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) {
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) {
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) {
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) {

View File

@ -6,6 +6,7 @@ import (
"github.com/jinzhu/gorm"
"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/query"
)
@ -15,9 +16,10 @@ type View struct {
keyAlgorithm crypto.EncryptionAlgorithm
idGenerator id.Generator
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)
if err != nil {
return nil, err
@ -27,6 +29,7 @@ func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, queri
keyAlgorithm: keyAlgorithm,
idGenerator: idGenerator,
query: queries,
es: es,
}, nil
}

View File

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

View File

@ -292,8 +292,8 @@ var (
}
)
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*User, error) {
if shouldTriggered {
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, queries ...SearchQuery) (*User, error) {
if shouldTriggerBulk {
projection.UserProjection.TriggerBulk(ctx)
}
@ -314,7 +314,11 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID
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()
query, scan := prepareUserQuery(instanceID)
for _, q := range queries {