Skip to content

Commit

Permalink
fix(auth): fix an error preventing email verification to be validated
Browse files Browse the repository at this point in the history
The Firebase Auth Emulator has been updated also to verify local users created during development
  • Loading branch information
rhahao authored Dec 24, 2022
1 parent 25ea16f commit f9e2aac
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 57 deletions.
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,20 @@ Environment variables are required in order to locally run this API. Please find
- FIREBASE_AUTH_EMULATOR_HOST: the `Host:Port` for Authentication Emulator.
- FIRESTORE_EMULATOR_HOST: the `Host:Port` for Firestore Emulator.

### Starting the Development Server

You are now ready to start the development server, and interact with the API.

- Run `npm run dev` to start the server.
- Pleae note that when you are working with the front end applications, the congregation request is automatically approved.

## Sending a Pull Request (PR)

We are monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation. We’ll do our best to provide updates and feedback throughout the process.

**Before submitting a PR**, please make sure the following is done:

1. Run `npm run dev` to make sure that the local development server is running correctly.
2. Test your changes to make sure that they are working as intended.
- Test your changes to make sure that they are working as intended.

**When commiting your changes**, we recommend the following command to be run:

Expand Down
2 changes: 0 additions & 2 deletions src/classes/Congregation.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ Congregation.prototype.loadDetails = async function () {
const user = users.findUserById(congSnap.data().last_backup.by);
this.last_backup.by = user?.username || '';
}

return this;
};

Congregation.prototype.reloadMembers = async function () {
Expand Down
28 changes: 13 additions & 15 deletions src/classes/CongregationRequest.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { getFirestore } from "firebase-admin/firestore";
import { congregationRequests } from "./CongregationRequests.js";
import { users } from "./Users.js";
import { getFirestore } from 'firebase-admin/firestore';
import { congregationRequests } from './CongregationRequests.js';
import { users } from './Users.js';

const db = getFirestore();

class CongregationRequest {
constructor(id) {
this.id = id;
this.cong_name = "";
this.cong_number = "";
this.email = "";
this.username = "";
this.user_id = "";
this.cong_name = '';
this.cong_number = '';
this.email = '';
this.username = '';
this.user_id = '';
this.cong_role = [];
this.approved = false;
this.request_date = "";
this.request_date = '';
this.request_open = true;
}
}

CongregationRequest.prototype.loadDetails = async function () {
const requestRef = db.collection("congregation_request").doc(this.id);
const requestRef = db.collection('congregation_request').doc(this.id);
const requestSnap = await requestRef.get();

const user = await users.findUserByEmail(requestSnap.data().email);
Expand All @@ -30,24 +30,22 @@ CongregationRequest.prototype.loadDetails = async function () {
this.email = user.user_uid;
this.username = user.username;
this.user_id = user.id;
this.cong_role = ["admin", requestSnap.data().cong_role];
this.cong_role = ['admin', requestSnap.data().cong_role];
this.approved = requestSnap.data().approved;
this.request_date = requestSnap.data().request_date;
this.request_open = requestSnap.data().request_open;

return this;
};

CongregationRequest.prototype.approve = async function () {
const requestData = { approved: true, request_open: false };
await db.collection("congregation_request").doc(this.id).set(requestData, { merge: true });
await db.collection('congregation_request').doc(this.id).set(requestData, { merge: true });

congregationRequests.list = congregationRequests.list.filter((item) => item.id !== this.id);
};

CongregationRequest.prototype.disapprove = async function () {
const requestData = { approved: false, request_open: false };
await db.collection("congregation_request").doc(this.id).set(requestData, { merge: true });
await db.collection('congregation_request').doc(this.id).set(requestData, { merge: true });

congregationRequests.list = congregationRequests.list.filter((item) => item.id !== this.id);
};
Expand Down
16 changes: 9 additions & 7 deletions src/classes/CongregationRequests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getFirestore } from "firebase-admin/firestore";
import { dbFetchRequestsCongregation } from "../utils/congregation-request-utils.js";
import CongregationRequest from "./CongregationRequest.js";
import { getFirestore } from 'firebase-admin/firestore';
import { dbFetchRequestsCongregation } from '../utils/congregation-request-utils.js';
import CongregationRequest from './CongregationRequest.js';

// get firestore
const db = getFirestore(); //get default database
Expand Down Expand Up @@ -34,13 +34,15 @@ CongregationRequests.prototype.findRequestById = function (id) {

CongregationRequests.prototype.create = async function (data) {
try {
const reqRef = await db.collection("congregation_request").add(data);
const reqRef = await db.collection('congregation_request').add(data);

const ReqClass = new CongregationRequest();
const request = await ReqClass.loadDetails(reqRef.id);
const requestCong = new CongregationRequest(reqRef.id);
await requestCong.loadDetails();

this.list.push(request);
this.list.push(requestCong);
this.sort();

return requestCong;
} catch (error) {
throw new Error(error.message);
}
Expand Down
16 changes: 8 additions & 8 deletions src/classes/Congregations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getFirestore } from "firebase-admin/firestore";
import { dbFetchCongregations } from "../utils/congregation-utils.js";
import { Congregation } from "./Congregation.js";
import { getFirestore } from 'firebase-admin/firestore';
import { dbFetchCongregations } from '../utils/congregation-utils.js';
import { Congregation } from './Congregation.js';

const db = getFirestore(); //get default database

Expand All @@ -27,18 +27,18 @@ Congregations.prototype.findCongregationById = function (id) {
};

Congregations.prototype.create = async function (congInfo) {
const cong = await db.collection("congregations").add(congInfo);
const cong = await db.collection('congregations').add(congInfo);

const NewCong = new Congregation(cong.id);
const newCong = await NewCong.loadDetails();
const newCong = new Congregation(cong.id);
await newCong.loadDetails();
this.list.push(newCong);
this.sort();

return cong;
return newCong;
};

Congregations.prototype.delete = async function (id) {
await db.collection("congregations").doc(id).delete();
await db.collection('congregations').doc(id).delete();

this.list = this.list.filter((cong) => cong.id !== id);
};
Expand Down
4 changes: 3 additions & 1 deletion src/classes/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ User.prototype.adminLogout = async function () {
};

User.prototype.generateSecret = async function () {
const isProd = process.env.NODE_ENV === 'production';

try {
const tempSecret = new OTPAuth.Secret().base32;

const totp = new OTPAuth.TOTP({
issuer: 'sws2apps',
issuer: isProd ? 'sws2apps' : 'sws2apps-test',
label: this.user_uid,
algorithm: 'SHA1',
digits: 6,
Expand Down
10 changes: 0 additions & 10 deletions src/classes/Users.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';
import { remoteApp } from '../config/firebase-config.js';
import { decryptData } from '../utils/encryption-utils.js';
import { sendVerificationEmail } from '../utils/sendEmail.js';
import { dbFetchUsers } from '../utils/user-utils.js';
Expand Down Expand Up @@ -92,10 +91,6 @@ Users.prototype.create = async function (fullname, email, password) {
disabled: false,
};

if (remoteApp) {
await getAuth(remoteApp).createUser(userData);
}

const userRecord = await getAuth().createUser(userData);

const userEmail = userRecord.email;
Expand All @@ -118,11 +113,6 @@ Users.prototype.create = async function (fullname, email, password) {
Users.prototype.delete = async function (userId, authId) {
await db.collection('users').doc(userId).delete();

// remove from auth if qualified
if (remoteApp && authId) {
await getAuth(remoteApp).deleteUser(authId);
}

if (authId) await getAuth().deleteUser(authId);

// get congregation id
Expand Down
5 changes: 1 addition & 4 deletions src/config/firebase-config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { initializeApp, cert } from 'firebase-admin/app';

const serviceAccount = JSON.parse(Buffer.from(process.env.GOOGLE_CONFIG_BASE64, 'base64').toString('ascii'));

if (process.env.FIREBASE_AUTH_EMULATOR_HOST && process.env.FIRESTORE_EMULATOR_HOST) {
initializeApp({ projectId: process.env.FIREBASE_PROJECT_ID });
} else {
const serviceAccount = JSON.parse(Buffer.from(process.env.GOOGLE_CONFIG_BASE64, 'base64').toString('ascii'));
initializeApp({
credential: cert(serviceAccount),
});
}

export const remoteApp = process.env.FIREBASE_AUTH_EMULATOR_HOST ? initializeApp({ credential: cert(serviceAccount) }, 'remoteApp') : undefined;
10 changes: 9 additions & 1 deletion src/controllers/auth-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ export const loginUser = async (req, res, next) => {

if (visitorHistory.visits?.length > 0) {
// pass to google toolkit for authentication
const googleKit = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`;

let googleKit;
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
googleKit = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`;
} else {
googleKit = `http://${process.env.FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`;
}

const response = await fetch(googleKit, {
method: 'POST',
Expand All @@ -64,6 +71,7 @@ export const loginUser = async (req, res, next) => {
// clean expired session
const user = users.findUserByEmail(email);
if (user) {
await user.loadDetails();
if (user.id) await user.removeExpiredSession();

if (user.emailVerified) {
Expand Down
35 changes: 28 additions & 7 deletions src/controllers/congregation-controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { validationResult } from 'express-validator';
import { decryptData } from '../utils/encryption-utils.js';
import { sendCongregationRequest } from '../utils/sendEmail.js';
import { sendCongregationAccountCreated, sendCongregationRequest } from '../utils/sendEmail.js';
import { congregationRequests } from '../classes/CongregationRequests.js';
import { users } from '../classes/Users.js';
import { congregations } from '../classes/Congregations.js';
Expand Down Expand Up @@ -58,14 +58,35 @@ export const requestCongregation = async (req, res, next) => {
request_open: true,
};

await congregationRequests.create(data);
const requestCong = await congregationRequests.create(data);

const userInfo = users.findUserByEmail(email);
sendCongregationRequest(cong_name, cong_number, userInfo.username);
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
const userInfo = users.findUserByEmail(email);
sendCongregationRequest(cong_name, cong_number, userInfo.username);

res.locals.type = 'info';
res.locals.message = 'congregation request sent for approval';
res.status(200).json({ message: 'OK' });
res.locals.type = 'info';
res.locals.message = 'congregation request sent for approval';
res.status(200).json({ message: 'OK' });
} else {
// auto-approve during development
// create congregation data
const congData = { cong_name, cong_number };
const cong = await congregations.create(congData);

// update requestor info
await cong.addUser(requestCong.user_id, requestCong.cong_role);

// update request props
await requestCong.approve();

// send email to user
sendCongregationAccountCreated(email, requestCong.username, cong_name, cong_number);

res.locals.type = 'info';
res.locals.message = 'congregation created';
res.status(200).json({ message: 'OK' });
}
} catch (err) {
next(err);
}
Expand Down

0 comments on commit f9e2aac

Please sign in to comment.