Skip to content

Commit

Permalink
Merge branch 'main' into miho-prisma-5
Browse files Browse the repository at this point in the history
  • Loading branch information
infomiho committed Aug 12, 2024
2 parents e64a137 + b9c0285 commit 580206d
Show file tree
Hide file tree
Showing 28 changed files with 711 additions and 94 deletions.
48 changes: 47 additions & 1 deletion waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Request as ExpressRequest } from 'express'
import type { ProviderId, createUser } from '../../auth/utils.js'
import { type ProviderId, createUser, findAuthWithUserBy } from '../../auth/utils.js'
import { prisma } from '../index.js'
import { Expand } from '../../universal/types.js'

Expand All @@ -21,6 +21,16 @@ export type OnBeforeOAuthRedirectHook = (
params: Expand<OnBeforeOAuthRedirectHookParams>,
) => { url: URL } | Promise<{ url: URL }>

// PUBLIC API
export type OnBeforeLoginHook = (
params: Expand<OnBeforeLoginHookParams>,
) => void | Promise<void>

// PUBLIC API
export type OnAfterLoginHook = (
params: Expand<OnAfterLoginHookParams>,
) => void | Promise<void>

// PRIVATE API (used in the SDK and the server)
export type InternalAuthHookParams = {
/**
Expand Down Expand Up @@ -85,3 +95,39 @@ type OnBeforeOAuthRedirectHookParams = {
*/
req: ExpressRequest
} & InternalAuthHookParams

type OnBeforeLoginHookParams = {
/**
* Provider ID object that contains the provider name and the provide user ID.
*/
providerId: ProviderId
/**
* Request object that can be used to access the incoming request.
*/
req: ExpressRequest
} & InternalAuthHookParams

type OnAfterLoginHookParams = {
/**
* Provider ID object that contains the provider name and the provide user ID.
*/
providerId: ProviderId
oauth?: {
/**
* Access token that was received during the OAuth flow.
*/
accessToken: string
/**
* Unique request ID that was generated during the OAuth flow.
*/
uniqueRequestId: string
},
/**
* User that is logged in.
*/
user: Awaited<ReturnType<typeof findAuthWithUserBy>>['user']
/**
* Request object that can be used to access the incoming request.
*/
req: ExpressRequest
} & InternalAuthHookParams
2 changes: 2 additions & 0 deletions waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export type {
OnBeforeSignupHook,
OnAfterSignupHook,
OnBeforeOAuthRedirectHook,
OnBeforeLoginHook,
OnAfterLoginHook,
InternalAuthHookParams,
} from './hooks.js'

Expand Down
37 changes: 37 additions & 0 deletions waspc/data/Generator/templates/server/src/auth/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {
OnAfterSignupHook,
OnBeforeOAuthRedirectHook,
OnBeforeSignupHook,
OnBeforeLoginHook,
OnAfterLoginHook,
InternalAuthHookParams,
} from 'wasp/server/auth'
{=# onBeforeSignupHook.isDefined =}
Expand All @@ -15,6 +17,12 @@ import type {
{=# onBeforeOAuthRedirectHook.isDefined =}
{=& onBeforeOAuthRedirectHook.importStatement =}
{=/ onBeforeOAuthRedirectHook.isDefined =}
{=# onBeforeLoginHook.isDefined =}
{=& onBeforeLoginHook.importStatement =}
{=/ onBeforeLoginHook.isDefined =}
{=# onAfterLoginHook.isDefined =}
{=& onAfterLoginHook.importStatement =}
{=/ onAfterLoginHook.isDefined =}

/*
These are "internal hook functions" based on the user defined hook functions.
Expand Down Expand Up @@ -67,6 +75,35 @@ export const onBeforeOAuthRedirectHook: InternalFunctionForHook<OnBeforeOAuthRed
export const onBeforeOAuthRedirectHook: InternalFunctionForHook<OnBeforeOAuthRedirectHook> = async (params) => params
{=/ onBeforeOAuthRedirectHook.isDefined =}


{=# onBeforeLoginHook.isDefined =}
export const onBeforeLoginHook: InternalFunctionForHook<OnBeforeLoginHook> = (params) =>
{= onBeforeLoginHook.importIdentifier =}({
prisma,
...params,
})
{=/ onBeforeLoginHook.isDefined =}
{=^ onBeforeLoginHook.isDefined =}
/**
* This is a no-op function since the user didn't define the onBeforeLogin hook.
*/
export const onBeforeLoginHook: InternalFunctionForHook<OnBeforeLoginHook> = async (_params) => {}
{=/ onBeforeLoginHook.isDefined =}

{=# onAfterLoginHook.isDefined =}
export const onAfterLoginHook: InternalFunctionForHook<OnAfterLoginHook> = (params) =>
{= onAfterLoginHook.importIdentifier =}({
prisma,
...params,
})
{=/ onAfterLoginHook.isDefined =}
{=^ onAfterLoginHook.isDefined =}
/**
* This is a no-op function since the user didn't define the onAfterLogin hook.
*/
export const onAfterLoginHook: InternalFunctionForHook<OnAfterLoginHook> = async (_params) => {}
{=/ onAfterLoginHook.isDefined =}

/*
We pass extra params to the user defined hook functions, but we don't want to
pass the extra params (e.g. 'prisma') when we call the hooks in the server code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from 'wasp/auth/utils'
import { createSession } from 'wasp/auth/session'
import { ensureValidEmail, ensurePasswordIsPresent } from 'wasp/auth/validation'
import { onBeforeLoginHook, onAfterLoginHook } from '../../hooks.js';

export function getLoginRoute() {
return async function login(
Expand All @@ -18,9 +19,8 @@ export function getLoginRoute() {
const fields = req.body ?? {}
ensureValidArgs(fields)

const authIdentity = await findAuthIdentity(
createProviderId("email", fields.email)
)
const providerId = createProviderId("email", fields.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throwInvalidCredentialsError()
}
Expand All @@ -35,7 +35,16 @@ export function getLoginRoute() {
}

const auth = await findAuthWithUserBy({ id: authIdentity.authId })

await onBeforeLoginHook({ req, providerId })

const session = await createSession(auth.id)

await onAfterLoginHook({
req,
providerId,
user: auth.user,
})

return res.json({
sessionId: session.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import {
sanitizeAndSerializeProviderData,
validateAndGetUserFields,
createProviderId,
findAuthWithUserBy,
} from 'wasp/auth/utils'
import { type {= authEntityUpper =} } from 'wasp/entities'
import { prisma } from 'wasp/server'
import { type UserSignupFields, type ProviderConfig } from 'wasp/auth/providers/types'
import { getRedirectUriForOneTimeCode } from './redirect'
import { tokenStore } from './oneTimeCode'
import { onBeforeSignupHook, onAfterSignupHook } from '../../hooks.js';
import {
onBeforeSignupHook,
onAfterSignupHook,
onBeforeLoginHook,
onAfterLoginHook,
} from '../../hooks.js'

export async function finishOAuthFlowAndGetRedirectUri({
provider,
Expand Down Expand Up @@ -42,9 +48,9 @@ export async function finishOAuthFlowAndGetRedirectUri({
oAuthState,
});

const oneTimeCode = await tokenStore.createToken(authId);
const oneTimeCode = await tokenStore.createToken(authId)

return getRedirectUriForOneTimeCode(oneTimeCode);
return getRedirectUriForOneTimeCode(oneTimeCode)
}

// We need a user id to create the auth token, so we either find an existing user
Expand Down Expand Up @@ -78,12 +84,37 @@ async function getAuthIdFromProviderDetails({
})

if (existingAuthIdentity) {
return existingAuthIdentity.{= authFieldOnAuthIdentityEntityName =}.id
const authId = existingAuthIdentity.{= authFieldOnAuthIdentityEntityName =}.id

// NOTE: We are calling login hooks here even though we didn't log in the user yet.
// It's because we have access to the OAuth tokens here and we want to pass them to the hooks.
// We could have stored the tokens temporarily and called the hooks after the session is created,
// but this keeps the implementation simpler.
// The downside of this approach is that we can't provide the session to the login hooks, but this is
// an okay trade-off because OAuth tokens are more valuable to users than the session ID.
await onBeforeLoginHook({ req, providerId })

// NOTE: Fetching the user to pass it to the onAfterLoginHook - it's a bit wasteful
// but we wanted to keep the onAfterLoginHook params consistent for all auth providers.
const auth = await findAuthWithUserBy({ id: authId })

// NOTE: check the comment above onBeforeLoginHook for the explanation why we call onAfterLoginHook here.
await onAfterLoginHook({
req,
providerId,
oauth: {
accessToken,
uniqueRequestId: oAuthState.state,
},
user: auth.user,
})

return authId
} else {
const userFields = await validateAndGetUserFields(
{ profile: providerProfile },
userSignupFields,
);
)

// For now, we don't have any extra data for the oauth providers, so we just pass an empty object.
const providerData = await sanitizeAndSerializeProviderData({})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'wasp/auth/utils'
import { createSession } from 'wasp/auth/session'
import { ensureValidUsername, ensurePasswordIsPresent } from 'wasp/auth/validation'
import { onBeforeLoginHook, onAfterLoginHook } from '../../hooks.js';

export default handleRejection(async (req, res) => {
const fields = req.body ?? {}
Expand All @@ -34,8 +35,16 @@ export default handleRejection(async (req, res) => {
id: authIdentity.authId
})

await onBeforeLoginHook({ req, providerId })

const session = await createSession(auth.id)

await onAfterLoginHook({
req,
providerId,
user: auth.user,
})

return res.json({
sessionId: session.id,
})
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 580206d

Please sign in to comment.