Skip to content

Commit a613bb9

Browse files
DanZfsdvibbyg
authored andcommitted
Implement create password functionality
1 parent 90374fc commit a613bb9

File tree

19 files changed

+542
-3
lines changed

19 files changed

+542
-3
lines changed

backend/typescript/rest/authRoutes.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,34 @@ authRouter.post(
117117
},
118118
);
119119

120+
// updates user password and updates status
121+
authRouter.post(
122+
"/setPassword/:email",
123+
isAuthorizedByEmail("email"),
124+
async (req, res) => {
125+
try {
126+
const responseSuccess = await authService.setPassword(
127+
req.params.email,
128+
req.body.newPassword,
129+
);
130+
if (responseSuccess.success) {
131+
const user = await userService.getUserByEmail(req.params.email);
132+
if (user.status === UserStatus.INVITED) {
133+
userService.updateUserById(user.id, {
134+
...user,
135+
status: UserStatus.ACTIVE,
136+
});
137+
}
138+
res.status(200).json(responseSuccess);
139+
} else {
140+
res.status(400).json(responseSuccess);
141+
}
142+
} catch (error) {
143+
res.status(500).json({ error: getErrorMessage(error) });
144+
}
145+
},
146+
);
147+
120148
/* Invite a user */
121149
authRouter.post("/invite-user", inviteUserDtoValidator, async (req, res) => {
122150
try {

backend/typescript/services/implementations/authService.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as firebaseAdmin from "firebase-admin";
33
import IAuthService from "../interfaces/authService";
44
import IEmailService from "../interfaces/emailService";
55
import IUserService from "../interfaces/userService";
6-
import { AuthDTO, Role, Token } from "../../types";
6+
import { AuthDTO, Role, Token, ResponseSuccessDTO } from "../../types";
77
import { getErrorMessage } from "../../utilities/errorUtils";
88
import FirebaseRestClient from "../../utilities/firebaseRestClient";
99
import logger from "../../utilities/logger";
@@ -286,6 +286,29 @@ class AuthService implements IAuthService {
286286
return false;
287287
}
288288
}
289+
290+
async setPassword(
291+
email: string,
292+
newPassword: string,
293+
): Promise<ResponseSuccessDTO> {
294+
let errorMessage = "An unknown error occured. Please try again later.";
295+
try {
296+
const uid = await (await firebaseAdmin.auth().getUserByEmail(email)).uid;
297+
await firebaseAdmin.auth().updateUser(uid, {
298+
password: newPassword,
299+
});
300+
return { success: true } as ResponseSuccessDTO;
301+
} catch (error: any) {
302+
Logger.error(`Failed to update password. Error: ${error}`);
303+
if (error.code === "auth/invalid-password") {
304+
errorMessage =
305+
"Password is too weak! Make sure it matches the password policy in Firebase.";
306+
} else if (error.code === "auth/user-not-found") {
307+
errorMessage = "No user found with the provided email!";
308+
}
309+
return { success: false, errorMessage };
310+
}
311+
}
289312
}
290313

291314
export default AuthService;

backend/typescript/services/interfaces/authService.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AuthDTO, Role, Token } from "../../types";
1+
import { AuthDTO, Role, Token, ResponseSuccessDTO } from "../../types";
22

33
interface IAuthService {
44
/**
@@ -98,6 +98,14 @@ interface IAuthService {
9898
accessToken: string,
9999
requestedEmail: string,
100100
): Promise<boolean>;
101+
102+
/**
103+
* Set password for the specified email.
104+
* @param accessToken user's access token
105+
* @param requestedEmail email address of requested user
106+
* @returns success (boolean) and errorMessage (string)
107+
*/
108+
setPassword(email: string, newPassword: string): Promise<ResponseSuccessDTO>;
101109
}
102110

103111
export default IAuthService;

backend/typescript/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export type RegisterUserDTO = Omit<CreateUserDTO, "role">;
3131

3232
export type AuthDTO = Token & UserDTO;
3333

34+
export type ResponseSuccessDTO = {
35+
success: boolean;
36+
errorMessage?: string;
37+
};
38+
3439
export type Letters = "A" | "B" | "C" | "D";
3540

3641
const sexValues = ["M", "F"] as const;

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"dependencies": {
66
"@apollo/client": "^3.3.16",
7+
"@chakra-ui/icons": "^2.2.4",
78
"@chakra-ui/react": "^2.8.2",
89
"@emotion/react": "^11.11.4",
910
"@emotion/styled": "^11.11.5",
9.01 KB
Loading

frontend/src/APIClients/AuthAPIClient.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { signInWithEmailLink } from "firebase/auth";
22
import { FirebaseError } from "firebase/app";
33
import auth from "../firebase/firebase";
44
import AUTHENTICATED_USER_KEY from "../constants/AuthConstants";
5-
import { AuthenticatedUser } from "../types/AuthTypes";
5+
import { AuthenticatedUser, PasswordSetResponse } from "../types/AuthTypes";
66
import baseAPIClient from "./BaseAPIClient";
77
import {
88
getLocalStorageObjProperty,
@@ -144,6 +144,35 @@ const refresh = async (): Promise<boolean> => {
144144
}
145145
};
146146

147+
const getEmailOfCurrentUser = async (): Promise<string> => {
148+
const email = getLocalStorageObjProperty(AUTHENTICATED_USER_KEY, "email");
149+
if (typeof email === "string") {
150+
return email;
151+
}
152+
throw new Error("Email not found for the current user");
153+
};
154+
155+
const setPassword = async (
156+
newPassword: string,
157+
): Promise<PasswordSetResponse> => {
158+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
159+
AUTHENTICATED_USER_KEY,
160+
"accessToken",
161+
)}`;
162+
try {
163+
const email = await getEmailOfCurrentUser();
164+
// set password
165+
const response = await baseAPIClient.post(
166+
`/auth/setPassword/${email}`,
167+
{ newPassword },
168+
{ headers: { Authorization: bearerToken } },
169+
);
170+
return response.data;
171+
} catch (error) {
172+
return { success: false, errorMessage: "An unknown error occured." };
173+
}
174+
};
175+
147176
export default {
148177
login,
149178
loginWithSignInLink,
@@ -152,4 +181,6 @@ export default {
152181
register,
153182
resetPassword,
154183
refresh,
184+
setPassword,
185+
getEmailOfCurrentUser,
155186
};

frontend/src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SimpleEntityDisplayPage from "./components/pages/SimpleEntityDisplayPage"
1515
import NotFound from "./components/pages/NotFound";
1616
import UpdatePage from "./components/pages/UpdatePage";
1717
import SimpleEntityUpdatePage from "./components/pages/SimpleEntityUpdatePage";
18+
import CreatePasswordPage from "./components/pages/CreatePasswordPage";
1819
import * as Routes from "./constants/Routes";
1920
import * as AuthConstants from "./constants/AuthConstants";
2021
import AUTHENTICATED_USER_KEY from "./constants/AuthConstants";
@@ -62,6 +63,12 @@ const App = (): React.ReactElement => {
6263
<Switch>
6364
<Route exact path={Routes.LOGIN_PAGE} component={Login} />
6465
<Route exact path={Routes.SIGNUP_PAGE} component={Signup} />
66+
<PrivateRoute
67+
exact
68+
path={Routes.CREATE_PASSWORD_PAGE}
69+
component={CreatePasswordPage}
70+
allowedRoles={AuthConstants.ALL_ROLES}
71+
/>
6572
<Route
6673
exact
6774
path={Routes.FORGOT_PASSWORD_PAGE}
109 KB
Loading
42.4 KB
Loading

0 commit comments

Comments
 (0)