mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:37:31 +00:00
feat(saml): implementation of saml for ZITADEL v2 (#3618)
This commit is contained in:
895
console/package-lock.json
generated
895
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -64,8 +64,8 @@
|
||||
"@types/jasminewd2": "~2.0.10",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/node": "^17.0.42",
|
||||
"@typescript-eslint/eslint-plugin": "5.35.1",
|
||||
"@typescript-eslint/parser": "5.30.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.36.1",
|
||||
"@typescript-eslint/parser": "5.36.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"eslint": "^8.18.0",
|
||||
"jasmine-core": "~4.2.0",
|
||||
|
@@ -86,6 +86,7 @@ const authConfig: AuthConfig = {
|
||||
scope: 'openid profile email', // offline_access
|
||||
responseType: 'code',
|
||||
oidc: true,
|
||||
requireHttps: false,
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<div class="cnsl-app-card" [ngClass]="{'add': type === OIDCAppType.ADD,'web': type === OIDCAppType.OIDC_APP_TYPE_WEB,
|
||||
'useragent': type === OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
|
||||
'native': type === OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}">
|
||||
<div
|
||||
class="cnsl-app-card"
|
||||
[ngClass]="{
|
||||
add: type === OIDCAppType.ADD,
|
||||
web: type === OIDCAppType.OIDC_APP_TYPE_WEB,
|
||||
useragent: type === OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
|
||||
native: type === OIDCAppType.OIDC_APP_TYPE_NATIVE,
|
||||
api: isApiApp,
|
||||
saml: type === 'SAML'
|
||||
}"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -55,5 +55,11 @@
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.saml {
|
||||
background: linear-gradient(40deg, rgb(110, 56, 124), rgb(88, 37, 103));
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import { OIDCAppType } from 'src/app/proto/generated/zitadel/app_pb';
|
||||
})
|
||||
export class AppCardComponent {
|
||||
@Input() public outline: boolean = false;
|
||||
@Input() public type: OIDCAppType | undefined = undefined;
|
||||
@Input() public type: OIDCAppType | 'SAML' | undefined = undefined;
|
||||
@Input() public isApiApp: boolean = false;
|
||||
public OIDCAppType: any = OIDCAppType;
|
||||
}
|
||||
|
@@ -2,12 +2,13 @@
|
||||
<ng-container *ngFor="let type of types">
|
||||
<input class="app" type="radio" (change)="emitChange()" [value]="type" [(ngModel)]="selected" [id]="type.prefix" />
|
||||
<label class="cnsl-type-radio-button" [for]="type.prefix">
|
||||
<div class="cnsl-type-radio-header" [ngStyle]="{'background': type.background}">
|
||||
<span>{{type.prefix}}</span>
|
||||
<div class="cnsl-type-radio-header" [ngStyle]="{ background: type.background }">
|
||||
<span>{{ type.prefix }}</span>
|
||||
</div>
|
||||
<p>{{type.titleI18nKey | translate}}</p>
|
||||
<p class="type-desc cnsl-secondary-text">{{type.descI18nKey | translate}}</p>
|
||||
<p>{{ type.titleI18nKey | translate }}</p>
|
||||
<p class="type-desc cnsl-secondary-text">{{ type.descI18nKey | translate }}</p>
|
||||
<span class="fill-space"></span>
|
||||
<span class="cnsl-type-protocol state" *ngIf="type.protocol">{{ type.protocol }}</span>
|
||||
</label>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -60,6 +60,8 @@
|
||||
box-sizing: border-box;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
margin: 2rem;
|
||||
@@ -76,5 +78,10 @@
|
||||
.type-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cnsl-type-protocol {
|
||||
width: fit-content;
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,24 @@
|
||||
<cnsl-create-layout
|
||||
title="{{ 'APP.PAGES.CREATE_OIDC' | translate }}"
|
||||
[createSteps]="createSteps"
|
||||
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
||||
[createSteps]="
|
||||
appType?.value?.createType === AppCreateType.OIDC
|
||||
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
||||
? 4
|
||||
: 3
|
||||
: appType?.value?.createType === AppCreateType.API
|
||||
? 3
|
||||
: appType?.value?.createType === AppCreateType.SAML
|
||||
? 3
|
||||
: 0
|
||||
"
|
||||
[currentCreateStep]="currentCreateStep"
|
||||
(closed)="close()"
|
||||
>
|
||||
<h1>{{ 'APP.PAGES.CREATE_OIDC_DESC_TITLE' | translate }}</h1>
|
||||
<h1>{{ 'APP.PAGES.CREATE_DESC_TITLE' | translate }}</h1>
|
||||
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
|
||||
{{ 'APP.OIDC.PROSWITCH' | translate }}
|
||||
{{ 'APP.PROSWITCH' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-horizontal-stepper
|
||||
@@ -21,16 +31,16 @@
|
||||
>
|
||||
<mat-step [stepControl]="firstFormGroup" [editable]="true">
|
||||
<form [formGroup]="firstFormGroup">
|
||||
<ng-template matStepLabel>{{ 'APP.OIDC.NAMEANDTYPESECTION' | translate }}</ng-template>
|
||||
<ng-template matStepLabel>{{ 'APP.NAMEANDTYPESECTION' | translate }}</ng-template>
|
||||
|
||||
<p class="step-title">{{ 'APP.OIDC.TITLEFIRST' | translate }}</p>
|
||||
<p class="step-title">{{ 'APP.TITLEFIRST' | translate }}</p>
|
||||
<cnsl-form-field class="name-formfield">
|
||||
<cnsl-label>{{ 'APP.NAME' | translate }}</cnsl-label>
|
||||
<input cnslInput cdkFocusInitial formControlName="name" />
|
||||
<span cnslError *ngIf="name?.errors?.required">{{ 'PROJECT.APP.NAMEREQUIRED' | translate }}</span>
|
||||
</cnsl-form-field>
|
||||
|
||||
<p class="step-title">{{ 'APP.OIDC.TYPETITLE' | translate }}</p>
|
||||
<p class="step-title">{{ 'APP.TYPETITLE' | translate }}</p>
|
||||
|
||||
<cnsl-type-radio [types]="appTypes" (selectedType)="appType?.setValue($event)" [selected]="appType?.value">
|
||||
</cnsl-type-radio>
|
||||
@@ -48,9 +58,13 @@
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- skip for native applications -->
|
||||
<!-- skip for native OIDC and SAML applications -->
|
||||
<mat-step
|
||||
*ngIf="oidcAppRequest.appType !== OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
*ngIf="
|
||||
(appType?.value?.createType === AppCreateType.OIDC &&
|
||||
appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE) ||
|
||||
appType?.value?.createType === AppCreateType.API
|
||||
"
|
||||
[stepControl]="secondFormGroup"
|
||||
[editable]="true"
|
||||
>
|
||||
@@ -85,33 +99,39 @@
|
||||
<ng-template matStepLabel>{{ 'APP.OIDC.REDIRECTSECTION' | translate }}</ng-template>
|
||||
|
||||
<p class="step-title">{{ 'APP.OIDC.REDIRECTTITLE' | translate }}</p>
|
||||
<p class="step-description cnsl-secondary-text" *ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
|
||||
<p
|
||||
class="step-description cnsl-secondary-text"
|
||||
*ngIf="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
>
|
||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
|
||||
</p>
|
||||
<p class="step-description cnsl-secondary-text" *ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB">
|
||||
<p class="step-description cnsl-secondary-text" *ngIf="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_WEB">
|
||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
||||
</p>
|
||||
|
||||
<cnsl-redirect-uris
|
||||
class="redirect-section"
|
||||
[canWrite]="true"
|
||||
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)"
|
||||
[urisList]="oidcAppRequest.redirectUrisList"
|
||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
(changedUris)="oidcAppRequest.setRedirectUrisList($any($event))"
|
||||
[urisList]="oidcAppRequest.toObject().redirectUrisList"
|
||||
[getValues]="requestRedirectValuesSubject$"
|
||||
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
||||
>
|
||||
</cnsl-redirect-uris>
|
||||
|
||||
<p class="step-title">{{ 'APP.OIDC.POSTREDIRECTTITLE' | translate }}</p>
|
||||
<p class="step-description cnsl-secondary-text" *ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
|
||||
<p
|
||||
class="step-description cnsl-secondary-text"
|
||||
*ngIf="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
>
|
||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
|
||||
</p>
|
||||
<p
|
||||
class="step-description cnsl-secondary-text"
|
||||
*ngIf="
|
||||
oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB ||
|
||||
oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT
|
||||
appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_WEB ||
|
||||
appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT
|
||||
"
|
||||
>
|
||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
||||
@@ -120,11 +140,11 @@
|
||||
<cnsl-redirect-uris
|
||||
class="redirect-section"
|
||||
[canWrite]="true"
|
||||
(changedUris)="oidcAppRequest.postLogoutRedirectUrisList = $any($event)"
|
||||
[urisList]="oidcAppRequest.postLogoutRedirectUrisList"
|
||||
(changedUris)="oidcAppRequest.setPostLogoutRedirectUrisList($any($event))"
|
||||
[urisList]="oidcAppRequest.toObject().postLogoutRedirectUrisList"
|
||||
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
||||
[getValues]="requestRedirectValuesSubject$"
|
||||
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
>
|
||||
</cnsl-redirect-uris>
|
||||
|
||||
@@ -136,97 +156,173 @@
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step *ngIf="appType?.value?.createType === AppCreateType.SAML" [editable]="true">
|
||||
<ng-template matStepLabel>{{ 'APP.SAML.CONFIGSECTION' | translate }}</ng-template>
|
||||
|
||||
<form [formGroup]="samlConfigForm">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'APP.SAML.URL' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="metadataUrl" placeholder="https://" />
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
|
||||
<span class="cnsl-app-or cnsl-secondary-text">{{ 'APP.SAML.OR' | translate }}</span>
|
||||
|
||||
<input
|
||||
#xmlFileInput
|
||||
style="display: none"
|
||||
class="file-input"
|
||||
type="file"
|
||||
accept="text/xml,application/xml"
|
||||
(change)="onDropXML($any($event.target).files)"
|
||||
/>
|
||||
<button type="button" mat-stroked-button (click)="$event.preventDefault(); xmlFileInput.click()">
|
||||
{{ 'APP.SAML.XML' | translate }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="saml-xml"
|
||||
[ngClass]="{ disabled: !!metadataUrl?.value }"
|
||||
*ngIf="decodedBase64 && samlAppRequest.toObject().metadataXml && !samlAppRequest.toObject().metadataUrl"
|
||||
>
|
||||
<!-- <p class="preview-text cnsl-secondary-text">PREVIEW</p> -->
|
||||
<ngx-codemirror
|
||||
[(ngModel)]="decodedBase64"
|
||||
[options]="{
|
||||
lineNumbers: true,
|
||||
theme: 'material',
|
||||
mode: 'application/xml'
|
||||
}"
|
||||
></ngx-codemirror>
|
||||
</div>
|
||||
|
||||
<div class="app-create-actions">
|
||||
<button mat-stroked-button class="bck-button" matStepperPrevious>{{ 'ACTIONS.BACK' | translate }}</button>
|
||||
<button mat-raised-button color="primary" matStepperNext [attr.data-e2e]="'continue-button-redirecturis'">
|
||||
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'APP.OIDC.OVERVIEWSECTION' | translate }}</ng-template>
|
||||
<p class="step-title">{{ 'APP.OIDC.OVERVIEWTITLE' | translate }}</p>
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.NAME' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
{{ oidcAppRequest.name }}
|
||||
{{ name?.value }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="appType?.value?.createType === AppCreateType.OIDC">
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.TYPE' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
{{ 'APP.OIDC.APPTYPE.' + oidcAppRequest.appType | translate }}
|
||||
{{ 'APP.OIDC.APPTYPE.' + oidcAppRequest.toObject().appType | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.GRANT' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="oidcAppRequest.grantTypesList && oidcAppRequest.grantTypesList.length > 0">
|
||||
[<span *ngFor="let element of oidcAppRequest.grantTypesList; index as i">
|
||||
<span
|
||||
class="right"
|
||||
*ngIf="oidcAppRequest.toObject().grantTypesList && oidcAppRequest.toObject().grantTypesList.length > 0"
|
||||
>
|
||||
[<span *ngFor="let element of oidcAppRequest.toObject().grantTypesList; index as i">
|
||||
{{ 'APP.OIDC.GRANT.' + element | translate }}
|
||||
{{ i < oidcAppRequest.grantTypesList.length - 1 ? ', ' : '' }} </span
|
||||
{{ i < oidcAppRequest.toObject().grantTypesList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="oidcAppRequest.responseTypesList && oidcAppRequest.responseTypesList.length > 0">
|
||||
[<span *ngFor="let element of oidcAppRequest.responseTypesList; index as i">
|
||||
<span
|
||||
class="right"
|
||||
*ngIf="oidcAppRequest.toObject().responseTypesList && oidcAppRequest.toObject().responseTypesList.length > 0"
|
||||
>
|
||||
[<span *ngFor="let element of oidcAppRequest.toObject().responseTypesList; index as i">
|
||||
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
|
||||
{{ i < oidcAppRequest.responseTypesList.length - 1 ? ', ' : '' }} </span
|
||||
{{ i < oidcAppRequest.toObject().responseTypesList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.AUTHMETHOD' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span>
|
||||
{{ 'APP.OIDC.AUTHMETHOD.' + oidcAppRequest.authMethodType | translate }}
|
||||
{{ 'APP.OIDC.AUTHMETHOD.' + oidcAppRequest.toObject().authMethodType | translate }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.OIDC.REDIRECT' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="oidcAppRequest.redirectUrisList && oidcAppRequest.redirectUrisList.length > 0">
|
||||
[<span *ngFor="let redirect of oidcAppRequest.redirectUrisList; index as i">
|
||||
<span
|
||||
class="right"
|
||||
*ngIf="oidcAppRequest.toObject().redirectUrisList && oidcAppRequest.toObject().redirectUrisList.length > 0"
|
||||
>
|
||||
[<span *ngFor="let redirect of oidcAppRequest.toObject().redirectUrisList; index as i">
|
||||
{{ redirect }}
|
||||
{{ i < oidcAppRequest.redirectUrisList.length - 1 ? ', ' : '' }} </span
|
||||
{{ i < oidcAppRequest.toObject().redirectUrisList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
|
||||
</span>
|
||||
<span
|
||||
class="right"
|
||||
*ngIf="oidcAppRequest.postLogoutRedirectUrisList && oidcAppRequest.postLogoutRedirectUrisList.length > 0"
|
||||
*ngIf="
|
||||
oidcAppRequest.toObject().postLogoutRedirectUrisList &&
|
||||
oidcAppRequest.toObject().postLogoutRedirectUrisList.length > 0
|
||||
"
|
||||
>
|
||||
[<span *ngFor="let redirect of oidcAppRequest.postLogoutRedirectUrisList; index as i">
|
||||
[<span *ngFor="let redirect of oidcAppRequest.toObject().postLogoutRedirectUrisList; index as i">
|
||||
{{ redirect }}
|
||||
{{ i < oidcAppRequest.postLogoutRedirectUrisList.length - 1 ? ', ' : '' }} </span
|
||||
{{ i < oidcAppRequest.toObject().postLogoutRedirectUrisList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
|
||||
<div class="row cnsl-secondary-text">
|
||||
<span class="left">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.AUTHMETHOD' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span>
|
||||
{{ 'APP.API.AUTHMETHOD.' + apiAppRequest.authMethodType | translate }}
|
||||
{{ 'APP.API.AUTHMETHOD.' + authMethodType?.value | translate }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="appType?.value?.createType === AppCreateType.SAML">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.SAML.METADATA' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span *ngIf="metadataUrl?.value">
|
||||
{{ metadataUrl?.value }}
|
||||
</span>
|
||||
<span *ngIf="samlAppRequest.toObject().metadataXml">
|
||||
{{ 'APP.SAML.METADATAFROMFILE' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -278,15 +374,56 @@
|
||||
</cnsl-form-field>
|
||||
</ng-container>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'APP.AUTHMETHOD' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="authMethodType">
|
||||
<mat-option *ngFor="let type of authMethodTypes" [value]="type.type">
|
||||
<span *ngIf="type.oidc">{{ 'APP.OIDC.AUTHMETHOD.' + type.type | translate }}</span>
|
||||
<span *ngIf="type.api">{{ 'APP.API.AUTHMETHOD.' + type.type | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<ng-container *ngIf="formappType?.value?.createType !== AppCreateType.SAML">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'APP.AUTHMETHOD' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="authMethodType">
|
||||
<mat-option *ngFor="let type of authMethodTypes" [value]="type.type">
|
||||
<span *ngIf="type.oidc">{{ 'APP.OIDC.AUTHMETHOD.' + type.type | translate }}</span>
|
||||
<span *ngIf="type.api">{{ 'APP.API.AUTHMETHOD.' + type.type | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<ng-container *ngIf="formappType?.value?.createType === AppCreateType.SAML">
|
||||
<div class="saml">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'APP.SAML.URL' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="metadataUrl" placeholder="https://" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<span class="cnsl-app-or cnsl-secondary-text">{{ 'APP.SAML.OR' | translate }}</span>
|
||||
|
||||
<input
|
||||
#xmlFileInput
|
||||
style="display: none"
|
||||
class="file-input"
|
||||
type="file"
|
||||
(change)="onDropXML($any($event.target).files)"
|
||||
/>
|
||||
<button type="button" mat-stroked-button (click)="$event.preventDefault(); xmlFileInput.click()">
|
||||
{{ 'APP.SAML.XML' | translate }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="saml-xml"
|
||||
[ngClass]="{ disabled: !!formMetadataUrl?.value }"
|
||||
*ngIf="decodedBase64 && samlAppRequest.toObject().metadataXml && !samlAppRequest.toObject().metadataUrl"
|
||||
>
|
||||
<ngx-codemirror
|
||||
[(ngModel)]="decodedBase64"
|
||||
[options]="{
|
||||
lineNumbers: true,
|
||||
theme: 'material',
|
||||
mode: 'application/xml'
|
||||
}"
|
||||
></ngx-codemirror>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="content" *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
|
||||
@@ -294,22 +431,22 @@
|
||||
<cnsl-redirect-uris
|
||||
class="redirect-section"
|
||||
[canWrite]="true"
|
||||
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)"
|
||||
[urisList]="oidcAppRequest.redirectUrisList"
|
||||
(changedUris)="oidcAppRequest.setRedirectUrisList($any($event))"
|
||||
[urisList]="oidcAppRequest.toObject().redirectUrisList"
|
||||
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
||||
[getValues]="requestRedirectValuesSubject$"
|
||||
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
>
|
||||
</cnsl-redirect-uris>
|
||||
|
||||
<cnsl-redirect-uris
|
||||
class="redirect-section"
|
||||
[canWrite]="true"
|
||||
(changedUris)="oidcAppRequest.postLogoutRedirectUrisList = $any($event)"
|
||||
[urisList]="oidcAppRequest.postLogoutRedirectUrisList"
|
||||
(changedUris)="oidcAppRequest.setPostLogoutRedirectUrisList($any($event))"
|
||||
[urisList]="oidcAppRequest.toObject().postLogoutRedirectUrisList"
|
||||
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
||||
[getValues]="requestRedirectValuesSubject$"
|
||||
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||
>
|
||||
</cnsl-redirect-uris>
|
||||
</div>
|
||||
|
@@ -61,6 +61,10 @@ p.desc {
|
||||
}
|
||||
}
|
||||
|
||||
.cnsl-app-or {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -72,10 +76,20 @@ p.desc {
|
||||
}
|
||||
}
|
||||
|
||||
.saml-xml {
|
||||
margin-top: 2rem;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.app-create-actions {
|
||||
margin-top: 1rem;
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.bck-button {
|
||||
margin-right: 1rem;
|
||||
@@ -102,6 +116,12 @@ p.desc {
|
||||
flex-basis: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.saml {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.continue-button {
|
||||
|
@@ -5,6 +5,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { RadioItemAuthType } from 'src/app/modules/app-radio/app-auth-method-radio/app-auth-method-radio.component';
|
||||
@@ -14,12 +15,14 @@ import {
|
||||
OIDCAuthMethodType,
|
||||
OIDCGrantType,
|
||||
OIDCResponseType,
|
||||
SAMLConfig,
|
||||
} from 'src/app/proto/generated/zitadel/app_pb';
|
||||
import {
|
||||
AddAPIAppRequest,
|
||||
AddAPIAppResponse,
|
||||
AddOIDCAppRequest,
|
||||
AddOIDCAppResponse,
|
||||
AddSAMLAppRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
@@ -35,7 +38,9 @@ import {
|
||||
PKCE_METHOD,
|
||||
POST_METHOD,
|
||||
} from '../authmethods';
|
||||
import { API_TYPE, AppCreateType, NATIVE_TYPE, RadioItemAppType, USER_AGENT_TYPE, WEB_TYPE } from '../authtypes';
|
||||
import { API_TYPE, AppCreateType, NATIVE_TYPE, RadioItemAppType, SAML_TYPE, USER_AGENT_TYPE, WEB_TYPE } from '../authtypes';
|
||||
|
||||
const MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-app-create',
|
||||
@@ -49,11 +54,11 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
public projectId: string = '';
|
||||
public loading: boolean = false;
|
||||
|
||||
public createSteps: number = 4;
|
||||
public currentCreateStep: number = 1;
|
||||
|
||||
public oidcAppRequest: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
|
||||
public apiAppRequest: AddAPIAppRequest.AsObject = new AddAPIAppRequest().toObject();
|
||||
public oidcAppRequest: AddOIDCAppRequest = new AddOIDCAppRequest();
|
||||
public apiAppRequest: AddAPIAppRequest = new AddAPIAppRequest();
|
||||
public samlAppRequest: AddSAMLAppRequest = new AddSAMLAppRequest();
|
||||
|
||||
public oidcResponseTypes: { type: OIDCResponseType; checked: boolean; disabled: boolean }[] = [
|
||||
{ type: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE, checked: false, disabled: false },
|
||||
@@ -66,7 +71,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
OIDCAppType.OIDC_APP_TYPE_NATIVE,
|
||||
OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
|
||||
];
|
||||
public appTypes: any = [WEB_TYPE, NATIVE_TYPE, USER_AGENT_TYPE, API_TYPE];
|
||||
public appTypes: any = [WEB_TYPE, NATIVE_TYPE, USER_AGENT_TYPE, API_TYPE, SAML_TYPE];
|
||||
|
||||
public authMethods: RadioItemAuthType[] = [PKCE_METHOD, CODE_METHOD, PK_JWT_METHOD, POST_METHOD];
|
||||
|
||||
@@ -84,8 +89,12 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
|
||||
// stepper
|
||||
firstFormGroup!: UntypedFormGroup;
|
||||
secondFormGroup!: UntypedFormGroup;
|
||||
public firstFormGroup!: UntypedFormGroup;
|
||||
public secondFormGroup!: UntypedFormGroup;
|
||||
public samlConfigForm!: UntypedFormGroup;
|
||||
|
||||
public redirectUrisList: string[] = [];
|
||||
public postLogoutRedirectUrisList: string[] = [];
|
||||
|
||||
// devmode
|
||||
public form!: UntypedFormGroup;
|
||||
@@ -121,10 +130,13 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
responseTypesList: ['', [Validators.required]],
|
||||
grantTypesList: ['', [Validators.required]],
|
||||
appType: ['', [Validators.required]],
|
||||
authMethodType: ['', [Validators.required]],
|
||||
// apptype OIDC
|
||||
responseTypesList: ['', []],
|
||||
grantTypesList: ['', []],
|
||||
authMethodType: ['', []],
|
||||
// apptype SAML
|
||||
metadataUrl: ['', []],
|
||||
});
|
||||
|
||||
this.initForm();
|
||||
@@ -134,25 +146,30 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
appType: [WEB_TYPE, [Validators.required]],
|
||||
});
|
||||
|
||||
this.samlConfigForm = this.fb.group({
|
||||
metadataUrl: ['', []],
|
||||
});
|
||||
|
||||
this.firstFormGroup.valueChanges.subscribe((value) => {
|
||||
if (this.firstFormGroup.valid) {
|
||||
this.oidcAppRequest.name = this.name?.value;
|
||||
this.apiAppRequest.name = this.name?.value;
|
||||
this.oidcAppRequest.setName(this.name?.value);
|
||||
this.apiAppRequest.setName(this.name?.value);
|
||||
this.samlAppRequest.setName(this.name?.value);
|
||||
|
||||
if (this.isStepperOIDC) {
|
||||
const oidcAppType = (this.appType?.value as RadioItemAppType).oidcAppType;
|
||||
if (oidcAppType !== undefined) {
|
||||
this.oidcAppRequest.appType = oidcAppType;
|
||||
this.oidcAppRequest.setAppType(oidcAppType);
|
||||
}
|
||||
|
||||
switch (this.oidcAppRequest.appType) {
|
||||
switch (this.appType?.value.oidcAppType) {
|
||||
case OIDCAppType.OIDC_APP_TYPE_NATIVE:
|
||||
this.authMethods = [PKCE_METHOD];
|
||||
|
||||
// automatically set to PKCE and skip step
|
||||
this.oidcAppRequest.responseTypesList = [OIDCResponseType.OIDC_RESPONSE_TYPE_CODE];
|
||||
this.oidcAppRequest.grantTypesList = [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE];
|
||||
this.oidcAppRequest.authMethodType = OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE;
|
||||
this.oidcAppRequest.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE]);
|
||||
this.oidcAppRequest.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE]);
|
||||
this.oidcAppRequest.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE);
|
||||
|
||||
break;
|
||||
case OIDCAppType.OIDC_APP_TYPE_WEB:
|
||||
@@ -179,19 +196,28 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
this.secondFormGroup = this.fb.group({
|
||||
authMethod: [this.authMethods[0].key, [Validators.required]],
|
||||
});
|
||||
|
||||
this.secondFormGroup.valueChanges.subscribe((form) => {
|
||||
const partialConfig = getPartialConfigFromAuthMethod(form.authMethod);
|
||||
|
||||
if (this.isStepperOIDC && partialConfig && partialConfig.oidc) {
|
||||
this.oidcAppRequest.responseTypesList = partialConfig.oidc?.responseTypesList ?? [];
|
||||
this.oidcAppRequest.setResponseTypesList(partialConfig.oidc?.responseTypesList ?? []);
|
||||
|
||||
this.oidcAppRequest.grantTypesList = partialConfig.oidc?.grantTypesList ?? [];
|
||||
this.oidcAppRequest.setGrantTypesList(partialConfig.oidc?.grantTypesList ?? []);
|
||||
|
||||
this.oidcAppRequest.authMethodType =
|
||||
partialConfig.oidc?.authMethodType ?? OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE;
|
||||
this.oidcAppRequest.setAuthMethodType(
|
||||
partialConfig.oidc?.authMethodType ?? OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
);
|
||||
} else if (this.isStepperAPI && partialConfig && partialConfig.api) {
|
||||
this.apiAppRequest.authMethodType =
|
||||
partialConfig.api?.authMethodType ?? APIAuthMethodType.API_AUTH_METHOD_TYPE_BASIC;
|
||||
this.apiAppRequest.setAuthMethodType(
|
||||
partialConfig.api?.authMethodType ?? APIAuthMethodType.API_AUTH_METHOD_TYPE_BASIC,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.samlConfigForm.valueChanges.subscribe((form) => {
|
||||
if (form.metadataUrl && form.metadataUrl.length > 0) {
|
||||
this.samlAppRequest.setMetadataUrl(form.metadataUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -225,18 +251,23 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
public initForm(): void {
|
||||
this.form.valueChanges.pipe(takeUntil(this.destroyed$), debounceTime(150)).subscribe(() => {
|
||||
this.oidcAppRequest.name = this.formname?.value;
|
||||
this.apiAppRequest.name = this.formname?.value;
|
||||
this.oidcAppRequest.setName(this.formname?.value);
|
||||
this.apiAppRequest.setName(this.formname?.value);
|
||||
this.samlAppRequest.setName(this.formname?.value);
|
||||
|
||||
this.oidcAppRequest.responseTypesList = this.formresponseTypesList?.value;
|
||||
this.oidcAppRequest.grantTypesList = this.formgrantTypesList?.value;
|
||||
this.oidcAppRequest.setResponseTypesList(this.formresponseTypesList?.value);
|
||||
this.oidcAppRequest.setGrantTypesList(this.grantTypesList?.value);
|
||||
|
||||
this.oidcAppRequest.authMethodType = this.formauthMethodType?.value;
|
||||
this.apiAppRequest.authMethodType = this.formauthMethodType?.value;
|
||||
this.oidcAppRequest.setAuthMethodType(this.authMethodType?.value);
|
||||
this.apiAppRequest.setAuthMethodType(this.authMethodType?.value);
|
||||
|
||||
if (this.formMetadataUrl?.value) {
|
||||
this.samlAppRequest.setMetadataUrl(this.formMetadataUrl?.value);
|
||||
}
|
||||
|
||||
const oidcAppType = (this.formappType?.value as RadioItemAppType).oidcAppType;
|
||||
if (oidcAppType !== undefined) {
|
||||
this.oidcAppRequest.appType = oidcAppType;
|
||||
this.oidcAppRequest.setAppType(oidcAppType);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -282,17 +313,41 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
private async getData({ projectid }: Params): Promise<void> {
|
||||
this.projectId = projectid;
|
||||
this.oidcAppRequest.projectId = projectid;
|
||||
this.apiAppRequest.projectId = projectid;
|
||||
this.oidcAppRequest.setProjectId(projectid);
|
||||
this.apiAppRequest.setProjectId(projectid);
|
||||
this.samlAppRequest.setProjectId(projectid);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
|
||||
public onDropXML(filelist: FileList): void {
|
||||
const file = filelist.item(0);
|
||||
this.metadataUrl?.setValue('');
|
||||
if (file) {
|
||||
if (file.size > MAX_ALLOWED_SIZE) {
|
||||
this.toast.showInfo('POLICY.PRIVATELABELING.MAXSIZEEXCEEDED', true);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((aXML) => {
|
||||
return (e) => {
|
||||
const xmlBase64 = e.target?.result;
|
||||
if (xmlBase64 && typeof xmlBase64 === 'string') {
|
||||
const cropped = xmlBase64.replace('data:text/xml;base64,', '');
|
||||
this.samlAppRequest.setMetadataXml(cropped);
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createApp(): void {
|
||||
const appOIDCCheck = this.devmode ? this.isDevOIDC : this.isStepperOIDC;
|
||||
const appAPICheck = this.devmode ? this.isDevAPI : this.isStepperAPI;
|
||||
const appSAMLCheck = this.devmode ? this.isDevSAML : this.isStepperSAML;
|
||||
|
||||
if (appOIDCCheck) {
|
||||
this.requestRedirectValuesSubject$.next();
|
||||
@@ -331,6 +386,19 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (appSAMLCheck) {
|
||||
this.loading = true;
|
||||
this.toast.showInfo('APP.TOAST.CREATED', true);
|
||||
this.mgmtService
|
||||
.addSAMLApp(this.samlAppRequest)
|
||||
.then((resp) => {
|
||||
this.loading = false;
|
||||
this.router.navigate(['projects', this.projectId, 'apps', resp.appId]);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,19 +449,27 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
get formname(): AbstractControl | null {
|
||||
return this.form.get('name');
|
||||
}
|
||||
|
||||
get formresponseTypesList(): AbstractControl | null {
|
||||
return this.form.get('responseTypesList');
|
||||
}
|
||||
get formgrantTypesList(): AbstractControl | null {
|
||||
|
||||
get grantTypesList(): AbstractControl | null {
|
||||
return this.form.get('grantTypesList');
|
||||
}
|
||||
|
||||
get formappType(): AbstractControl | null {
|
||||
return this.form.get('appType');
|
||||
}
|
||||
|
||||
get formMetadataUrl(): AbstractControl | null {
|
||||
return this.form.get('metadataUrl');
|
||||
}
|
||||
// get formapplicationType(): AbstractControl | null {
|
||||
// return this.form.get('applicationType');
|
||||
// }
|
||||
get formauthMethodType(): AbstractControl | null {
|
||||
|
||||
get authMethodType(): AbstractControl | null {
|
||||
return this.form.get('authMethodType');
|
||||
}
|
||||
|
||||
@@ -409,7 +485,35 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
return (this.formappType?.value as RadioItemAppType).createType === AppCreateType.API;
|
||||
}
|
||||
|
||||
get isDevSAML(): boolean {
|
||||
return (this.formappType?.value as RadioItemAppType).createType === AppCreateType.SAML;
|
||||
}
|
||||
|
||||
get isStepperAPI(): boolean {
|
||||
return (this.appType?.value as RadioItemAppType).createType === AppCreateType.API;
|
||||
}
|
||||
|
||||
get isStepperSAML(): boolean {
|
||||
return (this.appType?.value as RadioItemAppType).createType === AppCreateType.SAML;
|
||||
}
|
||||
|
||||
get decodedBase64(): string {
|
||||
const samlReq = this.samlAppRequest.toObject();
|
||||
if (samlReq && samlReq.metadataXml && typeof samlReq.metadataXml === 'string') {
|
||||
return Buffer.from(samlReq.metadataXml, 'base64').toString('ascii');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
set decodedBase64(xmlString) {
|
||||
if (this.samlAppRequest) {
|
||||
const base64 = Buffer.from(xmlString, 'ascii').toString('base64');
|
||||
this.samlAppRequest.setMetadataXml(base64);
|
||||
}
|
||||
}
|
||||
|
||||
public get metadataUrl(): AbstractControl | null {
|
||||
return this.samlConfigForm.get('metadataUrl');
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
title="{{ app?.name }}"
|
||||
[hasActions]="isZitadel === false && (['project.app.write:' + projectId, 'project.app.write'] | hasRole | async)"
|
||||
docLink="https://docs.zitadel.com/docs/guides/basics/projects"
|
||||
[sub]="app?.oidcConfig ? ('APP.OIDC.APPTYPE.' + app?.oidcConfig?.appType | translate) : 'API'"
|
||||
[sub]="app?.oidcConfig ? ('APP.OIDC.APPTYPE.' + app?.oidcConfig?.appType | translate) : app?.apiConfig ? 'API' : 'SAML'"
|
||||
[isActive]="app?.state === AppState.APP_STATE_ACTIVE"
|
||||
[isInactive]="app?.state === AppState.APP_STATE_INACTIVE"
|
||||
stateTooltip="{{ 'APP.PAGES.DETAIL.STATE.' + app?.state | translate }}"
|
||||
@@ -156,6 +156,60 @@
|
||||
</div>
|
||||
</form>
|
||||
</cnsl-card>
|
||||
|
||||
<cnsl-card *ngIf="samlForm && app?.samlConfig">
|
||||
<form [formGroup]="samlForm">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'APP.SAML.URL' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="metadataUrl" placeholder="https://" />
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
|
||||
<div class="cnsl-saml-config-line">
|
||||
<span class="cnsl-app-or cnsl-secondary-text">{{ 'APP.SAML.OR' | translate }}</span>
|
||||
|
||||
<input
|
||||
#xmlFileInput
|
||||
style="display: none"
|
||||
class="file-input"
|
||||
type="file"
|
||||
accept="text/xml,application/xml"
|
||||
(change)="onDropXML($any($event.target).files)"
|
||||
/>
|
||||
<button mat-stroked-button (click)="$event.preventDefault(); xmlFileInput.click()">
|
||||
{{ 'APP.SAML.XML' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="saml-xml"
|
||||
[ngClass]="{ disabled: !!metadataUrl?.value }"
|
||||
*ngIf="decodedBase64 && app && app.samlConfig && !app.samlConfig.metadataUrl"
|
||||
>
|
||||
<ngx-codemirror
|
||||
[(ngModel)]="decodedBase64"
|
||||
[disabled]="!!metadataUrl?.value"
|
||||
[options]="{
|
||||
lineNumbers: true,
|
||||
theme: 'material',
|
||||
mode: 'application/xml'
|
||||
}"
|
||||
></ngx-codemirror>
|
||||
</div>
|
||||
|
||||
<div class="btn-container">
|
||||
<button
|
||||
class="submit-button"
|
||||
type="submit"
|
||||
color="primary"
|
||||
(click)="saveSAMLApp()"
|
||||
[disabled]="samlForm.invalid || !canWrite"
|
||||
mat-raised-button
|
||||
>
|
||||
{{ 'ACTIONS.SAVE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</cnsl-card>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentSetting === 'token'">
|
||||
|
@@ -158,6 +158,24 @@
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cnsl-saml-config-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.cnsl-app-or {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.saml-xml {
|
||||
margin-top: 2rem;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
@@ -27,11 +28,13 @@ import {
|
||||
OIDCGrantType,
|
||||
OIDCResponseType,
|
||||
OIDCTokenType,
|
||||
SAMLConfig,
|
||||
} from 'src/app/proto/generated/zitadel/app_pb';
|
||||
import {
|
||||
GetOIDCInformationResponse,
|
||||
UpdateAPIAppConfigRequest,
|
||||
UpdateOIDCAppConfigRequest,
|
||||
UpdateSAMLAppConfigRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
@@ -52,6 +55,8 @@ import {
|
||||
} from '../authmethods';
|
||||
import { AuthMethodDialogComponent } from './auth-method-dialog/auth-method-dialog.component';
|
||||
|
||||
const MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-app-detail',
|
||||
templateUrl: './app-detail.component.html',
|
||||
@@ -104,6 +109,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
public oidcForm!: UntypedFormGroup;
|
||||
public oidcTokenForm!: UntypedFormGroup;
|
||||
public apiForm!: UntypedFormGroup;
|
||||
public samlForm!: UntypedFormGroup;
|
||||
|
||||
public redirectUrisList: string[] = [];
|
||||
public postLogoutRedirectUrisList: string[] = [];
|
||||
@@ -162,6 +168,11 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
authMethodType: [{ value: '', disabled: true }],
|
||||
});
|
||||
|
||||
this.samlForm = this.fb.group({
|
||||
metadataUrl: [{ value: '', disabled: true }],
|
||||
metadataXml: [{ value: '', disabled: true }],
|
||||
});
|
||||
|
||||
this.http.get('./assets/environment.json').subscribe((env: any) => {
|
||||
this.environmentMap = {
|
||||
issuer: env.issuer,
|
||||
@@ -290,12 +301,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.authMethods = this.authMethods.filter((element) => element !== CUSTOM_METHOD);
|
||||
}
|
||||
} else if (this.app.samlConfig) {
|
||||
this.settingsList = [{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' }];
|
||||
}
|
||||
|
||||
if (allowed) {
|
||||
this.oidcForm.enable();
|
||||
this.oidcTokenForm.enable();
|
||||
this.apiForm.enable();
|
||||
this.samlForm.enable();
|
||||
}
|
||||
|
||||
if (this.app.oidcConfig?.redirectUrisList) {
|
||||
@@ -370,6 +384,30 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public onDropXML(filelist: FileList): void {
|
||||
const file = filelist.item(0);
|
||||
if (file) {
|
||||
if (file.size > MAX_ALLOWED_SIZE) {
|
||||
this.toast.showInfo('POLICY.PRIVATELABELING.MAXSIZEEXCEEDED', true);
|
||||
} else {
|
||||
this.metadataUrl?.setValue('');
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((aXML) => {
|
||||
return (e) => {
|
||||
const xmlBase64 = e.target?.result;
|
||||
if (xmlBase64 && typeof xmlBase64 === 'string' && this.app?.samlConfig) {
|
||||
const samlConfig = new SAMLConfig();
|
||||
const cropped = xmlBase64.replace('data:text/xml;base64,', '');
|
||||
samlConfig.setMetadataXml(cropped);
|
||||
this.app.samlConfig.metadataXml = cropped;
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public authMethodFromPartialConfig(config: { oidc?: OIDCConfig.AsObject; api?: APIConfig.AsObject }): string {
|
||||
const key = getAuthMethodFromPartialConfig(config);
|
||||
return key;
|
||||
@@ -581,6 +619,28 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public saveSAMLApp(): void {
|
||||
if (this.samlForm.valid && this.app?.samlConfig) {
|
||||
const req = new UpdateSAMLAppConfigRequest();
|
||||
req.setProjectId(this.projectId);
|
||||
req.setAppId(this.app.id);
|
||||
|
||||
if (this.app.samlConfig) {
|
||||
req.setMetadataUrl(this.app.samlConfig?.metadataUrl);
|
||||
req.setMetadataXml(this.app.samlConfig?.metadataXml);
|
||||
}
|
||||
|
||||
this.mgmtService
|
||||
.updateSAMLAppConfig(req)
|
||||
.then(() => {
|
||||
this.toast.showInfo('APP.TOAST.APIUPDATED', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public regenerateOIDCClientSecret(): void {
|
||||
if (this.app) {
|
||||
this.mgmtService
|
||||
@@ -693,4 +753,31 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
public get clockSkewSeconds(): AbstractControl | null {
|
||||
return this.oidcTokenForm.get('clockSkewSeconds');
|
||||
}
|
||||
|
||||
public get metadataUrl(): AbstractControl | null {
|
||||
return this.samlForm.get('metadataUrl');
|
||||
}
|
||||
|
||||
get decodedBase64(): string {
|
||||
if (
|
||||
this.app &&
|
||||
this.app.samlConfig &&
|
||||
this.app.samlConfig.metadataXml &&
|
||||
typeof this.app.samlConfig.metadataXml === 'string'
|
||||
) {
|
||||
return Buffer.from(this.app?.samlConfig.metadataXml, 'base64').toString('ascii');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
set decodedBase64(xmlString: string) {
|
||||
if (this.app && this.app.samlConfig && this.app.samlConfig.metadataXml) {
|
||||
const base64 = Buffer.from(xmlString, 'ascii').toString('base64');
|
||||
|
||||
if (this.app.samlConfig) {
|
||||
this.app.samlConfig.metadataXml = base64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { CodemirrorModule } from '@ctrl/ngx-codemirror';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
@@ -89,6 +90,7 @@ import { RedirectUrisComponent } from './redirect-uris/redirect-uris.component';
|
||||
InputModule,
|
||||
MetaLayoutModule,
|
||||
MatSliderModule,
|
||||
CodemirrorModule,
|
||||
ChangesModule,
|
||||
InfoSectionModule,
|
||||
],
|
||||
|
@@ -10,6 +10,7 @@ import { OIDCAppType } from 'src/app/proto/generated/zitadel/app_pb';
|
||||
export enum AppCreateType {
|
||||
API = 'API',
|
||||
OIDC = 'OIDC',
|
||||
SAML = 'SAML',
|
||||
}
|
||||
|
||||
export interface RadioItemAppType {
|
||||
@@ -20,6 +21,7 @@ export interface RadioItemAppType {
|
||||
descI18nKey: string;
|
||||
prefix: string;
|
||||
background: string;
|
||||
protocol: 'OIDC' | 'SAML';
|
||||
}
|
||||
|
||||
export const WEB_TYPE: RadioItemAppType = {
|
||||
@@ -30,6 +32,7 @@ export const WEB_TYPE: RadioItemAppType = {
|
||||
oidcAppType: OIDCAppType.OIDC_APP_TYPE_WEB,
|
||||
prefix: 'WEB',
|
||||
background: 'linear-gradient(40deg, #059669 30%, #047857)',
|
||||
protocol: 'OIDC',
|
||||
};
|
||||
|
||||
export const USER_AGENT_TYPE: RadioItemAppType = {
|
||||
@@ -40,6 +43,7 @@ export const USER_AGENT_TYPE: RadioItemAppType = {
|
||||
oidcAppType: OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
|
||||
prefix: 'UA',
|
||||
background: 'linear-gradient(40deg, #dc2626 30%, #db2777)',
|
||||
protocol: 'OIDC',
|
||||
};
|
||||
|
||||
export const NATIVE_TYPE: RadioItemAppType = {
|
||||
@@ -50,6 +54,7 @@ export const NATIVE_TYPE: RadioItemAppType = {
|
||||
oidcAppType: OIDCAppType.OIDC_APP_TYPE_NATIVE,
|
||||
prefix: 'N',
|
||||
background: 'linear-gradient(40deg, #306ccc 30%, #4f46e5)',
|
||||
protocol: 'OIDC',
|
||||
};
|
||||
|
||||
export const API_TYPE: RadioItemAppType = {
|
||||
@@ -59,4 +64,14 @@ export const API_TYPE: RadioItemAppType = {
|
||||
createType: AppCreateType.API,
|
||||
prefix: 'API',
|
||||
background: 'linear-gradient(40deg, #1f2937, #111827)',
|
||||
protocol: 'OIDC',
|
||||
};
|
||||
|
||||
export const SAML_TYPE: RadioItemAppType = {
|
||||
titleI18nKey: 'APP.SAML.SELECTION.TITLE',
|
||||
descI18nKey: 'APP.SAML.SELECTION.DESCRIPTION',
|
||||
createType: AppCreateType.SAML,
|
||||
prefix: 'SAML',
|
||||
background: 'linear-gradient(40deg,rgb(110, 56, 124), rgb(88, 37, 103))',
|
||||
protocol: 'SAML',
|
||||
};
|
||||
|
@@ -17,20 +17,27 @@
|
||||
*ngFor="let app of appsSubject | async"
|
||||
matTooltip="{{ 'ACTIONS.EDIT' | translate }}"
|
||||
>
|
||||
<cnsl-app-card class="grid-card" matRipple [type]="app.oidcConfig?.appType" [isApiApp]="app.apiConfig !== undefined">
|
||||
<cnsl-app-card
|
||||
class="grid-card"
|
||||
matRipple
|
||||
[type]="app.samlConfig ? 'SAML' : app.oidcConfig?.appType"
|
||||
[isApiApp]="app.apiConfig !== undefined"
|
||||
>
|
||||
{{ app.name.charAt(0) }}
|
||||
<ng-container *ngIf="app.oidcConfig?.appType !== undefined">
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE" class="las la-mobile"></i>
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_WEB" class="las la-code"></i>
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT" class="las la-code"></i>
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE" class="lab la-openid"></i>
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_WEB" class="lab la-openid"></i>
|
||||
<i *ngIf="app.oidcConfig?.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT" class="lab la-openid"></i>
|
||||
<i *ngIf="app.apiConfig" class="las la-robot"></i>
|
||||
</ng-container>
|
||||
<span *ngIf="app.samlConfig" class="samlspan">SAML</span>
|
||||
</cnsl-app-card>
|
||||
<span class="name">{{ app.name }}</span>
|
||||
<span *ngIf="app.oidcConfig?.appType !== undefined && app.oidcConfig?.appType !== null" class="type">
|
||||
{{ 'APP.OIDC.APPTYPE.' + app.oidcConfig?.appType | translate }}</span
|
||||
>
|
||||
<span *ngIf="app.apiConfig !== undefined" class="type"> API</span>
|
||||
<span *ngIf="app.samlConfig !== undefined" class="type"> SAML</span>
|
||||
</div>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['project.app.write']">
|
||||
|
@@ -49,6 +49,17 @@
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 800;
|
||||
box-sizing: border-box;
|
||||
|
||||
i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.samlspan {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
|
@@ -63,6 +63,8 @@ import {
|
||||
AddProjectResponse,
|
||||
AddProjectRoleRequest,
|
||||
AddProjectRoleResponse,
|
||||
AddSAMLAppRequest,
|
||||
AddSAMLAppResponse,
|
||||
AddSecondFactorToLoginPolicyRequest,
|
||||
AddSecondFactorToLoginPolicyResponse,
|
||||
AddUserGrantRequest,
|
||||
@@ -423,6 +425,8 @@ import {
|
||||
UpdateProjectResponse,
|
||||
UpdateProjectRoleRequest,
|
||||
UpdateProjectRoleResponse,
|
||||
UpdateSAMLAppConfigRequest,
|
||||
UpdateSAMLAppConfigResponse,
|
||||
UpdateUserGrantRequest,
|
||||
UpdateUserGrantResponse,
|
||||
UpdateUserNameRequest,
|
||||
@@ -2268,27 +2272,18 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.reactivateProjectGrant(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addOIDCApp(app: AddOIDCAppRequest.AsObject): Promise<AddOIDCAppResponse.AsObject> {
|
||||
const req: AddOIDCAppRequest = new AddOIDCAppRequest();
|
||||
req.setAuthMethodType(app.authMethodType);
|
||||
req.setName(app.name);
|
||||
req.setProjectId(app.projectId);
|
||||
req.setResponseTypesList(app.responseTypesList);
|
||||
req.setGrantTypesList(app.grantTypesList);
|
||||
req.setAppType(app.appType);
|
||||
req.setPostLogoutRedirectUrisList(app.postLogoutRedirectUrisList);
|
||||
req.setRedirectUrisList(app.redirectUrisList);
|
||||
public addOIDCApp(req: AddOIDCAppRequest): Promise<AddOIDCAppResponse.AsObject> {
|
||||
return this.grpcService.mgmt.addOIDCApp(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addAPIApp(app: AddAPIAppRequest.AsObject): Promise<AddAPIAppResponse.AsObject> {
|
||||
const req: AddAPIAppRequest = new AddAPIAppRequest();
|
||||
req.setAuthMethodType(app.authMethodType);
|
||||
req.setName(app.name);
|
||||
req.setProjectId(app.projectId);
|
||||
public addAPIApp(req: AddAPIAppRequest): Promise<AddAPIAppResponse.AsObject> {
|
||||
return this.grpcService.mgmt.addAPIApp(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addSAMLApp(req: AddSAMLAppRequest): Promise<AddSAMLAppResponse.AsObject> {
|
||||
return this.grpcService.mgmt.addSAMLApp(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public regenerateAPIClientSecret(appId: string, projectId: string): Promise<RegenerateAPIClientSecretResponse.AsObject> {
|
||||
const req = new RegenerateAPIClientSecretRequest();
|
||||
req.setAppId(appId);
|
||||
@@ -2312,6 +2307,10 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.updateAPIAppConfig(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public updateSAMLAppConfig(req: UpdateSAMLAppConfigRequest): Promise<UpdateSAMLAppConfigResponse.AsObject> {
|
||||
return this.grpcService.mgmt.updateSAMLAppConfig(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeApp(projectId: string, appId: string): Promise<RemoveAppResponse.AsObject> {
|
||||
const req = new RemoveAppRequest();
|
||||
req.setAppId(appId);
|
||||
|
@@ -1609,9 +1609,9 @@
|
||||
"TITLE": "Anwendung",
|
||||
"ID": "ID",
|
||||
"DESCRIPTION": "Hier kannst Du Deine Applikationen bearbeiten und deren Konfiguration anpassen.",
|
||||
"CREATE_OIDC": "OIDC-Anwendung",
|
||||
"CREATE_OIDC_DESC_TITLE": "Gebe die Daten der Anwendung Schritt für Schritt ein.",
|
||||
"CREATE_OIDC_DESC_SUB": "Es wird automatisch eine empfohlene Konfiguration generiert.",
|
||||
"CREATE": "Applikation erstellen",
|
||||
"CREATE_DESC_TITLE": "Gebe die Daten der Anwendung Schritt für Schritt ein.",
|
||||
"CREATE_DESC_SUB": "Es wird automatisch eine empfohlene Konfiguration generiert.",
|
||||
"STATE": "Status",
|
||||
"DATECREATED": "Erstellt",
|
||||
"DATECHANGED": "Geändert",
|
||||
@@ -1664,6 +1664,10 @@
|
||||
"ADDITIONALORIGINSDESC": "Wenn sie zusätzliche Origins definieren wollen, die nicht den Redirect URIs gleichzusetzen sind, können Sie dies hier tun.",
|
||||
"ORIGINS": "Origins",
|
||||
"NOTANORIGIN": "Der Angegebene Wert ist kein Origin.",
|
||||
"PROSWITCH": "Konfigurator überspringen",
|
||||
"NAMEANDTYPESECTION": "Name und Typ",
|
||||
"TITLEFIRST": "Name der Applikation.",
|
||||
"TYPETITLE": "Art der Anwendung",
|
||||
"OIDC": {
|
||||
"INFO": {
|
||||
"ISSUER": "Issuer",
|
||||
@@ -1672,10 +1676,6 @@
|
||||
"CURRENT": "Aktuelle Konfiguration",
|
||||
"TOKENSECTIONTITLE": "AuthToken Optionen",
|
||||
"REDIRECTSECTIONTITLE": "Weiterleitungseinstellungen",
|
||||
"PROSWITCH": "Konfigurator überspringen",
|
||||
"NAMEANDTYPESECTION": "Name und Typ",
|
||||
"TITLEFIRST": "Gebe zuerst einen Namen ein.",
|
||||
"TYPETITLE": "Welche Art von Anwendung möchtest Du erstellen?",
|
||||
"REDIRECTTITLE": "Wohin soll nach dem Log-in weitergeleitet werden?",
|
||||
"REDIRECTDESCRIPTIONWEB": "Die Weiterleitung muss mit https:// beginnen. http:// ist nur im Entwicklermodus zulässig.",
|
||||
"REDIRECTDESCRIPTIONNATIVE": "Die Weiterleitung muss mit einem eigenen Protokoll, http://127.0.0.1, http://[::1] oder http://localhost beginnen.",
|
||||
@@ -1767,6 +1767,18 @@
|
||||
"1": "Private Key JWT"
|
||||
}
|
||||
},
|
||||
"SAML": {
|
||||
"SELECTION": {
|
||||
"TITLE": "SAML",
|
||||
"DESCRIPTION": "SAML Applikationen"
|
||||
},
|
||||
"CONFIGSECTION": "SAML Konfiguration",
|
||||
"URL": "Url des Metadata Files",
|
||||
"OR": "oder",
|
||||
"XML": "Metadata XML hochladen",
|
||||
"METADATA": "Metadata",
|
||||
"METADATAFROMFILE": "Metadata aus Datei"
|
||||
},
|
||||
"AUTHMETHODS": {
|
||||
"CODE": {
|
||||
"TITLE": "Code",
|
||||
|
@@ -1609,9 +1609,9 @@
|
||||
"TITLE": "Application",
|
||||
"ID": "ID",
|
||||
"DESCRIPTION": "Here you can edit your application data and it's configuration.",
|
||||
"CREATE_OIDC": "OIDC Application",
|
||||
"CREATE_OIDC_DESC_TITLE": "Enter Your Application Details Step by Step",
|
||||
"CREATE_OIDC_DESC_SUB": "A recommended configuration will be automatically generated.",
|
||||
"CREATE": "Create application",
|
||||
"CREATE_DESC_TITLE": "Enter Your Application Details Step by Step",
|
||||
"CREATE_DESC_SUB": "A recommended configuration will be automatically generated.",
|
||||
"STATE": "Status",
|
||||
"DATECREATED": "Created",
|
||||
"DATECHANGED": "Changed",
|
||||
@@ -1664,6 +1664,10 @@
|
||||
"ADDITIONALORIGINSDESC": "If you want to add additional Origins to your app which is not used as a redirect you can do that here.",
|
||||
"ORIGINS": "Origins",
|
||||
"NOTANORIGIN": "The entered value is not an origin",
|
||||
"PROSWITCH": "I'm a pro. Skip this wizard.",
|
||||
"NAMEANDTYPESECTION": "Name and Type",
|
||||
"TITLEFIRST": "Name of the application",
|
||||
"TYPETITLE": "Type of application",
|
||||
"OIDC": {
|
||||
"INFO": {
|
||||
"ISSUER": "Issuer",
|
||||
@@ -1672,10 +1676,6 @@
|
||||
"CURRENT": "Current Config",
|
||||
"TOKENSECTIONTITLE": "AuthToken Options",
|
||||
"REDIRECTSECTIONTITLE": "Redirect Settings",
|
||||
"PROSWITCH": "I'm a pro. Skip this wizard.",
|
||||
"NAMEANDTYPESECTION": "Name and Type",
|
||||
"TITLEFIRST": "Insert a name first.",
|
||||
"TYPETITLE": "What type of application do you want to create?",
|
||||
"REDIRECTTITLE": "Specify the URIs where the login will redirect to.",
|
||||
"POSTREDIRECTTITLE": "This is the redirect URI after logout.",
|
||||
"REDIRECTDESCRIPTIONWEB": "Redirect URIs must begin with https://. http:// is only valid with enabled development mode.",
|
||||
@@ -1767,6 +1767,18 @@
|
||||
"1": "Private Key JWT"
|
||||
}
|
||||
},
|
||||
"SAML": {
|
||||
"SELECTION": {
|
||||
"TITLE": "SAML",
|
||||
"DESCRIPTION": "SAML Applications"
|
||||
},
|
||||
"CONFIGSECTION": "SAML Configuration",
|
||||
"URL": "Url where Metadata file is located",
|
||||
"OR": "or",
|
||||
"XML": "Upload Metadata XML",
|
||||
"METADATA": "Metadata",
|
||||
"METADATAFROMFILE": "Metadata from File"
|
||||
},
|
||||
"AUTHMETHODS": {
|
||||
"CODE": {
|
||||
"TITLE": "Code",
|
||||
|
@@ -1609,9 +1609,9 @@
|
||||
"TITLE": "Applicazione",
|
||||
"ID": "ID",
|
||||
"DESCRIPTION": "Qui puoi modificare i dati della tua applicazione e la sua configurazione.",
|
||||
"CREATE_OIDC": "Applicazione OIDC",
|
||||
"CREATE_OIDC_DESC_TITLE": "Inserisci i dettagli della tua applicazione passo dopo passo",
|
||||
"CREATE_OIDC_DESC_SUB": "Una configurazione raccomandata sar\u00e0 generata automaticamente.",
|
||||
"CREATE": "Crea Applicazione",
|
||||
"CREATE_DESC_TITLE": "Inserisci i dettagli della tua applicazione passo dopo passo",
|
||||
"CREATE_DESC_SUB": "Una configurazione raccomandata sar\u00e0 generata automaticamente.",
|
||||
"STATE": "Stato",
|
||||
"DATECREATED": "Creato",
|
||||
"DATECHANGED": "Cambiato",
|
||||
@@ -1664,6 +1664,10 @@
|
||||
"ADDITIONALORIGINSDESC": "Se vuoi aggiungere ulteriori Origini alla tua app che non \u00e8 usata come reindirizzamento puoi farlo qui.",
|
||||
"ORIGINS": "Origini",
|
||||
"NOTANORIGIN": "Il valore inserito non \u00e8 un'origine",
|
||||
"PROSWITCH": "Sono un professionista. Salta questo passo.",
|
||||
"NAMEANDTYPESECTION": "Nome e tipo",
|
||||
"TITLEFIRST": "Nome dell' applicazione",
|
||||
"TYPETITLE": "Che tipo di applicazione vuoi creare?",
|
||||
"OIDC": {
|
||||
"INFO": {
|
||||
"ISSUER": "Issuer",
|
||||
@@ -1672,10 +1676,6 @@
|
||||
"CURRENT": "Configurazione attuale",
|
||||
"TOKENSECTIONTITLE": "Opzioni AuthToken",
|
||||
"REDIRECTSECTIONTITLE": "Impostazioni di reindirizzamento",
|
||||
"PROSWITCH": "Sono un professionista. Salta questo passo.",
|
||||
"NAMEANDTYPESECTION": "Nome e tipo",
|
||||
"TITLEFIRST": "Inserisci un nome.",
|
||||
"TYPETITLE": "Che tipo di applicazione vuoi creare?",
|
||||
"REDIRECTTITLE": "Specifica gli URI a cui il login sar\u00e0 reindirizzato.",
|
||||
"POSTREDIRECTTITLE": "Questo \u00e8 l'URI di reindirizzamento dopo il logout.",
|
||||
"REDIRECTDESCRIPTIONWEB": "Gli URI di reindirizzamento devono iniziare con https://. http:// \u00e8 valido solo con la modalit\u00e0 di sviluppo abilitata (DEV Mode).",
|
||||
@@ -1767,6 +1767,18 @@
|
||||
"1": "Private Key JWT"
|
||||
}
|
||||
},
|
||||
"SAML": {
|
||||
"SELECTION": {
|
||||
"TITLE": "SAML",
|
||||
"DESCRIPTION": "Applicazioni SAMML"
|
||||
},
|
||||
"CONFIGSECTION": "Configurazione SAML",
|
||||
"URL": "URL in cui si trova il file di metadati",
|
||||
"OR": "o",
|
||||
"XML": "Carica Metadata XML",
|
||||
"METADATA": "Metadata",
|
||||
"METADATAFROMFILE": "Metadati dal file"
|
||||
},
|
||||
"AUTHMETHODS": {
|
||||
"CODE": {
|
||||
"TITLE": "Code",
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/xml/xml';
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
Reference in New Issue
Block a user