Skip to content

Commit

Permalink
WIP, tests stall
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronshiel committed Oct 1, 2024
1 parent 173662a commit 57ba297
Show file tree
Hide file tree
Showing 12 changed files with 1,207 additions and 36 deletions.
1,068 changes: 1,035 additions & 33 deletions node/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-graphql": "^0.12.0",
"firebase-admin": "^12.6.0",
"graphql": "^15.8.0",
"graphql-type-json": "^0.3.2",
"ishex": "^2.1.0",
Expand Down Expand Up @@ -68,14 +69,15 @@
"@types/ejson": "^2.2.0",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
"@types/mocha": "^10.0.1",
"@types/mocha": "^10.0.8",
"@types/morgan": "^1.9.5",
"@types/node": "^20.5.6",
"@types/nodemailer": "^6.4.10",
"@types/passport": "^1.0.12",
"@types/passport-http-bearer": "^1.0.37",
"@types/passport-jwt": "^3.0.9",
"@types/remove-markdown": "^0.3.1",
"@types/sinon": "^17.0.3",
"@types/supertest": "^2.0.12",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.4.1",
Expand Down
1 change: 1 addition & 0 deletions node/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ provider:
NOTIFY_ADMIN_EMAILS_LIST: ${ssm:/mentorpal/shared/notify_admin_emails_list}
CONCURRENT_LAMBDAS: ${self:custom.stages.${self:provider.stage}.CONCURRENT_LAMBDAS}
MONGO_CONNECTION_POOL_MAX: ${self:custom.stages.${self:provider.stage}.MONGO_CONNECTION_POOL_MAX}
FIREBASE_SERVICE_ACCOUNT: ${ssm:/mentorpal/shared/FIREBASE_SERVICE_ACCOUNT}

stackTags:
Environment: ${self:provider.stage}
Expand Down
13 changes: 13 additions & 0 deletions node/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ import * as Sentry from '@sentry/serverless';
import { logger } from './utils/logging';
import requireEnv from './utils/require-env';
import { User as UserSchema } from './models';
import { initializeApp } from 'firebase-admin/app';
import * as admin from "firebase-admin";
const firebaseServiceAccount = process.env.FIREBASE_SERVICE_ACCOUNT;
const serviceAccount = JSON.parse(firebaseServiceAccount || "{}");

const isInTest = typeof global.it === "function";
export const firebaseApp = isInTest
? undefined
: initializeApp({
credential: admin.credential.cert(serviceAccount),
});



const CORS_ORIGIN = process.env.CORS_ORIGIN
? process.env.CORS_ORIGIN.split(',')
Expand Down
22 changes: 22 additions & 0 deletions node/src/gql/middleware-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { firebaseApp } from "../app";
import { DecodedIdToken, getAuth } from "firebase-admin/auth";

export async function getFirebaseUserFromReqAccessToken(
authHeader: string
): Promise<DecodedIdToken | undefined> {
if (!authHeader) {
return undefined;
}
const auth = getAuth(firebaseApp);
const token = authHeader.split(" ")[1];
if (!token) {
return undefined;
}
try {
const decodedToken = await auth.verifyIdToken(token);
return decodedToken;
} catch (e) {
console.error(e);
return undefined;
}
}
7 changes: 7 additions & 0 deletions node/src/gql/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { User } from '../models/User';
import OrganizationModel, { Organization } from '../models/Organization';
import { getRefreshedToken } from './types/user-access-token';
import { logger } from '../utils/logging';
import { DecodedIdToken, getAuth } from "firebase-admin/auth";
import { firebaseApp } from '../app';
import { getFirebaseUserFromReqAccessToken } from './middleware-helpers';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extensions = ({ context }: any) => {
Expand Down Expand Up @@ -71,15 +74,19 @@ async function refreshToken(
}
}


export default graphqlHTTP((req: Request, res: Response) => {
return new Promise((resolve) => {
// const authHeader = req.headers.authorization;
// const firebaseUser = await getFirebaseUserFromReqAccessToken(authHeader);
const next = (user: User, org: Organization, newToken = '') => {
resolve({
schema,
...(!process.env.NODE_ENV?.includes('prod') && {
graphiql: { headerEditorEnabled: true },
}),
context: {
// firebaseUser: firebaseUser || null,
user: user || null,
org: org || null,
newToken: newToken || '',
Expand Down
2 changes: 2 additions & 0 deletions node/src/gql/mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import userQuestionSetFeedback from './userQuestion-setFeedback';
import userQuestionSetAnswer from './userQuestion-setAnswer';
import mentorPreviewed from './mentor-previewed';
import refreshAccessToken from './refresh-access-token';
import loginFirebase from './login-firebase';

export default new GraphQLObjectType({
name: 'Mutation',
Expand All @@ -31,5 +32,6 @@ export default new GraphQLObjectType({
userQuestionSetAnswer,
mentorPreviewed,
refreshAccessToken,
loginFirebase,
},
});
35 changes: 35 additions & 0 deletions node/src/gql/mutation/login-firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
This software is Copyright ©️ 2020 The University of Southern California. All Rights Reserved.
Permission to use, copy, modify, and distribute this software and its documentation for educational, research and non-profit purposes, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and subject to the full license file found in the root of this software deliverable. Permission to make commercial use of this software may be obtained by contacting: USC Stevens Center for Innovation University of Southern California 1150 S. Olive Street, Suite 2300, Los Angeles, CA 90115, USA Email: [email protected]
The full terms of this copyright and license should always be found in the root directory of this software deliverable as "license.txt" and if these terms are not found with this software, please contact the USC Stevens Center for the full license.
*/
import { GraphQLObjectType } from "graphql";
import { DecodedIdToken } from "firebase-admin/auth";
import UserModel, { User } from '../../models/User';
import UserType from '../types/user';

export const loginFirebase = {
type: UserType,
resolve: async (
_root: GraphQLObjectType,
args: {},
context: { firebaseUser: DecodedIdToken }
): Promise<User> => {
console.log(context.firebaseUser);
if (!context.firebaseUser) {
throw new Error("unauthenticated");
}
const user = await UserModel.findOneAndUpdate(
{ firebaseId: context.firebaseUser.uid },
{
email: context.firebaseUser.email || "",
lastLoginAt: new Date(),
},
{ upsert: true, new: true }
);
return user;
},
};

export default loginFirebase;
2 changes: 2 additions & 0 deletions node/src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const FirstTimeTrackingSchema = new Schema<FirstTimeTracking>({
});

export interface User extends Document {
firebaseId: string;
googleId: string;
name: string;
email: string;
Expand All @@ -48,6 +49,7 @@ export interface User extends Document {

export const UserSchema = new Schema<User, UserModel>(
{
firebaseId: { type: String },
googleId: { type: String },
name: { type: String },
email: { type: String },
Expand Down
48 changes: 48 additions & 0 deletions node/test/graphql/mutation/login-firebase.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
This software is Copyright ©️ 2020 The University of Southern California. All Rights Reserved.
Permission to use, copy, modify, and distribute this software and its documentation for educational, research and non-profit purposes, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and subject to the full license file found in the root of this software deliverable. Permission to make commercial use of this software may be obtained by contacting: USC Stevens Center for Innovation University of Southern California 1150 S. Olive Street, Suite 2300, Los Angeles, CA 90115, USA Email: [email protected]
The full terms of this copyright and license should always be found in the root directory of this software deliverable as "license.txt" and if these terms are not found with this software, please contact the USC Stevens Center for the full license.
*/

import createApp, { appStart, appStop } from "app";
import { expect } from "chai";
import { Express } from "express";
import mongoUnit from "mongo-unit";
import request from "supertest";
import { getFirebaseToken, getToken } from "../../helpers";

describe.only("login-firebase", () => {
let app: Express;
beforeEach(async () => {
await mongoUnit.load(require("test/fixtures/mongodb/data-default.js"));
app = await createApp();
await appStart();
});

afterEach(async () => {
await appStop();
await mongoUnit.drop();
});

it(`can log in`, async () => {
const token = getFirebaseToken({ uid: "5ffdf1231ee2c62320b49e99" });

const fetchResult = await request(app)
.post("/graphql")
.set("Authorization", `bearer ${token}`)
.send({
query: `mutation FirebaseLogin {
firebaseLogin {
firebaseId
name
email
userRole
friends
lastLoginAt
}
}`,
});
expect(fetchResult.status).to.equal(200);
});
});
35 changes: 33 additions & 2 deletions node/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import mongoUnit from 'mongo-unit';
import path from 'path';
import request from 'supertest';
import jwt from 'jsonwebtoken';

import sinon from 'sinon';
import * as app from 'app';
import { DecodedIdToken } from 'firebase-admin/auth';
import * as helpers from '../src/gql/middleware-helpers';

export function fixturePath(p: string): string {
return path.join(__dirname, 'fixtures', p);
Expand Down Expand Up @@ -55,12 +57,41 @@ export function getToken(userId: string, expiresIn?: number): string {
const expirationDate = new Date(Date.now() + expiresIn * 1000);
const accessToken = jwt.sign(
{ id: userId, expirationDate },
process.env.JWT_SECRET,
process.env.JWT_SECRET || "",
{ expiresIn: expirationDate.getTime() - new Date().getTime() }
);
return accessToken;
}

export async function getFirebaseToken(user: Partial<DecodedIdToken>): Promise<string> {
const emptyToken: DecodedIdToken = {
uid: "",
aud: "",
auth_time: 0,
user_id: "",
sub: "",
iat: 0,
exp: 0,
email: "",
email_verified: false,
firebase: {
sign_in_provider: "",
identities: {},
},
iss: "",
};
sinon.restore();
sinon.stub(helpers, "getFirebaseUserFromReqAccessToken").returns(
new Promise((resolve) =>
resolve({
...emptyToken,
...user,
})
)
);
return "token";
}

export const USER_DEFAULT = '5ffdf41a1ee2c62320b49ea1';
export async function gqlWithAuth(
app: Express,
Expand Down
6 changes: 6 additions & 0 deletions node/test/init.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { appStop } from 'app';
import { logger } from 'utils/logging';
import mongoUnit from 'mongo-unit';
import { fixturePath } from './helpers';
import * as sinon from 'sinon';

before(() => {
dotenv.config({ path: fixturePath('.env') });
Expand All @@ -32,3 +33,8 @@ mongoUnit.start().then((url) => {
process.env.MONGO_URI = url; // this const process.env.DATABASE_URL = will keep link to fake mongo
run(); // this line start mocha tests
});


afterEach(async () => {
sinon.restore();
});

0 comments on commit 57ba297

Please sign in to comment.