Skip to content

Commit

Permalink
Merge branch 'main' into miho-prisma-5
Browse files Browse the repository at this point in the history
  • Loading branch information
infomiho committed Sep 2, 2024
2 parents 6e219aa + ccc652e commit 7383a72
Show file tree
Hide file tree
Showing 112 changed files with 3,583 additions and 736 deletions.
22 changes: 22 additions & 0 deletions waspc/ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 0.14.1 (2024-08-26)

### 🎉 New Features

- Wasp now supports `onBeforeLogin` and `onAfterLogin` auth hooks! You can use these hooks to run custom logic before and after a user logs in. For example, you can use the `onBeforeLogin` hook to check if the user is allowed to log in.
- OAuth refresh tokens are here. If the OAuth provider supports refresh tokens, you'll be able to use them to refresh the access token when it expires. This is useful for using OAuth provider APIs in the background e.g. accessing user's calendar events.

### ⚠️ Breaking Changes

- To make the API consistent across different auth hooks, we change how the `onBeforeOAuthRedirect` hook receives the `uniqueRequestId` value to `oauth.uniqueRequestId`.

### 🐞 Bug fixes

- Prisma file parser now allows using empty arrays as default values.

### 🔧 Small improvements

- Replace `oslo/password` with directly using `@node-rs/argon2`
- We now use `websocket` transport for the WebSocket client to avoid issues when deploying the server behind a load balancer.

Community contributions by @rubyisrust @santolucito @sezercik @LLxD!

## 0.14.0 (2024-07-17)

### 🎉 New Features
Expand Down
87 changes: 52 additions & 35 deletions waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -35,7 +36,7 @@ export type OnAfterLoginHook = (
export type InternalAuthHookParams = {
/**
* Prisma instance that can be used to interact with the database.
*/
*/
prisma: typeof prisma
}

Expand All @@ -48,86 +49,102 @@ 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<ReturnType<typeof createUser>>
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<OAuthData, 'uniqueRequestId'>
/**
* 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
/**
* User that is trying to log in.
*/
user: Awaited<ReturnType<typeof findAuthWithUserBy>>['user']
/**
* Request object that can be used to access the incoming request.
*/
*/
req: ExpressRequest
} & InternalAuthHookParams

type OnAfterLoginHookParams = {
/**
* Provider ID object that contains the provider name and the provide user ID.
*/
*/
providerId: ProviderId
oauth?: {
/**
* Access token that was received during the OAuth flow.
*/
accessToken: string
/**
* Unique request ID that was generated during the OAuth flow.
*/
uniqueRequestId: string
},
/**
* User that is logged in.
*/
*/
user: Awaited<ReturnType<typeof findAuthWithUserBy>>['user']
/**
* 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
)
13 changes: 9 additions & 4 deletions waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { type ProviderConfig } from "wasp/auth/providers/types";

// PRIVATE API (SDK)
export function ensureEnvVarsForProvider<EnvVarName extends string>(
envVarNames: EnvVarName[],
provider: ProviderConfig,
providerName: string,
): Record<EnvVarName, string> {
const result: Record<string, string> = {};
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;
}
Expand Down
31 changes: 31 additions & 0 deletions waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createJWT, validateJWT, TimeSpan } from '../../../auth/jwt.js'

export const tokenStore = createTokenStore();

function createTokenStore() {
const usedTokens = new Map<string, number>();

const validFor = new TimeSpan(1, 'm') // 1 minute
const cleanupAfter = 1000 * 60 * 60; // 1 hour

function createToken(userId: string): Promise<string> {
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,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic";

export function defineProvider<
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE,
Env extends Record<string, string>
>({
id,
displayName,
env,
oAuthClient,
}: {
id: string;
displayName: string;
env: Env;
oAuthClient: OAuthClient;
}) {
return {
id,
displayName,
env,
oAuthClient,
};
}
Original file line number Diff line number Diff line change
@@ -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,
});
Original file line number Diff line number Diff line change
@@ -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,
});
Loading

0 comments on commit 7383a72

Please sign in to comment.