Skip to content

Commit

Permalink
feat: added dbxFirebaseLoginModule
Browse files Browse the repository at this point in the history
- added DbxFirebaseLoginModule and related services for Firebase authentication
  • Loading branch information
dereekb committed Apr 18, 2022
1 parent 8739ba5 commit bf99f2d
Show file tree
Hide file tree
Showing 15 changed files with 89 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export class DocActionMapComponent {
saveThrottleTime = ms('2s');

handleSaveDraft: HandleActionFunction<DocActionFormExampleValue, any> = (value: DocActionFormExampleValue) => {
console.log('Save?');
return of(value).pipe(
delay(ms('1s')),
tap(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { map, Observable } from "rxjs";
export class DocFormExampleComponent extends AbstractConfigAsyncFormlyFormDirective<any, FormlyFieldConfig[]> {

value: any;

readonly fields$: Observable<FormlyFieldConfig[]> = this.config$.pipe(map((fields: FormlyFieldConfig[]) => fields ?? []));

}
2 changes: 1 addition & 1 deletion apps/demo/src/app/modules/doc/modules/form/doc.form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const DOC_FORM_ROUTES = [{
detail: 'form wrappers',
ref: 'doc.form.wrapper'
}, {
icon: 'template',
icon: 'article',
title: 'Templates',
detail: 'form field templates',
ref: 'doc.form.template'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { DbxFirebaseLoginComponent } from './login.component';
import { DbxFirebaseLoginEmailComponent } from './login.email.component';
import { DbxFirebaseLoginFacebookComponent } from './login.facebook.component';
import { DbxFirebaseLoginGoogleComponent } from './login.google.component';
import { DbxFirebaseAuthLoginProvider, DbxFirebaseAuthLoginService, DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN } from './login.service';
import { DbxFirebaseAuthLoginProvider, DbxFirebaseAuthLoginService, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN } from './login.service';
import { DbxFirebaseRegisterComponent } from './register.component';
import { DbxFirebaseLoginGitHubComponent } from './login.github.component';
import { DbxFirebaseLoginTwitterComponent } from './login.twitter.component';
Expand All @@ -27,12 +27,14 @@ import { DbxFirebaseLoginTermsComponent } from './login.terms.component';
import { DbxFirebaseLoginTermsViewComponent } from './login.terms.default.component';
import { DbxFirebaseLoginContextBackButtonComponent } from './login.context.back.component';
import { DbxFirebaseEmailRecoveryFormComponent } from './login.email.recovery.form.component';
import { DbxFirebaseAuthLoginPasswordConfig } from './login.password';

export abstract class DbxFirebaseLoginModuleRootConfig {
abstract readonly tosUrl: string;
abstract readonly privacyUrl: string;
abstract readonly enabledLoginMethods: FirebaseLoginMethodType[] | true;
abstract readonly termsComponentClass?: Type<any>;
abstract readonly passwordConfig?: DbxFirebaseAuthLoginPasswordConfig;
}

export function defaultFirebaseAuthLoginProvidersFactory(): DbxFirebaseAuthLoginProvider[] {
Expand Down Expand Up @@ -204,6 +206,9 @@ export class DbxFirebaseLoginModule {
}, {
provide: DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN,
useValue: config.termsComponentClass
}, {
provide: DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN,
useValue: config.passwordConfig
}, {
provide: DbxFirebaseLoginModuleRootConfig,
useValue: config
Expand Down
1 change: 1 addition & 0 deletions packages/dbx-firebase/src/lib/auth/login/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './login.github.component';
export * from './login.google.component';
export * from './login.list.component';
export * from './login.microsoft.component';
export * from './login.password';
export * from './login.service';
export * from './login';
export * from './login.twitter.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export abstract class AbstractConfiguredDbxFirebaseLoginButtonDirective implemen
readonly dbxFirebaseLoginContext: DbxFirebaseLoginContext) { }

ngOnInit(): void {
const assets = this.dbxFirebaseAuthLoginService.getProviderAssets(this.loginProvider) ?? {};
const assets = this.assetConfig;

this._config = {
text: assets.loginText ?? `<loginText not configured>`,
Expand All @@ -118,6 +118,14 @@ export abstract class AbstractConfiguredDbxFirebaseLoginButtonDirective implemen

abstract handleLogin(): Promise<any>;

get providerConfig() {
return this.dbxFirebaseAuthLoginService.getLoginProvider(this.loginProvider);
}

get assetConfig() {
return this.dbxFirebaseAuthLoginService.getProviderAssets(this.loginProvider) ?? {};
}

get config() {
return this._config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class DbxFirebaseLoginEmailComponent extends AbstractConfiguredDbxFirebas
readonly loginProvider = 'email';

handleLogin(): Promise<any> {
return DbxFirebaseLoginEmailContentComponent.openEmailLoginContext(this.dbxFirebaseLoginContext, { loginMode: 'login' });
return DbxFirebaseLoginEmailContentComponent.openEmailLoginContext(this.dbxFirebaseLoginContext, { loginMode: 'login', passwordConfig: this.dbxFirebaseAuthLoginService.getPasswordConfig() });
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<!-- Login View -->
<ng-template #loginView>
<ng-container dbxAction [dbxActionHandler]="handleLoginAction">
<dbx-firebase-email-form [loginMode]="loginMode" dbxActionForm [dbxFormSource]="emailFormValue">
<dbx-firebase-email-form [config]="formConfig" dbxActionForm [dbxFormSource]="emailFormValue">
</dbx-firebase-email-form>
<div class="dbx-firebase-login-email-forgot-prompt" *ngIf="isLoginMode">
<dbx-link [anchor]="forgotAnchor">Forgot Password?</dbx-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { DbxFirebaseAuthService } from './../service/firebase.auth.service';
import { firstValueFrom, from, tap, BehaviorSubject } from 'rxjs';
import { Component, EventEmitter, OnDestroy } from "@angular/core";
import { DbxFirebaseLoginContext } from "./login.context";
import { DbxFirebaseEmailFormValue } from './login.email.form.component';
import { DbxFirebaseEmailFormValue, DbxFirebaseEmailFormConfig } from './login.email.form.component';
import { DbxFirebaseLoginMode } from './login';
import { Inject } from '@angular/core';
import { firebaseAuthErrorToReadableError } from '../error';
import { Maybe } from '@dereekb/util';

export interface DbxFirebaseLoginEmailContentComponentConfig {
export interface DbxFirebaseLoginEmailContentComponentConfig extends DbxFirebaseEmailFormConfig {
loginMode: DbxFirebaseLoginMode;
}

Expand All @@ -21,6 +21,11 @@ export type DbxFirebaseLoginEmailContentMode = 'login' | 'recover' | 'recovering
})
export class DbxFirebaseLoginEmailContentComponent implements OnDestroy {

readonly formConfig: DbxFirebaseEmailFormConfig = {
loginMode: this.config.loginMode,
passwordConfig: this.config.passwordConfig
};

emailFormValue: Maybe<DbxFirebaseEmailFormValue>;
recoveryFormValue: Maybe<DbxFirebaseEmailRecoveryFormValue>;

Expand All @@ -31,7 +36,7 @@ export class DbxFirebaseLoginEmailContentComponent implements OnDestroy {
onClick: () => {
this.openRecovery();
}
}
};

readonly doneOrCancelled = new EventEmitter<boolean>();

Expand Down Expand Up @@ -66,6 +71,7 @@ export class DbxFirebaseLoginEmailContentComponent implements OnDestroy {
get buttonText() {
return this.config.loginMode === 'register' ? 'Register' : 'Log In';
}

readonly handleLoginAction: HandleActionFunction = (value: DbxFirebaseEmailFormValue) => {
this.emailFormValue = value;
this.recoveryFormValue = { email: value.username }; // cache value for recovery
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
import { Component, Input, OnDestroy } from "@angular/core";
import { ProvideFormlyContext, AbstractAsyncFormlyFormDirective, usernamePasswordLoginFields, UsernameLoginFieldsConfig, DefaultUsernameLoginFieldsValue } from "@dereekb/dbx-form";
import { TextPasswordFieldConfig, ProvideFormlyContext, AbstractAsyncFormlyFormDirective, usernamePasswordLoginFields, DefaultUsernameLoginFieldsValue } from "@dereekb/dbx-form";
import { Maybe } from "@dereekb/util";
import { FormlyFieldConfig } from "@ngx-formly/core";
import { BehaviorSubject, map, Observable } from "rxjs";
import { DbxFirebaseLoginMode } from "./login";

export interface DbxFirebaseEmailFormValue extends DefaultUsernameLoginFieldsValue { }

export interface DbxFirebaseEmailFormConfig {
loginMode: DbxFirebaseLoginMode;
passwordConfig?: TextPasswordFieldConfig;
}

@Component({
template: `<dbx-formly></dbx-formly>`,
selector: 'dbx-firebase-email-form',
providers: [ProvideFormlyContext()]
})
export class DbxFirebaseEmailFormComponent extends AbstractAsyncFormlyFormDirective<DbxFirebaseEmailFormValue> implements OnDestroy {

private _mode = new BehaviorSubject<DbxFirebaseLoginMode>('login');
private _config = new BehaviorSubject<DbxFirebaseEmailFormConfig>({ loginMode: 'login' });

readonly fields$: Observable<Maybe<FormlyFieldConfig[]>> = this._mode.pipe(
map((mode) => {
const fields: FormlyFieldConfig[] = usernamePasswordLoginFields({ username: 'email', verifyPassword: (mode === 'register') });
readonly fields$: Observable<Maybe<FormlyFieldConfig[]>> = this._config.pipe(
map(({ loginMode = 'login', passwordConfig }) => {
const fields: FormlyFieldConfig[] = usernamePasswordLoginFields({ username: 'email', password: passwordConfig, verifyPassword: (loginMode === 'register') });
return fields;
})
);

@Input()
set loginMode(loginMode: DbxFirebaseLoginMode) {
this._mode.next(loginMode);
set config(config: DbxFirebaseEmailFormConfig) {
this._config.next(config);
}

override ngOnDestroy() {
super.ngOnDestroy();
this._mode.complete();
this._config.complete();
}

}
7 changes: 7 additions & 0 deletions packages/dbx-firebase/src/lib/auth/login/login.password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TextPasswordFieldPasswordParameters } from "@dereekb/dbx-form";

export interface DbxFirebaseAuthLoginPasswordConfig extends TextPasswordFieldPasswordParameters { }

export const DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG: DbxFirebaseAuthLoginPasswordConfig = {
minLength: 6
};
23 changes: 21 additions & 2 deletions packages/dbx-firebase/src/lib/auth/login/login.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { mapIterable, addToSet, removeFromSet, Maybe, ArrayOrValue, filterMaybeV
import { Inject, Injectable, InjectionToken, Optional, Type } from "@angular/core";
import { FirebaseLoginMethodType, KnownFirebaseLoginMethodType } from "./login";
import { DbxFirebaseLoginTermsViewComponent } from "./login.terms.default.component";
import { DbxFirebaseAuthLoginPasswordConfig, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG } from "./login.password";

/**
* Default providers to inject.
*/
export const DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN = new InjectionToken('DefaultDbxFirebaseAuthLoginProviders');
export const DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN = new InjectionToken('DefaultDbxFirebaseAuthLoginTermsComponentClass');
export const DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN = new InjectionToken('DefaultDbxFirebaseAuthLoginPasswordConfig');


export interface DbxFirebaseAuthLoginProvider {
export interface DbxFirebaseAuthLoginProvider<D = any> {
/**
* Login method key for this type.
*/
Expand All @@ -23,6 +24,12 @@ export interface DbxFirebaseAuthLoginProvider {
* Custom registration type to use instead. If false, registration is not allowd for this type.
*/
readonly registrationComponentClass?: Type<any> | false;
/**
* Custom data available to the components.
*
* Components are responsible for knowing the typing information of this data.
*/
readonly componentData?: D;
/**
* Asset configuration for this type.
*/
Expand Down Expand Up @@ -66,17 +73,21 @@ export interface DbxFirebaseAuthLoginProviderAssets {
export class DbxFirebaseAuthLoginService {

private _enableAll = false;
private _passwordConfig: DbxFirebaseAuthLoginPasswordConfig;
private _providers = new Map<FirebaseLoginMethodType, DbxFirebaseAuthLoginProvider>();
private _assets = new Map<FirebaseLoginMethodType, DbxFirebaseAuthLoginProviderAssets>();
private _enabled = new Set<FirebaseLoginMethodType>();

constructor(
@Optional() @Inject(DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN) defaultProviders: DbxFirebaseAuthLoginProvider[],
@Optional() @Inject(DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN) passwordConfig: DbxFirebaseAuthLoginPasswordConfig,
@Optional() @Inject(DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN) readonly termsComponentClass: Type<any> = DbxFirebaseLoginTermsViewComponent
) {
if (defaultProviders) {
defaultProviders.forEach((x) => this.register(x, false));
}

this._passwordConfig = passwordConfig ?? DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG;
}

/**
Expand Down Expand Up @@ -168,4 +179,12 @@ export class DbxFirebaseAuthLoginService {
return this._assets.get(type);
}

getPasswordConfig() {
return this._passwordConfig;
}

setPasswordConfig(passwordConfig: DbxFirebaseAuthLoginPasswordConfig) {
this._passwordConfig = passwordConfig;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class DbxFirebaseRegisterEmailComponent extends AbstractConfiguredDbxFire
readonly loginProvider = 'email';

handleLogin(): Promise<any> {
return DbxFirebaseLoginEmailContentComponent.openEmailLoginContext(this.dbxFirebaseLoginContext, { loginMode: 'register' });
return DbxFirebaseLoginEmailContentComponent.openEmailLoginContext(this.dbxFirebaseLoginContext, { loginMode: 'register', passwordConfig: this.dbxFirebaseAuthLoginService.getPasswordConfig() });
}

}
18 changes: 12 additions & 6 deletions packages/dbx-form/src/lib/form/io/form.changes.directive.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { Directive, EventEmitter, Host, Output, OnInit } from '@angular/core';
import { AbstractSubscriptionDirective } from '@dereekb/dbx-core';
import { filter, first, mergeMap, delay } from 'rxjs';
import { Maybe } from '@dereekb/util';
import { first, mergeMap, delay, map } from 'rxjs';
import { DbxForm } from '../form';

/**
* Used to see form value changes.
*
* Emits undefined when the form is incomplete, and the value when the form is complete.
*/
@Directive({
selector: '[dbxFormValueChange]'
})
export class DbxFormValueChangesDirective<T extends object = any> extends AbstractSubscriptionDirective implements OnInit {

@Output()
readonly dbxFormValueChange = new EventEmitter<T>();
readonly dbxFormValueChange = new EventEmitter<Maybe<T>>();

constructor(@Host() public readonly form: DbxForm) {
super();
}

ngOnInit(): void {
this.sub = this.form.stream$.pipe(
filter(x => x.isComplete),
mergeMap(() => this.form.getValue().pipe(first())),
mergeMap((x) => this.form.getValue().pipe(first(), map((value) => ({ isComplete: x.isComplete, value })))),
delay(0)
).subscribe((value) => {
this.dbxFormValueChange.next(value);
).subscribe(({ isComplete, value }) => {
if (isComplete) {
this.dbxFormValueChange.next(value);
} else {
this.dbxFormValueChange.next(undefined);
}
});
}

Expand Down
9 changes: 6 additions & 3 deletions packages/dbx-form/src/lib/formly/template/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { fieldValuesAreEqualValidator } from '../../validator/field';
import { FormlyFieldConfig } from "@ngx-formly/core";
import { capitalizeFirstLetter, Maybe } from '@dereekb/util';

/**
* Convenience interface for the password parameters configuration for a TextPasswordField.
*/
export interface TextPasswordFieldPasswordParameters extends Partial<Pick<TextFieldConfig, 'maxLength' | 'minLength' | 'pattern'>> { };

/**
* textPasswordField() configuration.
*/
Expand Down Expand Up @@ -31,8 +36,6 @@ export function textPasswordField(config?: TextPasswordFieldConfig): FormlyField
* @returns
*/
export function textVerifyPasswordField(config?: TextPasswordFieldConfig): FormlyFieldConfig {
console.log('Verify password config: ', config);

return textPasswordField({
key: 'verifyPassword',
label: 'Verify Password',
Expand All @@ -59,7 +62,7 @@ export function textPasswordWithVerifyFieldGroup(config: TextPasswordWithVerifyF
const validators: any = {
validation: [{
errorPath: verifyPasswordFieldKey,
expression: fieldValuesAreEqualValidator({ message: 'The passwords do not match.' })
expression: fieldValuesAreEqualValidator({ keysFilter: [passwordFieldConfig.key, verifyPasswordField.key] as string[], message: 'The passwords do not match.' })
}]
};

Expand Down

0 comments on commit bf99f2d

Please sign in to comment.