mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:37:30 +00:00
feat: add apple as idp (#6442)
* feat: manage apple idp * handle apple idp callback * add tests for provider * basic console implementation * implement flow for login UI and add logos / styling * tests * cleanup * add upload button * begin i18n * apple logo positioning, file upload component * fix add apple instance idp * add missing apple logos for login * update to go 1.21 * fix slice compare * revert permission changes * concrete error messages * translate login apple logo -y-2px * change form parsing * sign in button * fix tests * lint console --------- Co-authored-by: peintnermax <max@caos.ch>
This commit is contained in:
@@ -80,6 +80,11 @@
|
||||
<i class="idp-icon las la-building"></i>
|
||||
Active Directory / LDAP
|
||||
</div>
|
||||
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_APPLE">
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||
Apple
|
||||
</div>
|
||||
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
|
||||
</div>
|
||||
</td>
|
||||
|
@@ -21,6 +21,10 @@
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
@@ -261,6 +261,8 @@ export class IdpTableComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
case ProviderType.PROVIDER_TYPE_GITHUB:
|
||||
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'github', row.id];
|
||||
case ProviderType.PROVIDER_TYPE_APPLE:
|
||||
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'apple', row.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -109,6 +109,23 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="
|
||||
serviceType === PolicyComponentServiceType.ADMIN
|
||||
? ['/instance', 'provider', 'apple', 'create']
|
||||
: serviceType === PolicyComponentServiceType.MGMT
|
||||
? ['/org', 'provider', 'apple', 'create']
|
||||
: []
|
||||
"
|
||||
>
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="Apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="Apple" />
|
||||
<div class="text-container">
|
||||
<span class="title">Apple</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="
|
||||
|
@@ -64,6 +64,10 @@
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
@@ -0,0 +1,146 @@
|
||||
<cnsl-create-layout
|
||||
title="{{ id ? ('IDP.DETAIL.TITLE' | translate) : ('IDP.CREATE.TITLE' | translate) }}"
|
||||
(closed)="close()"
|
||||
>
|
||||
<div class="identity-provider-create-content">
|
||||
<div class="title-row">
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||
<h1>{{ 'IDP.CREATE.APPLE.TITLE' | translate }}</h1>
|
||||
<mat-spinner diameter="25" *ngIf="loading" color="primary"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<p class="identity-provider-desc cnsl-secondary-text">
|
||||
{{ !provider ? ('IDP.CREATE.APPLE.DESCRIPTION' | translate) : ('IDP.DETAIL.DESCRIPTION' | translate) }}
|
||||
</p>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="submitForm()">
|
||||
<div class="identity-provider-content">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="clientId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.APPLE.TEAMID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="teamId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.APPLE.KEYID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="keyId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-checkbox
|
||||
class="update-secret-checkbox"
|
||||
*ngIf="provider"
|
||||
[(ngModel)]="updatePrivateKey"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>{{ 'IDP.APPLE.UPDATEPRIVATEKEY' | translate }}</mat-checkbox
|
||||
>
|
||||
|
||||
<ng-container *ngIf="!provider || (provider && updatePrivateKey)">
|
||||
<span class="pk-label cnsl-secondary-text">{{ 'IDP.APPLE.PRIVATEKEY' | translate }}</span>
|
||||
<div class="private-key-wrapper">
|
||||
<ng-container *ngIf="privateKey?.value; else addLogoButton">
|
||||
<i class="las la-file"></i>
|
||||
<button
|
||||
class="dl-btn"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
(click)="privateKey?.setValue('')"
|
||||
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-template #addLogoButton>
|
||||
<input
|
||||
#keyFileInput
|
||||
style="display: none"
|
||||
class="file-input"
|
||||
type="file"
|
||||
accept=".p8"
|
||||
(change)="onDropKey($any($event.target).files)"
|
||||
/>
|
||||
<button
|
||||
class="asset-add-btn"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'IDP.APPLE.UPLOADPRIVATEKEY' | translate }}"
|
||||
(click)="$event.preventDefault(); keyFileInput.click()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="identity-provider-optional-h-wrapper">
|
||||
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
|
||||
<button (click)="showOptional = !showOptional" type="button" mat-icon-button>
|
||||
<mat-icon *ngIf="showOptional">keyboard_arrow_up</mat-icon
|
||||
><mat-icon *ngIf="!showOptional">keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showOptional">
|
||||
<div class="idp-scopes">
|
||||
<div class="flex-line">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
|
||||
|
||||
<input
|
||||
cnslInput
|
||||
[matChipInputFor]="chipScopesList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
(matChipInputTokenEnd)="addScope($event)"
|
||||
/>
|
||||
</cnsl-form-field>
|
||||
<button class="scope-add-button" (click)="addScope($any($event))" mat-icon-button>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<mat-chip-list #chipScopesList aria-label="scope selection">
|
||||
<mat-chip
|
||||
class="chip"
|
||||
*ngFor="let scope of scopesList?.value"
|
||||
selectable="false"
|
||||
removable
|
||||
(removed)="removeScope(scope)"
|
||||
>
|
||||
{{ scope }} <mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="name" />
|
||||
<span class="name-hint cnsl-secondary-text" cnslHint>{{ 'IDP.NAMEHINT' | translate }}</span>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-provider-options
|
||||
[initialOptions]="provider?.config?.options"
|
||||
(optionsChanged)="options = $event"
|
||||
></cnsl-provider-options>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="identity-provider-create-actions">
|
||||
<button
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
class="continue-button"
|
||||
[disabled]="form.invalid || form.disabled"
|
||||
type="submit"
|
||||
>
|
||||
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
|
||||
<span *ngIf="!id">{{ 'ACTIONS.CREATE' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</cnsl-create-layout>
|
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ProviderAppleComponent } from './provider-apple.component';
|
||||
|
||||
describe('ProviderGoogleComponent', () => {
|
||||
let component: ProviderAppleComponent;
|
||||
let fixture: ComponentFixture<ProviderAppleComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProviderAppleComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderAppleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,306 @@
|
||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, Injector, Type } from '@angular/core';
|
||||
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
|
||||
import { MatLegacyChipInputEvent as MatChipInputEvent } from '@angular/material/legacy-chips';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { take } from 'rxjs';
|
||||
import {
|
||||
AddAppleProviderRequest as AdminAddAppleProviderRequest,
|
||||
GetProviderByIDRequest as AdminGetProviderByIDRequest,
|
||||
UpdateAppleProviderRequest as AdminUpdateAppleProviderRequest,
|
||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||
import {
|
||||
AddAppleProviderRequest as MgmtAddAppleProviderRequest,
|
||||
GetProviderByIDRequest as MgmtGetProviderByIDRequest,
|
||||
UpdateAppleProviderRequest as MgmtUpdateAppleProviderRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { requiredValidator } from '../../form-field/validators/validators';
|
||||
|
||||
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
|
||||
|
||||
const MAX_ALLOWED_SIZE = 5 * 1024;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-provider-apple',
|
||||
templateUrl: './provider-apple.component.html',
|
||||
})
|
||||
export class ProviderAppleComponent {
|
||||
public showOptional: boolean = false;
|
||||
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
|
||||
public id: string | null = '';
|
||||
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||
private service!: ManagementService | AdminService;
|
||||
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
|
||||
public form!: FormGroup;
|
||||
|
||||
public loading: boolean = false;
|
||||
|
||||
public provider?: Provider.AsObject;
|
||||
public updatePrivateKey: boolean = false;
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private route: ActivatedRoute,
|
||||
private toast: ToastService,
|
||||
private injector: Injector,
|
||||
private _location: Location,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
) {
|
||||
this.form = new FormGroup({
|
||||
name: new FormControl('', []),
|
||||
clientId: new FormControl('', [requiredValidator]),
|
||||
teamId: new FormControl('', [requiredValidator]),
|
||||
keyId: new FormControl('', [requiredValidator]),
|
||||
privateKey: new FormControl('', [requiredValidator]),
|
||||
scopesList: new FormControl(['name', 'email'], []),
|
||||
});
|
||||
|
||||
this.authService
|
||||
.isAllowed(
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||
? ['iam.idp.write']
|
||||
: this.serviceType === PolicyComponentServiceType.MGMT
|
||||
? ['org.idp.write']
|
||||
: [],
|
||||
)
|
||||
.pipe(take(1))
|
||||
.subscribe((allowed) => {
|
||||
if (allowed) {
|
||||
this.form.enable();
|
||||
} else {
|
||||
this.form.disable();
|
||||
}
|
||||
});
|
||||
|
||||
this.route.data.pipe(take(1)).subscribe((data) => {
|
||||
this.serviceType = data['serviceType'];
|
||||
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||
|
||||
const bread: Breadcrumb = {
|
||||
type: BreadcrumbType.ORG,
|
||||
routerLink: ['/org'],
|
||||
};
|
||||
|
||||
this.breadcrumbService.setBreadcrumb([bread]);
|
||||
break;
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||
|
||||
const iamBread = new Breadcrumb({
|
||||
type: BreadcrumbType.ORG,
|
||||
name: 'Instance',
|
||||
routerLink: ['/instance'],
|
||||
});
|
||||
this.breadcrumbService.setBreadcrumb([iamBread]);
|
||||
break;
|
||||
}
|
||||
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
if (this.id) {
|
||||
this.privateKey?.setValidators([]);
|
||||
this.getData(this.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getData(id: string): void {
|
||||
const req =
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||
? new AdminGetProviderByIDRequest()
|
||||
: new MgmtGetProviderByIDRequest();
|
||||
req.setId(id);
|
||||
this.service
|
||||
.getProviderByID(req)
|
||||
.then((resp) => {
|
||||
this.provider = resp.idp;
|
||||
this.loading = false;
|
||||
if (this.provider?.config?.apple) {
|
||||
this.form.patchValue(this.provider.config.apple);
|
||||
this.name?.setValue(this.provider.name);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
public submitForm(): void {
|
||||
this.provider ? this.updateAppleProvider() : this.addAppleProvider();
|
||||
}
|
||||
|
||||
public addAppleProvider(): void {
|
||||
const req =
|
||||
this.serviceType === PolicyComponentServiceType.MGMT
|
||||
? new MgmtAddAppleProviderRequest()
|
||||
: new AdminAddAppleProviderRequest();
|
||||
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
this.loading = true;
|
||||
this.service
|
||||
.addAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
public updateAppleProvider(): void {
|
||||
if (this.provider) {
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||
const req = new MgmtUpdateAppleProviderRequest();
|
||||
req.setId(this.provider.id);
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
if (this.updatePrivateKey) {
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
(this.service as ManagementService)
|
||||
.updateAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (PolicyComponentServiceType.ADMIN) {
|
||||
const req = new AdminUpdateAppleProviderRequest();
|
||||
req.setId(this.provider.id);
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
if (this.updatePrivateKey) {
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
(this.service as AdminService)
|
||||
.updateAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
|
||||
public onDropKey(filelist: FileList): void {
|
||||
const file = filelist.item(0);
|
||||
if (file) {
|
||||
if (file.size > MAX_ALLOWED_SIZE) {
|
||||
this.toast.showInfo('IDP.APPLE.KEYMAXSIZEEXCEEDED', true);
|
||||
} else {
|
||||
this.privateKey?.setValue('');
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((aXML) => {
|
||||
return (e) => {
|
||||
const keyBase64 = e.target?.result;
|
||||
if (keyBase64 && typeof keyBase64 === 'string') {
|
||||
const cropped = keyBase64.replace('data:application/octet-stream;base64,', '');
|
||||
this.privateKey?.setValue(cropped);
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public addScope(event: MatChipInputEvent): void {
|
||||
const input = event.chipInput?.inputElement;
|
||||
const value = event.value.trim();
|
||||
|
||||
if (value !== '') {
|
||||
if (this.scopesList?.value) {
|
||||
this.scopesList.value.push(value);
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeScope(uri: string): void {
|
||||
if (this.scopesList?.value) {
|
||||
const index = this.scopesList.value.indexOf(uri);
|
||||
|
||||
if (index !== undefined && index >= 0) {
|
||||
this.scopesList.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.form.get('name');
|
||||
}
|
||||
|
||||
public get clientId(): AbstractControl | null {
|
||||
return this.form.get('clientId');
|
||||
}
|
||||
|
||||
public get teamId(): AbstractControl | null {
|
||||
return this.form.get('teamId');
|
||||
}
|
||||
|
||||
public get keyId(): AbstractControl | null {
|
||||
return this.form.get('keyId');
|
||||
}
|
||||
|
||||
public get privateKey(): AbstractControl | null {
|
||||
return this.form.get('privateKey');
|
||||
}
|
||||
|
||||
public get scopesList(): AbstractControl | null {
|
||||
return this.form.get('scopesList');
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ProviderType } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||
@@ -27,6 +28,7 @@ const typeMap = {
|
||||
[ProviderType.PROVIDER_TYPE_OAUTH]: { path: 'oauth', component: ProviderOAuthComponent },
|
||||
[ProviderType.PROVIDER_TYPE_OIDC]: { path: 'oidc', component: ProviderOIDCComponent },
|
||||
[ProviderType.PROVIDER_TYPE_LDAP]: { path: 'ldap', component: ProviderLDAPComponent },
|
||||
[ProviderType.PROVIDER_TYPE_APPLE]: { path: 'apple', component: ProviderAppleComponent },
|
||||
};
|
||||
|
||||
const routes: Routes = Object.entries(typeMap).map(([key, value]) => {
|
||||
|
@@ -17,6 +17,7 @@ import { InfoSectionModule } from '../info-section/info-section.module';
|
||||
import { ProviderOptionsModule } from '../provider-options/provider-options.module';
|
||||
import { StringListModule } from '../string-list/string-list.module';
|
||||
import { LDAPAttributesComponent } from './ldap-attributes/ldap-attributes.component';
|
||||
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||
@@ -43,6 +44,7 @@ import { ProvidersRoutingModule } from './providers-routing.module';
|
||||
ProviderOIDCComponent,
|
||||
ProviderOAuthComponent,
|
||||
ProviderLDAPComponent,
|
||||
ProviderAppleComponent,
|
||||
],
|
||||
imports: [
|
||||
ProvidersRoutingModule,
|
||||
|
@@ -3,6 +3,7 @@
|
||||
@mixin identity-provider-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.identity-provider-desc {
|
||||
font-size: 14px;
|
||||
@@ -19,6 +20,10 @@
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
@@ -74,6 +79,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pk-label {
|
||||
font-size: 12px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.private-key-wrapper {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
width: 70px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid map-get($foreground, divider);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.dl-btn {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
transform: translateX(50%) translateY(-50%);
|
||||
}
|
||||
|
||||
img {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&.icon {
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.dl-btn {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.string-list-component-wrapper {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@ import {
|
||||
ActivateLabelPolicyResponse,
|
||||
ActivateSMSProviderRequest,
|
||||
ActivateSMSProviderResponse,
|
||||
AddAppleProviderRequest,
|
||||
AddAppleProviderResponse,
|
||||
AddAzureADProviderRequest,
|
||||
AddAzureADProviderResponse,
|
||||
AddCustomDomainPolicyRequest,
|
||||
@@ -214,6 +216,8 @@ import {
|
||||
SetSecurityPolicyResponse,
|
||||
SetUpOrgRequest,
|
||||
SetUpOrgResponse,
|
||||
UpdateAppleProviderRequest,
|
||||
UpdateAppleProviderResponse,
|
||||
UpdateAzureADProviderRequest,
|
||||
UpdateAzureADProviderResponse,
|
||||
UpdateCustomDomainPolicyRequest,
|
||||
@@ -1173,6 +1177,14 @@ export class AdminService {
|
||||
return this.grpcService.admin.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.admin.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.admin.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||
const req = new DeleteProviderRequest();
|
||||
req.setId(id);
|
||||
|
@@ -15,6 +15,8 @@ import {
|
||||
AddAPIAppResponse,
|
||||
AddAppKeyRequest,
|
||||
AddAppKeyResponse,
|
||||
AddAppleProviderRequest,
|
||||
AddAppleProviderResponse,
|
||||
AddAzureADProviderRequest,
|
||||
AddAzureADProviderResponse,
|
||||
AddCustomLabelPolicyRequest,
|
||||
@@ -441,6 +443,8 @@ import {
|
||||
UpdateActionResponse,
|
||||
UpdateAPIAppConfigRequest,
|
||||
UpdateAPIAppConfigResponse,
|
||||
UpdateAppleProviderRequest,
|
||||
UpdateAppleProviderResponse,
|
||||
UpdateAppRequest,
|
||||
UpdateAppResponse,
|
||||
UpdateAzureADProviderRequest,
|
||||
@@ -1041,6 +1045,14 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.mgmt.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.mgmt.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||
const req = new DeleteProviderRequest();
|
||||
req.setId(id);
|
||||
|
@@ -1706,6 +1706,10 @@
|
||||
"LDAP": {
|
||||
"TITLE": "Active Directory / LDAP",
|
||||
"DESCRIPTION": "Enter the credentials for your LDAP Provider"
|
||||
},
|
||||
"APPLE": {
|
||||
"TITLE": "Sign in with Apple",
|
||||
"DESCRIPTION": "Enter the credentials for your Apple Provider"
|
||||
}
|
||||
},
|
||||
"DETAIL": {
|
||||
@@ -1819,6 +1823,14 @@
|
||||
"JWTENDPOINT": "JWT Endpoint",
|
||||
"JWTKEYSENDPOINT": "JWT Keys Endpoint"
|
||||
},
|
||||
"APPLE": {
|
||||
"TEAMID": "Team ID",
|
||||
"KEYID": "Key ID",
|
||||
"PRIVATEKEY": "Private Key",
|
||||
"UPDATEPRIVATEKEY": "Update Private Key",
|
||||
"UPLOADPRIVATEKEY": "Upload Private Key",
|
||||
"KEYMAXSIZEEXCEEDED": "Maximum size of 5kB exceeded."
|
||||
},
|
||||
"TOAST": {
|
||||
"SAVED": "Successfully saved.",
|
||||
"REACTIVATED": "Idp reactivated.",
|
||||
|
10
console/src/assets/images/idp/apple-dark.svg
Executable file
10
console/src/assets/images/idp/apple-dark.svg
Executable file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>White Logo Square </title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
10
console/src/assets/images/idp/apple.svg
Executable file
10
console/src/assets/images/idp/apple.svg
Executable file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>Black Logo Square</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
Reference in New Issue
Block a user