Skip to content

Commit f6de499

Browse files
committed
Gate wasp-auth-only codegen behind the provider and expose the active provider identity
1 parent 4010708 commit f6de499

24 files changed

Lines changed: 388 additions & 55 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: 41 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 =}canIssueSessions as canProviderIssueSessions, {=/ isCustomAuthProviderUsed =}type AuthProvider } from './types.js'
33
{=# isCustomAuthProviderUsed =}
44
{=& authProvider.importStatement =}
55
{=/ isCustomAuthProviderUsed =}
@@ -25,6 +25,46 @@ export {
2525
*/
2626
export const authProvider: AuthProvider =
2727
{=# isCustomAuthProviderUsed =}{= authProvider.importIdentifier =}{=/ isCustomAuthProviderUsed =}{=^ isCustomAuthProviderUsed =}waspAuthProvider{=/ isCustomAuthProviderUsed =}
28+
{=# isCustomAuthProviderUsed =}
29+
30+
/**
31+
* The manifest in `main.wasp.ts` made compile-time claims about this provider
32+
* (its id, its capabilities), and code was generated from them. Checking the
33+
* claims against the adapter object at boot turns a wrong manifest into a
34+
* loud startup failure instead of a subtly broken app.
35+
*/
36+
function assertProviderMatchesManifest(): void {
37+
const manifestProviderId = "{= manifestProviderId =}";
38+
const manifestCapabilities: string[] = {=& manifestCapabilities =};
39+
40+
if (authProvider.id !== manifestProviderId) {
41+
throw new Error(
42+
`The auth provider manifest declares id '${manifestProviderId}', but the adapter's id is '${authProvider.id}'. ` +
43+
`Identities are recorded under the provider id, so the two must match.`,
44+
);
45+
}
46+
47+
if (
48+
manifestCapabilities.includes('issue-sessions') &&
49+
!canProviderIssueSessions(authProvider)
50+
) {
51+
throw new Error(
52+
`The auth provider manifest for '${manifestProviderId}' declares the 'issue-sessions' capability, but the adapter does not implement issueSession/revokeAllSessions.`,
53+
);
54+
}
55+
56+
if (
57+
manifestCapabilities.includes('session-revocation') &&
58+
typeof authProvider.revokeSession !== 'function'
59+
) {
60+
throw new Error(
61+
`The auth provider manifest for '${manifestProviderId}' declares the 'session-revocation' capability, but the adapter does not implement revokeSession.`,
62+
);
63+
}
64+
}
65+
66+
assertProviderMatchesManifest()
67+
{=/ isCustomAuthProviderUsed =}
2868

2969
// PRIVATE API
3070
/**

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
@@ -25,3 +26,15 @@ export {
2526
* contract at build time rather than failing somewhere inside the session layer.
2627
*/
2728
export type RegisteredAuthProvider = FromRegister<'authProvider', AuthProvider>
29+
30+
// PRIVATE API
31+
/**
32+
* The `userSignupFields` the developer registered on the external provider's
33+
* manifest, if any. Feeds just-in-time provisioning: when Wasp first sees a
34+
* subject, these fields populate the new row of the app's own user entity from
35+
* the claims the provider verified.
36+
*/
37+
export type RegisteredAuthProviderUserSignupFields = FromRegister<
38+
'authProviderUserSignupFields',
39+
UserSignupFields
40+
>

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 { type VerifiedSession } from "./provider/types.js";
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)