-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add googleID/googleIDToken handling
- Loading branch information
1 parent
e46c681
commit 0f8df48
Showing
15 changed files
with
305 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
export * from "./findUserByEmail.js"; | ||
export * from "./generateAuthToken.js"; | ||
export * from "./getUserFromAuthHeaderToken.js"; | ||
export * from "./parseGoogleIDToken.js"; | ||
export * from "./queryUserItems.js"; | ||
export * from "./registerNewUser.js"; | ||
export * from "./shouldUserLoginExist.js"; | ||
export * from "./validateGqlReqContext.js"; | ||
export * from "./validatePassword.js"; | ||
export * from "./validateLogin.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { parseGoogleOAuth2IDToken } from "@/lib/googleOAuth2Client"; | ||
import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; | ||
import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; | ||
|
||
/** | ||
* This middleware parses and validates a `googleIDToken` if provided. If valid, | ||
* it is decoded to obtain the fields listed below. These fields are then used to | ||
* create login args, which are added to `res.locals.googleIDTokenFields` and | ||
* `req.body` to be read by downstream auth middleware. | ||
* | ||
* **Fields obtained from the `googleIDToken`:** | ||
* | ||
* - `googleID` | ||
* - `email` | ||
* - `givenName` | ||
* - `familyName` | ||
* - `picture` (profile photo URL) | ||
* | ||
* > **The structure of Google JWT ID tokens is available here:** | ||
* > https://developers.google.com/identity/gsi/web/reference/js-reference#credential | ||
* | ||
* @see https://developers.google.com/identity/gsi/web/guides/verify-google-id-token | ||
*/ | ||
export const parseGoogleIDToken = mwAsyncCatchWrapper< | ||
/** | ||
* Since this mw creates user login args from the supplied Google ID token and adds | ||
* them to the `req.body` object, an intersection is used for the type param here to | ||
* tell downstream mw that `req.body` will include fields they require. Without this | ||
* intersection, TS complains about the perceived `req.body` type mismatch. | ||
*/ | ||
RestApiRequestBodyByPath["/auth/google-token"] & | ||
RestApiRequestBodyByPath["/auth/login"] & | ||
RestApiRequestBodyByPath["/auth/register"] | ||
>(async (req, res, next) => { | ||
// Since this mw is used on routes where auth via GoogleIDToken is optional, check if provided. | ||
if (!req.body.googleIDToken) return next(); | ||
|
||
// Parse the google ID token: | ||
const { _isValid, email, googleID, profile } = await parseGoogleOAuth2IDToken( | ||
req.body.googleIDToken | ||
); | ||
|
||
// Add the fields to res.locals.googleIDTokenFields: | ||
res.locals.googleIDTokenFields = { | ||
_isValid, | ||
email, | ||
googleID, | ||
profile, | ||
}; | ||
|
||
// If not already present, add email to req.body for use by downstream mw (used by findUserByEmail) | ||
if (!req.body.email) req.body.email = email; | ||
|
||
next(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; | ||
import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; | ||
import { passwordHasher } from "@/utils/passwordHasher.js"; | ||
import type { CombineUnionOfObjects } from "@/types/helpers.js"; | ||
import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; | ||
|
||
/** | ||
* This middleware validates User's Login objects. | ||
* | ||
* - If the User's login type is `"LOCAL"`, it compares the provided password | ||
* against the passwordHash stored in the db. | ||
* | ||
* - If the User's login type is `"GOOGLE_OAUTH"`, it checks the value of | ||
* `res.locals.googleIDTokenFields?._isValid`, which is set by the `parseGoogleIDToken` | ||
* middleware. | ||
* | ||
* If it's invalid, an AuthError is thrown. | ||
*/ | ||
export const validateLogin = mwAsyncCatchWrapper< | ||
CombineUnionOfObjects<RestApiRequestBodyByPath["/auth/login"]> | ||
>(async (req, res, next) => { | ||
const userItem = res.locals?.user; | ||
|
||
if (!userItem) return next(new AuthError("User not found")); | ||
|
||
// LOCAL LOGIN — validate password | ||
if (userItem.login.type === "LOCAL") { | ||
// Ensure password was provided | ||
if (!req.body?.password) return next(new AuthError("Password is required")); | ||
|
||
const isValidPassword = await passwordHasher.validate( | ||
req.body.password, | ||
userItem.login.passwordHash | ||
); | ||
|
||
if (!isValidPassword) next(new AuthError("Invalid email or password")); | ||
|
||
/* Note: res.locals.user does not have `subscription`/`stripeConnectAccount` fields. | ||
For `generateAuthToken`, these fields are obtained from the `queryUserItems` mw. */ | ||
res.locals.authenticatedUser = userItem; | ||
|
||
// GOOGLE_OAUTH LOGIN — check res.locals.googleIDTokenFields._isValid | ||
} else if (userItem.login.type === "GOOGLE_OAUTH") { | ||
// The parseGoogleIDToken mw provides this res.locals field, check `_isValid`. The | ||
// field should always be true here, else the fn would throw, but it provides clarity. | ||
if (res.locals.googleIDTokenFields?._isValid) res.locals.authenticatedUser = userItem; | ||
} else { | ||
next(new InternalServerError("Invalid login")); | ||
} | ||
|
||
next(); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.