From a6573c2201a16b38a5f9f9c3c7b9bbaafb677b9e Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Mon, 26 Aug 2024 14:32:04 +0200 Subject: [PATCH] Add support for refresh tokens (#2212) --- .../templates/sdk/wasp/server/auth/hooks.ts | 83 +-- .../templates/sdk/wasp/server/auth/index.ts | 13 +- .../wasp/server/auth}/oauth/env.ts | 7 +- .../sdk/wasp/server/auth/oauth/index.ts | 31 ++ .../sdk/wasp/server/auth/oauth/oneTimeCode.ts | 50 ++ .../sdk/wasp/server/auth/oauth/provider.ts | 23 + .../server/auth/oauth/providers/discord.ts | 28 + .../server/auth/oauth/providers/github.ts | 26 + .../server/auth/oauth/providers/google.ts | 28 + .../server/auth/oauth/providers/keycloak.ts | 29 + .../wasp/server/auth}/oauth/redirect.ts | 22 +- .../src/auth/providers/config/discord.ts | 23 +- .../src/auth/providers/config/github.ts | 21 +- .../src/auth/providers/config/google.ts | 25 +- .../src/auth/providers/config/keycloak.ts | 26 +- .../server/src/auth/providers/email/login.ts | 6 +- .../src/auth/providers/oauth/handler.ts | 33 +- .../src/auth/providers/oauth/oneTimeCode.ts | 52 +- .../server/src/auth/providers/oauth/user.ts | 29 +- .../src/auth/providers/username/login.ts | 6 +- .../waspComplexTest-golden/files.manifest | 26 +- .../waspComplexTest/.wasp/out/.waspchecksums | 72 ++- .../.wasp/out/installedNpmDepsLog.json | 2 +- .../out/sdk/wasp/dist/server/auth/hooks.d.ts | 69 ++- .../out/sdk/wasp/dist/server/auth/index.d.ts | 3 +- .../out/sdk/wasp/dist/server/auth/index.js | 1 + .../sdk/wasp/dist/server/auth/index.js.map | 2 +- .../sdk/wasp/dist/server/auth/oauth/env.d.ts | 1 + .../sdk/wasp/dist/server/auth/oauth/env.js | 13 + .../wasp/dist/server/auth/oauth/env.js.map | 1 + .../wasp/dist/server/auth/oauth/index.d.ts | 3 + .../sdk/wasp/dist/server/auth/oauth/index.js | 7 + .../wasp/dist/server/auth/oauth/index.js.map | 1 + .../dist/server/auth/oauth/oneTimeCode.d.ts | 8 + .../dist/server/auth/oauth/oneTimeCode.js | 39 ++ .../dist/server/auth/oauth/oneTimeCode.js.map | 1 + .../wasp/dist/server/auth/oauth/provider.d.ts | 12 + .../wasp/dist/server/auth/oauth/provider.js | 9 + .../dist/server/auth/oauth/provider.js.map | 1 + .../server/auth/oauth/providers/google.d.ts | 7 + .../server/auth/oauth/providers/google.js | 16 + .../server/auth/oauth/providers/google.js.map | 1 + .../wasp/dist/server/auth/oauth/redirect.d.ts | 6 + .../wasp/dist/server/auth/oauth/redirect.js | 34 ++ .../dist/server/auth/oauth/redirect.js.map | 1 + .../.wasp/out/sdk/wasp/package.json | 1 + .../.wasp/out/sdk/wasp/server/auth/hooks.ts | 71 +-- .../.wasp/out/sdk/wasp/server/auth/index.ts | 3 + .../wasp/server/auth}/oauth/env.ts | 7 +- .../out/sdk/wasp/server/auth/oauth/index.ts | 16 + .../sdk/wasp/server/auth/oauth/oneTimeCode.ts | 50 ++ .../sdk/wasp/server/auth/oauth/provider.ts | 23 + .../server/auth/oauth/providers/google.ts | 27 + .../wasp/server/auth}/oauth/redirect.ts | 22 +- .../.wasp/out/server/package.json | 1 - .../src/auth/providers/config/google.ts | 25 +- .../src/auth/providers/oauth/handler.ts | 33 +- .../src/auth/providers/oauth/oneTimeCode.ts | 52 +- .../server/src/auth/providers/oauth/user.ts | 29 +- waspc/examples/todoApp/package-lock.json | 496 ++++++++++++++++++ waspc/examples/todoApp/src/auth/hooks.ts | 8 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 2 + .../src/Wasp/Generator/SdkGenerator/AuthG.hs | 2 + .../Generator/SdkGenerator/Server/AuthG.hs | 14 +- .../Generator/SdkGenerator/Server/OAuthG.hs | 103 ++++ waspc/src/Wasp/Generator/ServerGenerator.hs | 2 - .../ServerGenerator/Auth/OAuthAuthG.hs | 34 +- waspc/waspc.cabal | 1 + web/docs/auth/auth-hooks.md | 189 +++++-- 69 files changed, 1594 insertions(+), 514 deletions(-) rename waspc/data/Generator/templates/{server/src/auth/providers => sdk/wasp/server/auth}/oauth/env.ts (74%) create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/oneTimeCode.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/provider.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/discord.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/github.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/google.ts create mode 100644 waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/keycloak.ts rename waspc/data/Generator/templates/{server/src/auth/providers => sdk/wasp/server/auth}/oauth/redirect.ts (87%) create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js.map create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js.map create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js.map create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js.map create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js.map create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.d.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js.map rename waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/{server/src/auth/providers => sdk/wasp/server/auth}/oauth/env.ts (74%) create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/oneTimeCode.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/provider.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/providers/google.ts rename waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/{server/src/auth/providers => sdk/wasp/server/auth}/oauth/redirect.ts (86%) create mode 100644 waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts index a3754e7f6a..6b5824dec8 100644 --- a/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts @@ -1,3 +1,4 @@ +{{={= =}=}} import type { Request as ExpressRequest } from 'express' import { type ProviderId, createUser, findAuthWithUserBy } from '../../auth/utils.js' import { prisma } from '../index.js' @@ -35,7 +36,7 @@ export type OnAfterLoginHook = ( export type InternalAuthHookParams = { /** * Prisma instance that can be used to interact with the database. - */ + */ prisma: typeof prisma } @@ -48,86 +49,98 @@ export type InternalAuthHookParams = { type OnBeforeSignupHookParams = { /** * 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 OnAfterSignupHookParams = { /** * Provider ID object that contains the provider name and the provide user ID. - */ + */ providerId: ProviderId /** * User object that was created during the signup process. - */ + */ user: Awaited> - oauth?: { - /** - * Access token that was received during the OAuth flow. - */ - accessToken: string - /** - * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string - }, + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user signed up using OAuth. + */ + oauth?: OAuthData /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest } & InternalAuthHookParams type OnBeforeOAuthRedirectHookParams = { /** * URL that the OAuth flow should redirect to. - */ + */ url: URL /** * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string + */ + oauth: Pick /** * Request object that can be used to access the incoming request. - */ + */ 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>['user'] + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user logged in using OAuth. + */ + oauth?: OAuthData /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest } & InternalAuthHookParams + +// PUBLIC API +export type OAuthData = { + /** + * Unique request ID that was generated during the OAuth flow. + */ + uniqueRequestId: string +} & ( + {=# enabledProviders.isGoogleAuthEnabled =} + | { providerName: 'google'; tokens: import('arctic').GoogleTokens } + {=/ enabledProviders.isGoogleAuthEnabled =} + {=# enabledProviders.isDiscordAuthEnabled =} + | { providerName: 'discord'; tokens: import('arctic').DiscordTokens } + {=/ enabledProviders.isDiscordAuthEnabled =} + {=# enabledProviders.isGitHubAuthEnabled =} + | { providerName: 'github'; tokens: import('arctic').GitHubTokens } + {=/ enabledProviders.isGitHubAuthEnabled =} + {=# enabledProviders.isKeycloakAuthEnabled =} + | { providerName: 'keycloak'; tokens: import('arctic').KeycloakTokens } + {=/ enabledProviders.isKeycloakAuthEnabled =} + | never +) diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts index 9c305a8749..945793b0bc 100644 --- a/waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts @@ -30,12 +30,17 @@ export type { OnBeforeLoginHook, OnAfterLoginHook, InternalAuthHookParams, + OAuthData, } from './hooks.js' -{=# isEmailAuthEnabled =} +{=# isExternalAuthEnabled =} +export * from './oauth/index.js' +{=/ isExternalAuthEnabled =} + +{=# enabledProviders.isEmailAuthEnabled =} export * from './email/index.js' -{=/ isEmailAuthEnabled =} +{=/ enabledProviders.isEmailAuthEnabled =} -{=# isUsernameAndPasswordAuthEnabled =} +{=# enabledProviders.isUsernameAndPasswordAuthEnabled =} export * from './username.js' -{=/ isUsernameAndPasswordAuthEnabled =} +{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/env.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/env.ts similarity index 74% rename from waspc/data/Generator/templates/server/src/auth/providers/oauth/env.ts rename to waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/env.ts index 24776c6dd3..ada2452b8e 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/env.ts +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/env.ts @@ -1,14 +1,13 @@ -import { type ProviderConfig } from "wasp/auth/providers/types"; - +// PRIVATE API (SDK) export function ensureEnvVarsForProvider( envVarNames: EnvVarName[], - provider: ProviderConfig, + providerName: string, ): Record { const result: Record = {}; for (const envVarName of envVarNames) { const value = process.env[envVarName]; if (!value) { - throw new Error(`${envVarName} env variable is required when using the ${provider.displayName} auth provider.`); + throw new Error(`${envVarName} env variable is required when using the ${providerName} auth provider.`); } result[envVarName] = value; } diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts new file mode 100644 index 0000000000..7fc28ed4c2 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts @@ -0,0 +1,31 @@ +{{={= =}=}} +{=# enabledProviders.isGoogleAuthEnabled =} +// PUBLIC API +export { google } from './providers/google.js'; +{=/ enabledProviders.isGoogleAuthEnabled =} +{=# enabledProviders.isDiscordAuthEnabled =} +// PUBLIC API +export { discord } from './providers/discord.js'; +{=/ enabledProviders.isDiscordAuthEnabled =} +{=# enabledProviders.isGitHubAuthEnabled =} +// PUBLIC API +export { github } from './providers/github.js'; +{=/ enabledProviders.isGitHubAuthEnabled =} +{=# enabledProviders.isKeycloakAuthEnabled =} +// PUBLIC API +export { keycloak } from './providers/keycloak.js'; +{=/ enabledProviders.isKeycloakAuthEnabled =} + +// PRIVATE API +export { + loginPath, + callbackPath, + exchangeCodeForTokenPath, + handleOAuthErrorAndGetRedirectUri, + getRedirectUriForOneTimeCode, +} from './redirect.js' + +// PRIVATE API +export { + tokenStore, +} from './oneTimeCode.js' diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/oneTimeCode.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/oneTimeCode.ts new file mode 100644 index 0000000000..4f2d8ebb08 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/oneTimeCode.ts @@ -0,0 +1,50 @@ +import { createJWT, validateJWT, TimeSpan } from '../../../auth/jwt.js' + +export const tokenStore = createTokenStore(); + +function createTokenStore() { + const usedTokens = new Map(); + + const validFor = new TimeSpan(1, 'm') // 1 minute + const cleanupAfter = 1000 * 60 * 60; // 1 hour + + function createToken(userId: string): Promise { + return createJWT( + { + id: userId, + }, + { + expiresIn: validFor, + } + ); + } + + function verifyToken(token: string): Promise<{ id: string }> { + return validateJWT(token); + } + + function isUsed(token: string): boolean { + return usedTokens.has(token); + } + + function markUsed(token: string): void { + usedTokens.set(token, Date.now()); + cleanUp(); + } + + function cleanUp(): void { + const now = Date.now(); + for (const [token, timestamp] of usedTokens.entries()) { + if (now - timestamp > cleanupAfter) { + usedTokens.delete(token); + } + } + } + + return { + createToken, + verifyToken, + isUsed, + markUsed, + }; +} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/provider.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/provider.ts new file mode 100644 index 0000000000..c2aee70897 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/provider.ts @@ -0,0 +1,23 @@ +import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic"; + +export function defineProvider< + OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE, + Env extends Record +>({ + id, + displayName, + env, + oAuthClient, +}: { + id: string; + displayName: string; + env: Env; + oAuthClient: OAuthClient; +}) { + return { + id, + displayName, + env, + oAuthClient, + }; +} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/discord.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/discord.ts new file mode 100644 index 0000000000..52e396f7a6 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/discord.ts @@ -0,0 +1,28 @@ +{{={= =}=}} +import { Discord } from "arctic"; + +import { defineProvider } from "../provider.js"; +import { ensureEnvVarsForProvider } from "../env.js"; +import { getRedirectUriForCallback } from "../redirect.js"; + +const id = "{= providerId =}"; +const displayName = "{= displayName =}"; + +const env = ensureEnvVarsForProvider( + ["DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET"], + displayName +); + +const oAuthClient = new Discord( + env.DISCORD_CLIENT_ID, + env.DISCORD_CLIENT_SECRET, + getRedirectUriForCallback(id).toString(), +); + +// PUBLIC API +export const discord = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/github.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/github.ts new file mode 100644 index 0000000000..e9c5019c37 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/github.ts @@ -0,0 +1,26 @@ +{{={= =}=}} +import { GitHub } from "arctic"; + +import { ensureEnvVarsForProvider } from "../env.js"; +import { defineProvider } from "../provider.js"; + +const id = "{= providerId =}"; +const displayName = "{= displayName =}"; + +const env = ensureEnvVarsForProvider( + ["GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET"], + displayName +); + +const oAuthClient = new GitHub( + env.GITHUB_CLIENT_ID, + env.GITHUB_CLIENT_SECRET, +); + +// PUBLIC API +export const github = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/google.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/google.ts new file mode 100644 index 0000000000..cf157ae12b --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/google.ts @@ -0,0 +1,28 @@ +{{={= =}=}} +import { Google } from "arctic"; + +import { ensureEnvVarsForProvider } from "../env.js"; +import { getRedirectUriForCallback } from "../redirect.js"; +import { defineProvider } from "../provider.js"; + +const id = "{= providerId =}"; +const displayName = "{= displayName =}"; + +const env = ensureEnvVarsForProvider( + ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"], + displayName, +); + +const oAuthClient = new Google( + env.GOOGLE_CLIENT_ID, + env.GOOGLE_CLIENT_SECRET, + getRedirectUriForCallback(id).toString(), +); + +// PUBLIC API +export const google = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/keycloak.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/keycloak.ts new file mode 100644 index 0000000000..93136732d9 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/keycloak.ts @@ -0,0 +1,29 @@ +{{={= =}=}} +import { Keycloak } from "arctic"; + +import { ensureEnvVarsForProvider } from "../env.js"; +import { getRedirectUriForCallback } from "../redirect.js"; +import { defineProvider } from "../provider.js"; + +const id = "{= providerId =}"; +const displayName = "{= displayName =}"; + +const env = ensureEnvVarsForProvider( + ["KEYCLOAK_REALM_URL", "KEYCLOAK_CLIENT_ID", "KEYCLOAK_CLIENT_SECRET"], + displayName, +); + +const oAuthClient = new Keycloak( + env.KEYCLOAK_REALM_URL, + env.KEYCLOAK_CLIENT_ID, + env.KEYCLOAK_CLIENT_SECRET, + getRedirectUriForCallback(id).toString(), +); + +// PUBLIC API +export const keycloak = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/redirect.ts similarity index 87% rename from waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts rename to waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/redirect.ts index 2408a27b5e..415459bf17 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts +++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/redirect.ts @@ -1,20 +1,23 @@ {{={= =}=}} -import { config } from 'wasp/server' -import { HttpError } from 'wasp/server' +import { config, HttpError } from '../../index.js' +// PRIVATE API (server) export const loginPath = '{= serverOAuthLoginHandlerPath =}' -export const callbackPath = '{= serverOAuthCallbackHandlerPath =}' + +// PRIVATE API (server) export const exchangeCodeForTokenPath = '{= serverExchangeCodeForTokenHandlerPath =}' -const clientOAuthCallbackPath = '{= clientOAuthCallbackPath =}' -export function getRedirectUriForCallback(providerName: string): URL { - return new URL(`${config.serverUrl}/auth/${providerName}/${callbackPath}`); -} +// PRIVATE API (server) +export const callbackPath = '{= serverOAuthCallbackHandlerPath =}' +const clientOAuthCallbackPath = '{= clientOAuthCallbackPath =}' + +// PRIVATE API (server) export function getRedirectUriForOneTimeCode(oneTimeCode: string): URL { return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}#${oneTimeCode}`); } +// PRIVATE API (server) export function handleOAuthErrorAndGetRedirectUri(error: unknown): URL { if (error instanceof HttpError) { const errorMessage = isHttpErrorWithExtraMessage(error) @@ -26,6 +29,11 @@ export function handleOAuthErrorAndGetRedirectUri(error: unknown): URL { return getRedirectUriForError("An unknown error occurred while trying to log in with the OAuth provider."); } +// PRIVATE API (SDK) +export function getRedirectUriForCallback(providerName: string): URL { + return new URL(`${config.serverUrl}/auth/${providerName}/${callbackPath}`); +} + function getRedirectUriForError(error: string): URL { return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}?error=${error}`); } diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/discord.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/discord.ts index a5e3bcd6fe..0f7d11bbc7 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/discord.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/discord.ts @@ -1,9 +1,7 @@ {{={= =}=}} -import { Discord } from "arctic"; import type { ProviderConfig } from "wasp/auth/providers/types"; -import { getRedirectUriForCallback } from "../oauth/redirect.js"; -import { ensureEnvVarsForProvider } from "../oauth/env.js"; +import { discord } from "wasp/server/auth"; import { mergeDefaultAndUserConfig } from "../oauth/config.js"; import { createOAuthProviderRouter } from "../oauth/handler.js"; @@ -23,20 +21,9 @@ const _waspUserDefinedConfigFn = undefined {=/ configFn.isDefined =} const _waspConfig: ProviderConfig = { - id: "{= providerId =}", - displayName: "{= displayName =}", + id: discord.id, + displayName: discord.displayName, createRouter(provider) { - const env = ensureEnvVarsForProvider( - ["DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET"], - provider - ); - - const discord = new Discord( - env.DISCORD_CLIENT_ID, - env.DISCORD_CLIENT_SECRET, - getRedirectUriForCallback(provider.id).toString(), - ); - const config = mergeDefaultAndUserConfig({ scopes: {=& requiredScopes =}, }, _waspUserDefinedConfigFn); @@ -72,8 +59,8 @@ const _waspConfig: ProviderConfig = { provider, oAuthType: 'OAuth2', userSignupFields: _waspUserSignupFields, - getAuthorizationUrl: ({ state }) => discord.createAuthorizationURL(state, config), - getProviderTokens: ({ code }) => discord.validateAuthorizationCode(code), + getAuthorizationUrl: ({ state }) => discord.oAuthClient.createAuthorizationURL(state, config), + getProviderTokens: ({ code }) => discord.oAuthClient.validateAuthorizationCode(code), getProviderInfo: ({ accessToken }) => getDiscordProfile(accessToken), }); }, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts index 12723302f9..da79e04bf2 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts @@ -1,8 +1,7 @@ {{={= =}=}} -import { GitHub } from "arctic"; import type { ProviderConfig } from "wasp/auth/providers/types"; -import { ensureEnvVarsForProvider } from "../oauth/env.js"; +import { github } from "wasp/server/auth"; import { mergeDefaultAndUserConfig } from "../oauth/config.js"; import { createOAuthProviderRouter } from "../oauth/handler.js"; @@ -22,19 +21,9 @@ const _waspUserDefinedConfigFn = undefined {=/ configFn.isDefined =} const _waspConfig: ProviderConfig = { - id: "{= providerId =}", - displayName: "{= displayName =}", + id: github.id, + displayName: github.displayName, createRouter(provider) { - const env = ensureEnvVarsForProvider( - ["GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET"], - provider - ); - - const github = new GitHub( - env.GITHUB_CLIENT_ID, - env.GITHUB_CLIENT_SECRET, - ); - const config = mergeDefaultAndUserConfig({ scopes: {=& requiredScopes =}, }, _waspUserDefinedConfigFn); @@ -79,8 +68,8 @@ const _waspConfig: ProviderConfig = { provider, oAuthType: 'OAuth2', userSignupFields: _waspUserSignupFields, - getAuthorizationUrl: ({ state }) => github.createAuthorizationURL(state, config), - getProviderTokens: ({ code }) => github.validateAuthorizationCode(code), + getAuthorizationUrl: ({ state }) => github.oAuthClient.createAuthorizationURL(state, config), + getProviderTokens: ({ code }) => github.oAuthClient.validateAuthorizationCode(code), getProviderInfo: ({ accessToken }) => getGithubProfile(accessToken), }); }, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts index 93ba70bf2e..0209353162 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts @@ -1,9 +1,7 @@ {{={= =}=}} -import { Google } from "arctic"; - import type { ProviderConfig } from "wasp/auth/providers/types"; -import { getRedirectUriForCallback } from "../oauth/redirect.js"; -import { ensureEnvVarsForProvider } from "../oauth/env.js"; +import { google } from "wasp/server/auth"; + import { mergeDefaultAndUserConfig } from "../oauth/config.js"; import { createOAuthProviderRouter } from "../oauth/handler.js"; @@ -23,20 +21,9 @@ const _waspUserDefinedConfigFn = undefined {=/ configFn.isDefined =} const _waspConfig: ProviderConfig = { - id: "{= providerId =}", - displayName: "{= displayName =}", + id: google.id, + displayName: google.displayName, createRouter(provider) { - const env = ensureEnvVarsForProvider( - ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"], - provider - ); - - const google = new Google( - env.GOOGLE_CLIENT_ID, - env.GOOGLE_CLIENT_SECRET, - getRedirectUriForCallback(provider.id).toString(), - ); - const config = mergeDefaultAndUserConfig({ scopes: {=& requiredScopes =}, }, _waspUserDefinedConfigFn); @@ -68,8 +55,8 @@ const _waspConfig: ProviderConfig = { provider, oAuthType: 'OAuth2WithPKCE', userSignupFields: _waspUserSignupFields, - getAuthorizationUrl: ({ state, codeVerifier }) => google.createAuthorizationURL(state, codeVerifier, config), - getProviderTokens: ({ code, codeVerifier }) => google.validateAuthorizationCode(code, codeVerifier), + getAuthorizationUrl: ({ state, codeVerifier }) => google.oAuthClient.createAuthorizationURL(state, codeVerifier, config), + getProviderTokens: ({ code, codeVerifier }) => google.oAuthClient.validateAuthorizationCode(code, codeVerifier), getProviderInfo: ({ accessToken }) => getGoogleProfile(accessToken), }); }, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/keycloak.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/keycloak.ts index 95d75c656f..f80d56ad46 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/keycloak.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/keycloak.ts @@ -1,9 +1,7 @@ {{={= =}=}} -import { Keycloak } from "arctic"; import type { ProviderConfig } from "wasp/auth/providers/types"; -import { getRedirectUriForCallback } from "../oauth/redirect.js"; -import { ensureEnvVarsForProvider } from "../oauth/env.js"; +import { keycloak } from "wasp/server/auth"; import { mergeDefaultAndUserConfig } from "../oauth/config.js"; import { createOAuthProviderRouter } from "../oauth/handler.js"; @@ -23,21 +21,9 @@ const _waspUserDefinedConfigFn = undefined {=/ configFn.isDefined =} const _waspConfig: ProviderConfig = { - id: "{= providerId =}", - displayName: "{= displayName =}", + id: keycloak.id, + displayName: keycloak.displayName, createRouter(provider) { - const env = ensureEnvVarsForProvider( - ["KEYCLOAK_REALM_URL", "KEYCLOAK_CLIENT_ID", "KEYCLOAK_CLIENT_SECRET"], - provider - ); - - const keycloak = new Keycloak( - env.KEYCLOAK_REALM_URL, - env.KEYCLOAK_CLIENT_ID, - env.KEYCLOAK_CLIENT_SECRET, - getRedirectUriForCallback(provider.id).toString(), - ); - const config = mergeDefaultAndUserConfig({ scopes: {=& requiredScopes =}, }, _waspUserDefinedConfigFn); @@ -46,7 +32,7 @@ const _waspConfig: ProviderConfig = { providerProfile: unknown; providerUserId: string; }> { - const userInfoEndpoint = `${env.KEYCLOAK_REALM_URL}/protocol/openid-connect/userinfo`; + const userInfoEndpoint = `${keycloak.env.KEYCLOAK_REALM_URL}/protocol/openid-connect/userinfo`; const response = await fetch( userInfoEndpoint, { @@ -70,8 +56,8 @@ const _waspConfig: ProviderConfig = { provider, oAuthType: 'OAuth2WithPKCE', userSignupFields: _waspUserSignupFields, - getAuthorizationUrl: ({ state, codeVerifier }) => keycloak.createAuthorizationURL(state, codeVerifier, config), - getProviderTokens: ({ code, codeVerifier }) => keycloak.validateAuthorizationCode(code, codeVerifier), + getAuthorizationUrl: ({ state, codeVerifier }) => keycloak.oAuthClient.createAuthorizationURL(state, codeVerifier, config), + getProviderTokens: ({ code, codeVerifier }) => keycloak.oAuthClient.validateAuthorizationCode(code, codeVerifier), getProviderInfo: ({ accessToken }) => getKeycloakProfile(accessToken), }); }, 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 9fdf60b8aa..4e3ddb3913 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 @@ -36,7 +36,11 @@ export function getLoginRoute() { const auth = await findAuthWithUserBy({ id: authIdentity.authId }) - await onBeforeLoginHook({ req, providerId }) + await onBeforeLoginHook({ + req, + providerId, + user: auth.user, + }) const session = await createSession(auth.id) diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/handler.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/handler.ts index 869052d7ac..ec02cf5a67 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/handler.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/handler.ts @@ -6,7 +6,6 @@ import { type UserSignupFields, type ProviderConfig, } from 'wasp/auth/providers/types' - import { type OAuthType, type OAuthStateFor, @@ -19,10 +18,11 @@ import { callbackPath, loginPath, handleOAuthErrorAndGetRedirectUri, -} from './redirect.js' +} from 'wasp/server/auth' +import { OAuthData } from 'wasp/server/auth' import { onBeforeOAuthRedirectHook } from '../../hooks.js' -export function createOAuthProviderRouter({ +export function createOAuthProviderRouter({ provider, oAuthType, userSignupFields, @@ -51,14 +51,12 @@ export function createOAuthProviderRouter({ */ getProviderTokens: ( oAuthState: OAuthStateWithCodeFor, - ) => Promise<{ - accessToken: string - }> + ) => Promise /* The function that returns the user's profile and ID using the access token. */ - getProviderInfo: ({ accessToken }: { accessToken: string }) => Promise<{ + getProviderInfo: (tokens: Tokens) => Promise<{ providerUserId: string providerProfile: unknown }> @@ -77,7 +75,7 @@ export function createOAuthProviderRouter({ const { url: redirectUrlAfterHook } = await onBeforeOAuthRedirectHook({ req, url: redirectUrl, - uniqueRequestId: oAuthState.state, + oauth: { uniqueRequestId: oAuthState.state } }) return redirect(res, redirectUrlAfterHook.toString()) }), @@ -92,11 +90,9 @@ export function createOAuthProviderRouter({ provider, req, }) - const { accessToken } = await getProviderTokens(oAuthState) + const tokens = await getProviderTokens(oAuthState) - const { providerProfile, providerUserId } = await getProviderInfo({ - accessToken, - }) + const { providerProfile, providerUserId } = await getProviderInfo(tokens) try { const redirectUri = await finishOAuthFlowAndGetRedirectUri({ provider, @@ -104,8 +100,17 @@ export function createOAuthProviderRouter({ providerUserId, userSignupFields, req, - accessToken, - oAuthState, + oauth: { + uniqueRequestId: oAuthState.state, + // OAuth params are built as a discriminated union + // of provider names and their respective tokens. + // We are using a generic ProviderConfig and tokens type + // is inferred from the getProviderTokens function. + // Instead of building complex TS machinery to ensure that + // the providerName and tokens match, we are using any here. + providerName: provider.id as any, + tokens, + }, }) // Redirect to the client with the one time code return redirect(res, redirectUri.toString()) diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/oneTimeCode.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/oneTimeCode.ts index 0b98a1d879..cf1410c10f 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/oneTimeCode.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/oneTimeCode.ts @@ -2,12 +2,9 @@ import { Router } from "express"; import { HttpError } from 'wasp/server'; import { handleRejection } from 'wasp/server/utils' -import { createJWT, validateJWT, TimeSpan } from 'wasp/auth/jwt' import { findAuthWithUserBy } from 'wasp/auth/utils' import { createSession } from 'wasp/auth/session' -import { exchangeCodeForTokenPath } from "./redirect.js"; - -export const tokenStore = createTokenStore(); +import { exchangeCodeForTokenPath, tokenStore } from "wasp/server/auth"; export function setupOneTimeCodeRoute(router: Router) { router.post( @@ -40,50 +37,3 @@ export function setupOneTimeCodeRoute(router: Router) { }) ); } - -function createTokenStore() { - const usedTokens = new Map(); - - const validFor = new TimeSpan(1, 'm') // 1 minute - const cleanupAfter = 1000 * 60 * 60; // 1 hour - - function createToken(userId: string): Promise { - return createJWT( - { - id: userId, - }, - { - expiresIn: validFor, - } - ); - } - - function verifyToken(token: string): Promise<{ id: string }> { - return validateJWT(token); - } - - function isUsed(token: string): boolean { - return usedTokens.has(token); - } - - function markUsed(token: string): void { - usedTokens.set(token, Date.now()); - cleanUp(); - } - - function cleanUp(): void { - const now = Date.now(); - for (const [token, timestamp] of usedTokens.entries()) { - if (now - timestamp > cleanupAfter) { - usedTokens.delete(token); - } - } - } - - return { - createToken, - verifyToken, - isUsed, - markUsed, - }; -} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts index e1958d0510..5733fb8943 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts @@ -11,8 +11,8 @@ import { 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 { type OAuthData } from 'wasp/server/auth' +import { getRedirectUriForOneTimeCode, tokenStore } from 'wasp/server/auth' import { onBeforeSignupHook, onAfterSignupHook, @@ -26,16 +26,14 @@ export async function finishOAuthFlowAndGetRedirectUri({ providerUserId, userSignupFields, req, - accessToken, - oAuthState, + oauth }: { provider: ProviderConfig; providerProfile: unknown; providerUserId: string; userSignupFields: UserSignupFields | undefined; req: ExpressRequest; - accessToken: string; - oAuthState: { state: string }; + oauth: OAuthData; }): Promise { const providerId = createProviderId(provider.id, providerUserId); @@ -44,8 +42,7 @@ export async function finishOAuthFlowAndGetRedirectUri({ providerProfile, userSignupFields, req, - accessToken, - oAuthState, + oauth, }); const oneTimeCode = await tokenStore.createToken(authId) @@ -60,15 +57,13 @@ async function getAuthIdFromProviderDetails({ providerProfile, userSignupFields, req, - accessToken, - oAuthState, + oauth, }: { providerId: ProviderId; providerProfile: any; userSignupFields: UserSignupFields | undefined; req: ExpressRequest; - accessToken: string; - oAuthState: { state: string }; + oauth: OAuthData; }): Promise<{= authEntityUpper =}['id']> { const existingAuthIdentity = await prisma.{= authIdentityEntityLower =}.findUnique({ where: { @@ -102,10 +97,7 @@ async function getAuthIdFromProviderDetails({ await onAfterLoginHook({ req, providerId, - oauth: { - accessToken, - uniqueRequestId: oAuthState.state, - }, + oauth, user: auth.user, }) @@ -131,10 +123,7 @@ async function getAuthIdFromProviderDetails({ req, providerId, user, - oauth: { - accessToken, - uniqueRequestId: oAuthState.state, - }, + oauth, }) return user.auth.id diff --git a/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts b/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts index 106de623ac..e45aecfae5 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts @@ -35,7 +35,11 @@ export default handleRejection(async (req, res) => { id: authIdentity.authId }) - await onBeforeLoginHook({ req, providerId }) + await onBeforeLoginHook({ + req, + providerId, + user: auth.user, + }) const session = await createSession(auth.id) diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/files.manifest b/waspc/e2e-test/test-outputs/waspComplexTest-golden/files.manifest index 6ea2d6db28..951d996bb5 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/files.manifest +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/files.manifest @@ -278,6 +278,24 @@ waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/hooks.js.map waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.d.ts waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js.map +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.d.ts +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js +waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js.map waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.d.ts waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js.map @@ -397,6 +415,12 @@ waspComplexTest/.wasp/out/sdk/wasp/server/_types/taggedEntities.ts waspComplexTest/.wasp/out/sdk/wasp/server/api/index.ts waspComplexTest/.wasp/out/sdk/wasp/server/auth/hooks.ts waspComplexTest/.wasp/out/sdk/wasp/server/auth/index.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/env.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/index.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/oneTimeCode.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/provider.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/providers/google.ts +waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/redirect.ts waspComplexTest/.wasp/out/sdk/wasp/server/auth/user.ts waspComplexTest/.wasp/out/sdk/wasp/server/config.ts waspComplexTest/.wasp/out/sdk/wasp/server/crud/index.ts @@ -445,10 +469,8 @@ waspComplexTest/.wasp/out/server/src/auth/providers/config/google.ts waspComplexTest/.wasp/out/server/src/auth/providers/index.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/config.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/cookies.ts -waspComplexTest/.wasp/out/server/src/auth/providers/oauth/env.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/handler.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/oneTimeCode.ts -waspComplexTest/.wasp/out/server/src/auth/providers/oauth/redirect.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/state.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/types.ts waspComplexTest/.wasp/out/server/src/auth/providers/oauth/user.ts diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums index caae341236..d935f01b32 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums @@ -487,7 +487,7 @@ "file", "../out/sdk/wasp/package.json" ], - "ffe30beba7b57b22ad6ca7c28fe2639b492bd3054117b36e9010a48d447b52a0" + "cb651813806e472b31ebc0691b88efd7361439781c6e10dc47363f15567294a3" ], [ [ @@ -529,14 +529,56 @@ "file", "../out/sdk/wasp/server/auth/hooks.ts" ], - "3105d318bcb86a6638f414a155aad29dcea3613f7f14c6c282b677b9333eac57" + "1112920618b85e793ed676fd2987cf1dde59820e9a3a58bc43208f86c752b3a2" ], [ [ "file", "../out/sdk/wasp/server/auth/index.ts" ], - "20277db8773191867bd7938215abdf4a6814f84ba38fd55dd36ae5ebbbbe0754" + "9f932aba0d2ca6d1d2ff6fd826d238da3719b7676e7b42e97430af6f86fd03e2" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/env.ts" + ], + "50416a8c284fdbb3952e21e1617da2270b99dba717023babc7c32f5b12a63a32" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/index.ts" + ], + "bc8cf4d3b89018a68704d30bc3183bbd58461c9db0796f603f3e13179d604630" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/oneTimeCode.ts" + ], + "0cee805003b69e48c01bb1e7bfefb4bc6d0fab504ada926f5c3de6f8a5505335" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/provider.ts" + ], + "2a2015d905a2ceddb45ff1034b283b242df153ddf2cabd05fddd4746365622df" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/providers/google.ts" + ], + "2a30b51f7be3a627d652e164669b01832345763597aa1e0badf7d321ccdc3bdb" + ], + [ + [ + "file", + "../out/sdk/wasp/server/auth/oauth/redirect.ts" + ], + "2fb01e01e8e5209c8b96b0f25f2fcab5fb30c0c8c3a228d65711f3ce11031942" ], [ [ @@ -837,7 +879,7 @@ "file", "server/package.json" ], - "23fbe60e2f89a7fb12a11bd11aef43e3ed713ffd49697ab40c1cbbf0b3c4c84d" + "204a25f8a86edd2b492f841d2314c510a8f450205620427bb52de7d3ebb1ea1c" ], [ [ @@ -879,7 +921,7 @@ "file", "server/src/auth/providers/config/google.ts" ], - "d53d1fb541e3ef3f2b2996731c4da7ef5db0568aed39f03f1de3f9b6d3d30471" + "b2ebe3b7789b4abfb66a4c206a189e8559627ab0ebcf8b9b812747a962c35cce" ], [ [ @@ -902,33 +944,19 @@ ], "7fb6658564e8af442d0f0b5874879e6788e91e89d98a8265621dd323135f5916" ], - [ - [ - "file", - "server/src/auth/providers/oauth/env.ts" - ], - "65c5ce3b4ead10faad600435654d2614a862281ce5177996599bc159c39bb551" - ], [ [ "file", "server/src/auth/providers/oauth/handler.ts" ], - "7ca4820825cb4cc445c9161c391f2aaefe7c0d072e84fd9569f8d607e2bf2a28" + "a8ce8d2b140a59e07d767cc640b9a2b0116e2dafc3ef98ae67fa7cdab3527416" ], [ [ "file", "server/src/auth/providers/oauth/oneTimeCode.ts" ], - "6a23d30c2d98e14f6e293ebf3bb9392d25a3eeb7f1ec98078440a11af0aa0c78" - ], - [ - [ - "file", - "server/src/auth/providers/oauth/redirect.ts" - ], - "099d0b7aee0eafd258f4e656f405816e3f0d26dcaa705d97d3c9044f67824146" + "c9bf9cb3ce635f8f53f2b176e4e9f8e39851ee1a2ba972221f3eaf1a0826fab9" ], [ [ @@ -949,7 +977,7 @@ "file", "server/src/auth/providers/oauth/user.ts" ], - "4ae56549dccf3fa5d75250ef9593e1cf65012bf48219895dc92df586e5d95a45" + "088acfe21e47d5a4a68faefa898c0174529e1b5a76dd86d32a0fce7d2a781f44" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json index 68f57c2ba9..208cc81108 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"4.16.2"},{"name":"prisma","version":"4.16.2"},{"name":"@tanstack/react-query","version":"^4.29.0"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~4.18.1"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"lodash.merge","version":"^4.6.2"},{"name":"react-router-dom","version":"^5.3.3"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^1.12.2"},{"name":"@types/express-serve-static-core","version":"^4.17.13"},{"name":"@types/react-router-dom","version":"^5.3.3"},{"name":"@stitches/react","version":"^1.2.8"},{"name":"@node-rs/argon2","version":"^1.8.3"},{"name":"lucia","version":"^3.0.1"},{"name":"oslo","version":"^1.1.2"},{"name":"@lucia-auth/adapter-prisma","version":"^4.0.0"},{"name":"@sendgrid/mail","version":"^7.7.0"},{"name":"uuid","version":"^9.0.0"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"}],"devDependencies":[{"name":"@tsconfig/node18","version":"latest"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"4.16.2"},{"name":"typescript","version":"^5.1.0"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"^4.29.0"},{"name":"axios","version":"^1.4.0"},{"name":"mitt","version":"3.0.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"react-router-dom","version":"^5.3.3"},{"name":"superjson","version":"^1.12.2"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@types/react-router-dom","version":"^5.3.3"},{"name":"@vitejs/plugin-react","version":"^4.2.1"},{"name":"dotenv","version":"^16.0.3"}]},"npmDepsForServer":{"dependencies":[{"name":"arctic","version":"^1.2.1"},{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"16.0.2"},{"name":"express","version":"~4.18.1"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"rate-limiter-flexible","version":"^2.4.1"},{"name":"superjson","version":"^1.12.2"}],"devDependencies":[{"name":"@tsconfig/node18","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^4.17.13"},{"name":"@types/express-serve-static-core","version":"^4.17.13"},{"name":"@types/node","version":"^18.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"},{"name":"standard","version":"^17.0.0"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"4.16.2"},{"name":"prisma","version":"4.16.2"},{"name":"@tanstack/react-query","version":"^4.29.0"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~4.18.1"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"lodash.merge","version":"^4.6.2"},{"name":"react-router-dom","version":"^5.3.3"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^1.12.2"},{"name":"@types/express-serve-static-core","version":"^4.17.13"},{"name":"@types/react-router-dom","version":"^5.3.3"},{"name":"@stitches/react","version":"^1.2.8"},{"name":"@node-rs/argon2","version":"^1.8.3"},{"name":"arctic","version":"^1.2.1"},{"name":"lucia","version":"^3.0.1"},{"name":"oslo","version":"^1.1.2"},{"name":"@lucia-auth/adapter-prisma","version":"^4.0.0"},{"name":"@sendgrid/mail","version":"^7.7.0"},{"name":"uuid","version":"^9.0.0"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"}],"devDependencies":[{"name":"@tsconfig/node18","version":"latest"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"4.16.2"},{"name":"typescript","version":"^5.1.0"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"^4.29.0"},{"name":"axios","version":"^1.4.0"},{"name":"mitt","version":"3.0.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"react-router-dom","version":"^5.3.3"},{"name":"superjson","version":"^1.12.2"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@types/react-router-dom","version":"^5.3.3"},{"name":"@vitejs/plugin-react","version":"^4.2.1"},{"name":"dotenv","version":"^16.0.3"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"16.0.2"},{"name":"express","version":"~4.18.1"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"rate-limiter-flexible","version":"^2.4.1"},{"name":"superjson","version":"^1.12.2"}],"devDependencies":[{"name":"@tsconfig/node18","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^4.17.13"},{"name":"@types/express-serve-static-core","version":"^4.17.13"},{"name":"@types/node","version":"^18.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"},{"name":"standard","version":"^17.0.0"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/hooks.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/hooks.d.ts index e8ab8850ae..866269e469 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/hooks.d.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/hooks.d.ts @@ -17,89 +17,88 @@ export type OnAfterLoginHook = (params: Expand) => void export type InternalAuthHookParams = { /** * Prisma instance that can be used to interact with the database. - */ + */ prisma: typeof prisma; }; type OnBeforeSignupHookParams = { /** * 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 OnAfterSignupHookParams = { /** * Provider ID object that contains the provider name and the provide user ID. - */ + */ providerId: ProviderId; /** * User object that was created during the signup process. - */ + */ user: Awaited>; - oauth?: { - /** - * Access token that was received during the OAuth flow. - */ - accessToken: string; - /** - * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string; - }; + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user signed up using OAuth. + */ + oauth?: OAuthData; /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest; } & InternalAuthHookParams; type OnBeforeOAuthRedirectHookParams = { /** * URL that the OAuth flow should redirect to. - */ + */ url: URL; /** * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string; + */ + oauth: Pick; /** * Request object that can be used to access the incoming request. - */ + */ 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>['user']; + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user logged in using OAuth. + */ + oauth?: OAuthData; /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest; } & InternalAuthHookParams; +export type OAuthData = { + /** + * Unique request ID that was generated during the OAuth flow. + */ + uniqueRequestId: string; +} & ({ + providerName: 'google'; + tokens: import('arctic').GoogleTokens; +} | never); export {}; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.d.ts index 54d5306997..d387a6172f 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.d.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.d.ts @@ -1,4 +1,5 @@ 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, OnBeforeLoginHook, OnAfterLoginHook, InternalAuthHookParams, } from './hooks.js'; +export type { OnBeforeSignupHook, OnAfterSignupHook, OnBeforeOAuthRedirectHook, OnBeforeLoginHook, OnAfterLoginHook, InternalAuthHookParams, OAuthData, } from './hooks.js'; +export * from './oauth/index.js'; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js index 66289fd100..7f489c9a62 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js @@ -1,4 +1,5 @@ export { defineUserSignupFields, } from '../../auth/providers/types.js'; export { createProviderId, sanitizeAndSerializeProviderData, updateAuthIdentityProviderData, deserializeAndSanitizeProviderData, findAuthIdentity, createUser, } from '../../auth/utils.js'; export { ensurePasswordIsPresent, ensureValidPassword, ensureTokenIsPresent, } from '../../auth/validation.js'; +export * from './oauth/index.js'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js.map index 279d2fda3a..4fad2ccaeb 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js.map +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GACvB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EACL,gBAAgB,EAChB,gCAAgC,EAChC,8BAA8B,EAC9B,kCAAkC,EAClC,gBAAgB,EAChB,UAAU,GAMX,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GACvB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EACL,gBAAgB,EAChB,gCAAgC,EAChC,8BAA8B,EAC9B,kCAAkC,EAClC,gBAAgB,EAChB,UAAU,GAMX,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAA;AAYjC,cAAc,kBAAkB,CAAA"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.d.ts new file mode 100644 index 0000000000..08d2627b88 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.d.ts @@ -0,0 +1 @@ +export declare function ensureEnvVarsForProvider(envVarNames: EnvVarName[], providerName: string): Record; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js new file mode 100644 index 0000000000..56281b5e36 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js @@ -0,0 +1,13 @@ +// PRIVATE API (SDK) +export function ensureEnvVarsForProvider(envVarNames, providerName) { + const result = {}; + for (const envVarName of envVarNames) { + const value = process.env[envVarName]; + if (!value) { + throw new Error(`${envVarName} env variable is required when using the ${providerName} auth provider.`); + } + result[envVarName] = value; + } + return result; +} +//# sourceMappingURL=env.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js.map new file mode 100644 index 0000000000..b25f2f9870 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/env.js.map @@ -0,0 +1 @@ +{"version":3,"file":"env.js","sourceRoot":"","sources":["../../../../server/auth/oauth/env.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,MAAM,UAAU,wBAAwB,CACtC,WAAyB,EACzB,YAAoB;IAEpB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,4CAA4C,YAAY,iBAAiB,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;IAC7B,CAAC;IACD,OAAO,MAAoC,CAAC;AAC9C,CAAC"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.d.ts new file mode 100644 index 0000000000..8e9db44404 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.d.ts @@ -0,0 +1,3 @@ +export { google } from './providers/google.js'; +export { loginPath, callbackPath, exchangeCodeForTokenPath, handleOAuthErrorAndGetRedirectUri, getRedirectUriForOneTimeCode, } from './redirect.js'; +export { tokenStore, } from './oneTimeCode.js'; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js new file mode 100644 index 0000000000..64a91e37b4 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js @@ -0,0 +1,7 @@ +// PUBLIC API +export { google } from './providers/google.js'; +// PRIVATE API +export { loginPath, callbackPath, exchangeCodeForTokenPath, handleOAuthErrorAndGetRedirectUri, getRedirectUriForOneTimeCode, } from './redirect.js'; +// PRIVATE API +export { tokenStore, } from './oneTimeCode.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js.map new file mode 100644 index 0000000000..0e31443824 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../server/auth/oauth/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,cAAc;AACd,OAAO,EACL,SAAS,EACT,YAAY,EACZ,wBAAwB,EACxB,iCAAiC,EACjC,4BAA4B,GAC7B,MAAM,eAAe,CAAA;AAEtB,cAAc;AACd,OAAO,EACL,UAAU,GACX,MAAM,kBAAkB,CAAA"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.d.ts new file mode 100644 index 0000000000..6d3404f4a3 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.d.ts @@ -0,0 +1,8 @@ +export declare const tokenStore: { + createToken: (userId: string) => Promise; + verifyToken: (token: string) => Promise<{ + id: string; + }>; + isUsed: (token: string) => boolean; + markUsed: (token: string) => void; +}; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js new file mode 100644 index 0000000000..202551ec8a --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js @@ -0,0 +1,39 @@ +import { createJWT, validateJWT, TimeSpan } from '../../../auth/jwt.js'; +export const tokenStore = createTokenStore(); +function createTokenStore() { + const usedTokens = new Map(); + const validFor = new TimeSpan(1, 'm'); // 1 minute + const cleanupAfter = 1000 * 60 * 60; // 1 hour + function createToken(userId) { + return createJWT({ + id: userId, + }, { + expiresIn: validFor, + }); + } + function verifyToken(token) { + return validateJWT(token); + } + function isUsed(token) { + return usedTokens.has(token); + } + function markUsed(token) { + usedTokens.set(token, Date.now()); + cleanUp(); + } + function cleanUp() { + const now = Date.now(); + for (const [token, timestamp] of usedTokens.entries()) { + if (now - timestamp > cleanupAfter) { + usedTokens.delete(token); + } + } + } + return { + createToken, + verifyToken, + isUsed, + markUsed, + }; +} +//# sourceMappingURL=oneTimeCode.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js.map new file mode 100644 index 0000000000..9eea0cae38 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/oneTimeCode.js.map @@ -0,0 +1 @@ +{"version":3,"file":"oneTimeCode.js","sourceRoot":"","sources":["../../../../server/auth/oauth/oneTimeCode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAEvE,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;AAE7C,SAAS,gBAAgB;IACvB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE7C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA,CAAC,WAAW;IACjD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS;IAE9C,SAAS,WAAW,CAAC,MAAc;QACjC,OAAO,SAAS,CACd;YACE,EAAE,EAAE,MAAM;SACX,EACD;YACE,SAAS,EAAE,QAAQ;SACpB,CACF,CAAC;IACJ,CAAC;IAED,SAAS,WAAW,CAAC,KAAa;QAChC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,SAAS,MAAM,CAAC,KAAa;QAC3B,OAAO,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,SAAS,QAAQ,CAAC,KAAa;QAC7B,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,SAAS,OAAO;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,IAAI,GAAG,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;gBACnC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,WAAW;QACX,WAAW;QACX,MAAM;QACN,QAAQ;KACT,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.d.ts new file mode 100644 index 0000000000..44d23b4952 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.d.ts @@ -0,0 +1,12 @@ +import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic"; +export declare function defineProvider>({ id, displayName, env, oAuthClient, }: { + id: string; + displayName: string; + env: Env; + oAuthClient: OAuthClient; +}): { + id: string; + displayName: string; + env: Env; + oAuthClient: OAuthClient; +}; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js new file mode 100644 index 0000000000..bc0d9a2fd8 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js @@ -0,0 +1,9 @@ +export function defineProvider({ id, displayName, env, oAuthClient, }) { + return { + id, + displayName, + env, + oAuthClient, + }; +} +//# sourceMappingURL=provider.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js.map new file mode 100644 index 0000000000..02fe380099 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/provider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../server/auth/oauth/provider.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAG5B,EACA,EAAE,EACF,WAAW,EACX,GAAG,EACH,WAAW,GAMZ;IACC,OAAO;QACL,EAAE;QACF,WAAW;QACX,GAAG;QACH,WAAW;KACZ,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.d.ts new file mode 100644 index 0000000000..7910449649 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.d.ts @@ -0,0 +1,7 @@ +import { Google } from "arctic"; +export declare const google: { + id: string; + displayName: string; + env: Record<"GOOGLE_CLIENT_ID" | "GOOGLE_CLIENT_SECRET", string>; + oAuthClient: Google; +}; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js new file mode 100644 index 0000000000..7a49678dec --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js @@ -0,0 +1,16 @@ +import { Google } from "arctic"; +import { ensureEnvVarsForProvider } from "../env.js"; +import { getRedirectUriForCallback } from "../redirect.js"; +import { defineProvider } from "../provider.js"; +const id = "google"; +const displayName = "Google"; +const env = ensureEnvVarsForProvider(["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"], displayName); +const oAuthClient = new Google(env.GOOGLE_CLIENT_ID, env.GOOGLE_CLIENT_SECRET, getRedirectUriForCallback(id).toString()); +// PUBLIC API +export const google = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); +//# sourceMappingURL=google.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js.map new file mode 100644 index 0000000000..1bebbd38aa --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/providers/google.js.map @@ -0,0 +1 @@ +{"version":3,"file":"google.js","sourceRoot":"","sources":["../../../../../server/auth/oauth/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAG,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,EAAE,GAAG,QAAQ,CAAC;AACpB,MAAM,WAAW,GAAG,QAAQ,CAAC;AAE7B,MAAM,GAAG,GAAG,wBAAwB,CAClC,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,EAC5C,WAAW,CACZ,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,MAAM,CAC5B,GAAG,CAAC,gBAAgB,EACpB,GAAG,CAAC,oBAAoB,EACxB,yBAAyB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CACzC,CAAC;AAEF,aAAa;AACb,MAAM,CAAC,MAAM,MAAM,GAAG,cAAc,CAAC;IACnC,EAAE;IACF,WAAW;IACX,GAAG;IACH,WAAW;CACZ,CAAC,CAAC"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.d.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.d.ts new file mode 100644 index 0000000000..cb65431704 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.d.ts @@ -0,0 +1,6 @@ +export declare const loginPath = "login"; +export declare const exchangeCodeForTokenPath = "exchange-code"; +export declare const callbackPath = "callback"; +export declare function getRedirectUriForOneTimeCode(oneTimeCode: string): URL; +export declare function handleOAuthErrorAndGetRedirectUri(error: unknown): URL; +export declare function getRedirectUriForCallback(providerName: string): URL; diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js new file mode 100644 index 0000000000..c228948ef6 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js @@ -0,0 +1,34 @@ +import { config, HttpError } from '../../index.js'; +// PRIVATE API (server) +export const loginPath = 'login'; +// PRIVATE API (server) +export const exchangeCodeForTokenPath = 'exchange-code'; +// PRIVATE API (server) +export const callbackPath = 'callback'; +const clientOAuthCallbackPath = '/oauth/callback'; +// PRIVATE API (server) +export function getRedirectUriForOneTimeCode(oneTimeCode) { + return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}#${oneTimeCode}`); +} +// PRIVATE API (server) +export function handleOAuthErrorAndGetRedirectUri(error) { + if (error instanceof HttpError) { + const errorMessage = isHttpErrorWithExtraMessage(error) + ? `${error.message}: ${error.data.message}` + : error.message; + return getRedirectUriForError(errorMessage); + } + console.error("Unknown OAuth error:", error); + return getRedirectUriForError("An unknown error occurred while trying to log in with the OAuth provider."); +} +// PRIVATE API (SDK) +export function getRedirectUriForCallback(providerName) { + return new URL(`${config.serverUrl}/auth/${providerName}/${callbackPath}`); +} +function getRedirectUriForError(error) { + return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}?error=${error}`); +} +function isHttpErrorWithExtraMessage(error) { + return error.data && typeof error.data.message === 'string'; +} +//# sourceMappingURL=redirect.js.map \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js.map b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js.map new file mode 100644 index 0000000000..d8e07016ca --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/oauth/redirect.js.map @@ -0,0 +1 @@ +{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../../../server/auth/oauth/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAElD,uBAAuB;AACvB,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAA;AAEhC,uBAAuB;AACvB,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAA;AAEvD,uBAAuB;AACvB,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAA;AAEtC,MAAM,uBAAuB,GAAG,iBAAiB,CAAA;AAEjD,uBAAuB;AACvB,MAAM,UAAU,4BAA4B,CAAC,WAAmB;IAC9D,OAAO,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,GAAG,uBAAuB,IAAI,WAAW,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,iCAAiC,CAAC,KAAc;IAC9D,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,2BAA2B,CAAC,KAAK,CAAC;YACrD,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE;YAC3C,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;QAClB,OAAO,sBAAsB,CAAC,YAAY,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC7C,OAAO,sBAAsB,CAAC,2EAA2E,CAAC,CAAC;AAC7G,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,yBAAyB,CAAC,YAAoB;IAC5D,OAAO,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,SAAS,YAAY,IAAI,YAAY,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,OAAO,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,GAAG,uBAAuB,UAAU,KAAK,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAgB;IACnD,OAAO,KAAK,CAAC,IAAI,IAAI,OAAQ,KAAK,CAAC,IAAY,CAAC,OAAO,KAAK,QAAQ,CAAC;AACvE,CAAC"} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/package.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/package.json index a599b9c91e..01bd69e2b5 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/package.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/package.json @@ -11,6 +11,7 @@ "@types/express-serve-static-core": "^4.17.13", "@types/react-router-dom": "^5.3.3", "@vitest/ui": "^1.2.1", + "arctic": "^1.2.1", "axios": "^1.4.0", "express": "~4.18.1", "jsdom": "^21.1.1", diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/hooks.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/hooks.ts index a3754e7f6a..af64f6df9d 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/hooks.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/hooks.ts @@ -35,7 +35,7 @@ export type OnAfterLoginHook = ( export type InternalAuthHookParams = { /** * Prisma instance that can be used to interact with the database. - */ + */ prisma: typeof prisma } @@ -48,86 +48,87 @@ export type InternalAuthHookParams = { type OnBeforeSignupHookParams = { /** * 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 OnAfterSignupHookParams = { /** * Provider ID object that contains the provider name and the provide user ID. - */ + */ providerId: ProviderId /** * User object that was created during the signup process. - */ + */ user: Awaited> - oauth?: { - /** - * Access token that was received during the OAuth flow. - */ - accessToken: string - /** - * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string - }, + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user signed up using OAuth. + */ + oauth?: OAuthData /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest } & InternalAuthHookParams type OnBeforeOAuthRedirectHookParams = { /** * URL that the OAuth flow should redirect to. - */ + */ url: URL /** * Unique request ID that was generated during the OAuth flow. - */ - uniqueRequestId: string + */ + oauth: Pick /** * Request object that can be used to access the incoming request. - */ + */ 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>['user'] + /** + * OAuth flow data that was generated during the OAuth flow. This is only + * available if the user logged in using OAuth. + */ + oauth?: OAuthData /** * Request object that can be used to access the incoming request. - */ + */ req: ExpressRequest } & InternalAuthHookParams + +// PUBLIC API +export type OAuthData = { + /** + * Unique request ID that was generated during the OAuth flow. + */ + uniqueRequestId: string +} & ( + | { providerName: 'google'; tokens: import('arctic').GoogleTokens } + | never +) diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/index.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/index.ts index 15b3c36b07..117225dae7 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/index.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/index.ts @@ -29,6 +29,9 @@ export type { OnBeforeLoginHook, OnAfterLoginHook, InternalAuthHookParams, + OAuthData, } from './hooks.js' +export * from './oauth/index.js' + diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/env.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/env.ts similarity index 74% rename from waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/env.ts rename to waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/env.ts index 24776c6dd3..ada2452b8e 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/env.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/env.ts @@ -1,14 +1,13 @@ -import { type ProviderConfig } from "wasp/auth/providers/types"; - +// PRIVATE API (SDK) export function ensureEnvVarsForProvider( envVarNames: EnvVarName[], - provider: ProviderConfig, + providerName: string, ): Record { const result: Record = {}; for (const envVarName of envVarNames) { const value = process.env[envVarName]; if (!value) { - throw new Error(`${envVarName} env variable is required when using the ${provider.displayName} auth provider.`); + throw new Error(`${envVarName} env variable is required when using the ${providerName} auth provider.`); } result[envVarName] = value; } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/index.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/index.ts new file mode 100644 index 0000000000..a63e4264fb --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/index.ts @@ -0,0 +1,16 @@ +// PUBLIC API +export { google } from './providers/google.js'; + +// PRIVATE API +export { + loginPath, + callbackPath, + exchangeCodeForTokenPath, + handleOAuthErrorAndGetRedirectUri, + getRedirectUriForOneTimeCode, +} from './redirect.js' + +// PRIVATE API +export { + tokenStore, +} from './oneTimeCode.js' diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/oneTimeCode.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/oneTimeCode.ts new file mode 100644 index 0000000000..4f2d8ebb08 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/oneTimeCode.ts @@ -0,0 +1,50 @@ +import { createJWT, validateJWT, TimeSpan } from '../../../auth/jwt.js' + +export const tokenStore = createTokenStore(); + +function createTokenStore() { + const usedTokens = new Map(); + + const validFor = new TimeSpan(1, 'm') // 1 minute + const cleanupAfter = 1000 * 60 * 60; // 1 hour + + function createToken(userId: string): Promise { + return createJWT( + { + id: userId, + }, + { + expiresIn: validFor, + } + ); + } + + function verifyToken(token: string): Promise<{ id: string }> { + return validateJWT(token); + } + + function isUsed(token: string): boolean { + return usedTokens.has(token); + } + + function markUsed(token: string): void { + usedTokens.set(token, Date.now()); + cleanUp(); + } + + function cleanUp(): void { + const now = Date.now(); + for (const [token, timestamp] of usedTokens.entries()) { + if (now - timestamp > cleanupAfter) { + usedTokens.delete(token); + } + } + } + + return { + createToken, + verifyToken, + isUsed, + markUsed, + }; +} diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/provider.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/provider.ts new file mode 100644 index 0000000000..c2aee70897 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/provider.ts @@ -0,0 +1,23 @@ +import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic"; + +export function defineProvider< + OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE, + Env extends Record +>({ + id, + displayName, + env, + oAuthClient, +}: { + id: string; + displayName: string; + env: Env; + oAuthClient: OAuthClient; +}) { + return { + id, + displayName, + env, + oAuthClient, + }; +} diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/providers/google.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/providers/google.ts new file mode 100644 index 0000000000..9f6aaf216c --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/providers/google.ts @@ -0,0 +1,27 @@ +import { Google } from "arctic"; + +import { ensureEnvVarsForProvider } from "../env.js"; +import { getRedirectUriForCallback } from "../redirect.js"; +import { defineProvider } from "../provider.js"; + +const id = "google"; +const displayName = "Google"; + +const env = ensureEnvVarsForProvider( + ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"], + displayName, +); + +const oAuthClient = new Google( + env.GOOGLE_CLIENT_ID, + env.GOOGLE_CLIENT_SECRET, + getRedirectUriForCallback(id).toString(), +); + +// PUBLIC API +export const google = defineProvider({ + id, + displayName, + env, + oAuthClient, +}); diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/redirect.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/redirect.ts similarity index 86% rename from waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/redirect.ts rename to waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/redirect.ts index be677f0ff6..1eac920af7 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/redirect.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/server/auth/oauth/redirect.ts @@ -1,19 +1,22 @@ -import { config } from 'wasp/server' -import { HttpError } from 'wasp/server' +import { config, HttpError } from '../../index.js' +// PRIVATE API (server) export const loginPath = 'login' -export const callbackPath = 'callback' + +// PRIVATE API (server) export const exchangeCodeForTokenPath = 'exchange-code' -const clientOAuthCallbackPath = '/oauth/callback' -export function getRedirectUriForCallback(providerName: string): URL { - return new URL(`${config.serverUrl}/auth/${providerName}/${callbackPath}`); -} +// PRIVATE API (server) +export const callbackPath = 'callback' +const clientOAuthCallbackPath = '/oauth/callback' + +// PRIVATE API (server) export function getRedirectUriForOneTimeCode(oneTimeCode: string): URL { return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}#${oneTimeCode}`); } +// PRIVATE API (server) export function handleOAuthErrorAndGetRedirectUri(error: unknown): URL { if (error instanceof HttpError) { const errorMessage = isHttpErrorWithExtraMessage(error) @@ -25,6 +28,11 @@ export function handleOAuthErrorAndGetRedirectUri(error: unknown): URL { return getRedirectUriForError("An unknown error occurred while trying to log in with the OAuth provider."); } +// PRIVATE API (SDK) +export function getRedirectUriForCallback(providerName: string): URL { + return new URL(`${config.serverUrl}/auth/${providerName}/${callbackPath}`); +} + function getRedirectUriForError(error: string): URL { return new URL(`${config.frontendUrl}${clientOAuthCallbackPath}?error=${error}`); } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/package.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/package.json index c857d727d6..3a4ce1d6b9 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/package.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/package.json @@ -1,7 +1,6 @@ { "comment-filip": "The server.js location changed because we have now included client source files above .wasp/out/server/src.", "dependencies": { - "arctic": "^1.2.1", "cookie-parser": "~1.4.6", "cors": "^2.8.5", "dotenv": "16.0.2", diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/config/google.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/config/google.ts index dae5b818a6..3358c5ada4 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/config/google.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/config/google.ts @@ -1,8 +1,6 @@ -import { Google } from "arctic"; - import type { ProviderConfig } from "wasp/auth/providers/types"; -import { getRedirectUriForCallback } from "../oauth/redirect.js"; -import { ensureEnvVarsForProvider } from "../oauth/env.js"; +import { google } from "wasp/server/auth"; + import { mergeDefaultAndUserConfig } from "../oauth/config.js"; import { createOAuthProviderRouter } from "../oauth/handler.js"; @@ -10,20 +8,9 @@ const _waspUserSignupFields = undefined const _waspUserDefinedConfigFn = undefined const _waspConfig: ProviderConfig = { - id: "google", - displayName: "Google", + id: google.id, + displayName: google.displayName, createRouter(provider) { - const env = ensureEnvVarsForProvider( - ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"], - provider - ); - - const google = new Google( - env.GOOGLE_CLIENT_ID, - env.GOOGLE_CLIENT_SECRET, - getRedirectUriForCallback(provider.id).toString(), - ); - const config = mergeDefaultAndUserConfig({ scopes: ['profile'], }, _waspUserDefinedConfigFn); @@ -55,8 +42,8 @@ const _waspConfig: ProviderConfig = { provider, oAuthType: 'OAuth2WithPKCE', userSignupFields: _waspUserSignupFields, - getAuthorizationUrl: ({ state, codeVerifier }) => google.createAuthorizationURL(state, codeVerifier, config), - getProviderTokens: ({ code, codeVerifier }) => google.validateAuthorizationCode(code, codeVerifier), + getAuthorizationUrl: ({ state, codeVerifier }) => google.oAuthClient.createAuthorizationURL(state, codeVerifier, config), + getProviderTokens: ({ code, codeVerifier }) => google.oAuthClient.validateAuthorizationCode(code, codeVerifier), getProviderInfo: ({ accessToken }) => getGoogleProfile(accessToken), }); }, diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/handler.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/handler.ts index 869052d7ac..ec02cf5a67 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/handler.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/handler.ts @@ -6,7 +6,6 @@ import { type UserSignupFields, type ProviderConfig, } from 'wasp/auth/providers/types' - import { type OAuthType, type OAuthStateFor, @@ -19,10 +18,11 @@ import { callbackPath, loginPath, handleOAuthErrorAndGetRedirectUri, -} from './redirect.js' +} from 'wasp/server/auth' +import { OAuthData } from 'wasp/server/auth' import { onBeforeOAuthRedirectHook } from '../../hooks.js' -export function createOAuthProviderRouter({ +export function createOAuthProviderRouter({ provider, oAuthType, userSignupFields, @@ -51,14 +51,12 @@ export function createOAuthProviderRouter({ */ getProviderTokens: ( oAuthState: OAuthStateWithCodeFor, - ) => Promise<{ - accessToken: string - }> + ) => Promise /* The function that returns the user's profile and ID using the access token. */ - getProviderInfo: ({ accessToken }: { accessToken: string }) => Promise<{ + getProviderInfo: (tokens: Tokens) => Promise<{ providerUserId: string providerProfile: unknown }> @@ -77,7 +75,7 @@ export function createOAuthProviderRouter({ const { url: redirectUrlAfterHook } = await onBeforeOAuthRedirectHook({ req, url: redirectUrl, - uniqueRequestId: oAuthState.state, + oauth: { uniqueRequestId: oAuthState.state } }) return redirect(res, redirectUrlAfterHook.toString()) }), @@ -92,11 +90,9 @@ export function createOAuthProviderRouter({ provider, req, }) - const { accessToken } = await getProviderTokens(oAuthState) + const tokens = await getProviderTokens(oAuthState) - const { providerProfile, providerUserId } = await getProviderInfo({ - accessToken, - }) + const { providerProfile, providerUserId } = await getProviderInfo(tokens) try { const redirectUri = await finishOAuthFlowAndGetRedirectUri({ provider, @@ -104,8 +100,17 @@ export function createOAuthProviderRouter({ providerUserId, userSignupFields, req, - accessToken, - oAuthState, + oauth: { + uniqueRequestId: oAuthState.state, + // OAuth params are built as a discriminated union + // of provider names and their respective tokens. + // We are using a generic ProviderConfig and tokens type + // is inferred from the getProviderTokens function. + // Instead of building complex TS machinery to ensure that + // the providerName and tokens match, we are using any here. + providerName: provider.id as any, + tokens, + }, }) // Redirect to the client with the one time code return redirect(res, redirectUri.toString()) diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/oneTimeCode.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/oneTimeCode.ts index 0b98a1d879..cf1410c10f 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/oneTimeCode.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/oneTimeCode.ts @@ -2,12 +2,9 @@ import { Router } from "express"; import { HttpError } from 'wasp/server'; import { handleRejection } from 'wasp/server/utils' -import { createJWT, validateJWT, TimeSpan } from 'wasp/auth/jwt' import { findAuthWithUserBy } from 'wasp/auth/utils' import { createSession } from 'wasp/auth/session' -import { exchangeCodeForTokenPath } from "./redirect.js"; - -export const tokenStore = createTokenStore(); +import { exchangeCodeForTokenPath, tokenStore } from "wasp/server/auth"; export function setupOneTimeCodeRoute(router: Router) { router.post( @@ -40,50 +37,3 @@ export function setupOneTimeCodeRoute(router: Router) { }) ); } - -function createTokenStore() { - const usedTokens = new Map(); - - const validFor = new TimeSpan(1, 'm') // 1 minute - const cleanupAfter = 1000 * 60 * 60; // 1 hour - - function createToken(userId: string): Promise { - return createJWT( - { - id: userId, - }, - { - expiresIn: validFor, - } - ); - } - - function verifyToken(token: string): Promise<{ id: string }> { - return validateJWT(token); - } - - function isUsed(token: string): boolean { - return usedTokens.has(token); - } - - function markUsed(token: string): void { - usedTokens.set(token, Date.now()); - cleanUp(); - } - - function cleanUp(): void { - const now = Date.now(); - for (const [token, timestamp] of usedTokens.entries()) { - if (now - timestamp > cleanupAfter) { - usedTokens.delete(token); - } - } - } - - return { - createToken, - verifyToken, - isUsed, - markUsed, - }; -} diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/user.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/user.ts index ac7abc0121..88c2c33eac 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/user.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/providers/oauth/user.ts @@ -10,8 +10,8 @@ import { 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 { type OAuthData } from 'wasp/server/auth' +import { getRedirectUriForOneTimeCode, tokenStore } from 'wasp/server/auth' import { onBeforeSignupHook, onAfterSignupHook, @@ -25,16 +25,14 @@ export async function finishOAuthFlowAndGetRedirectUri({ providerUserId, userSignupFields, req, - accessToken, - oAuthState, + oauth }: { provider: ProviderConfig; providerProfile: unknown; providerUserId: string; userSignupFields: UserSignupFields | undefined; req: ExpressRequest; - accessToken: string; - oAuthState: { state: string }; + oauth: OAuthData; }): Promise { const providerId = createProviderId(provider.id, providerUserId); @@ -43,8 +41,7 @@ export async function finishOAuthFlowAndGetRedirectUri({ providerProfile, userSignupFields, req, - accessToken, - oAuthState, + oauth, }); const oneTimeCode = await tokenStore.createToken(authId) @@ -59,15 +56,13 @@ async function getAuthIdFromProviderDetails({ providerProfile, userSignupFields, req, - accessToken, - oAuthState, + oauth, }: { providerId: ProviderId; providerProfile: any; userSignupFields: UserSignupFields | undefined; req: ExpressRequest; - accessToken: string; - oAuthState: { state: string }; + oauth: OAuthData; }): Promise { const existingAuthIdentity = await prisma.authIdentity.findUnique({ where: { @@ -101,10 +96,7 @@ async function getAuthIdFromProviderDetails({ await onAfterLoginHook({ req, providerId, - oauth: { - accessToken, - uniqueRequestId: oAuthState.state, - }, + oauth, user: auth.user, }) @@ -130,10 +122,7 @@ async function getAuthIdFromProviderDetails({ req, providerId, user, - oauth: { - accessToken, - uniqueRequestId: oAuthState.state, - }, + oauth, }) return user.auth.id diff --git a/waspc/examples/todoApp/package-lock.json b/waspc/examples/todoApp/package-lock.json index 4d72d07985..e944c46469 100644 --- a/waspc/examples/todoApp/package-lock.json +++ b/waspc/examples/todoApp/package-lock.json @@ -35,6 +35,7 @@ "@types/express-serve-static-core": "^4.17.13", "@types/react-router-dom": "^5.3.3", "@vitest/ui": "^1.2.1", + "arctic": "^1.2.1", "autoprefixer": "^10.4.13", "axios": "^1.4.0", "express": "~4.18.1", @@ -3011,6 +3012,501 @@ "node": ">= 8" } }, + "node_modules/arctic": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/arctic/-/arctic-1.9.2.tgz", + "integrity": "sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==", + "dependencies": { + "oslo": "1.2.0" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", + "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "1.7.0", + "@node-rs/argon2-android-arm64": "1.7.0", + "@node-rs/argon2-darwin-arm64": "1.7.0", + "@node-rs/argon2-darwin-x64": "1.7.0", + "@node-rs/argon2-freebsd-x64": "1.7.0", + "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", + "@node-rs/argon2-linux-arm64-gnu": "1.7.0", + "@node-rs/argon2-linux-arm64-musl": "1.7.0", + "@node-rs/argon2-linux-x64-gnu": "1.7.0", + "@node-rs/argon2-linux-x64-musl": "1.7.0", + "@node-rs/argon2-wasm32-wasi": "1.7.0", + "@node-rs/argon2-win32-arm64-msvc": "1.7.0", + "@node-rs/argon2-win32-ia32-msvc": "1.7.0", + "@node-rs/argon2-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", + "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", + "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", + "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", + "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0", + "@tybys/wasm-util": "^0.8.1", + "memfs-browser": "^3.4.13000" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-1.9.0.tgz", + "integrity": "sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig==", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/bcrypt-android-arm-eabi": "1.9.0", + "@node-rs/bcrypt-android-arm64": "1.9.0", + "@node-rs/bcrypt-darwin-arm64": "1.9.0", + "@node-rs/bcrypt-darwin-x64": "1.9.0", + "@node-rs/bcrypt-freebsd-x64": "1.9.0", + "@node-rs/bcrypt-linux-arm-gnueabihf": "1.9.0", + "@node-rs/bcrypt-linux-arm64-gnu": "1.9.0", + "@node-rs/bcrypt-linux-arm64-musl": "1.9.0", + "@node-rs/bcrypt-linux-x64-gnu": "1.9.0", + "@node-rs/bcrypt-linux-x64-musl": "1.9.0", + "@node-rs/bcrypt-wasm32-wasi": "1.9.0", + "@node-rs/bcrypt-win32-arm64-msvc": "1.9.0", + "@node-rs/bcrypt-win32-ia32-msvc": "1.9.0", + "@node-rs/bcrypt-win32-x64-msvc": "1.9.0" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-android-arm-eabi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm-eabi/-/bcrypt-android-arm-eabi-1.9.0.tgz", + "integrity": "sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-android-arm64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm64/-/bcrypt-android-arm64-1.9.0.tgz", + "integrity": "sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-darwin-arm64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-arm64/-/bcrypt-darwin-arm64-1.9.0.tgz", + "integrity": "sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-darwin-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-x64/-/bcrypt-darwin-x64-1.9.0.tgz", + "integrity": "sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-freebsd-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-freebsd-x64/-/bcrypt-freebsd-x64-1.9.0.tgz", + "integrity": "sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-linux-arm-gnueabihf": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm-gnueabihf/-/bcrypt-linux-arm-gnueabihf-1.9.0.tgz", + "integrity": "sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-linux-arm64-gnu": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-gnu/-/bcrypt-linux-arm64-gnu-1.9.0.tgz", + "integrity": "sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-linux-arm64-musl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-1.9.0.tgz", + "integrity": "sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-linux-x64-gnu": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-gnu/-/bcrypt-linux-x64-gnu-1.9.0.tgz", + "integrity": "sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-linux-x64-musl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-musl/-/bcrypt-linux-x64-musl-1.9.0.tgz", + "integrity": "sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-wasm32-wasi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-wasm32-wasi/-/bcrypt-wasm32-wasi-1.9.0.tgz", + "integrity": "sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0", + "@tybys/wasm-util": "^0.8.1", + "memfs-browser": "^3.4.13000" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-win32-arm64-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-arm64-msvc/-/bcrypt-win32-arm64-msvc-1.9.0.tgz", + "integrity": "sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-win32-ia32-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-ia32-msvc/-/bcrypt-win32-ia32-msvc-1.9.0.tgz", + "integrity": "sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/@node-rs/bcrypt-win32-x64-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-x64-msvc/-/bcrypt-win32-x64-msvc-1.9.0.tgz", + "integrity": "sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/arctic/node_modules/oslo": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.0.tgz", + "integrity": "sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==", + "dependencies": { + "@node-rs/argon2": "1.7.0", + "@node-rs/bcrypt": "1.9.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", diff --git a/waspc/examples/todoApp/src/auth/hooks.ts b/waspc/examples/todoApp/src/auth/hooks.ts index 2bbbe416cd..8e4e6612a2 100644 --- a/waspc/examples/todoApp/src/auth/hooks.ts +++ b/waspc/examples/todoApp/src/auth/hooks.ts @@ -25,7 +25,7 @@ export const onAfterSignup: OnAfterSignupHook = async (args) => { // If this is a OAuth signup, we have access token and uniqueRequestId if (args.oauth) { - log('accessToken', args.oauth.accessToken) + log('accessToken', args.oauth.tokens.accessToken) log('uniqueRequestId', args.oauth.uniqueRequestId) const id = args.oauth.uniqueRequestId const query = oAuthQueryStore.get(id) @@ -43,7 +43,7 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ( log('query params before oAuth redirect', args.req.query) // Saving query params for later use in onAfterSignup hook - const id = args.uniqueRequestId + const id = args.oauth.uniqueRequestId oAuthQueryStore.set(id, args.req.query) return { url: args.url } @@ -58,8 +58,8 @@ 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) + if (args.oauth && args.oauth.providerName === 'google') { + log('accessToken', args.oauth.tokens.accessToken) } } diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 3ea36a85f2..11dc7ac956 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -48,6 +48,7 @@ import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi) import Wasp.Generator.SdkGenerator.Server.JobGenerator (depsRequiredByJobs, genNewJobsApi) +import Wasp.Generator.SdkGenerator.Server.OAuthG (depsRequiredByOAuth) import qualified Wasp.Generator.SdkGenerator.Server.OperationsGenerator as ServerOpsGen import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) @@ -195,6 +196,7 @@ npmDepsForSdk spec = ("@types/react-router-dom", "^5.3.3") ] ++ depsRequiredForAuth spec + ++ depsRequiredByOAuth spec -- This must be installed in the SDK because it lists prisma/client as a dependency. -- Installing it inside .wasp/out/server/node_modules would also -- install prisma/client in the same folder, which would cause our diff --git a/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs index 72712c70d8..40283b5ca1 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs @@ -19,6 +19,7 @@ import Wasp.Generator.SdkGenerator.Auth.EmailAuthG (genEmailAuth) import Wasp.Generator.SdkGenerator.Auth.LocalAuthG (genLocalAuth) import Wasp.Generator.SdkGenerator.Auth.OAuthAuthG (genOAuthAuth) import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.Server.OAuthG (genOAuth) import Wasp.Generator.WebAppGenerator.Auth.Common (getOnAuthSucceededRedirectToOrDefault) import Wasp.Util ((<++>)) import qualified Wasp.Util as Util @@ -54,6 +55,7 @@ genAuth spec = genUtils auth, genProvidersTypes auth ] + <++> genOAuth auth <++> genIndexTs auth where maybeAuth = AS.App.auth $ snd $ getApp spec diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs index 9689736115..4ca826c216 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs @@ -26,7 +26,7 @@ genNewServerApi spec = sequence [ genAuthIndex auth, genAuthUser auth, - genFileCopy [relfile|server/auth/hooks.ts|] + genHooks auth ] <++> genAuthEmail auth <++> genAuthUsername auth @@ -40,7 +40,12 @@ genAuthIndex auth = [relfile|server/auth/index.ts|] tmplData where - tmplData = AuthProviders.getEnabledAuthProvidersJson auth + tmplData = + object + [ "enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth, + "isExternalAuthEnabled" .= isExternalAuthEnabled + ] + isExternalAuthEnabled = AS.Auth.isExternalAuthEnabled auth genAuthUser :: AS.Auth.Auth -> Generator FileDraft genAuthUser auth = @@ -61,6 +66,11 @@ genAuthUser auth = "enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth ] +genHooks :: AS.Auth.Auth -> Generator FileDraft +genHooks auth = return $ C.mkTmplFdWithData [relfile|server/auth/hooks.ts|] tmplData + where + tmplData = object ["enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth] + genAuthEmail :: AS.Auth.Auth -> Generator [FileDraft] genAuthEmail auth = if AS.Auth.isEmailAuthEnabled auth diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs new file mode 100644 index 0000000000..fb533e22f1 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs @@ -0,0 +1,103 @@ +module Wasp.Generator.SdkGenerator.Server.OAuthG + ( genOAuth, + depsRequiredByOAuth, + ) +where + +import Data.Aeson (KeyValue ((.=)), object) +import Data.Maybe (fromJust, isJust) +import StrongPath (Dir', File', Path', Rel, reldir, relfile, ()) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.App.Auth +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import qualified Wasp.AppSpec.App.Dependency as AS.Dependency +import qualified Wasp.AppSpec.Valid as AS.Valid +import Wasp.Generator.AuthProviders (discordAuthProvider, getEnabledAuthProvidersJson, gitHubAuthProvider, googleAuthProvider, keycloakAuthProvider) +import Wasp.Generator.AuthProviders.OAuth + ( OAuthAuthProvider, + clientOAuthCallbackPath, + serverExchangeCodeForTokenHandlerPath, + serverOAuthCallbackHandlerPath, + serverOAuthLoginHandlerPath, + ) +import qualified Wasp.Generator.AuthProviders.OAuth as OAuth +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util ((<++>)) + +genOAuth :: AS.Auth.Auth -> Generator [FileDraft] +genOAuth auth + | AS.Auth.isExternalAuthEnabled auth = + sequence + [ genIndexTs auth, + genRedirectHelper, + genFileCopy $ oauthDirInSdkTemplatesDir [relfile|env.ts|], + genFileCopy $ oauthDirInSdkTemplatesDir [relfile|oneTimeCode.ts|], + genFileCopy $ oauthDirInSdkTemplatesDir [relfile|provider.ts|] + ] + <++> genOAuthProvider discordAuthProvider (AS.Auth.discord . AS.Auth.methods $ auth) + <++> genOAuthProvider googleAuthProvider (AS.Auth.google . AS.Auth.methods $ auth) + <++> genOAuthProvider keycloakAuthProvider (AS.Auth.keycloak . AS.Auth.methods $ auth) + <++> genOAuthProvider gitHubAuthProvider (AS.Auth.gitHub . AS.Auth.methods $ auth) + | otherwise = return [] + where + genFileCopy = return . C.mkTmplFd + +genIndexTs :: AS.Auth.Auth -> Generator FileDraft +genIndexTs auth = return $ C.mkTmplFdWithData tmplFile tmplData + where + tmplFile = oauthDirInSdkTemplatesDir [relfile|index.ts|] + tmplData = + object + [ "enabledProviders" .= getEnabledAuthProvidersJson auth + ] + +genRedirectHelper :: Generator FileDraft +genRedirectHelper = return $ C.mkTmplFdWithData tmplFile tmplData + where + tmplFile = oauthDirInSdkTemplatesDir [relfile|redirect.ts|] + tmplData = + object + [ "serverOAuthCallbackHandlerPath" .= serverOAuthCallbackHandlerPath, + "clientOAuthCallbackPath" .= clientOAuthCallbackPath, + "serverOAuthLoginHandlerPath" .= serverOAuthLoginHandlerPath, + "serverExchangeCodeForTokenHandlerPath" .= serverExchangeCodeForTokenHandlerPath + ] + +genOAuthProvider :: + OAuthAuthProvider -> + Maybe AS.Auth.ExternalAuthConfig -> + Generator [FileDraft] +genOAuthProvider provider maybeUserConfig + | isJust maybeUserConfig = sequence [genOAuthConfig provider] + | otherwise = return [] + +genOAuthConfig :: + OAuthAuthProvider -> + Generator FileDraft +genOAuthConfig provider = return $ C.mkTmplFdWithData tmplFile tmplData + where + tmplFile = oauthDirInSdkTemplatesDir [reldir|providers|] providerTsFile + tmplData = + object + [ "providerId" .= OAuth.providerId provider, + "displayName" .= OAuth.displayName provider + ] + + providerTsFile :: Path' (Rel ()) File' + providerTsFile = fromJust $ SP.parseRelFile $ providerId ++ ".ts" + + providerId = OAuth.providerId provider + +depsRequiredByOAuth :: AppSpec -> [AS.Dependency.Dependency] +depsRequiredByOAuth spec = + [AS.Dependency.make ("arctic", "^1.2.1") | (AS.App.Auth.isExternalAuthEnabled <$> maybeAuth) == Just True] + where + maybeAuth = AS.App.auth $ snd $ AS.Valid.getApp spec + +oauthDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) Dir' +oauthDirInSdkTemplatesDir = [reldir|server/auth/oauth|] diff --git a/waspc/src/Wasp/Generator/ServerGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator.hs index 7705473b61..d35c0446eb 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator.hs @@ -44,7 +44,6 @@ import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.ServerGenerator.ApiRoutesG (genApis) -import Wasp.Generator.ServerGenerator.Auth.OAuthAuthG (depsRequiredByOAuth) import Wasp.Generator.ServerGenerator.AuthG (genAuth) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.CrudG (genCrud) @@ -153,7 +152,6 @@ npmDepsForWasp spec = ("rate-limiter-flexible", "^2.4.1"), ("superjson", "^1.12.2") ] - ++ depsRequiredByOAuth spec ++ depsRequiredByWebSockets spec, N.waspDevDependencies = AS.Dependency.fromList diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs index 789a2a03bf..a44da128ce 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs @@ -1,6 +1,5 @@ module Wasp.Generator.ServerGenerator.Auth.OAuthAuthG ( genOAuthAuth, - depsRequiredByOAuth, ) where @@ -19,26 +18,15 @@ import StrongPath (), ) import qualified StrongPath as SP -import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS -import qualified Wasp.AppSpec.App as AS.App -import qualified Wasp.AppSpec.App.Auth as AS.App.Auth import qualified Wasp.AppSpec.App.Auth as AS.Auth -import qualified Wasp.AppSpec.App.Dependency as App.Dependency -import Wasp.AppSpec.Valid (getApp) import Wasp.Generator.AuthProviders ( discordAuthProvider, gitHubAuthProvider, googleAuthProvider, keycloakAuthProvider, ) -import Wasp.Generator.AuthProviders.OAuth - ( OAuthAuthProvider, - clientOAuthCallbackPath, - serverExchangeCodeForTokenHandlerPath, - serverOAuthCallbackHandlerPath, - serverOAuthLoginHandlerPath, - ) +import Wasp.Generator.AuthProviders.OAuth (OAuthAuthProvider) import qualified Wasp.Generator.AuthProviders.OAuth as OAuth import qualified Wasp.Generator.DbGenerator.Auth as DbAuth import Wasp.Generator.FileDraft (FileDraft) @@ -64,11 +52,9 @@ genOAuthHelpers auth = sequence [ genTypes auth, genUser, - genRedirectHelpers, return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/handler.ts|], return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/state.ts|], return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/cookies.ts|], - return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/env.ts|], return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/config.ts|], return $ C.mkSrcTmplFd [relfile|auth/providers/oauth/oneTimeCode.ts|] ] @@ -85,18 +71,6 @@ genUser = return $ C.mkTmplFdWithData tmplFile (Just tmplData) "userFieldOnAuthEntityName" .= (DbAuth.userFieldOnAuthEntityName :: String) ] -genRedirectHelpers :: Generator FileDraft -genRedirectHelpers = return $ C.mkTmplFdWithData tmplFile (Just tmplData) - where - tmplFile = C.srcDirInServerTemplatesDir [relfile|auth/providers/oauth/redirect.ts|] - tmplData = - object - [ "clientOAuthCallbackPath" .= clientOAuthCallbackPath, - "serverOAuthLoginHandlerPath" .= serverOAuthLoginHandlerPath, - "serverOAuthCallbackHandlerPath" .= serverOAuthCallbackHandlerPath, - "serverExchangeCodeForTokenHandlerPath" .= serverExchangeCodeForTokenHandlerPath - ] - genTypes :: AS.Auth.Auth -> Generator FileDraft genTypes auth = return $ C.mkTmplFdWithData tmplFile (Just tmplData) where @@ -144,9 +118,3 @@ genOAuthConfig provider maybeUserConfig pathToConfigTmpl = return $ C.mkTmplFdWi relPathFromAuthConfigToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir) relPathFromAuthConfigToServerSrcDir = [reldirP|../../../|] - -depsRequiredByOAuth :: AppSpec -> [App.Dependency.Dependency] -depsRequiredByOAuth spec = - [App.Dependency.make ("arctic", "^1.2.1") | (AS.App.Auth.isExternalAuthEnabled <$> maybeAuth) == Just True] - where - maybeAuth = AS.App.auth $ snd $ getApp spec diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index ed91e620cf..2515e921d0 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -311,6 +311,7 @@ library Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers Wasp.Generator.SdkGenerator.Server.AuthG + Wasp.Generator.SdkGenerator.Server.OAuthG Wasp.Generator.SdkGenerator.Server.CrudG Wasp.Generator.SdkGenerator.Server.EmailSenderG Wasp.Generator.SdkGenerator.Server.JobGenerator diff --git a/web/docs/auth/auth-hooks.md b/web/docs/auth/auth-hooks.md index a5de3c0839..9106e988fd 100644 --- a/web/docs/auth/auth-hooks.md +++ b/web/docs/auth/auth-hooks.md @@ -4,12 +4,14 @@ title: Auth Hooks import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill } from "./Pills"; import ImgWithCaption from '@site/blog/components/ImgWithCaption' +import { ShowForTs } from '@site/src/components/TsJsHelpers' Auth hooks allow you to "hook into" the auth process at various stages and run your custom code. For example, if you want to forbid certain emails from signing up, or if you wish to send a welcome email to the user after they sign up, auth hooks are the way to go. ## Supported hooks 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) @@ -35,7 +37,6 @@ We'll go through each of these hooks in detail. But first, let's see how the hoo \* 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. - If you are using OAuth, the flow includes extra steps before the auth flow: @@ -90,6 +92,7 @@ app myApp { }, } ``` + @@ -123,11 +126,7 @@ app myApp { ```js title="src/auth/hooks.js" import { HttpError } from 'wasp/server' -export const onBeforeSignup = async ({ - providerId, - prisma, - req, -}) => { +export const onBeforeSignup = async ({ providerId, prisma, req }) => { const count = await prisma.user.count() console.log('number of users before', count) console.log('provider name', providerId.providerName) @@ -137,7 +136,10 @@ export const onBeforeSignup = async ({ throw new HttpError(403, 'Too many users') } - if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { throw new HttpError(403, 'This email is not allowed') } } @@ -174,7 +176,10 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({ throw new HttpError(403, 'Too many users') } - if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { throw new HttpError(403, 'This email is not allowed') } } @@ -191,7 +196,7 @@ Wasp calls the `onAfterSignup` hook after the user is created. The `onAfterSignup` hook can be useful if you want to send the user a welcome email or perform some other action after the user signs up like syncing the user with a third-party service. -Since the `onAfterSignup` hook receives the OAuth access token, it can also be used to store the OAuth access token for the user in your database. +Since the `onAfterSignup` hook receives the OAuth tokens, you can use this hook to store the OAuth access token and/or [refresh token](#refreshing-the-oauth-access-token) in your database. Works with @@ -220,9 +225,9 @@ export const onAfterSignup = async ({ console.log('number of users after', count) console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -262,9 +267,9 @@ export const onAfterSignup: OnAfterSignupHook = async ({ console.log('number of users after', count) console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -306,14 +311,14 @@ app myApp { ```js title="src/auth/hooks.js" export const onBeforeOAuthRedirect = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { console.log('query params before oAuth redirect', req.query) // Saving query params for later use in onAfterSignup or onAfterLogin hooks - const id = uniqueRequestId + const id = oauth.uniqueRequestId someKindOfStore.set(id, req.query) return { url } @@ -338,14 +343,14 @@ import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth' export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { console.log('query params before oAuth redirect', req.query) // Saving query params for later use in onAfterSignup or onAfterLogin hooks - const id = uniqueRequestId + const id = oauth.uniqueRequestId someKindOfStore.set(id, req.query) return { url } @@ -383,12 +388,11 @@ app myApp { ```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') { +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') } } @@ -416,7 +420,10 @@ export const onBeforeLogin: OnBeforeLoginHook = async ({ prisma, req, }) => { - if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { throw new HttpError(403, 'You cannot log in with this email') } } @@ -433,7 +440,7 @@ 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. +Since the `onAfterLogin` hook receives the OAuth tokens, you can use it to update the OAuth access token for the user in your database. You can also use it to [refresh the OAuth access token](#refreshing-the-oauth-access-token) if the provider supports it. Works with @@ -460,9 +467,9 @@ export const onAfterLogin = async ({ }) => { console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -500,9 +507,9 @@ export const onAfterLogin: OnAfterLoginHook = async ({ }) => { console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -520,6 +527,58 @@ export const onAfterLogin: OnAfterLoginHook = async ({ Read more about the data the `onAfterLogin` hook receives in the [API Reference](#the-onafterlogin-hook). +### Refreshing the OAuth access token + +Some OAuth providers support refreshing the access token when it expires. To refresh the access token, you need the OAuth **refresh token**. + +Wasp exposes the OAuth refresh token in the `onAfterSignup` and `onAfterLogin` hooks. You can store the refresh token in your database and use it to refresh the access token when it expires. + +Import the provider object with the OAuth client from the `wasp/server/oauth` module. For example, to refresh the Google OAuth access token, import the `google` object from the `wasp/server/oauth` module. You use the `refreshAccessToken` method of the OAuth client to refresh the access token. + +Here's an example of how you can refresh the access token for Google OAuth: + + + + + +```js title="src/auth/hooks.js" +import { google } from 'wasp/server/oauth' + +export const onAfterLogin = async ({ oauth }) => { + if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) { + const newTokens = await google.oAuthClient.refreshAccessToken( + oauth.tokens.refreshToken + ) + log('new tokens', newTokens) + } +} +``` + + + + + +```ts title="src/auth/hooks.ts" +import type { OnAfterLoginHook } from 'wasp/server/auth' +import { google } from 'wasp/server/oauth' + +export const onAfterLogin: OnAfterLoginHook = async ({ oauth }) => { + if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) { + const newTokens = await google.oAuthClient.refreshAccessToken( + oauth.tokens.refreshToken + ) + log('new tokens', newTokens) + } +} +``` + + + + +Google exposes the `accessTokenExpiresAt` field in the `oauth.tokens` object. You can use this field to determine when the access token expires. + +If you want to refresh the token periodically, use a [Wasp Job](../advanced/jobs.md). + ## API Reference @@ -543,6 +602,7 @@ app myApp { }, } ``` + @@ -564,6 +624,7 @@ app myApp { }, } ``` + @@ -585,11 +646,7 @@ The following properties are available in all auth hooks: ```js title="src/auth/hooks.js" -export const onBeforeSignup = async ({ - providerId, - prisma, - req, -}) => { +export const onBeforeSignup = async ({ providerId, prisma, req }) => { // Hook code goes here } ``` @@ -658,11 +715,12 @@ export const onAfterSignup: OnAfterSignupHook = async ({ The hook receives an object as **input** with the following properties: + - [`providerId: ProviderId`](#providerid-fields) - - `user: User` - + The user object that was created. + - [`oauth?: OAuthFields`](#oauth-fields) - Plus the [common hook input](#common-hook-input) @@ -677,7 +735,7 @@ Wasp ignores this hook's **return value**. ```js title="src/auth/hooks.js" export const onBeforeOAuthRedirect = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { @@ -695,7 +753,7 @@ import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth' export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { @@ -709,14 +767,21 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ The hook receives an object as **input** with the following properties: + - `url: URL` - Wasp uses the URL for the OAuth redirect. -- `uniqueRequestId: string` + Wasp uses the URL for the OAuth redirect. + +- `oauth: { uniqueRequestId: string }` + + The `oauth` object has the following fields: + + - `uniqueRequestId: string` + + The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) - 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` or `onAfterLogin` hooks. - 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. @@ -727,19 +792,16 @@ This hook's return value must be an object that looks like this: `{ url: URL }`. ```js title="src/auth/hooks.js" -export const onBeforeLogin = async ({ - providerId, - prisma, - req, -}) => { +export const onBeforeLogin = async ({ providerId, prisma, req }) => { // Hook code goes here } ``` + ```ts title="src/auth/hooks.ts" -import type { OnBeforeLoginHook } from 'wasp/server/auth' +import type { OnBeforeLoginHook } from 'wasp/server/auth' export const onBeforeLogin: OnBeforeLoginHook = async ({ providerId, @@ -749,10 +811,12 @@ export const onBeforeLogin: OnBeforeLoginHook = async ({ // Hook code goes here } ``` + The hook receives an object as **input** with the following properties: + - [`providerId: ProviderId`](#providerid-fields) - Plus the [common hook input](#common-hook-input) @@ -776,6 +840,7 @@ export const onAfterLogin = async ({ // Hook code goes here } ``` + @@ -792,10 +857,12 @@ export const onAfterLogin: OnAfterLoginHook = async ({ // Hook code goes here } ``` + The hook receives an object as **input** with the following properties: + - [`providerId: ProviderId`](#providerid-fields) - `user: User` @@ -828,9 +895,31 @@ Wasp passes the `oauth` object to the `onAfterSignup` and `onAfterLogin` hooks o It has the following fields: -- `accessToken: string` +- `providerName: string` + + The name of the OAuth provider the user authenticated with (e.g. `'google'`, `'github'`). + +- `tokens: Tokens` + + You can use the OAuth tokens to make requests to the provider's API on the user's behalf. + + Depending on the OAuth provider, the `tokens` object might have different fields. For example, Google has the fields `accessToken`, `refreshToken`, `idToken`, and `accessTokenExpiresAt`. + + + + To access the provider-specific fields, you must first narrow down the `oauth.tokens` object type to the specific OAuth provider type. + + ```ts + if (oauth && oauth.providerName === 'google') { + console.log(oauth.tokens.accessToken) + // ^ Google specific tokens are available here + console.log(oauth.tokens.refreshToken) + console.log(oauth.tokens.idToken) + console.log(oauth.tokens.accessTokenExpiresAt) + } + ``` - You can use the OAuth access token to make requests to the provider's API on the user's behalf. + - `uniqueRequestId: string`