From ce1c89bc28bc907db6ddf931153dc09a601ed4bc Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 21 Dec 2023 18:09:19 +0100 Subject: [PATCH] PR comments --- .../server/src/auth/providers/email/login.ts | 5 ++- .../providers/email/requestPasswordReset.ts | 5 ++- .../src/auth/providers/email/resetPassword.ts | 6 ++- .../server/src/auth/providers/email/signup.ts | 7 +-- .../server/src/auth/providers/email/utils.ts | 6 ++- .../src/auth/providers/email/verifyEmail.ts | 6 ++- .../server/src/auth/providers/local/login.ts | 5 ++- .../src/auth/providers/oauth/createRouter.ts | 13 ++---- .../templates/server/src/auth/utils.ts | 43 +++++++++---------- waspc/src/Wasp/Generator/DbGenerator.hs | 2 +- waspc/src/Wasp/Generator/DbGenerator/Auth.hs | 4 +- 11 files changed, 55 insertions(+), 47 deletions(-) diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts index f601922b50..b50422e3a6 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { verifyPassword, throwInvalidCredentialsError } from "../../../core/auth.js"; import { + createProviderId, findAuthIdentity, findAuthWithUserBy, createAuthToken, @@ -20,7 +21,9 @@ export function getLoginRoute({ const fields = req.body ?? {} ensureValidArgs(fields) - const authIdentity = await findAuthIdentity("email", fields.email) + const authIdentity = await findAuthIdentity( + createProviderId("email", fields.email) + ) if (!authIdentity) { throwInvalidCredentialsError() } diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts index 7616f2ec95..b559c3d77f 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import { + createProviderId, findAuthIdentity, doFakeWork, deserializeAndSanitizeProviderData, @@ -29,7 +30,9 @@ export function getRequestPasswordResetRoute({ const args = req.body ?? {}; ensureValidEmail(args); - const authIdentity = await findAuthIdentity("email", args.email); + const authIdentity = await findAuthIdentity( + createProviderId("email", args.email), + ); // User not found or not verified - don't leak information if (!authIdentity) { diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts index de96933a93..ac0e4542f8 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import { + createProviderId, findAuthIdentity, updateAuthIdentityProviderData, verifyToken, @@ -19,13 +20,14 @@ export async function resetPassword( try { const { id: email } = await verifyToken(token); - const authIdentity = await findAuthIdentity('email', email); + const providerId = createProviderId('email', email); + const authIdentity = await findAuthIdentity(providerId); if (!authIdentity) { return res.status(400).json({ success: false, message: 'Invalid token' }); } const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity.providerData); - await updateAuthIdentityProviderData('email', email, providerData, { + await updateAuthIdentityProviderData(providerId, providerData, { // The act of resetting the password verifies the email isEmailVerified: true, password, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts index fae514ba94..a1d0857656 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express'; import { EmailFromField } from "../../../email/core/types.js"; import { createUser, + createProviderId, findAuthIdentity, deleteUserByAuthId, doFakeWork, @@ -33,7 +34,8 @@ export function getSignupRoute({ const fields = req.body; ensureValidArgs(fields); - const existingAuthIdentity = await findAuthIdentity("email", fields.email); + const providerId = createProviderId("email", fields.email); + const existingAuthIdentity = await findAuthIdentity(providerId); if (existingAuthIdentity) { const providerData = deserializeAndSanitizeProviderData<'email'>(existingAuthIdentity.providerData); // User already exists and is verified - don't leak information @@ -60,8 +62,7 @@ export function getSignupRoute({ }); const user = await createUser( - 'email', - fields.email, + providerId, newUserProviderData, userFields, ); diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts index 4284958a7e..fc89a201fa 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts @@ -4,6 +4,7 @@ import { emailSender } from '../../../email/index.js'; import { Email } from '../../../email/core/types.js'; import { rethrowPossibleAuthError, + createProviderId, updateAuthIdentityProviderData, findAuthIdentity, deserializeAndSanitizeProviderData, @@ -52,9 +53,10 @@ async function sendEmailAndLogTimestamp( // so the user can't send multiple requests while // the email is being sent. try { - const authIdentity = await findAuthIdentity("email", email); + const providerId = createProviderId("email", email); + const authIdentity = await findAuthIdentity(providerId); const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity.providerData); - await updateAuthIdentityProviderData<'email'>('email', email, providerData, { + await updateAuthIdentityProviderData<'email'>(providerId, providerData, { [field]: new Date() }); } catch (e) { diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts index 6c6884b1b7..c1edfc8790 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { verifyToken, + createProviderId, findAuthIdentity, updateAuthIdentityProviderData, deserializeAndSanitizeProviderData, @@ -15,9 +16,10 @@ export async function verifyEmail( const { token } = req.body; const { id: email } = await verifyToken(token); - const authIdentity = await findAuthIdentity('email', email); + const providerId = createProviderId('email', email); + const authIdentity = await findAuthIdentity(providerId); const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity.providerData); - await updateAuthIdentityProviderData('email', email, providerData, { + await updateAuthIdentityProviderData(providerId, providerData, { isEmailVerified: true, }); } catch (e) { diff --git a/waspc/data/Generator/templates/server/src/auth/providers/local/login.ts b/waspc/data/Generator/templates/server/src/auth/providers/local/login.ts index 59725b7274..ec6c7d6559 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/local/login.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/local/login.ts @@ -3,6 +3,7 @@ import { verifyPassword, throwInvalidCredentialsError } from '../../../core/auth import { handleRejection } from '../../../utils.js' import { + createProviderId, findAuthIdentity, findAuthWithUserBy, createAuthToken, @@ -14,7 +15,9 @@ export default handleRejection(async (req, res) => { const fields = req.body ?? {} ensureValidArgs(fields) - const authIdentity = await findAuthIdentity('username', fields.username) + const authIdentity = await findAuthIdentity( + createProviderId('username', fields.username), + ) if (!authIdentity) { throwInvalidCredentialsError() } diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts index 03a89a109c..2322e4af4c 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts @@ -7,7 +7,7 @@ import prisma from '../../../dbClient.js' import waspServerConfig from '../../../config.js' import { type ProviderName, - createProviderUserId, + createProviderId, authConfig, contextWithUserEntity, createUser, @@ -53,16 +53,12 @@ export function createRouter(provider: ProviderConfig, initData: { passportStrat // TODO: In the future we could make this configurable, possibly associating an external account // with the currently logged in account, or by some DB lookup. - const providerName = provider.id; - const providerUserId = createProviderUserId(providerProfile.id); + const providerId = createProviderId(provider.id, providerProfile.id); try { const existingAuthIdentity = await prisma.{= authIdentityEntityLower =}.findUnique({ where: { - providerName_providerUserId: { - providerName, - providerUserId: providerUserId.id, - }, + providerName_providerUserId: providerId, }, include: { {= authFieldOnAuthIdentityEntityName =}: { @@ -84,8 +80,7 @@ export function createRouter(provider: ProviderConfig, initData: { passportStrat const userFields = await getUserFields() const user = await createUser( - providerName, - providerUserId, + providerId, undefined, userFields, ) diff --git a/waspc/data/Generator/templates/server/src/auth/utils.ts b/waspc/data/Generator/templates/server/src/auth/utils.ts index b395fe6146..b4809d290c 100644 --- a/waspc/data/Generator/templates/server/src/auth/utils.ts +++ b/waspc/data/Generator/templates/server/src/auth/utils.ts @@ -11,7 +11,7 @@ import { } from '../entities/index.js' import { Prisma } from '@prisma/client'; -import { PASSWORD_FIELD, throwValidationError } from './validation.js' +import { throwValidationError } from './validation.js' {=# additionalSignupFields.isDefined =} {=& additionalSignupFields.importStatement =} @@ -58,30 +58,31 @@ export const authConfig = { successRedirectPath: "{= successRedirectPath =}", } -type ProviderUserId = { - id: string; +// ProviderId represents one user account in a specific provider. +// We are packing it into a single object to make it easier to +// make sure that the providerUserId is always lowercased. +type ProviderId = { + providerName: ProviderName; + providerUserId: string; } -export function createProviderUserId(providerUserId: string): ProviderUserId { +export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { return { - id: providerUserId.toLowerCase(), + providerName, + providerUserId: providerUserId.toLowerCase(), } } -export async function findAuthIdentity(providerName: ProviderName, providerUserId: ProviderUserId): Promise<{= authIdentityEntityUpper =} | null> { +export async function findAuthIdentity(providerId: ProviderId): Promise<{= authIdentityEntityUpper =} | null> { return prisma.{= authIdentityEntityLower =}.findUnique({ where: { - providerName_providerUserId: { - providerName, - providerUserId: providerUserId.id, - } + providerName_providerUserId: providerId, } }); } export async function updateAuthIdentityProviderData( - providerName: ProviderName, - providerUserId: ProviderUserId, + providerId: ProviderId, existingProviderData: PossibleProviderData[PN], providerDataUpdates: Partial, ): Promise<{= authIdentityEntityUpper =}> { @@ -95,10 +96,7 @@ export async function updateAuthIdentityProviderData( const serializedProviderData = await serializeProviderData(newProviderData); return prisma.{= authIdentityEntityLower =}.update({ where: { - providerName_providerUserId: { - providerName, - providerUserId: providerUserId.id, - } + providerName_providerUserId: providerId, }, data: { providerData: serializedProviderData }, }); @@ -115,8 +113,7 @@ export async function findAuthWithUserBy( } export async function createUser( - providerName: ProviderName, - providerUserId: ProviderUserId, + providerId: ProviderId, serializedProviderData?: string, userFields?: PossibleAdditionalSignupFields, ): Promise<{= userEntityUpper =}> { @@ -130,8 +127,8 @@ export async function createUser( create: { {= identitiesFieldOnAuthEntityName =}: { create: { - providerName, - providerUserId: providerUserId.id, + providerName: providerId.providerName, + providerUserId: providerId.providerUserId, providerData: serializedProviderData, }, }, @@ -242,7 +239,7 @@ export function deserializeAndSanitizeProviderData( let data = JSON.parse(providerData) as PossibleProviderData[PN]; if (providerDataHasPasswordField(data) && shouldRemovePasswordField) { - delete data[PASSWORD_FIELD]; + delete data.hashedPassword; } return data; @@ -267,7 +264,7 @@ async function sanitizeProviderData( ...providerData, }; if (providerDataHasPasswordField(data)) { - data[PASSWORD_FIELD] = await hashPassword(data[PASSWORD_FIELD]); + data.hashedPassword = await hashPassword(data.hashedPassword); } return data; @@ -277,5 +274,5 @@ async function sanitizeProviderData( function providerDataHasPasswordField( providerData: PossibleProviderData[keyof PossibleProviderData], ): providerData is { hashedPassword: string } { - return PASSWORD_FIELD in providerData; + return 'hashedPassword' in providerData; } diff --git a/waspc/src/Wasp/Generator/DbGenerator.hs b/waspc/src/Wasp/Generator/DbGenerator.hs index be45302b9a..660e924a4f 100644 --- a/waspc/src/Wasp/Generator/DbGenerator.hs +++ b/waspc/src/Wasp/Generator/DbGenerator.hs @@ -66,7 +66,7 @@ genPrismaSchema spec = do then logAndThrowGeneratorError $ GenericGeneratorError "SQLite (a default database) is not supported in production. To build your Wasp app for production, switch to a different database. Switching to PostgreSQL: https://wasp-lang.dev/docs/data-model/backends#migrating-from-sqlite-to-postgresql ." else return ("sqlite", "\"file:./dev.db\"") - entities <- DbAuth.injectAuth maybeUserEntity userDefinedEntities + entities <- maybe (return userDefinedEntities) (DbAuth.injectAuth userDefinedEntities) maybeUserEntity let templateData = object diff --git a/waspc/src/Wasp/Generator/DbGenerator/Auth.hs b/waspc/src/Wasp/Generator/DbGenerator/Auth.hs index 66b8eb7639..8efc2d6329 100644 --- a/waspc/src/Wasp/Generator/DbGenerator/Auth.hs +++ b/waspc/src/Wasp/Generator/DbGenerator/Auth.hs @@ -59,8 +59,8 @@ authIdentityEntityName = "AuthIdentity" identitiesFieldOnAuthEntityName :: String identitiesFieldOnAuthEntityName = "identities" -injectAuth :: (String, AS.Entity.Entity) -> [(String, AS.Entity.Entity)] -> Generator [(String, AS.Entity.Entity)] -injectAuth (userEntityName, userEntity) entities = do +injectAuth :: [(String, AS.Entity.Entity)] -> (String, AS.Entity.Entity) -> Generator [(String, AS.Entity.Entity)] +injectAuth entities (userEntityName, userEntity) = do authEntity <- makeAuthEntity userEntityIdField (userEntityName, userEntity) authIdentityEntity <- makeAuthIdentityEntity let entitiesWithAuth = injectAuthIntoUserEntity userEntityName entities