Skip to content

Commit da2b6ed

Browse files
authored
Merge pull request #108 from cuappdev/ashley/refactoring-user-session
Refactoring all login bugs/flow
2 parents af7f43d + 2acda30 commit da2b6ed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+680
-784
lines changed

ormconfig.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
1+
// import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
22

33
module.exports = {
44
type: 'postgres',
@@ -11,7 +11,7 @@ module.exports = {
1111
'src/models/*.ts',
1212
],
1313
synchronize: false,
14-
namingStrategy: new SnakeNamingStrategy(),
14+
// namingStrategy: new SnakeNamingStrategy(),
1515
migrations: [
1616
'src/migrations/*.ts',
1717
],

src/api/controllers/AuthController.ts

+3-42
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@ import { Body, CurrentUser, Delete, Get, HeaderParam, JsonController, Params, Po
22

33
import { UserModel } from '../../models/UserModel';
44
import { AuthService } from '../../services/AuthService';
5-
import {
6-
APIUserSession,
7-
CreateUserRequest,
8-
GetSessionReponse,
9-
GetSessionsReponse,
10-
GetUserResponse,
11-
LogoutResponse,
12-
} from '../../types';
13-
import { LoginRequest } from '../validators/AuthControllerRequests';
14-
import { UuidParam } from '../validators/GenericRequests';
5+
import { FcmTokenRequest } from '../../types';
156

167
@JsonController('auth/')
178
export class AuthController {
@@ -21,38 +12,8 @@ export class AuthController {
2112
this.authService = authService;
2213
}
2314

24-
@Get()
25-
async currentUser(@CurrentUser() user: UserModel): Promise<GetUserResponse> {
26-
return { user: user.getUserProfile() };
27-
}
28-
2915
@Post()
30-
async createUser(@Body() createUserRequest: CreateUserRequest): Promise<GetUserResponse> {
31-
return { user: await this.authService.createUser(createUserRequest) };
32-
}
33-
34-
@Post('login/')
35-
async login(@Body() loginRequest: LoginRequest): Promise<APIUserSession> {
36-
return (await this.authService.loginUser(loginRequest)).serializeToken();
37-
}
38-
39-
@Post('logout/')
40-
async logout(@HeaderParam("authorization") accessToken: string): Promise<LogoutResponse> {
41-
return { logoutSuccess: await this.authService.deleteSessionByAccessToken(accessToken) };
42-
}
43-
44-
@Delete('id/:id/')
45-
async deleteUserById(@Params() params: UuidParam): Promise<GetUserResponse> {
46-
return { user: await this.authService.deleteUserById(params) };
47-
}
48-
49-
@Get('sessions/:id/')
50-
async getSessionsByUserId(@Params() params: UuidParam): Promise<GetSessionsReponse> {
51-
return { sessions: await this.authService.getSessionsByUserId(params) };
52-
}
53-
54-
@Get('refresh/')
55-
async refreshToken(@HeaderParam("authorization") refreshToken: string): Promise<GetSessionReponse> {
56-
return { session: await this.authService.updateSession(refreshToken) };
16+
async authorize(@CurrentUser() user: UserModel, @Body() fcmToken: FcmTokenRequest): Promise<UserModel | null> {
17+
return await this.authService.authorize(user, fcmToken.token);
5718
}
5819
}

src/api/controllers/FeedbackController.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Body, Delete, Get, JsonController, Params, Post } from 'routing-control
22

33
import { FeedbackService } from '../../services/FeedbackService';
44
import { CreateFeedbackRequest, GetFeedbackResponse, GetFeedbacksResponse, GetSearchedFeedbackRequest } from '../../types';
5-
import { UuidParam } from '../validators/GenericRequests';
5+
import { UuidParam, FirebaseUidParam } from '../validators/GenericRequests';
66

77
@JsonController('feedback/')
88
export class FeedbackController {
@@ -24,7 +24,7 @@ export class FeedbackController {
2424
}
2525

2626
@Get('userId/:id/')
27-
async getFeedbackByUserId(@Params() params: UuidParam): Promise<GetFeedbacksResponse> {
27+
async getFeedbackByUserId(@Params() params: FirebaseUidParam): Promise<GetFeedbacksResponse> {
2828
return { feedbacks: await this.feedbackService.getFeedbackByUserId(params) };
2929
}
3030

src/api/controllers/NotifController.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class NotifController {
1313

1414
@Get('recent')
1515
async getRecentNotifications(@CurrentUser() user: UserModel) {
16-
return this.notifService.getRecentNotifications(user.id);
16+
return this.notifService.getRecentNotifications(user.firebaseUid);
1717
}
1818

1919
@Post()

src/api/controllers/PostController.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
GetSearchedPostsRequest,
1515
IsSavedPostResponse,
1616
} from '../../types';
17-
import { UuidParam } from '../validators/GenericRequests';
17+
import { UuidParam, FirebaseUidParam } from '../validators/GenericRequests';
1818

1919
@JsonController('post/')
2020
export class PostController {
@@ -35,7 +35,7 @@ export class PostController {
3535
}
3636

3737
@Get('userId/:id/')
38-
async getPostsByUserId(@CurrentUser() user: UserModel, @Params() params: UuidParam): Promise<GetPostsResponse> {
38+
async getPostsByUserId(@CurrentUser() user: UserModel, @Params() params: FirebaseUidParam): Promise<GetPostsResponse> {
3939
return { posts: await this.postService.getPostsByUserId(user, params) };
4040
}
4141

@@ -90,7 +90,7 @@ export class PostController {
9090
}
9191

9292
@Get('archive/userId/:id/')
93-
async getArchivedPostsByUserId(@Params() params: UuidParam): Promise<GetPostsResponse> {
93+
async getArchivedPostsByUserId(@Params() params: FirebaseUidParam): Promise<GetPostsResponse> {
9494
return { posts: await this.postService.getArchivedPostsByUserId(params) };
9595
}
9696

@@ -100,7 +100,7 @@ export class PostController {
100100
}
101101

102102
@Post('archiveAll/userId/:id/')
103-
async archiveAllPostsByUserId(@Params() params: UuidParam): Promise<GetPostsResponse> {
103+
async archiveAllPostsByUserId(@Params() params: FirebaseUidParam): Promise<GetPostsResponse> {
104104
return { posts: await this.postService.archiveAllPostsByUserId(params) };
105105
}
106106

src/api/controllers/RequestController.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Body, CurrentUser, Delete, Get, JsonController, Params, Post } from 'ro
33
import { UserModel } from '../../models/UserModel';
44
import { RequestService } from '../../services/RequestService';
55
import { CreateRequestRequest, GetPostsResponse, GetRequestResponse, GetRequestsResponse } from '../../types';
6-
import { TimeParam, UuidParam } from '../validators/GenericRequests';
6+
import { TimeParam, UuidParam, FirebaseUidParam } from '../validators/GenericRequests';
77

88
@JsonController('request/')
99
export class RequestController {
@@ -25,7 +25,7 @@ export class RequestController {
2525
}
2626

2727
@Get('userId/:id/')
28-
async getRequestsByUserId(@Params() params: UuidParam): Promise<GetRequestsResponse> {
28+
async getRequestsByUserId(@Params() params: FirebaseUidParam): Promise<GetRequestsResponse> {
2929
return { requests: await this.requestService.getRequestByUserId(params) };
3030
}
3131

@@ -45,7 +45,7 @@ export class RequestController {
4545
}
4646

4747
@Post('archiveAll/userId/:id/')
48-
async archiveAllRequestsByUserId(@Params() params: UuidParam): Promise<GetRequestsResponse> {
48+
async archiveAllRequestsByUserId(@Params() params: FirebaseUidParam): Promise<GetRequestsResponse> {
4949
return { requests: await this.requestService.archiveAllRequestsByUserId(params) };
5050
}
5151

src/api/controllers/UserController.ts

+21-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Body, CurrentUser, Get, JsonController, Param, Params, Post, Delete} fr
22

33
import { UserModel } from '../../models/UserModel';
44
import { UserService } from '../../services/UserService';
5-
import { BlockUserRequest, UnblockUserRequest, EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, SaveTokenRequest, SetAdminByEmailRequest } from '../../types';
6-
import { UuidParam } from '../validators/GenericRequests';
5+
import { BlockUserRequest, UnblockUserRequest, EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, CreateUserRequest, SetAdminByEmailRequest, FcmTokenRequest } from '../../types';
6+
import { UuidParam, FirebaseUidParam } from '../validators/GenericRequests';
77

88
@JsonController('user/')
99
export class UserController {
@@ -13,6 +13,11 @@ export class UserController {
1313
this.userService = userService;
1414
}
1515

16+
@Post('create/')
17+
async createUser(@CurrentUser() user: UserModel, @Body() createUserRequest: CreateUserRequest): Promise<UserModel> {
18+
return await this.userService.createUser(user, createUserRequest);
19+
}
20+
1621
@Get()
1722
async getUsers(@CurrentUser() user: UserModel): Promise<GetUsersResponse> {
1823
const users = await this.userService.getAllUsers(user);
@@ -64,13 +69,24 @@ export class UserController {
6469
return { users: await this.userService.getBlockedUsersById(params) };
6570
}
6671

72+
@Delete()
73+
async deleteUser(@CurrentUser() user: UserModel): Promise<UserModel> {
74+
return await this.userService.deleteUser(user);
75+
}
76+
6777
@Delete('id/:id/')
68-
async deleteUser(@Params() params: UuidParam, @CurrentUser() user: UserModel): Promise<GetUserResponse> {
69-
return { user: await this.userService.deleteUser(user, params) };
78+
async deleteUserByOtherUser(@Params() params: FirebaseUidParam, @CurrentUser() user: UserModel): Promise<GetUserResponse> {
79+
return { user: await this.userService.deleteUserByOtherUser(user, params) };
7080
}
7181

7282
@Post('softDelete/id/:id/')
73-
async softDeleteUser(@Params() params: UuidParam): Promise<GetUserResponse> {
83+
async softDeleteUser(@Params() params: FirebaseUidParam): Promise<GetUserResponse> {
7484
return { user: await this.userService.softDeleteUser(params) };
7585
}
86+
87+
@Post('logout/')
88+
async logout(@Body() fcmToken: FcmTokenRequest): Promise<null> {
89+
return await this.userService.logout(fcmToken);
90+
}
91+
7692
}

src/api/validators/GenericRequests.ts

+4
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ export class TimeParam {
1616
@IsUUID()
1717
id: Uuid;
1818
time: Date | undefined
19+
}
20+
21+
export class FirebaseUidParam {
22+
id: string;
1923
}

src/app.ts

+75-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import 'reflect-metadata';
33

44
import dotenv from 'dotenv';
5-
import { createExpressServer, ForbiddenError, useContainer as routingUseContainer } from 'routing-controllers';
5+
import { createExpressServer, ForbiddenError, UnauthorizedError, useContainer as routingUseContainer } from 'routing-controllers';
66
import { EntityManager, getManager, useContainer } from 'typeorm';
77
import { Container } from 'typeorm-typedi-extensions';
88
import { Express } from 'express';
@@ -12,7 +12,6 @@ import * as path from 'path';
1212
import { controllers } from './api/controllers';
1313
import { middlewares } from './api/middlewares';
1414
import { UserModel } from './models/UserModel';
15-
import { UserSessionModel } from './models/UserSessionModel';
1615
import { ReportPostRequest, ReportProfileRequest, ReportMessageRequest } from './types';
1716
import { GetReportsResponse, Report } from './types/ApiResponses';
1817
import { ReportController } from './api/controllers/ReportController';
@@ -21,9 +20,23 @@ import { ReportService } from './services/ReportService';
2120
import { ReportRepository } from './repositories/ReportRepository';
2221
import { reportToString } from './utils/Requests';
2322
import { CurrentUserChecker } from 'routing-controllers/types/CurrentUserChecker';
23+
import * as firebaseAdmin from 'firebase-admin';
24+
2425

2526
dotenv.config();
27+
var serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH!;
28+
const serviceAccount = require(serviceAccountPath);
29+
2630

31+
if (!serviceAccountPath) {
32+
throw new Error('FIREBASE_SERVICE_ACCOUNT_PATH environment variable is not set.');
33+
}
34+
35+
// Initialize Firebase Admin SDK
36+
export const admin = firebaseAdmin.initializeApp({
37+
credential: firebaseAdmin.credential.cert(serviceAccount),
38+
// databaseURL: "https://resell-e99a2-default-rtdb.firebaseio.com"
39+
});
2740

2841
async function main() {
2942
routingUseContainer(Container);
@@ -40,18 +53,47 @@ async function main() {
4053
controllers: controllers,
4154
middlewares: middlewares,
4255
currentUserChecker: async (action: any) => {
43-
const accessToken = action.request.headers["authorization"];
44-
const manager = getManager();
45-
// find the user who has a token in their sessions field
46-
const session = await manager.findOne(UserSessionModel, { accessToken: accessToken });
47-
// check if the session token has expired
48-
if (session && session.expiresAt.getTime() > Date.now()) {
49-
const userId = session.userId;
50-
// find a user with id `userId` and join with posts, saved,
51-
// sessions, feedbacks, and requests
52-
return await manager.findOne(UserModel, { id: userId }, { relations: ["posts", "saved", "sessions", "feedbacks", "requests"] });
56+
const authHeader = action.request.headers["authorization"];
57+
if (!authHeader) {
58+
throw new ForbiddenError("No authorization token provided");
59+
}
60+
const token = authHeader.split(' ')[1];
61+
if (!token) {
62+
throw new ForbiddenError("Invalid authorization token format");
63+
}
64+
try {
65+
// Verify the token using Firebase Admin SDK
66+
const decodedToken = await admin.auth().verifyIdToken(token);
67+
// Check if the email is a Cornell email
68+
const email = decodedToken.email;
69+
const userId = decodedToken.uid;
70+
if (!email || !email.endsWith('@cornell.edu')) {
71+
throw new ForbiddenError('Only Cornell email addresses are allowed');
72+
}
73+
// Find or create user in your database using Firebase UID
74+
const manager = getManager();
75+
let user = await manager.findOne(UserModel, { firebaseUid: userId },
76+
{ relations: ["posts", "saved", "feedbacks", "requests"] });
77+
if (!user) {
78+
// Check if this is the user creation route
79+
const isUserCreateRoute = action.request.path === '/api/user/create' || action.request.path === 'api/authorize';
80+
if (!isUserCreateRoute) {
81+
throw new ForbiddenError('User not found. Please create an account first.');
82+
}
83+
// For user creation routes, return a minimal UserModel
84+
const tempUser = new UserModel();
85+
tempUser.googleId = email;
86+
tempUser.firebaseUid = decodedToken.uid;
87+
tempUser.isNewUser = true;
88+
return tempUser;
89+
}
90+
return user;
91+
} catch (error) {
92+
if (error.code === 'auth/id-token-expired') {
93+
throw new UnauthorizedError('Token has expired');
94+
}
95+
throw new UnauthorizedError('Invalid authorization token');
5396
}
54-
throw new ForbiddenError("User unauthorized");
5597
},
5698
defaults: {
5799
paramOptions: {
@@ -81,16 +123,29 @@ async function main() {
81123

82124
app.get('/api/reports/admin/', async (req: any, res: any) => {
83125
const userCheck = async (action: any) => {
84-
const accessToken = action.headers["authorization"];
85-
const manager = getManager();
86-
const session = await manager.findOne(UserSessionModel, { accessToken: accessToken });
87-
if (session && session.expiresAt.getTime() > Date.now()) {
88-
const userId = session.userId;
89-
const user = await manager.findOne(UserModel, { id: userId }, { relations: ["posts", "saved", "sessions", "feedbacks", "requests"] });
126+
const authHeader = action.request.headers["authorization"];
127+
if (!authHeader) {
128+
throw new ForbiddenError("No authorization token provided");
129+
}
130+
const token = authHeader.split(' ')[1];
131+
if (!token) {
132+
throw new ForbiddenError("Invalid authorization token format");
133+
}
134+
try {
135+
// Verify the token using Firebase Admin SDK
136+
const decodedToken = await admin.auth().verifyIdToken(token);
137+
const userId = decodedToken.uid;
138+
// Find or create user in your database using Firebase UID
139+
const manager = getManager();
140+
const user = await manager.findOne(UserModel, { firebaseUid: userId });
90141
if (!user || !user.admin) throw new ForbiddenError("User unauthorized");
91142
return user;
143+
} catch (error) {
144+
if (error.code === 'auth/id-token-expired') {
145+
throw new UnauthorizedError('Token has expired');
146+
}
147+
throw new UnauthorizedError('Invalid authorization token');
92148
}
93-
throw new ForbiddenError("User unauthorized");
94149
}
95150
const user = await userCheck(req);
96151
user.admin = true;

src/factories/NotifFactory.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ define(NotifModel, (_, context?: { user?: UserModel; index?: number }) => {
1313
notif.read = false;
1414

1515
if (context?.user) {
16-
notif.userId = context.user.id;
16+
notif.userId = context.user.firebaseUid;
1717
notif.user = context.user;
1818
}
1919

0 commit comments

Comments
 (0)