Skip to content

Commit

Permalink
Merge pull request #917 from activepieces/feat/custom-auth
Browse files Browse the repository at this point in the history
feat: custom auth property
  • Loading branch information
abuaboud committed Mar 28, 2023
2 parents 7e34820 + f9aab01 commit df29643
Show file tree
Hide file tree
Showing 16 changed files with 315 additions and 12 deletions.
6 changes: 3 additions & 3 deletions packages/backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ AP_REDIS_PORT="6379"

AP_TELEMETRY_ENABLED="false"
AP_SIGN_UP_ENABLED="true"
AP_WARNING_TEXT_BODY="To sign in, use these credentials: email - dev@ap.com and password - 12345678"
AP_WARNING_TEXT_HEADER="Development Mode"
AP_FLOW_WORKER_CONCURRENCY="5"
AP_WARNING_TEXT_BODY=""
AP_WARNING_TEXT_HEADER=""
AP_FLOW_WORKER_CONCURRENCY="5"
4 changes: 2 additions & 2 deletions packages/engine/src/lib/services/connections.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import axios from 'axios';
import { AppConnection, AppConnectionType, CloudOAuth2ConnectionValue, BasicAuthConnectionValue, OAuth2ConnectionValueWithApp } from '@activepieces/shared';
import { AppConnection, AppConnectionType, CloudOAuth2ConnectionValue, BasicAuthConnectionValue, OAuth2ConnectionValueWithApp, CustomAuthConnectionValue } from '@activepieces/shared';
import { globals } from '../globals';

export const connectionService = {
async obtain(connectionName: string): Promise<null | OAuth2ConnectionValueWithApp | CloudOAuth2ConnectionValue | BasicAuthConnectionValue | string> {
async obtain(connectionName: string): Promise<null | OAuth2ConnectionValueWithApp | CloudOAuth2ConnectionValue | BasicAuthConnectionValue | string | CustomAuthConnectionValue> {
const url = globals.apiUrl + `/v1/app-connections/${connectionName}?projectId=${globals.projectId}`;
try {
const result: AppConnection = (await axios({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AppConnection,
AppConnectionType,
BasicAuthConnection,
CustomAuthConnection,
OAuth2AppConnection,
PropertyType,
SecretKeyAppConnection,
Expand Down Expand Up @@ -33,6 +34,10 @@ import {
CloudOAuth2ConnectionDialogComponent,
USE_MY_OWN_CREDENTIALS,
} from '../../../../flow-builder/page/flow-builder/flow-right-sidebar/edit-step-sidebar/edit-step-accordion/input-forms/piece-input-forms/cloud-oauth2-connection-dialog/cloud-oauth2-connection-dialog.component';
import {
CustomAuthConnectionDialogComponent,
CustomAuthDialogData,
} from '../../../../flow-builder/page/flow-builder/flow-right-sidebar/edit-step-sidebar/edit-step-accordion/input-forms/piece-input-forms/custom-auth-connection-dialog/custom-auth-connection-dialog.component';
import {
OAuth2ConnectionDialogComponent,
USE_CLOUD_CREDENTIALS,
Expand Down Expand Up @@ -99,11 +104,38 @@ export class AddEditConnectionButtonComponent {
this.newOAuth2AuthenticationDialogProcess();
} else if (this.config.type === PropertyType.SECRET_TEXT) {
this.openNewSecretKeyConnection();
} else if (this.config.type === PropertyType.CUSTOM_AUTH) {
this.openNewCustomAuthConnection();
} else {
this.openNewBasicAuthConnection();
}
}
private openNewCustomAuthConnection() {
const dialogData: CustomAuthDialogData = {
pieceAuthConfig: this.config,
pieceName: this.pieceName,
};
this.updateOrAddConnectionDialogClosed$ = this.dialogService
.open(CustomAuthConnectionDialogComponent, {
data: dialogData,
})
.afterClosed()
.pipe(
tap((result: AppConnection | null) => {
if (result) {
const authConfigOptionValue: `\${connections.${string}}` = `\${connections.${result.name}}`;
this.connectionPropertyValueChanged.emit({
configKey: this.config.key,
value: authConfigOptionValue,
});
}
}),
map(() => void 0)
);
}
private openNewBasicAuthConnection() {
const dialogData: BasicAuthDialogData = {
pieceAuthConfig: this.config,
Expand Down Expand Up @@ -311,13 +343,35 @@ export class AddEditConnectionButtonComponent {
);
if (this.config.type === PropertyType.OAUTH2) {
this.editOAuth2Property(currentConnection$);
} else if (this.config.type === PropertyType.CUSTOM_AUTH) {
this.editCustomAuthConnection(currentConnection$);
} else if (this.config.type === PropertyType.SECRET_TEXT) {
this.editSecretKeyConnection(currentConnection$);
} else {
this.editBasicAuthConnection(currentConnection$);
}
}

private editCustomAuthConnection(
currentConnection$: Observable<AppConnection>
) {
this.updateOrAddConnectionDialogClosed$ = currentConnection$.pipe(
switchMap((connection) => {
const customAuthConnection = connection as CustomAuthConnection;
const dialogData: CustomAuthDialogData = {
pieceName: this.pieceName,
pieceAuthConfig: this.config,
connectionToUpdate: customAuthConnection,
};
return this.dialogService
.open(CustomAuthConnectionDialogComponent, {
data: dialogData,
})
.afterClosed();
})
);
}

private editSecretKeyConnection(
currentConnection$: Observable<AppConnection>
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@


<ng-container
*ngIf="config.type === PropertyType.OAUTH2 || config.type === PropertyType.SECRET_TEXT || config.type === PropertyType.BASIC_AUTH">
*ngIf="config.type === PropertyType.OAUTH2 || config.type === PropertyType.SECRET_TEXT || config.type === PropertyType.BASIC_AUTH || config.type === PropertyType.CUSTOM_AUTH">
<div class="ap-flex">
<div class="ap-flex-grow"></div>
<div class="ap-cursor-pointer">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface PieceConfig {
string,
Pick<PieceProperty, 'displayName' | 'description' | 'type' | 'options'>
>;
customAuthProps?: Record<string, PieceProperty>;
staticDropdownState?: DropdownState<unknown>;
defaultValue?: unknown;
}
Expand Down Expand Up @@ -62,7 +63,9 @@ export const propsConvertor = {
refreshers: prop.refreshers,
staticDropdownState: prop.options,
defaultValue: prop.defaultValue,
customAuthProps: prop.props,
};

if (prop.username && prop.password) {
pieceConfig.basicAuthConfigs = {
password: prop.password,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { MagicWandDialogComponent } from './page/flow-builder/flow-builder-heade
import { HorizontalSidebarSeparatorComponent } from './page/flow-builder/canvas-utils/horizontal-sidebar-separator/horizontal-sidebar-separator.component';
import { TestStepComponent } from './page/flow-builder/flow-right-sidebar/edit-step-sidebar/test-step/test-step.component';
import { StepNameEditorComponent } from './page/flow-builder/flow-right-sidebar/edit-step-sidebar/step-name-editor/step-name-editor.component';
import { CustomAuthConnectionDialogComponent } from './page/flow-builder/flow-right-sidebar/edit-step-sidebar/edit-step-accordion/input-forms/piece-input-forms/custom-auth-connection-dialog/custom-auth-connection-dialog.component';

@NgModule({
imports: [
Expand Down Expand Up @@ -157,6 +158,7 @@ import { StepNameEditorComponent } from './page/flow-builder/flow-right-sidebar/
HorizontalSidebarSeparatorComponent,
TestStepComponent,
StepNameEditorComponent,
CustomAuthConnectionDialogComponent,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
exports: [FlowBuilderHeaderComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<app-dialog-title-template>
<ng-container *ngIf="!dialogData.connectionToUpdate; else editConnectionHeader">New Connection</ng-container>
<ng-template #editConnectionHeader>Edit {{dialogData!.connectionToUpdate?.name}}</ng-template>
</app-dialog-title-template>

<mat-dialog-content>

<form class="ap-flex ap-flex-col ap-gap-2 ap-w-[430px]" [formGroup]="settingsForm" (submit)="submit()">
<mat-form-field class="ap-w-full" appearance="outline">
<mat-label>Name</mat-label>
<input [matTooltip]="keyTooltip" formControlName="name" matInput type="text" />
<mat-error *ngIf="settingsForm.controls['name'].invalid">
<ng-container *ngIf="settingsForm.get('name')!.getError('required'); else patternErrorOrAlreadyUsed">
Name is required
</ng-container>
<ng-template #patternErrorOrAlreadyUsed>
<ng-container *ngIf="settingsForm.get('name')!.getError('pattern');">
Name can only contain letters, numbers and underscores
</ng-container>
<ng-container *ngIf="settingsForm.get('name')!.getError('nameUsed');">
Name is already used
</ng-container>
</ng-template></mat-error>
</mat-form-field>

<ng-container *ngFor="let prop of dialogData.pieceAuthConfig.customAuthProps |keyvalue">
<mat-form-field class="ap-w-full" appearance="outline"
*ngIf="prop.value.type === PropertyType.SECRET_TEXT || prop.value.type === PropertyType.SHORT_TEXT || prop.value.type === PropertyType.LONG_TEXT">
<mat-label>{{prop.value.displayName}}</mat-label>
<input [matTooltip]="prop.value.description||''" [formControlName]="prop.key" matInput
[type]="prop.value.type === PropertyType.SECRET_TEXT? 'password':'text'" />
<mat-error *ngIf="settingsForm.get(prop.key)!.invalid">
{{prop.value.displayName}} is required
</mat-error>
</mat-form-field>
<ng-container *ngIf="prop.value.type === PropertyType.STATIC_DROPDOWN">
<mat-form-field class="ap-w-full" appearance="outline">
<mat-label> {{prop.value.displayName}}</mat-label>
<mat-select [formControlName]="prop.key" [matTooltip]="prop.value.description"
[compareWith]="dropdownCompareWithFunction">
<mat-option *ngFor="let opt of prop.value.options!.options" [value]="opt.value">
{{opt.label}}
</mat-option>
</mat-select>
<mat-error *ngIf="settingsForm.controls[prop.key]!.invalid">
{{prop.value.displayName}} is required
</mat-error>
</mat-form-field>
</ng-container>
</ng-container>
</form>
</mat-dialog-content>

<mat-dialog-actions align="end">
<div class="ap-flex ap-gap-2.5">
<app-button btnColor="basic" mat-dialog-close btnSize="default">
Cancel
</app-button>
<app-button cdkFocusInitial btnSize="default" (click)="submit()" btnColor="primary" type="submit"
[loading]="loading">
Save
</app-button>
</div>
</mat-dialog-actions>
<ng-container *ngIf="upsert$|async"></ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Component, Inject } from '@angular/core';
import {
FormBuilder,
FormControl,
UntypedFormGroup,
Validators,
} from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { catchError, Observable, of, take, tap } from 'rxjs';
import {
AppConnection,
AppConnectionType,
CustomAuthConnection,
PropertyType,
UpsertCustomAuthRequest,
} from '@activepieces/shared';
import { PieceConfig } from '../../../../../../../../../common/components/configs-form/connector-action-or-config';
import { BuilderSelectors } from '../../../../../../../../store/builder/builder.selector';
import { ConnectionValidator } from '../../../../../../validators/connectionNameValidator';
import deepEqual from 'deep-equal';
import { appConnectionsActions } from '../../../../../../../../store/app-connections/app-connections.action';
import { AppConnectionsService } from '../../../../../../../../../common/service/app-connections.service';
import { MatSnackBar } from '@angular/material/snack-bar';

export interface CustomAuthDialogData {
pieceAuthConfig: PieceConfig;
pieceName: string;
connectionToUpdate?: CustomAuthConnection;
}

@Component({
selector: 'app-custom-auth-connection-dialog',
templateUrl: './custom-auth-connection-dialog.component.html',
})
export class CustomAuthConnectionDialogComponent {
loading = false;
settingsForm: UntypedFormGroup;
PropertyType = PropertyType;
keyTooltip =
'The ID of this authentication definition. You will need to select this key whenever you want to reuse this authentication.';
upsert$: Observable<AppConnection | null>;
constructor(
private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA)
public dialogData: CustomAuthDialogData,
private store: Store,
private dialogRef: MatDialogRef<CustomAuthConnectionDialogComponent>,
private appConnectionsService: AppConnectionsService,
private snackBar: MatSnackBar
) {
const props: Record<string, FormControl> = {};
Object.entries(this.dialogData.pieceAuthConfig.customAuthProps!).forEach(
([propName, prop]) => {
if (prop.required) {
props[propName] = new FormControl('', Validators.required);
} else {
props[propName] = new FormControl('');
}
}
);

this.settingsForm = this.fb.group({
name: new FormControl(
this.dialogData.connectionToUpdate?.name ||
this.dialogData.pieceName.replace(/[^A-Za-z0-9_\\-]/g, '_'),
{
nonNullable: true,
validators: [
Validators.required,
Validators.pattern('[A-Za-z0-9_\\-]*'),
],
asyncValidators: [
ConnectionValidator.createValidator(
this.store
.select(BuilderSelectors.selectAllAppConnections)
.pipe(take(1)),
undefined
),
],
}
),
...props,
});
if (this.dialogData.connectionToUpdate) {
this.settingsForm.patchValue(
this.dialogData.connectionToUpdate.value.props
);
this.settingsForm.get('name')?.disable();
}
}
dropdownCompareWithFunction = (opt: any, formControlValue: any) => {
return formControlValue && deepEqual(opt, formControlValue);
};
submit() {
this.settingsForm.markAllAsTouched();
if (this.settingsForm.valid) {
this.loading = true;
const propsValues = this.settingsForm.getRawValue();
delete propsValues.name;
const upsertRequest: UpsertCustomAuthRequest = {
appName: this.dialogData.pieceName,
name: this.settingsForm.getRawValue().name,
value: {
type: AppConnectionType.CUSTOM_AUTH,
props: propsValues,
},
};
this.upsert$ = this.appConnectionsService.upsert(upsertRequest).pipe(
catchError((err) => {
console.error(err);
this.snackBar.open(
'Connection operation failed please check your console.',
'Close',
{
panelClass: 'error',
duration: 5000,
}
);
return of(null);
}),
tap((connection) => {
if (connection) {
this.store.dispatch(
appConnectionsActions.upsert({ connection: connection })
);
this.dialogRef.close(connection);
}
this.loading = false;
})
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

.mdc-dialog .mdc-dialog__content
{
padding: 0px 24px 20px 24px !important;
padding: 3px 24px 20px 24px !important;
}

.material-icons
Expand Down
2 changes: 1 addition & 1 deletion packages/pieces/framework/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@activepieces/framework",
"version": "0.3.3",
"version": "0.3.4",
"type": "commonjs"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PropertyType } from "@activepieces/shared";
import { BasePropertySchema, NumberProperty, SecretTextProperty, ShortTextProperty, TPropertyValue } from "./base-prop";
import { StaticDropdownProperty } from "./dropdown-prop";

type CustomAuthProp = ShortTextProperty<boolean> | SecretTextProperty<boolean> | NumberProperty<boolean> | StaticDropdownProperty<unknown, boolean>;

export type CustomAuthPropsValue = Record<string, CustomAuthProp['valueSchema']>;

export type CustomAuthPropertySchema = BasePropertySchema & {
props: Record<string, CustomAuthProp>
}

export type CustomAuthPropertyValue = {
props: CustomAuthPropsValue,
}

export type CustomAuthProperty<R extends boolean> = CustomAuthPropertySchema & TPropertyValue<
CustomAuthPropertyValue,
PropertyType.CUSTOM_AUTH,
R
>;
Loading

0 comments on commit df29643

Please sign in to comment.