mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Implement login hooks (#2211)
This commit is contained in:
parent
e9a55629af
commit
b9c02855cf
@ -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'
|
||||
|
||||
@ -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 = {
|
||||
/**
|
||||
@ -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
|
||||
|
@ -27,6 +27,8 @@ export type {
|
||||
OnBeforeSignupHook,
|
||||
OnAfterSignupHook,
|
||||
OnBeforeOAuthRedirectHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
InternalAuthHookParams,
|
||||
} from './hooks.js'
|
||||
|
||||
|
@ -4,6 +4,8 @@ import type {
|
||||
OnAfterSignupHook,
|
||||
OnBeforeOAuthRedirectHook,
|
||||
OnBeforeSignupHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
InternalAuthHookParams,
|
||||
} from 'wasp/server/auth'
|
||||
{=# onBeforeSignupHook.isDefined =}
|
||||
@ -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.
|
||||
@ -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.
|
||||
|
@ -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(
|
||||
@ -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()
|
||||
}
|
||||
@ -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,
|
||||
|
@ -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,
|
||||
@ -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
|
||||
@ -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({})
|
||||
|
@ -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 ?? {}
|
||||
@ -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,
|
||||
})
|
||||
|
@ -529,14 +529,14 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/server/auth/hooks.ts"
|
||||
],
|
||||
"0a33f1809cfbcf86e00c4cd1d349192f686a4a8324120c90b2e53381078fda2a"
|
||||
"3105d318bcb86a6638f414a155aad29dcea3613f7f14c6c282b677b9333eac57"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/server/auth/index.ts"
|
||||
],
|
||||
"4d44320b8bdfb4dfc07664f813677420319f0610071e73c5b62be96ad35dd959"
|
||||
"20277db8773191867bd7938215abdf4a6814f84ba38fd55dd36ae5ebbbbe0754"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -872,7 +872,7 @@
|
||||
"file",
|
||||
"server/src/auth/hooks.ts"
|
||||
],
|
||||
"c9e80d960136127dbd5a1cfc955d08323c40d5733221e2bd2bb28e12b85e274b"
|
||||
"74f3d9cd68ef3d01d1f9a0729c93173b42725ecb49f5f6218fa8c1175e4c2a94"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -949,7 +949,7 @@
|
||||
"file",
|
||||
"server/src/auth/providers/oauth/user.ts"
|
||||
],
|
||||
"260527c819fee29840447730e057391dca3205fc8cbb6c491db580d8722a7875"
|
||||
"4ae56549dccf3fa5d75250ef9593e1cf65012bf48219895dc92df586e5d95a45"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -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';
|
||||
export type OnBeforeSignupHook = (params: Expand<OnBeforeSignupHookParams>) => void | Promise<void>;
|
||||
@ -12,6 +12,8 @@ export type OnBeforeOAuthRedirectHook = (params: Expand<OnBeforeOAuthRedirectHoo
|
||||
} | Promise<{
|
||||
url: URL;
|
||||
}>;
|
||||
export type OnBeforeLoginHook = (params: Expand<OnBeforeLoginHookParams>) => void | Promise<void>;
|
||||
export type OnAfterLoginHook = (params: Expand<OnAfterLoginHookParams>) => void | Promise<void>;
|
||||
export type InternalAuthHookParams = {
|
||||
/**
|
||||
* Prisma instance that can be used to interact with the database.
|
||||
@ -66,4 +68,38 @@ 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;
|
||||
export {};
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { defineUserSignupFields, } from '../../auth/providers/types.js';
|
||||
export { createProviderId, sanitizeAndSerializeProviderData, updateAuthIdentityProviderData, deserializeAndSanitizeProviderData, findAuthIdentity, createUser, type ProviderId, type ProviderName, type EmailProviderData, type UsernameProviderData, type OAuthProviderData, } from '../../auth/utils.js';
|
||||
export { ensurePasswordIsPresent, ensureValidPassword, ensureTokenIsPresent, } from '../../auth/validation.js';
|
||||
export type { OnBeforeSignupHook, OnAfterSignupHook, OnBeforeOAuthRedirectHook, InternalAuthHookParams, } from './hooks.js';
|
||||
export type { OnBeforeSignupHook, OnAfterSignupHook, OnBeforeOAuthRedirectHook, OnBeforeLoginHook, OnAfterLoginHook, InternalAuthHookParams, } from './hooks.js';
|
||||
|
@ -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'
|
||||
|
||||
@ -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 = {
|
||||
/**
|
||||
@ -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
|
||||
|
@ -26,6 +26,8 @@ export type {
|
||||
OnBeforeSignupHook,
|
||||
OnAfterSignupHook,
|
||||
OnBeforeOAuthRedirectHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
InternalAuthHookParams,
|
||||
} from './hooks.js'
|
||||
|
||||
|
@ -3,6 +3,8 @@ import type {
|
||||
OnAfterSignupHook,
|
||||
OnBeforeOAuthRedirectHook,
|
||||
OnBeforeSignupHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
InternalAuthHookParams,
|
||||
} from 'wasp/server/auth'
|
||||
import { onBeforeSignup as onBeforeSignupHook_ext } from '../../../../../src/auth/hooks.js'
|
||||
@ -36,6 +38,17 @@ export const onBeforeOAuthRedirectHook: InternalFunctionForHook<OnBeforeOAuthRed
|
||||
...params,
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* This is a no-op function since the user didn't define the onBeforeLogin hook.
|
||||
*/
|
||||
export const onBeforeLoginHook: InternalFunctionForHook<OnBeforeLoginHook> = async (_params) => {}
|
||||
|
||||
/**
|
||||
* This is a no-op function since the user didn't define the onAfterLogin hook.
|
||||
*/
|
||||
export const onAfterLoginHook: InternalFunctionForHook<OnAfterLoginHook> = async (_params) => {}
|
||||
|
||||
/*
|
||||
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.
|
||||
|
@ -5,13 +5,19 @@ import {
|
||||
sanitizeAndSerializeProviderData,
|
||||
validateAndGetUserFields,
|
||||
createProviderId,
|
||||
findAuthWithUserBy,
|
||||
} from 'wasp/auth/utils'
|
||||
import { type Auth } 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,
|
||||
@ -41,9 +47,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
|
||||
@ -77,12 +83,37 @@ async function getAuthIdFromProviderDetails({
|
||||
})
|
||||
|
||||
if (existingAuthIdentity) {
|
||||
return existingAuthIdentity.auth.id
|
||||
const authId = existingAuthIdentity.auth.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({})
|
||||
|
@ -48,6 +48,8 @@ app todoApp {
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks.js",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks.js",
|
||||
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks.js",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks.js",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks.js",
|
||||
},
|
||||
server: {
|
||||
setupFn: import setup from "@src/serverSetup",
|
||||
|
@ -3,6 +3,8 @@ import type {
|
||||
OnAfterSignupHook,
|
||||
OnBeforeOAuthRedirectHook,
|
||||
OnBeforeSignupHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
} from 'wasp/server/auth'
|
||||
|
||||
export const onBeforeSignup: OnBeforeSignupHook = async (args) => {
|
||||
@ -47,6 +49,20 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async (
|
||||
return { url: args.url }
|
||||
}
|
||||
|
||||
export const onBeforeLogin: OnBeforeLoginHook = async (args) => {
|
||||
const log = createLoggerForHook('onBeforeLogin')
|
||||
log('providerId object', args.providerId)
|
||||
}
|
||||
|
||||
export const onAfterLogin: OnAfterLoginHook = async (args) => {
|
||||
const log = createLoggerForHook('onAfterLogin')
|
||||
log('providerId object', args.providerId)
|
||||
log('user object', args.user)
|
||||
if (args.oauth) {
|
||||
log('accessToken', args.oauth.accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
function createLoggerForHook(hookName: string) {
|
||||
return (...args: unknown[]) => {
|
||||
console.log(`[${hookName}]`, ...args)
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "isOnAfterLoginHookCalled" BOOLEAN NOT NULL DEFAULT false;
|
@ -12,7 +12,8 @@ generator client {
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
isOnAfterSignupHookCalled Boolean @default(false)
|
||||
tasks Task[]
|
||||
isOnAfterLoginHookCalled Boolean @default(false)
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Task {
|
||||
|
@ -2,19 +2,36 @@ import { HttpError } from 'wasp/server'
|
||||
import type {
|
||||
OnAfterSignupHook,
|
||||
OnBeforeSignupHook,
|
||||
OnBeforeLoginHook,
|
||||
OnAfterLoginHook,
|
||||
} from 'wasp/server/auth'
|
||||
|
||||
export const onBeforeSignup: OnBeforeSignupHook = async (args) => {
|
||||
if (args.providerId.providerUserId === 'notallowed@email.com') {
|
||||
export const onBeforeSignup: OnBeforeSignupHook = async ({ providerId }) => {
|
||||
if (providerId.providerUserId === 'notallowed@email.com') {
|
||||
throw new HttpError(403, 'On Before Signup Hook disallows this email.')
|
||||
}
|
||||
}
|
||||
|
||||
export const onAfterSignup: OnAfterSignupHook = async (args) => {
|
||||
await args.prisma.user.update({
|
||||
where: { id: args.user.id },
|
||||
export const onAfterSignup: OnAfterSignupHook = async ({ prisma, user }) => {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
isOnAfterSignupHookCalled: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const onBeforeLogin: OnBeforeLoginHook = async ({ providerId }) => {
|
||||
if (providerId.providerUserId === 'cantlogin@email.com') {
|
||||
throw new HttpError(403, 'On Before Login Hook disallows this email.')
|
||||
}
|
||||
}
|
||||
|
||||
export const onAfterLogin: OnAfterLoginHook = async ({ prisma, user }) => {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
isOnAfterLoginHookCalled: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ export const ProfilePage = ({ user }: { user: User }) => {
|
||||
Value of <code>user.isOnAfterSignupHookCalled</code> is{' '}
|
||||
<strong>{user.isOnAfterSignupHookCalled ? 'true' : 'false'}</strong>.
|
||||
</div>
|
||||
<div>
|
||||
Value of <code>user.isOnAfterLoginHookCalled</code> is{' '}
|
||||
<strong>{user.isOnAfterLoginHookCalled ? 'true' : 'false'}</strong>.
|
||||
</div>
|
||||
<br />
|
||||
<Link to="/">Go to dashboard</Link>
|
||||
</>
|
||||
|
@ -26,6 +26,8 @@ app todoApp {
|
||||
onAuthSucceededRedirectTo: "/profile",
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks.js",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks.js",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks.js",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks.js",
|
||||
},
|
||||
server: {
|
||||
setupFn: import setup from "@src/server/serverSetup.js",
|
||||
|
@ -27,8 +27,9 @@ test.describe('auth hooks', () => {
|
||||
|
||||
/*
|
||||
We set up the "after signup hook" to set a value in the user object.
|
||||
We also set up the "after login hook" to set a value in the user object.
|
||||
*/
|
||||
test('after signup hook works', async ({ page }) => {
|
||||
test('after signup and after login hooks work', async ({ page }) => {
|
||||
const { email, password } = generateRandomCredentials()
|
||||
|
||||
await performSignup(page, {
|
||||
@ -46,5 +47,31 @@ test.describe('auth hooks', () => {
|
||||
await expect(page.locator('body')).toContainText(
|
||||
'Value of user.isOnAfterSignupHookCalled is true.',
|
||||
)
|
||||
|
||||
await expect(page.locator('body')).toContainText(
|
||||
'Value of user.isOnAfterLoginHookCalled is true.',
|
||||
)
|
||||
})
|
||||
|
||||
/*
|
||||
We set up the "before login hook" to throw an error for a specific email address.
|
||||
*/
|
||||
test('before login hook works', async ({ page }) => {
|
||||
const emailThatThrowsError = 'cantlogin@email.com'
|
||||
const password = '12345678'
|
||||
|
||||
await performSignup(page, {
|
||||
email: emailThatThrowsError,
|
||||
password,
|
||||
})
|
||||
|
||||
await performLogin(page, {
|
||||
email: emailThatThrowsError,
|
||||
password,
|
||||
})
|
||||
|
||||
await expect(page.locator('body')).toContainText(
|
||||
'On Before Login Hook disallows this email.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -37,7 +37,9 @@ data Auth = Auth
|
||||
onAuthSucceededRedirectTo :: Maybe String,
|
||||
onBeforeSignup :: Maybe ExtImport,
|
||||
onAfterSignup :: Maybe ExtImport,
|
||||
onBeforeOAuthRedirect :: Maybe ExtImport
|
||||
onBeforeOAuthRedirect :: Maybe ExtImport,
|
||||
onBeforeLogin :: Maybe ExtImport,
|
||||
onAfterLogin :: Maybe ExtImport
|
||||
}
|
||||
deriving (Show, Eq, Data)
|
||||
|
||||
|
@ -110,11 +110,15 @@ genAuthHooks auth = return $ C.mkTmplFdWithData [relfile|src/auth/hooks.ts|] (Ju
|
||||
object
|
||||
[ "onBeforeSignupHook" .= onBeforeSignupHook,
|
||||
"onAfterSignupHook" .= onAfterSignupHook,
|
||||
"onBeforeOAuthRedirectHook" .= onBeforeOAuthRedirectHook
|
||||
"onBeforeOAuthRedirectHook" .= onBeforeOAuthRedirectHook,
|
||||
"onBeforeLoginHook" .= onBeforeLoginHook,
|
||||
"onAfterLoginHook" .= onAfterLoginHook
|
||||
]
|
||||
onBeforeSignupHook = extImportToAliasedImportJson "onBeforeSignupHook_ext" relPathToServerSrcDir $ AS.Auth.onBeforeSignup auth
|
||||
onAfterSignupHook = extImportToAliasedImportJson "onAfterSignupHook_ext" relPathToServerSrcDir $ AS.Auth.onAfterSignup auth
|
||||
onBeforeOAuthRedirectHook = extImportToAliasedImportJson "onBeforeOAuthRedirectHook_ext" relPathToServerSrcDir $ AS.Auth.onBeforeOAuthRedirect auth
|
||||
onBeforeLoginHook = extImportToAliasedImportJson "onBeforeLoginHook_ext" relPathToServerSrcDir $ AS.Auth.onBeforeLogin auth
|
||||
onAfterLoginHook = extImportToAliasedImportJson "onAfterLoginHook_ext" relPathToServerSrcDir $ AS.Auth.onAfterLogin auth
|
||||
|
||||
relPathToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir)
|
||||
relPathToServerSrcDir = [reldirP|../|]
|
||||
|
@ -152,7 +152,9 @@ spec_Analyzer = do
|
||||
Auth.onAuthSucceededRedirectTo = Nothing,
|
||||
Auth.onBeforeSignup = Nothing,
|
||||
Auth.onAfterSignup = Nothing,
|
||||
Auth.onBeforeOAuthRedirect = Nothing
|
||||
Auth.onBeforeOAuthRedirect = Nothing,
|
||||
Auth.onBeforeLogin = Nothing,
|
||||
Auth.onAfterLogin = Nothing
|
||||
},
|
||||
App.server =
|
||||
Just
|
||||
|
@ -123,7 +123,9 @@ spec_AppSpecValid = do
|
||||
AS.Auth.onAuthSucceededRedirectTo = Nothing,
|
||||
AS.Auth.onBeforeSignup = Nothing,
|
||||
AS.Auth.onAfterSignup = Nothing,
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing,
|
||||
AS.Auth.onBeforeLogin = Nothing,
|
||||
AS.Auth.onAfterLogin = Nothing
|
||||
}
|
||||
|
||||
describe "should validate that when a page has authRequired, app.auth is also set." $ do
|
||||
@ -169,7 +171,9 @@ spec_AppSpecValid = do
|
||||
AS.Auth.onAuthSucceededRedirectTo = Nothing,
|
||||
AS.Auth.onBeforeSignup = Nothing,
|
||||
AS.Auth.onAfterSignup = Nothing,
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing,
|
||||
AS.Auth.onBeforeLogin = Nothing,
|
||||
AS.Auth.onAfterLogin = Nothing
|
||||
},
|
||||
AS.App.emailSender =
|
||||
Just
|
||||
@ -311,7 +315,9 @@ spec_AppSpecValid = do
|
||||
AS.Auth.onAuthSucceededRedirectTo = Nothing,
|
||||
AS.Auth.onBeforeSignup = Nothing,
|
||||
AS.Auth.onAfterSignup = Nothing,
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing
|
||||
AS.Auth.onBeforeOAuthRedirect = Nothing,
|
||||
AS.Auth.onBeforeLogin = Nothing,
|
||||
AS.Auth.onAfterLogin = Nothing
|
||||
},
|
||||
AS.App.emailSender = emailSender
|
||||
},
|
||||
|
@ -13,8 +13,10 @@ The following auth hooks are available in Wasp:
|
||||
- [`onBeforeSignup`](#executing-code-before-the-user-signs-up)
|
||||
- [`onAfterSignup`](#executing-code-after-the-user-signs-up)
|
||||
- [`onBeforeOAuthRedirect`](#executing-code-before-the-oauth-redirect)
|
||||
- [`onBeforeLogin`](#executing-code-before-the-user-logs-in)
|
||||
- [`onAfterLogin`](#executing-code-after-the-user-logs-in)
|
||||
|
||||
We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the signup flow:
|
||||
We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the auth flows:
|
||||
|
||||
<ImgWithCaption
|
||||
source="/img/auth-hooks/signup_flow_with_hooks.png"
|
||||
@ -22,7 +24,19 @@ We'll go through each of these hooks in detail. But first, let's see how the hoo
|
||||
caption="Signup Flow with Hooks"
|
||||
/>
|
||||
|
||||
If you are using OAuth, the flow includes extra steps before the signup flow:
|
||||
<ImgWithCaption
|
||||
source="/img/auth-hooks/login_flow_with_hooks.png"
|
||||
alt="Login Flow with Hooks"
|
||||
caption="Login Flow with Hooks *"
|
||||
/>
|
||||
|
||||
<small>
|
||||
|
||||
\* When using the OAuth auth providers, the login hooks are both called before the session is created but the session is created quickly afterward, so it shouldn't make any difference in practice.
|
||||
</small>
|
||||
|
||||
|
||||
If you are using OAuth, the flow includes extra steps before the auth flow:
|
||||
|
||||
<ImgWithCaption
|
||||
source="/img/auth-hooks/oauth_flow_with_hooks.png"
|
||||
@ -50,6 +64,8 @@ app myApp {
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
|
||||
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -69,6 +85,8 @@ app myApp {
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
|
||||
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -268,7 +286,7 @@ Read more about the data the `onAfterSignup` hook receives in the [API Reference
|
||||
|
||||
Wasp calls the `onBeforeOAuthRedirect` hook after the OAuth redirect URL is generated but before redirecting the user. This hook can access the request object sent from the client at the start of the OAuth process.
|
||||
|
||||
The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that can be used later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` hook.
|
||||
The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that you can use later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` or `onAfterLogin` hooks.
|
||||
|
||||
Works with <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill />
|
||||
|
||||
@ -294,7 +312,7 @@ export const onBeforeOAuthRedirect = async ({
|
||||
}) => {
|
||||
console.log('query params before oAuth redirect', req.query)
|
||||
|
||||
// Saving query params for later use in the onAfterSignup hook
|
||||
// Saving query params for later use in onAfterSignup or onAfterLogin hooks
|
||||
const id = uniqueRequestId
|
||||
someKindOfStore.set(id, req.query)
|
||||
|
||||
@ -326,7 +344,7 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({
|
||||
}) => {
|
||||
console.log('query params before oAuth redirect', req.query)
|
||||
|
||||
// Saving query params for later use in the onAfterSignup hook
|
||||
// Saving query params for later use in onAfterSignup or onAfterLogin hooks
|
||||
const id = uniqueRequestId
|
||||
someKindOfStore.set(id, req.query)
|
||||
|
||||
@ -341,6 +359,167 @@ This hook's return value must be an object that looks like this: `{ url: URL }`.
|
||||
|
||||
Read more about the data the `onBeforeOAuthRedirect` hook receives in the [API Reference](#the-onbeforeoauthredirect-hook).
|
||||
|
||||
### Executing code before the user logs in
|
||||
|
||||
Wasp calls the `onBeforeLogin` hook before the user is logged in.
|
||||
|
||||
The `onBeforeLogin` hook can be useful if you want to reject a user based on some criteria before they log in.
|
||||
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill />
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```wasp title="main.wasp"
|
||||
app myApp {
|
||||
...
|
||||
auth: {
|
||||
...
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```js title="src/auth/hooks.js"
|
||||
import { HttpError } from 'wasp/server'
|
||||
|
||||
export const onBeforeLogin = async ({
|
||||
providerId,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') {
|
||||
throw new HttpError(403, 'You cannot log in with this email')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```wasp title="main.wasp"
|
||||
app myApp {
|
||||
...
|
||||
auth: {
|
||||
...
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```ts title="src/auth/hooks.ts"
|
||||
import { HttpError } from 'wasp/server'
|
||||
import type { OnBeforeLoginHook } from 'wasp/server/auth'
|
||||
|
||||
export const onBeforeLogin: OnBeforeLoginHook = async ({
|
||||
providerId,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') {
|
||||
throw new HttpError(403, 'You cannot log in with this email')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Read more about the data the `onBeforeLogin` hook receives in the [API Reference](#the-onbeforelogin-hook).
|
||||
|
||||
### Executing code after the user logs in
|
||||
|
||||
Wasp calls the `onAfterLogin` hook after the user logs in.
|
||||
|
||||
The `onAfterLogin` hook can be useful if you want to perform some action after the user logs in, like syncing the user with a third-party service.
|
||||
|
||||
Since the `onAfterLogin` hook receives the OAuth access token, it can also be used to update the OAuth access token for the user in your database.
|
||||
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill />
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```wasp title="main.wasp"
|
||||
app myApp {
|
||||
...
|
||||
auth: {
|
||||
...
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```js title="src/auth/hooks.js"
|
||||
export const onAfterLogin = async ({
|
||||
providerId,
|
||||
user,
|
||||
oauth,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
console.log('user object', user)
|
||||
|
||||
// If this is an OAuth signup, we have the access token and uniqueRequestId
|
||||
if (oauth) {
|
||||
console.log('accessToken', oauth.accessToken)
|
||||
console.log('uniqueRequestId', oauth.uniqueRequestId)
|
||||
|
||||
const id = oauth.uniqueRequestId
|
||||
const data = someKindOfStore.get(id)
|
||||
if (data) {
|
||||
console.log('saved data for the ID', data)
|
||||
}
|
||||
someKindOfStore.delete(id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```wasp title="main.wasp"
|
||||
app myApp {
|
||||
...
|
||||
auth: {
|
||||
...
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```ts title="src/auth/hooks.ts"
|
||||
import type { OnAfterLoginHook } from 'wasp/server/auth'
|
||||
|
||||
export const onAfterLogin: OnAfterLoginHook = async ({
|
||||
providerId,
|
||||
user,
|
||||
oauth,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
console.log('user object', user)
|
||||
|
||||
// If this is an OAuth signup, we have the access token and uniqueRequestId
|
||||
if (oauth) {
|
||||
console.log('accessToken', oauth.accessToken)
|
||||
console.log('uniqueRequestId', oauth.uniqueRequestId)
|
||||
|
||||
const id = oauth.uniqueRequestId
|
||||
const data = someKindOfStore.get(id)
|
||||
if (data) {
|
||||
console.log('saved data for the ID', data)
|
||||
}
|
||||
someKindOfStore.delete(id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Read more about the data the `onAfterLogin` hook receives in the [API Reference](#the-onafterlogin-hook).
|
||||
|
||||
## API Reference
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
@ -359,6 +538,8 @@ app myApp {
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
|
||||
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -378,26 +559,38 @@ app myApp {
|
||||
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
|
||||
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
|
||||
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
|
||||
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
|
||||
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Common hook input
|
||||
|
||||
The following properties are available in all auth hooks:
|
||||
|
||||
- `prisma: PrismaClient`
|
||||
|
||||
The Prisma client instance which you can use to query your database.
|
||||
|
||||
- `req: Request`
|
||||
|
||||
The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc.
|
||||
|
||||
### The `onBeforeSignup` hook
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```js title="src/auth/hooks.js"
|
||||
import { HttpError } from 'wasp/server'
|
||||
|
||||
export const onBeforeSignup = async ({
|
||||
providerId,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
|
||||
@ -405,7 +598,6 @@ export const onBeforeSignup = async ({
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```ts title="src/auth/hooks.ts"
|
||||
import { HttpError } from 'wasp/server'
|
||||
import type { OnBeforeSignupHook } from 'wasp/server/auth'
|
||||
|
||||
export const onBeforeSignup: OnBeforeSignupHook = async ({
|
||||
@ -413,7 +605,7 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
|
||||
@ -422,21 +614,9 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({
|
||||
|
||||
The hook receives an object as **input** with the following properties:
|
||||
|
||||
- `providerId: ProviderId`
|
||||
- [`providerId: ProviderId`](#providerid-fields)
|
||||
|
||||
The user's provider ID is an object with two properties:
|
||||
- `providerName: string`
|
||||
|
||||
The provider's name (e.g. `'email'`, `'google'`, `'github'`)
|
||||
- `providerUserId: string`
|
||||
|
||||
The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID)
|
||||
- `prisma: PrismaClient`
|
||||
|
||||
The Prisma client instance which you can use to query your database.
|
||||
- `req: Request`
|
||||
|
||||
The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc.
|
||||
- Plus the [common hook input](#common-hook-input)
|
||||
|
||||
Wasp ignores this hook's **return value**.
|
||||
|
||||
@ -453,7 +633,7 @@ export const onAfterSignup = async ({
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
|
||||
@ -470,7 +650,7 @@ export const onAfterSignup: OnAfterSignupHook = async ({
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
|
||||
@ -478,36 +658,14 @@ export const onAfterSignup: OnAfterSignupHook = async ({
|
||||
</Tabs>
|
||||
|
||||
The hook receives an object as **input** with the following properties:
|
||||
- `providerId: ProviderId`
|
||||
|
||||
The user's provider ID is an object with two properties:
|
||||
- `providerName: string`
|
||||
- [`providerId: ProviderId`](#providerid-fields)
|
||||
|
||||
The provider's name (e.g. `'email'`, `'google'`, `'github'`)
|
||||
- `providerUserId: string`
|
||||
|
||||
The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID)
|
||||
- `user: User`
|
||||
|
||||
The user object that was created.
|
||||
- `oauth?: OAuthFields`
|
||||
- [`oauth?: OAuthFields`](#oauth-fields)
|
||||
|
||||
This object is present only when the user is created using [Social Auth](./social-auth/overview.md).
|
||||
It contains the following fields:
|
||||
- `accessToken: string`
|
||||
|
||||
You can use the OAuth access token to use the provider's API on user's behalf.
|
||||
- `uniqueRequestId: string`
|
||||
|
||||
The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.)
|
||||
|
||||
You can use the unique request ID to get the data saved in the `onBeforeOAuthRedirect` hook.
|
||||
- `prisma: PrismaClient`
|
||||
|
||||
The Prisma client instance which you can use to query your database.
|
||||
- `req: Request`
|
||||
|
||||
The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc.
|
||||
- Plus the [common hook input](#common-hook-input)
|
||||
|
||||
Wasp ignores this hook's **return value**.
|
||||
|
||||
@ -523,7 +681,7 @@ export const onBeforeOAuthRedirect = async ({
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
|
||||
return { url }
|
||||
}
|
||||
@ -541,7 +699,7 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code here
|
||||
// Hook code goes here
|
||||
|
||||
return { url }
|
||||
}
|
||||
@ -558,12 +716,124 @@ The hook receives an object as **input** with the following properties:
|
||||
|
||||
The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.)
|
||||
|
||||
You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` hook.
|
||||
- `prisma: PrismaClient`
|
||||
|
||||
The Prisma client instance which you can use to query your database.
|
||||
- `req: Request`
|
||||
|
||||
The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc.
|
||||
You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` or `onAfterLogin` hooks.
|
||||
- Plus the [common hook input](#common-hook-input)
|
||||
|
||||
This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider.
|
||||
|
||||
### The `onBeforeLogin` hook
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```js title="src/auth/hooks.js"
|
||||
export const onBeforeLogin = async ({
|
||||
providerId,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```ts title="src/auth/hooks.ts"
|
||||
import type { OnBeforeLoginHook } from 'wasp/server/auth'
|
||||
|
||||
export const onBeforeLogin: OnBeforeLoginHook = async ({
|
||||
providerId,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The hook receives an object as **input** with the following properties:
|
||||
- [`providerId: ProviderId`](#providerid-fields)
|
||||
|
||||
- Plus the [common hook input](#common-hook-input)
|
||||
|
||||
Wasp ignores this hook's **return value**.
|
||||
|
||||
### The `onAfterLogin` hook
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```js title="src/auth/hooks.js"
|
||||
export const onAfterLogin = async ({
|
||||
providerId,
|
||||
user,
|
||||
oauth,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```ts title="src/auth/hooks.ts"
|
||||
import type { OnAfterLoginHook } from 'wasp/server/auth'
|
||||
|
||||
export const onAfterLogin: OnAfterLoginHook = async ({
|
||||
providerId,
|
||||
user,
|
||||
oauth,
|
||||
prisma,
|
||||
req,
|
||||
}) => {
|
||||
// Hook code goes here
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The hook receives an object as **input** with the following properties:
|
||||
- [`providerId: ProviderId`](#providerid-fields)
|
||||
|
||||
- `user: User`
|
||||
|
||||
The logged-in user's object.
|
||||
|
||||
- [`oauth?: OAuthFields`](#oauth-fields)
|
||||
|
||||
- Plus the [common hook input](#common-hook-input)
|
||||
|
||||
Wasp ignores this hook's **return value**.
|
||||
|
||||
### ProviderId fields
|
||||
|
||||
The `providerId` object represents the user for the current authentication method. Wasp passes it to the `onBeforeSignup`, `onAfterSignup`, `onBeforeLogin`, and `onAfterLogin` hooks.
|
||||
|
||||
It has the following fields:
|
||||
|
||||
- `providerName: string`
|
||||
|
||||
The provider's name (e.g. `'email'`, `'google'`, `'github`)
|
||||
|
||||
- `providerUserId: string`
|
||||
|
||||
The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID)
|
||||
|
||||
### OAuth fields
|
||||
|
||||
Wasp passes the `oauth` object to the `onAfterSignup` and `onAfterLogin` hooks only when the user is authenticated with [Social Auth](./social-auth/overview.md).
|
||||
|
||||
It has the following fields:
|
||||
|
||||
- `accessToken: string`
|
||||
|
||||
You can use the OAuth access token to make requests to the provider's API on the user's behalf.
|
||||
|
||||
- `uniqueRequestId: string`
|
||||
|
||||
The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.)
|
||||
|
||||
You can use the unique request ID to get the data that was saved in the `onBeforeOAuthRedirect` hook.
|
||||
|
BIN
web/static/img/auth-hooks/login_flow_with_hooks.png
Normal file
BIN
web/static/img/auth-hooks/login_flow_with_hooks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 452 KiB |
Binary file not shown.
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 417 KiB |
Loading…
Reference in New Issue
Block a user