Skip to content

Commit

Permalink
Restructue password validators into module. Add Firebase validatePass…
Browse files Browse the repository at this point in the history
…word. Setup emulation for Firebase Auth
  • Loading branch information
rgant committed Jan 22, 2025
1 parent 1f26f05 commit f1bfc73
Show file tree
Hide file tree
Showing 40 changed files with 823 additions and 315 deletions.
5 changes: 5 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "brainfry-app"
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ Thumbs.db
.firebase
*-debug.log
.runtimeconfig.json

apphosting.local.yaml
23 changes: 23 additions & 0 deletions apphosting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Settings for Backend (on Cloud Run).
# See https://firebase.google.com/docs/app-hosting/configure#cloud-run
runConfig:
minInstances: 0
# maxInstances: 100
# concurrency: 80
# cpu: 1
# memoryMiB: 512

# Environment variables and secrets.
# env:
# Configure environment variables.
# See https://firebase.google.com/docs/app-hosting/configure#user-defined-environment
# - variable: MESSAGE
# value: Hello world!
# availability:
# - BUILD
# - RUNTIME

# Grant access to secrets in Cloud Secret Manager.
# See https://firebase.google.com/docs/app-hosting/configure#secret-parameters
# - variable: MY_SECRET
# secret: mySecretRef
118 changes: 118 additions & 0 deletions docs/firebase-init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Firebase Init

Something happened when I ran `ng add @angular/fire` and I don't think the init
fully worked. So this is a record of my manual running of the command:

```
$ firebase init
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
/Users/rgant/Programming/brainfry
Before we get started, keep in mind:
* You are initializing within an existing Firebase project directory
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Firestore: Configure security rules and indexes files for Firestore,
Functions: Configure a Cloud Functions directory and its files, App Hosting: Configure an apphosting.yaml file for App Hosting, Storage: Configure a security rules file for Cloud Storage, Emulators: Set up local
emulators for Firebase products
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: brainfry-app (The Brain Fry)
i Using project brainfry-app (The Brain Fry)
=== Firestore Setup
Firestore Security Rules allow you to define how and when to allow
requests. You can keep these rules in your project directory
and publish them with firebase deploy.
? What file should be used for Firestore Rules? firestore.rules
? File firestore.rules already exists. Do you want to overwrite it with the Firestore Rules from the Firebase Console? No
Firestore indexes allow you to perform complex queries while
maintaining performance that scales with the size of the result
set. You can keep index definitions in your project directory
and publish them with firebase deploy.
? What file should be used for Firestore indexes? firestore.indexes.json
=== Functions Setup
Let's create a new codebase for your functions.
A directory corresponding to the codebase will be created in your project
with sample code pre-configured.
See https://firebase.google.com/docs/functions/organize-functions for
more information on organizing your functions using codebases.
Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔ Wrote functions/package.json
✔ Wrote functions/.eslintrc.js
✔ Wrote functions/tsconfig.json
✔ Wrote functions/tsconfig.dev.json
✔ Wrote functions/src/index.ts
✔ Wrote functions/.gitignore
? Do you want to install dependencies with npm now? No
=== Apphosting Setup
i Writing default settings to apphosting.yaml...
✔ Wrote apphosting.yaml
✔ Create a new App Hosting backend with `firebase apphosting:backends:create`
=== Storage Setup
Firebase Storage Security Rules allow you to define how and when to allow
uploads and downloads. You can keep these rules in your project directory
and publish them with firebase deploy.
? What file should be used for Storage Rules? storage.rules
? File storage.rules already exists. Overwrite? No
i Skipping write of storage.rules
=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. App Hosting Emulator, Authentication Emulator, Functions Emulator, Firestore Emulator,
Storage Emulator
? Which port do you want to use for the apphosting emulator? 5002
i apphosting: Initializing Emulator
? Specify your app's root directory relative to your repository ./
? What configs would you like to export? Secret
? Select a default Firebase project for this directory: brainfry-app (The Brain Fry)
? Which environment would you like to export secrets from Secret Manager for? base (apphosting.yaml)
Wrote secrets as environment variables to apphosting.local.yaml.
apphosting.local.yaml has been automatically added to your .gitignore.
? Which port do you want to use for the auth emulator? 9099
? Which port do you want to use for the functions emulator? 5001
? Which port do you want to use for the firestore emulator? 8080
? Which port do you want to use for the storage emulator? 9199
? Would you like to enable the Emulator UI? Yes
? Which port do you want to use for the Emulator UI (leave empty to use any available port)? 4000
? Would you like to download the emulators now? Yes
i firestore: downloading cloud-firestore-emulator-v1.19.8.jar...
Progress: ==========================================================================================================================================================================================> (100% of 64MB
i firestore: Removing outdated emulator files: cloud-firestore-emulator-v1.19.5.jar
i storage: downloading cloud-storage-rules-runtime-v1.1.3.jar...
Progress: ==========================================================================================================================================================================================> (100% of 53MB
i ui: downloading ui-v1.14.0.zip...
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
✔ Firebase initialization complete!
Progress: ===========================================================================>----------------------------------------------------------------------------------------------------------------- (40% of 4MB
```
51 changes: 50 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
{}
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": [
{
"source": "functions",
"codebase": "default",
"ignore": [
"node_modules",
".git",
"firebase-debug.log",
"firebase-debug.*.log",
"*.local"
],
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
],
"storage": {
"rules": "storage.rules"
},
"emulators": {
"apphosting": {
"port": 5002,
"rootDirectory": "./",
"startCommand": "npm run dev"
},
"auth": {
"port": 9099
},
"functions": {
"port": 5001
},
"firestore": {
"port": 8080
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true,
"port": 4000
},
"singleProjectMode": true
}
}
4 changes: 4 additions & 0 deletions firestore.indexes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"indexes": [],
"fieldOverrides": []
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"ng": "ng",
"start": "ng serve",
"stylelint": "stylelint --quiet-deprecation-warnings src/",
"test": "ng test",
"test": "firebase emulators:exec --only auth --ui 'ng test'",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
Expand Down
15 changes: 2 additions & 13 deletions src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import type { ApplicationConfig } from '@angular/core';
// UserTrackingService,
// } from '@angular/fire/analytics';
// import type { Analytics } from '@angular/fire/analytics';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import type { FirebaseApp } from '@angular/fire/app';
// import { initializeAppCheck, provideAppCheck, ReCaptchaEnterpriseProvider } from '@angular/fire/app-check';
// import type { AppCheck } from '@angular/fire/app-check';
import { getAuth, provideAuth } from '@angular/fire/auth';
Expand All @@ -29,21 +27,12 @@ import { provideServiceWorker } from '@angular/service-worker';

import { routes } from './app.routes';
import { AppTitleStrategyService } from './core/app-title-strategy.service';
import { provideOurFirebaseApp } from './core/firebase-app.provider';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideFirebaseApp(
(): FirebaseApp =>
initializeApp({
apiKey: 'AIzaSyB75fqz0szrfVCLvpil9_t9UPQlLYplNcI',
appId: '1:207926801743:web:e1402f665312fb7ab0813a',
authDomain: 'brainfry-app.firebaseapp.com',
messagingSenderId: '207926801743',
projectId: 'brainfry-app',
storageBucket: 'brainfry-app.appspot.com',
}),
),
provideOurFirebaseApp(),
provideAuth((): Auth => getAuth()),
// provideAnalytics((): Analytics => getAnalytics()),
// ScreenTrackingService,
Expand Down
17 changes: 17 additions & 0 deletions src/app/core/firebase-app.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { EnvironmentProviders } from '@angular/core';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import type { FirebaseApp } from '@angular/fire/app';

// Both app.config.ts and some tests need to provide the FirebaseApp
export const provideOurFirebaseApp = (): EnvironmentProviders =>
provideFirebaseApp(
(): FirebaseApp =>
initializeApp({
apiKey: 'AIzaSyB75fqz0szrfVCLvpil9_t9UPQlLYplNcI',
appId: '1:207926801743:web:e1402f665312fb7ab0813a',
authDomain: 'brainfry-app.firebaseapp.com',
messagingSenderId: '207926801743',
projectId: 'brainfry-app',
storageBucket: 'brainfry-app.appspot.com',
}),
);
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('ChangeEmailComponent', (): void => {
});

it('should configure password error messages', fakeAsync((): void => {
passwordErrorMessagesTest(component.passwordCntrl, fixture, 'fld-password-msgs');
passwordErrorMessagesTest(component.passwordCntrl, fixture, { errorsId: 'fld-password-msgs' });
}));

it('should configure login FormGroup', (): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
Your password is not very strong. Include a combination of upper and lowercase letters, numbers, and symbols.
</ng-container>
}
@if (errors['firebasevalidator']) {
<ng-container i18n="form field error message|Password was rejected by Firebase policy@@formErrors.password.firebasevalidator">
Your password needs to include: TODO
</ng-container>
}
</span>
}
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import type { ComponentFixture } from '@angular/core/testing';

import { provideOurFirebaseApp } from '@app/core/firebase-app.provider';
import { FORMS, PASSWORDS } from '@app/shared/constants';
import { getCompiled, safeQuerySelector } from '@testing/helpers';
import { getCompiled, provideEmulatedAuth, safeQuerySelector } from '@testing/helpers';

import { ariaInvalidTest } from '../testing/aria-invalid.spec';
import { passwordControlTest, passwordErrorMessagesTest, passwordInputTest } from '../testing/password-field.spec';
Expand Down Expand Up @@ -39,10 +40,10 @@ describe('ChangePasswordComponent', (): void => {
let fixture: ComponentFixture<ChangePasswordComponent>;

const passwordFieldTests = ({ autoComplete, control, errorId, inputId, validateStrength }: FieldSetup): void => {
it(`should configure current ${control} FormControl`, (): void => {
it(`should configure current ${control} FormControl`, fakeAsync((): void => {
const cntrl = component[control];
passwordControlTest(cntrl, validateStrength);
});
}));

it(`should configure ${control} input`, (): void => {
passwordInputTest(fixture, inputId, autoComplete);
Expand All @@ -55,13 +56,14 @@ describe('ChangePasswordComponent', (): void => {

it(`should configure ${control} error messages`, fakeAsync((): void => {
const cntrl = component[control];
passwordErrorMessagesTest(cntrl, fixture, errorId, validateStrength);
passwordErrorMessagesTest(cntrl, fixture, { errorsId: errorId, isNewPassword: validateStrength });
}));
};

beforeEach(async (): Promise<void> => {
await TestBed.configureTestingModule({
imports: [ ChangePasswordComponent ],
providers: [ provideOurFirebaseApp(), provideEmulatedAuth() ],
})
.compileComponents();

Expand Down Expand Up @@ -94,6 +96,7 @@ describe('ChangePasswordComponent', (): void => {

// Valid
component.changePasswordForm.setValue({ currentPw: 'b1851b66-191', password1: '3bBce4%2c731', password2: '3bBce4%2c731' });
tick(); // Firebase validatePassword is async

expect(component.changePasswordForm.valid).withContext('valid').toBeTrue();
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PASSWORDS } from '@app/shared/constants';
import { SpinnerComponent } from '@app/shared/spinner/spinner.component';

import { createPasswordControl } from '../identity-forms';
import { passwordsMatch, passwordsMatchFormErrors } from '../validators/passwords.validator';
import { passwordsMatch, passwordsMatchFormErrors } from '../validators/passwords';

type ChangePasswordFormGroup = FormGroup<{
currentPw: FormControl<string | null>;
Expand Down
7 changes: 6 additions & 1 deletion src/app/identity/identity-forms.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { TestBed } from '@angular/core/testing';

import { provideOurFirebaseApp } from '@app/core/firebase-app.provider';
import { provideEmulatedAuth } from '@testing/helpers';

import { createEmailControl, createPasswordControl } from './identity-forms';
import type { ControlStruct } from './identity-forms';
import { emailControlTest } from './testing/email-field.spec';
import { passwordControlTest } from './testing/password-field.spec';

describe('Identity Forms', (): void => {
beforeEach((): void => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [ provideOurFirebaseApp(), provideEmulatedAuth() ],
});
});

it('should create email control and signals', (): void => {
Expand Down
Loading

0 comments on commit f1bfc73

Please sign in to comment.