Skip to content

Commit 7f0a0d5

Browse files
committed
Gate wasp-auth-only codegen behind the provider and expose the active provider identity
1 parent aa06e72 commit 7f0a0d5

25 files changed

Lines changed: 393 additions & 56 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{{={= =}=}}
2+
// PUBLIC API
3+
/**
4+
* The auth provider this app runs on, as a literal the type system narrows.
5+
*
6+
* Guard provider-specific code with it and TypeScript will tell you at compile
7+
* time when the app switches providers:
8+
*
9+
* ```ts
10+
* import { authProviderId } from 'wasp/auth/provider'
11+
* ```
12+
*/
13+
export const authProviderId = "{= providerId =}" as const;
14+
15+
// PUBLIC API
16+
export type AuthProviderId = typeof authProviderId;
17+
18+
// PUBLIC API
19+
/**
20+
* The capabilities the provider declared. An open set: adapters may declare
21+
* capabilities newer than this version of Wasp knows about.
22+
*/
23+
export const authCapabilities = {=& capabilities =} as const;

waspc/data/Generator/templates/sdk/wasp/client/auth/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{{={= =}=}}
2+
{=^ isExternalAuthProviderUsed =}
23
export * from './ui'
4+
{=/ isExternalAuthProviderUsed =}
35
{=# isEmailAuthEnabled =}
46
export * from './email'
57
{=/ isEmailAuthEnabled =}

waspc/data/Generator/templates/sdk/wasp/client/env/schema.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,30 @@ const serverUrlSchema =
2525
})
2626
)
2727

28+
{=# isExternalAuthProviderUsed =}
29+
{=! Env vars the external auth provider's manifest declared for the client. =}
30+
const externalAuthProviderEnvSchema = z.object({
31+
{=# externalAuthProviderClientEnvVars =}
32+
"{= name =}": z.string({
33+
error: {=& errorJson =},
34+
}){=# isOptional =}.optional(){=/ isOptional =},
35+
{=/ externalAuthProviderClientEnvVars =}
36+
});
37+
38+
{=/ isExternalAuthProviderUsed =}
2839
const waspDevClientEnvSchema = z.object({
2940
"{= serverUrlEnvVarName =}": serverUrlSchema
3041
.default("{= defaultServerUrl =}"),
42+
{=# isExternalAuthProviderUsed =}
43+
...externalAuthProviderEnvSchema.shape,
44+
{=/ isExternalAuthProviderUsed =}
3145
});
3246

3347
const waspProdClientEnvSchema = z.object({
3448
"{= serverUrlEnvVarName =}": serverUrlSchema,
49+
{=# isExternalAuthProviderUsed =}
50+
...externalAuthProviderEnvSchema.shape,
51+
{=/ isExternalAuthProviderUsed =}
3552
});
3653

3754
const waspClientEnvSchema = import.meta.env.MODE === "production"

waspc/data/Generator/templates/sdk/wasp/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
===============================================================
6464
=}
6565
"./auth": "./dist/auth/index.js",
66+
{=# isAuthEnabled =}
67+
{=! Users: which auth provider the app runs on, as narrowable literals. =}
68+
"./auth/provider": "./dist/auth/provider.js",
69+
{=/ isAuthEnabled =}
6670
{=! FIXME: Documented only in example apps, not in docs. Also re-exported through `wasp/server/auth`.=}
6771
{=! FIXME: Candidate for removal? =}
6872
{=! Users: validators in custom auth actions. =}
@@ -116,10 +120,14 @@
116120
Internal server runtime API. Undocumented.
117121
===============================================================
118122
=}
123+
{=! Wasp-auth-only modules. Not generated under an external auth provider,
124+
so their exports are dropped too and importing them is an error. =}
125+
{=^ isCustomAuthProviderUsed =}
119126
"./server/auth/email": "./dist/server/auth/email/index.js",
120127
"./server/auth/email/utils": "./dist/server/auth/email/utils.js",
121128
"./server/auth/jwt": "./dist/server/auth/jwt.js",
122129
"./server/auth/password": "./dist/server/auth/password.js",
130+
{=/ isCustomAuthProviderUsed =}
123131
{=! Server: the contract a custom auth provider implements. Adapters live in
124132
user code, so they need to import this as a normal module. =}
125133
"./server/auth/provider/types": "./dist/server/auth/provider/types.js",

waspc/data/Generator/templates/sdk/wasp/server/auth/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ export {
33
defineUserSignupFields,
44
} from '../../auth/providers/types.js'
55

6+
{=# isCustomAuthProviderUsed =}
7+
{=! Under an external provider, `wasp/server/auth` keeps only what is
8+
provider-independent: typing `userSignupFields` and the error helper the
9+
generated code itself uses. Everything password- and hook-shaped belongs
10+
to Wasp's own auth and is not generated at all. =}
11+
export { createInvalidCredentialsError } from './utils.js'
12+
{=/ isCustomAuthProviderUsed =}
13+
{=^ isCustomAuthProviderUsed =}
614
export {
715
createProviderId,
816
sanitizeAndSerializeProviderData,
@@ -35,6 +43,7 @@ export type {
3543
InternalAuthHookParams,
3644
OAuthData,
3745
} from './hooks.js'
46+
{=/ isCustomAuthProviderUsed =}
3847

3948
{=# isExternalAuthEnabled =}
4049
export * from './oauth/index.js'

waspc/data/Generator/templates/sdk/wasp/server/auth/provider/index.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{={= =}=}}
2-
import { type AuthProvider } from './types.js'
2+
import { {=# isCustomAuthProviderUsed =}canManageSessions as canProviderManagesSessions, canRevokeSessions as canProviderRevokeSessions, {=/ isCustomAuthProviderUsed =}type AuthProvider } from './types.js'
33
{=# isCustomAuthProviderUsed =}
44
{=& authProvider.importStatement =}
55
{=/ isCustomAuthProviderUsed =}
@@ -11,8 +11,10 @@ import { waspAuthProvider } from './wasp.js'
1111
export {
1212
type AuthProvider,
1313
type SessionManagingAuthProvider,
14+
type SupportsSessionRevocation,
1415
type VerifiedSession,
1516
canManageSessions,
17+
canRevokeSessions,
1618
} from './types.js'
1719

1820
// PRIVATE API
@@ -25,6 +27,46 @@ export {
2527
*/
2628
export const authProvider: AuthProvider =
2729
{=# isCustomAuthProviderUsed =}{= authProvider.importIdentifier =}{=/ isCustomAuthProviderUsed =}{=^ isCustomAuthProviderUsed =}waspAuthProvider{=/ isCustomAuthProviderUsed =}
30+
{=# isCustomAuthProviderUsed =}
31+
32+
/**
33+
* The manifest in `main.wasp.ts` made compile-time claims about this provider
34+
* (its id, its capabilities), and code was generated from them. Checking the
35+
* claims against the adapter object at boot turns a wrong manifest into a
36+
* loud startup failure instead of a subtly broken app.
37+
*/
38+
function assertProviderMatchesManifest(): void {
39+
const manifestProviderId = "{= manifestProviderId =}";
40+
const manifestCapabilities: string[] = {=& manifestCapabilities =};
41+
42+
if (authProvider.id !== manifestProviderId) {
43+
throw new Error(
44+
`The auth provider manifest declares id '${manifestProviderId}', but the adapter's id is '${authProvider.id}'. ` +
45+
`Identities are recorded under the provider id, so the two must match.`,
46+
);
47+
}
48+
49+
if (
50+
manifestCapabilities.includes('issue-sessions') &&
51+
!canProviderManagesSessions(authProvider)
52+
) {
53+
throw new Error(
54+
`The auth provider manifest for '${manifestProviderId}' declares the 'issue-sessions' capability, but the adapter does not implement the full issueSession/revokeSession/revokeAllSessions set Wasp requires for session management.`,
55+
);
56+
}
57+
58+
if (
59+
manifestCapabilities.includes('session-revocation') &&
60+
!canProviderRevokeSessions(authProvider)
61+
) {
62+
throw new Error(
63+
`The auth provider manifest for '${manifestProviderId}' declares the 'session-revocation' capability, but the adapter does not implement revokeSession.`,
64+
);
65+
}
66+
}
67+
68+
assertProviderMatchesManifest()
69+
{=/ isCustomAuthProviderUsed =}
2870

2971
// PRIVATE API
3072
/**

waspc/data/Generator/templates/sdk/wasp/server/auth/provider/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type AuthProvider } from '@wasp.sh/auth-contract'
2+
import { type UserSignupFields } from '../../../auth/providers/types.js'
23
import { type FromRegister } from '../../../types/register.js'
34

45
// PRIVATE API
@@ -30,3 +31,15 @@ export {
3031
* contract at build time rather than failing somewhere inside the session layer.
3132
*/
3233
export type RegisteredAuthProvider = FromRegister<'authProvider', AuthProvider>
34+
35+
// PRIVATE API
36+
/**
37+
* The `userSignupFields` the developer registered on the external provider's
38+
* manifest, if any. Feeds just-in-time provisioning: when Wasp first sees a
39+
* subject, these fields populate the new row of the app's own user entity from
40+
* the claims the provider verified.
41+
*/
42+
export type RegisteredAuthProviderUserSignupFields = FromRegister<
43+
'authProviderUserSignupFields',
44+
UserSignupFields
45+
>

waspc/data/Generator/templates/sdk/wasp/server/auth/session.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { canManageSessions, canRevokeSessions, type VerifiedSession } from "./pr
88

99
import { config, prisma } from '../index.js';
1010
import { createAuthUserData } from "../../auth/user.js";
11+
{=# isCustomAuthProviderUsed =}
12+
import { validateAndGetUserFields } from './utils.js';
13+
{=# externalUserSignupFields.isDefined =}
14+
{=& externalUserSignupFields.importStatement =}
15+
{=/ externalUserSignupFields.isDefined =}
16+
{=/ isCustomAuthProviderUsed =}
1117

1218
/**
1319
* Wasp's session layer.
@@ -152,8 +158,19 @@ async function resolveExternalSubject(
152158
}
153159

154160
try {
161+
// The app's `userSignupFields` compute the new user's own fields from the
162+
// claims the provider verified -- the only way a user entity with required
163+
// columns can be provisioned at all.
164+
const userFields = await validateAndGetUserFields(
165+
{ ...(claims ?? {}) },
166+
{=# externalUserSignupFields.isDefined =}{= externalUserSignupFields.importIdentifier =}{=/ externalUserSignupFields.isDefined =}{=^ externalUserSignupFields.isDefined =}undefined{=/ externalUserSignupFields.isDefined =},
167+
);
168+
155169
const created = await prisma.{= userEntityLower =}.create({
156170
data: {
171+
// Using `any` to defer validation of required-but-unset fields to
172+
// Prisma, which reports them precisely.
173+
...(userFields as any),
157174
{= authFieldOnUserEntityName =}: {
158175
create: {
159176
{= identitiesFieldOnAuthEntityName =}: {

waspc/data/Generator/templates/sdk/wasp/server/auth/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{{={= =}=}}
2+
{=^ isCustomAuthProviderUsed =}
23
import { hashPassword } from './password.js'
4+
{=/ isCustomAuthProviderUsed =}
35
import { prisma, HttpError } from '../index.js'
46
import { sleep } from '../utils.js'
57
import {
@@ -57,6 +59,7 @@ export async function findAuthIdentity(providerId: ProviderId): Promise<{= authI
5759
});
5860
}
5961

62+
{=^ isCustomAuthProviderUsed =}
6063
// PUBLIC API
6164
/**
6265
* Updates the provider data for the given auth identity.
@@ -86,6 +89,7 @@ export async function updateAuthIdentityProviderData<PN extends ProviderName>(
8689
data: { providerData: serializedProviderData },
8790
});
8891
}
92+
{=/ isCustomAuthProviderUsed =}
8993

9094
// PRIVATE API
9195
export type FindAuthWithUserResult = {= authEntityUpper =} & {
@@ -237,6 +241,7 @@ export async function validateAndGetUserFields(
237241
return result;
238242
}
239243

244+
{=^ isCustomAuthProviderUsed =}
240245
// PUBLIC API
241246
export async function sanitizeAndSerializeProviderData<PN extends ProviderName>(
242247
providerData: PossibleProviderData[PN],
@@ -262,6 +267,7 @@ async function ensurePasswordIsHashed<PN extends ProviderName>(
262267

263268
return data;
264269
}
270+
{=/ isCustomAuthProviderUsed =}
265271

266272
// PRIVATE API
267273
export function createInvalidCredentialsError(message?: string): HttpError {

waspc/data/Generator/templates/sdk/wasp/server/config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ type Config = {
1212
frontendUrl: string;
1313
serverUrl: string;
1414
allowedCORSOrigins: (string | RegExp)[];
15-
{=# isAuthEnabled =}
15+
{=# isWaspAuthUsed =}
1616
auth: {
1717
jwtSecret: string;
1818
}
19-
{=/ isAuthEnabled =}
19+
{=/ isWaspAuthUsed =}
2020
}
2121

2222
const frontendUrl = stripTrailingSlash(env['{= clientUrlEnvVarName =}'])
@@ -36,11 +36,11 @@ const config: Config = {
3636
isDevelopment: env.NODE_ENV === 'development',
3737
port: env.PORT,
3838
databaseUrl: env.{= databaseUrlEnvVarName =},
39-
{=# isAuthEnabled =}
39+
{=# isWaspAuthUsed =}
4040
auth: {
4141
jwtSecret: env["{= jwtSecretEnvVarName =}"]
4242
}
43-
{=/ isAuthEnabled =}
43+
{=/ isWaspAuthUsed =}
4444
}
4545

4646
// PUBLIC API

0 commit comments

Comments
 (0)